美文网首页Android自定义View
Android-自定义纵向拽托关闭布局

Android-自定义纵向拽托关闭布局

作者: h2coder | 来源:发表于2022-06-07 15:56 被阅读0次

    效果

    示例.gif

    代码实现

    /**
     * 上下拽托移动布局
     */
    public class VerticalDragLayout extends FrameLayout {
        /**
         * 触发阈值
         */
        private static final float TRIGGER_THRESHOLD_VALUE = 0.75f;
    
        private ViewDragHelper mViewDragHelper;
    
        /**
         * 唯一内容子View
         */
        private View mContentView;
        /**
         * 当前内容子View的Top值,由于子View引起重绘或addView引起重绘会导致requestLayout(),会重新onLayout()子View
         * 而layout子View,要在onLayout中处理。如果在onViewPositionChanged()中单独onLayout,并且又没有保存top值为成员变量,就会因为其他原因引起的requestLayout()
         * 导致子View的top值默认值为0,会突然抖回顶部
         */
        private int mCurrentTop = 0;
        /**
         * 是否已关闭
         */
        private boolean isClosed = true;
        /**
         * 拽托状态回调
         */
        private OnDragStateChangeListener mOnDragStateChangeListener;
    
        public VerticalDragLayout(Context context) {
            super(context);
            init();
        }
    
        public VerticalDragLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
            init();
        }
    
        public VerticalDragLayout(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init();
        }
    
        private void init() {
            mViewDragHelper = ViewDragHelper.create(this, 0.5f, new ViewDragHelperCallBack());
        }
    
        private class ViewDragHelperCallBack extends ViewDragHelper.Callback {
            /**
             * 开始拽托时的X坐标
             */
            private int mDownX;
            /**
             * 开始拽托时的Y坐标
             */
            private int mDownY;
    
            @Override
            public boolean tryCaptureView(@NonNull View child, int pointerId) {
                //返回ture则表示可以捕获该view,手指摸上一瞬间调用
                return child == mContentView;
            }
    
            @Override
            public void onViewCaptured(@NonNull View capturedChild, int activePointerId) {
                super.onViewCaptured(capturedChild, activePointerId);
                mDownX = capturedChild.getLeft();
                mDownY = capturedChild.getTop();
            }
    
            @Override
            public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
                //手指触摸移动时实时回调, top表示要到的y位置
                //限制只能向下滑
                if (top < 0) {
                    return 0;
                }
                //最多只能滑动到内容View的高度
                int maxTop = mContentView.getHeight();
                return Math.min(top, maxTop);
            }
    
            @Override
            public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {
                //手指释放时回调
                int finalLeft = releasedChild.getLeft();
                final int currentLeft = releasedChild.getLeft();
                final int currentTop = releasedChild.getTop();
                //最小移动距离
                int minMoveDistance = releasedChild.getHeight() / 3;
                //计算移动距离
                int distanceX = currentLeft - mDownX;
                int distanceY = currentTop - mDownY;
                //滚动到顶部
                Runnable scrollToTop = new Runnable() {
                    @Override
                    public void run() {
                        mViewDragHelper.settleCapturedViewAt(finalLeft, 0);
                    }
                };
                //滚动到底部
                Runnable scrollToBottom = new Runnable() {
                    @Override
                    public void run() {
                        int finalTop = releasedChild.getHeight();
                        mViewDragHelper.settleCapturedViewAt(finalLeft, finalTop);
                    }
                };
                //根据移动距离占总大小的百分比,决定滚动到顶部还是底部
                Runnable scrollByDistancePercentage = new Runnable() {
                    @Override
                    public void run() {
                        //当前已移动距离的百分比值
                        float movePercentage = (releasedChild.getTop() * 1f) / releasedChild.getHeight();
                        //剩余的内容占的百分比
                        float remainDistancePercentage = 1f - movePercentage;
                        //剩余的内容,小于阈值,则移动到底部
                        if (remainDistancePercentage <= TRIGGER_THRESHOLD_VALUE) {
                            scrollToBottom.run();
                        } else {
                            //小于阈值,回弹回顶部
                            scrollToTop.run();
                        }
                    }
                };
                //判断是否是上下滑,左右滑不需要动
                if (Math.abs(distanceY) > Math.abs(distanceX)) {
                    //上下滑,滑动得少,并且速度很快,则为fling操作
                    if (Math.abs(distanceY) < minMoveDistance && Math.abs(yvel) > Math.abs(mViewDragHelper.getMinVelocity())) {
                        //距离相减为正数,并且惯性速度为正数(如果先下滑,再回去,速度值会为负),则为往下滑
                        if (distanceY > 0 && yvel > 0) {
                            scrollToBottom.run();
                        } else {
                            //否则为向上滑
                            scrollToTop.run();
                        }
                    } else {
                        //不是fling操作,根据移动距离占总大小的百分比,决定滚动到顶部还是底部
                        scrollByDistancePercentage.run();
                    }
                }
                invalidate();
            }
    
            @Override
            public void onViewPositionChanged(@NonNull View changedView, int left, int top, int dx, int dy) {
                mCurrentTop = top;
                //统一在onLayout中,重新布局子View位置,来移动子View
                requestLayout();
            }
    
            @Override
            public int getViewVerticalDragRange(@NonNull View child) {
                if (mContentView == null) {
                    return 0;
                }
                if (mContentView == child) {
                    return mContentView.getHeight();
                } else {
                    return 0;
                }
            }
    
            @Override
            public void onViewDragStateChanged(int state) {
                super.onViewDragStateChanged(state);
                if (state == ViewDragHelper.STATE_DRAGGING) {
                    if (mOnDragStateChangeListener != null) {
                        mOnDragStateChangeListener.onStartDrag();
                    }
                } else if (state == ViewDragHelper.STATE_IDLE) {
                    if (mOnDragStateChangeListener != null) {
                        mOnDragStateChangeListener.onStopDrag();
                    }
                    isClosed = (mContentView.getTop() == mContentView.getHeight());
                    if (isClosed) {
                        if (mOnDragStateChangeListener != null) {
                            mOnDragStateChangeListener.onDragToBottom();
                        }
                    }
                }
            }
        }
    
        @Override
        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
            super.onLayout(changed, left, top, right, bottom);
            //移动内容子View
            MarginLayoutParams params = (MarginLayoutParams) mContentView.getLayoutParams();
            mContentView.layout(
                    params.leftMargin,
                    mCurrentTop + params.topMargin,
                    mContentView.getMeasuredWidth() + params.leftMargin,
                    mCurrentTop + mContentView.getMeasuredHeight() + params.topMargin
            );
        }
    
        @Override
        protected void onFinishInflate() {
            super.onFinishInflate();
            int childCount = getChildCount();
            if (childCount != 1) {
                throw new RuntimeException("子View必须只有1个,就是内容View");
            }
            mContentView = getChildAt(0);
        }
    
        @Override
        public void computeScroll() {
            if (mViewDragHelper.continueSettling(true)) {
                ViewCompat.postInvalidateOnAnimation(this);
            }
        }
    
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            //将onInterceptTouchEvent委托给ViewDragHelper
            return mViewDragHelper.shouldInterceptTouchEvent(ev);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            //将onTouchEvent委托给ViewDragHelper
            mViewDragHelper.processTouchEvent(event);
            return true;
        }
    
        public boolean isClosed() {
            return isClosed;
        }
    
        /**
         * 结束回调
         */
        public interface OnDragStateChangeListener {
            void onStartDrag();
    
            void onStopDrag();
    
            void onDragToBottom();
        }
    
        public void setOnDragStateChangeListener(OnDragStateChangeListener listener) {
            this.mOnDragStateChangeListener = listener;
        }
    }
    

    使用

    • 布局xml
    <com.psy1.cosleep.library.view.VerticalDragLayout xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto"
      xmlns:tools="http://schemas.android.com/tools"
      android:id="@+id/vertical_drag_layout"
      android:layout_width="match_parent"
      android:layout_height="match_parent">
    
      //....
    
    </com.psy1.cosleep.library.view.VerticalDragLayout>
    
    • Java代码
    VerticalDragLayout verticalDragLayout = findViewById(R.id.vertical_drag_layout);
    verticalDragLayout.setOnDragStateChangeListener(new VerticalDragLayout.OnDragStateChangeListener() {
      @Override
      public void onStartDrag() {
        //开始拽托
      }
    
      @Override
      public void onStopDrag() {
        //停止拽托
      }
    
      @Override
      public void onDragToBottom() {
        //拽托到底部,可关闭页面
      }
    });
    

    相关文章

      网友评论

        本文标题:Android-自定义纵向拽托关闭布局

        本文链接:https://www.haomeiwen.com/subject/oxyemrtx.html