欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 金融 > Android第六次面试总结(自定义 View与事件分发)

Android第六次面试总结(自定义 View与事件分发)

2025/3/29 6:37:40 来源:https://blog.csdn.net/2301_80329517/article/details/146500950  浏览:    关键词:Android第六次面试总结(自定义 View与事件分发)

在 Android 中实现自定义 View 处理 1 万条数据的流畅滑动,需结合视图复用、按需绘制、硬件加速等核心技术。以下是具体实现方案:

一、核心优化策略

1. 视图复用机制(类似 RecyclerView)
  • ViewHolder 模式:将每个数据项的视图封装为 ViewHolder,通过对象池复用视图实例。
class ItemViewHolder {View itemView;TextView textView;// 其他子控件
}

对象池管理:使用LinkedList<ItemViewHolder>缓存闲置视图,避免频繁创建销毁

private final LinkedList<ItemViewHolder> viewPool = new LinkedList<>();private ItemViewHolder obtainViewHolder() {if (viewPool.isEmpty()) {View itemView = LayoutInflater.from(context).inflate(R.layout.item_layout, this, false);return new ItemViewHolder(itemView);}return viewPool.poll();
}private void recycleViewHolder(ItemViewHolder holder) {viewPool.offer(holder);
}
2. 按需绘制(仅渲染可见区域)
  • 计算可见范围:根据滚动偏移量(scrollY)和视图高度,确定需绘制的起始和结束索引。
@Override
protected void onDraw(Canvas canvas) {super.onDraw(canvas);int visibleStart = (int) Math.floor(scrollY / itemHeight);int visibleEnd = (int) Math.ceil((scrollY + getHeight()) / itemHeight);for (int i = visibleStart; i <= visibleEnd; i++) {if (i >= dataList.size()) break;drawItem(canvas, i);}
}

 

3. 硬件加速与缓存优化
  • 启用硬件加速:在构造函数中设置setLayerType(LAYER_TYPE_HARDWARE, null)
  • 缓存绘制结果:使用Bitmap缓存已绘制的视图区域,减少重复计算。
private Bitmap cacheBitmap;
private Canvas cacheCanvas;@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);cacheBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);cacheCanvas = new Canvas(cacheBitmap);
}@Override
protected void onDraw(Canvas canvas) {// 先绘制到缓存画布,再整体绘制到屏幕cacheCanvas.drawColor(Color.WHITE);// 绘制可见项canvas.drawBitmap(cacheBitmap, 0, 0, null);
}

二、实现步骤

1. 数据适配器设计
  • 抽象数据适配器:分离数据逻辑与视图渲染。
public abstract class DataAdapter<T> {public abstract int getItemCount();public abstract T getItem(int position);public abstract int getItemHeight(int position);public abstract void bindViewHolder(ItemViewHolder holder, T item);
}
2. 触摸事件处理
  • 惯性滚动实现:使用ScrollerVelocityTracker
private Scroller scroller;
private VelocityTracker velocityTracker;@Override
public boolean onTouchEvent(MotionEvent event) {if (velocityTracker == null) {velocityTracker = VelocityTracker.obtain();}velocityTracker.addMovement(event);switch (event.getAction()) {case MotionEvent.ACTION_DOWN:if (!scroller.isFinished()) {scroller.abortAnimation();}startY = (int) event.getY();break;case MotionEvent.ACTION_MOVE:int dy = (int) (event.getY() - startY);scrollBy(0, -dy);startY = (int) event.getY();break;case MotionEvent.ACTION_UP:velocityTracker.computeCurrentVelocity(1000);int velocityY = (int) velocityTracker.getYVelocity();scroller.fling(0, getScrollY(), 0, -velocityY, 0, 0, 0, maxScrollY);velocityTracker.recycle();velocityTracker = null;invalidate();break;}return true;
}@Override
public void computeScroll() {if (scroller.computeScrollOffset()) {scrollTo(scroller.getCurrX(), scroller.getCurrY());invalidate();}
}

三、性能监控与调优

1. 帧率监控
  • 使用 Android Profiler:监控 GPU 渲染帧率,确保稳定在 60fps。
  • 自定义帧率统计
private long frameTime = System.currentTimeMillis();
private int frameCount = 0;@Override
protected void onDraw(Canvas canvas) {super.onDraw(canvas);frameCount++;if (System.currentTimeMillis() - frameTime >= 1000) {Log.d(TAG, "FPS: " + frameCount);frameCount = 0;frameTime = System.currentTimeMillis();}
}
2. 内存分析
  • 使用 MAT 工具:分析内存快照,识别潜在泄漏点。
  • 资源释放:在onDetachedFromWindow中清理缓存。
@Override
protected void onDetachedFromWindow() {super.onDetachedFromWindow();if (cacheBitmap != null && !cacheBitmap.isRecycled()) {cacheBitmap.recycle();cacheBitmap = null;}
}

四、完整代码示例

public class CustomListView extends ViewGroup {private DataAdapter<?> adapter;private int itemHeight = 100; // 每项高度private int maxScrollY;// 初始化代码public CustomListView(Context context) {super(context);init();}private void init() {scroller = new Scroller(context);setWillNotDraw(false);setLayerType(LAYER_TYPE_HARDWARE, null);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int width = MeasureSpec.getSize(widthMeasureSpec);int height = MeasureSpec.getSize(heightMeasureSpec);setMeasuredDimension(width, height);maxScrollY = adapter.getItemCount() * itemHeight - height;}@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {// 布局逻辑(此处简化)}private void drawItem(Canvas canvas, int position) {ItemViewHolder holder = obtainViewHolder();adapter.bindViewHolder(holder, adapter.getItem(position));holder.itemView.layout(0, position * itemHeight - scrollY, getWidth(), (position + 1) * itemHeight - scrollY);holder.itemView.draw(canvas);recycleViewHolder(holder);}// 设置适配器public void setAdapter(DataAdapter<?> adapter) {this.adapter = adapter;requestLayout();invalidate();}
}

五、扩展建议

  1. 数据分批加载:结合分页加载,每次仅加载当前可见区域附近的数据。
  2. 预取优化:在滚动时预加载下一页数据。
  3. 动态高度支持:在DataAdapter中实现getItemHeight(int position)动态返回项高度。
  4. 缓存策略:使用 LruCache 缓存常用视图,提升复用效率。

在 Android 开发中,事件分发机制是实现用户交互的核心逻辑之一。理解事件如何被 View 接收和处理,对于解决滑动冲突、触摸响应异常等问题至关重要。以下是事件分发机制的详细解析:

一、事件分发的核心流程

事件分发遵循Activity → ViewGroup → View的传递路径,通过三个关键方法实现:

  1. dispatchTouchEvent(MotionEvent ev)

    • 作用:决定事件是否继续分发。
    • 返回值:
      • true:事件由当前 View 处理,停止向下传递。
      • false:事件回传给父 View 的onTouchEvent
      • super.dispatchTouchEvent(ev):继续调用子 View 的dispatchTouchEvent
  2. onInterceptTouchEvent(MotionEvent ev)(仅 ViewGroup 可用)

    • 作用:判断是否拦截事件。
    • 返回值:
      • true:拦截事件,事件由当前 ViewGroup 处理。
      • false:不拦截,事件继续传递给子 View。
  3. onTouchEvent(MotionEvent ev)

    • 作用:处理具体的触摸事件。
    • 返回值:
      • true:事件被消费,停止向上传递。
      • false:事件未被消费,回传给父 View 的onTouchEvent

二、事件传递的典型场景

场景 1:事件被 ViewGroup 拦截
public class CustomViewGroup extends LinearLayout {@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {// 拦截DOWN事件,后续事件(MOVE/UP)也会被拦截return ev.getAction() == MotionEvent.ACTION_DOWN;}@Overridepublic boolean onTouchEvent(MotionEvent event) {// 处理点击事件return true;}
}
场景 2:子 View 处理事件
public class CustomButton extends AppCompatButton {@Overridepublic boolean onTouchEvent(MotionEvent event) {// 处理点击事件return true;}
}

三、常见问题与解决方案

问题 1:父 View 拦截导致子 View 无法响应
  • 原因:父 ViewGroup 在onInterceptTouchEvent中错误拦截事件。
  • 解决方案
    onInterceptTouchEvent中根据业务逻辑选择性拦截(如仅拦截滑动事件)。
问题 2:事件未被消费导致冒泡
  • 原因:View 的onTouchEvent返回false,事件向上传递。
  • 解决方案
    onTouchEvent中处理完事件后返回true,或设置clickable="true"(默认消费事件)。
问题 3:滑动冲突(如嵌套滑动)
  • 解决方案
    1. 使用NestedScrollViewRecyclerView等支持嵌套滑动的控件。
    2. 重写父 ViewGroup 的onInterceptTouchEvent,根据滑动方向动态决定是否拦截。

四、优化事件分发的建议

  1. 减少层级嵌套:避免多层 ViewGroup 嵌套导致事件传递效率低下。
  2. 复用事件对象:通过MotionEvent.obtain()减少对象创建开销。
  3. 延迟处理复杂逻辑:在ACTION_UP事件中处理耗时操作,避免阻塞主线程。
  4. 使用GestureDetector:简化手势处理逻辑,如双击、长按等。

五、总结

事件分发机制的核心原则是:事件由父容器向下传递,子 View 可通过返回true消费事件。掌握这一机制能有效解决触摸响应问题,尤其在处理自定义 View 和复杂布局时更为关键。结合具体场景合理设计拦截逻辑,可显著提升用户交互体验。

感谢观看!!!

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词