待写UI

Android 从 0 开始学习自定义 View(十) 可拖动回

2021-05-31  本文已影响0人  是刘航啊

效果图

效果描述:当滑动距离\color{red}{小于}设定的距离( 默认后背景内容一半 )时会\color{red}{回弹到顶部}。当滑动的距离\color{red}{大于}设定的距离时会\color{red}{显示背景内容}。当列表向上滑动时,如果背景是\color{red}{展开状态}会先将背景关闭,之后再处理列表的滑动。

效果实现分析

  1. 处理 ViewDragHelper
  2. 处理拖动以及拖动的范围
  3. 处理回弹效果
  4. 滑动冲突

1、处理 ViewDragHelper

public class VerticalDragView extends FrameLayout {
    
    private ViewDragHelper mDragHelper;
    
    public VerticalDragView(@NonNull Context context) {
        this(context, null);
    }

    public VerticalDragView(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public VerticalDragView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mDragHelper = ViewDragHelper.create(this, mCallback);
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mDragHelper.processTouchEvent(event);
        return true;
    }
    
    private ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {
        @Override
        public boolean tryCaptureView(@NonNull View child, int pointerId) {
            return true;
        }

        @Override
        public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
            return top;
        }

        @Override
        public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) {
            return left;
        }

        @Override
        public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {

        }
    };
}

这里只是创建了 ViewDragHelper,并没有做什么特殊处理,接下来处理可以拖动的 View 以及拖动的范围。

2、处理拖动以及拖动的范围

public class VerticalDragView extends FrameLayout {
    
    private ViewDragHelper mDragHelper;
    //可以拖动的View
    private View mDragView;
    //拖动范围
    int menuHeight = 0;
    private boolean mMenuIsOpen = false;
    
    public VerticalDragView(@NonNull Context context) {
        this(context, null);
    }

    public VerticalDragView(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public VerticalDragView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mDragHelper = ViewDragHelper.create(this, mCallback);
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mDragHelper.processTouchEvent(event);
        return true;
    }
    
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        if (changed) {
            menuHeight = getChildAt(0).getMeasuredHeight();
        }
    }
    
    private ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {
        @Override
        public boolean tryCaptureView(@NonNull View child, int pointerId) {
            return  return child == mDragView;
        }

        @Override
        public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
            //垂直滑动移动的位置
            if (top <= 0) {
                top = 0;
            }

            if (top >= menuHeight) {
                top = menuHeight;
            }
            return top;
        }

        @Override
        public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {

        }
    };
}

需要注意的是 View 的高度在 onMeasere 才会被计算,如果直接获取是拿不到高度的。我是在 onLayout 获取高度,onMeasure 也可以,看个人选择。

3、处理回弹效果

public class VerticalDragView extends FrameLayout {
    
    private ViewDragHelper mDragHelper;
    //可以拖动的View
    private View mDragView;
    //拖动范围
    int menuHeight = 0;
    
    public VerticalDragView(@NonNull Context context) {
        this(context, null);
    }

    public VerticalDragView(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public VerticalDragView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mDragHelper = ViewDragHelper.create(this, mCallback);
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mDragHelper.processTouchEvent(event);
        return true;
    }
    
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        if (changed) {
            menuHeight = getChildAt(0).getMeasuredHeight();
        }
    }
    
    private ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {
        @Override
        public boolean tryCaptureView(@NonNull View child, int pointerId) {
            return  return child == mDragView;
        }

        @Override
        public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
            //垂直滑动移动的位置
            if (top <= 0) {
                top = 0;
            }

            if (top >= menuHeight) {
                top = menuHeight;
            }
            return top;
        }

        @Override
        public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {
            if (mDragView.getTop() > menuHeight / 2) {
                //滚动到菜单的高度(打开)
                mDragHelper.settleCapturedViewAt(0, menuHeight);
                mMenuIsOpen = true;
            } else {
                //滚动到0(关闭)
                mDragHelper.settleCapturedViewAt(0, 0);
                mMenuIsOpen = false;
            }
            invalidate();
        }
    };
    
    @Override
    public void computeScroll() {
        if (mDragHelper.continueSettling(true)) {
            invalidate();
        }
    }
}

4、滑动冲突

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {

        if (mMenuIsOpen) {
            return true;
        }

        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mDownY = ev.getY();
                mDragHelper.processTouchEvent(ev);
                break;
            case MotionEvent.ACTION_MOVE:
                float offSetY = ev.getY() - mDownY;
                if (offSetY > 0 && !canChildScrollUp()) {
                    return true;
                }
                break;
        }

        return super.onInterceptTouchEvent(ev);
    }

    /**
     * 判断是否滚动到顶部
     *
     * @return
     */
    public boolean canChildScrollUp() {

        if (mDragView instanceof ListView) {
            return ListViewCompat.canScrollList((ListView) mDragView, -1);
        }
        return mDragView.canScrollVertically(-1);
    }

滑动冲突主要是判断菜单的开合状态、滑动的状态( 向上滑动|向下划动 )、列表是否滑动到头部

基本的代码都在上面,自定义仿可拖动回弹布局就介绍到这里了,如果有什么写得不对的,可以在下方评论留言,我会第一时间改正。

Github 源码链接

上一篇下一篇

猜你喜欢

热点阅读