当前位置: 首页 > article >正文

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);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        setMeasuredDimension(width, height);
        maxScrollY = adapter.getItemCount() * itemHeight - height;
    }

    @Override
    protected 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 {
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        // 拦截DOWN事件,后续事件(MOVE/UP)也会被拦截
        return ev.getAction() == MotionEvent.ACTION_DOWN;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // 处理点击事件
        return true;
    }
}
场景 2:子 View 处理事件
public class CustomButton extends AppCompatButton {
    @Override
    public 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 和复杂布局时更为关键。结合具体场景合理设计拦截逻辑,可显著提升用户交互体验。

感谢观看!!!


http://www.kler.cn/a/612045.html

相关文章:

  • Unity Shader编程】之FallBack
  • CSS3:现代Web设计的魔法卷轴
  • 行为型——责任链模式
  • 本地文生图使用插件(Stable Diffusion)
  • MybatisPlus(SpringBoot版)学习第五讲:条件构造器和常用接口
  • poetry install --with aws
  • SQL语言的安全开发
  • 网易邮箱DolphinScheduler迁移实战:从部署到优化,10倍效率提升的内部经验
  • 数据结构6-图
  • 数制——FPGA
  • C++ set容器总结
  • Linux 目录结构(文件系统结构)示例说明
  • 《Spring Cloud Eureka 高可用集群实战:从零构建 99.99% 可靠性的微服务注册中心》
  • 【后端】CDN内容分发网络
  • 美摄科技智能汽车视频延迟摄影解决方案,开启智能出行新视界
  • ESP32S3 WIFI 实现TCP服务器和静态IP
  • 使用 OCRmyPDF 将扫描 PDF 转为可搜索文档和文本文件
  • <sa8650>QCX Camera Channel configuration
  • 如何根据目标网站调整Python爬虫的延迟时间?
  • Postman 版本信息速查:快速定位版本号