在 Android 中实现自定义 View 处理 1 万条数据的流畅滑动,需结合视图复用、按需绘制、硬件加速等核心技术。以下是具体实现方案:
class ItemViewHolder {
View itemView;
TextView textView;
// 其他子控件
}
对象池管理:使用LinkedList
缓存闲置视图,避免频繁创建销毁
private final LinkedList 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);
}
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);
}
}
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);
}
public abstract class DataAdapter {
public abstract int getItemCount();
public abstract T getItem(int position);
public abstract int getItemHeight(int position);
public abstract void bindViewHolder(ItemViewHolder holder, T item);
}
Scroller
和VelocityTracker
。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();
}
}
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();
}
}
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();
}
}
DataAdapter
中实现getItemHeight(int position)
动态返回项高度。在 Android 开发中,事件分发机制是实现用户交互的核心逻辑之一。理解事件如何被 View 接收和处理,对于解决滑动冲突、触摸响应异常等问题至关重要。以下是事件分发机制的详细解析:
事件分发遵循Activity → ViewGroup → View的传递路径,通过三个关键方法实现:
dispatchTouchEvent(MotionEvent ev)
true
:事件由当前 View 处理,停止向下传递。false
:事件回传给父 View 的onTouchEvent
。super.dispatchTouchEvent(ev)
:继续调用子 View 的dispatchTouchEvent
。onInterceptTouchEvent(MotionEvent ev)
(仅 ViewGroup 可用)
true
:拦截事件,事件由当前 ViewGroup 处理。false
:不拦截,事件继续传递给子 View。onTouchEvent(MotionEvent ev)
true
:事件被消费,停止向上传递。false
:事件未被消费,回传给父 View 的onTouchEvent
。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;
}
}
public class CustomButton extends AppCompatButton {
@Override
public boolean onTouchEvent(MotionEvent event) {
// 处理点击事件
return true;
}
}
onInterceptTouchEvent
中错误拦截事件。onInterceptTouchEvent
中根据业务逻辑选择性拦截(如仅拦截滑动事件)。onTouchEvent
返回false
,事件向上传递。onTouchEvent
中处理完事件后返回true
,或设置clickable="true"
(默认消费事件)。NestedScrollView
或RecyclerView
等支持嵌套滑动的控件。onInterceptTouchEvent
,根据滑动方向动态决定是否拦截。MotionEvent.obtain()
减少对象创建开销。ACTION_UP
事件中处理耗时操作,避免阻塞主线程。GestureDetector
:简化手势处理逻辑,如双击、长按等。事件分发机制的核心原则是:事件由父容器向下传递,子 View 可通过返回true
消费事件。掌握这一机制能有效解决触摸响应问题,尤其在处理自定义 View 和复杂布局时更为关键。结合具体场景合理设计拦截逻辑,可显著提升用户交互体验。
感谢观看!!!