CustomView:自定义 View 辅助 · AndroidX 源码指南
AAndroidX 源码指南
View 体系
View 体系 · androidx.core

CustomView:自定义 View 辅助Core 1.20.0-alpha01

ViewDragHelper 实现拖拽,PoolingContainer 实现 View 复用回收。

最后更新 2026-08-01

复制即用

用 ViewDragHelper 实现可拖拽子 View(含松手吸附回位):

import androidx.customview.widget.ViewDragHelper;

public class DragLayout extends FrameLayout {
    private final ViewDragHelper dragHelper;

    public DragLayout(Context context) {
        super(context);
        dragHelper = ViewDragHelper.create(this, 1f, new ViewDragHelper.Callback() {
            @Override
            public boolean tryCaptureView(View child, int pointerId) {
                return child == draggableView;
            }

            @Override
            public int clampViewPositionHorizontal(View child, int left, int dx) {
                return Math.max(0, Math.min(getWidth() - child.getWidth(), left));
            }

            @Override
            public int clampViewPositionVertical(View child, int top, int dy) {
                return Math.max(0, Math.min(getHeight() - child.getHeight(), top));
            }

            @Override
            public void onViewReleased(View releasedChild, float xvel, float yvel) {
                // 松手后吸附回原位
                dragHelper.settleCapturedViewAt(0, 0);
                invalidate();
            }
        });
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return dragHelper.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        dragHelper.processTouchEvent(ev);
        return true;
    }

    @Override
    public void computeScroll() {
        if (dragHelper.continueSettling(true)) {
            invalidate();
        }
    }
}

View 复用(PoolingContainer):

import androidx.customview.poolingcontainer.setPoolingContainerListener

// 在容器内复用 View 时,监听回收/复用事件
view.setPoolingContainerListener { isAcquired ->
    if (isAcquired) resetViewState() else releaseResources()
}

常见陷阱

  • 忘转发触摸事件shouldInterceptTouchEvent/processTouchEvent 不接,拖拽不生效。
  • 忘 computeScrollsettleCapturedViewAt 后要 computeScroll + continueSettling
  • clamp 返回越界值clampViewPosition* 必须限制在合法范围。
  • 先想 Compose:自定义 View 前确认 Compose 是否更合适。

要点

  • ViewDragHelper.create(parent, sensitivity, callback) 封装拖拽:tryCaptureView 决定可拖对象,clamp* 限制范围。
  • 需要手动把触摸事件转发给 helper(shouldInterceptTouchEvent/processTouchEvent)。
  • PoolingContainer 用于 View 池场景(如列表复用),监听 acquired/released 重置状态。
  • 实现自定义 View 前先确认 Compose 是否更合适(Compose 无 View 体系负担)。

相关页面