前言
正好项目中有个页面底部拖动上下滑的UI;
今天我们就来讲解下ViewDragHelper;
这几天项目比较忙,文章更新会慢,各位老铁可以看历史记录;
一、viewDragHleper详解
ViewDragHelper是针对 ViewGroup 中的拖拽和重新定位 views 操作时提供了一系列非常有用的方法和状态追踪;
1、ViewDragHelper初始化
- public class ViewDragTest extends LinearLayout {
- ViewDragHelper mViewDragHelper;
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- //中间参数表示灵敏度,比如滑动了多少像素才视为触发了滑动.值越大越灵敏.
- mViewDragHelper = ViewDragHelper.create(this, 1f, new DragCallback());
- }
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- //固定写法
- int action = MotionEventCompat.getActionMasked(ev);
- if (action == MotionEvent.ACTION_CANCEL
- || action == MotionEvent.ACTION_UP) {
- mViewDragHelper.cancel();
- return false;
- }
- return mViewDragHelper.shouldInterceptTouchEvent(ev);
- }
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- //固定写法
- mViewDragHelper.processTouchEvent(event);
- return true;
- }
- @Override
- public void computeScroll() {
- //固定写法
- //此方法用于自动滚动,比如自动回滚到默认位置.
- if (mViewDragHelper.continueSettling(true)) {
- ViewCompat.postInvalidateOnAnimation(this);
- }
- }
- }
2、ViewDragHelper.Callback
- //这个类的回调方法,才是ViewDragHelper的重点
- private class ViewDragCallback extends ViewDragHelper.Callback{
- @Override
- public boolean tryCaptureView(View child, int pointerId) {
- //child 表示想要滑动的view
- //pointerId 表示触摸点的id, 比如多点按压的那个id
- //返回值表示,是否可以capture,也就是是否可以滑动.可以根据不同的child决定是否可以滑动
- return true;
- }
- @Override
- public int clampViewPositionHorizontal(View child, int left, int dx) {
- //child 表示当前正在移动的view
- //left 表示当前的view正要移动到左边距为left的地方
- //dx 表示和上一次滑动的距离间隔
- //返回值就是child要移动的目标位置.可以通过控制返回值,从而控制child只能在ViewGroup的范围中移动.
- return left;
- }
- @Override
- public int clampViewPositionVertical(View child, int top, int dy) {
- //child 表示当前正在移动的view
- //top 表示当前的view正要移动到上边距为top的地方
- //dx 表示和上一次滑动的距离间隔
- return top;
- }
- }
重写以上3个方法,可以正常工作了.子View就可以被任意拖动了;
3、控制child的移动范围在父view中
- //控制child只能在ViewGroup的横向中移动
- @Override
- public int clampViewPositionHorizontal(View child, int left, int dx) {
- final int leftBound = getPaddingLeft();
- final int rightBound = getWidth() - mDragView.getWidth();
- final int newLeft = Math.min(Math.max(left, leftBound), rightBound);
- return newLeft;
- }
- //控制child只能在ViewGroup的纵向中移动
- @Override
- public int clampViewPositionVertical(View child, int top, int dy) {
- final int topBound = getPaddingTop();
- final int bottomBound = getHeight() - mDragView.getHeight();
- final int newTop = Math.min(Math.max(top, topBound), bottomBound);
- return newTop;
- }
4、开启边界滑动
- //开启4个边
- mViewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_ALL);
- //各个边
- public static final int EDGE_LEFT = 1 << 0;
- public static final int EDGE_RIGHT = 1 << 1;
- public static final int EDGE_TOP = 1 << 2;
- public static final int EDGE_BOTTOM = 1 << 3;
- //当开启边界滑动之后, 此方法就会回调
- @Override
- public void onEdgeTouched(int edgeFlags, int pointerId) {
- //通常开启边界之后, 都需要手动capture view.之后就可以滑动view了.
- mViewDragHelper.captureChildView(getChildAt(1), pointerId);
- }
- @Override
- public boolean tryCaptureView(View child, int pointerId) {
- //开启边界之后, 这个方法的返回值可能需要进一步处理.要不然开边界就没啥意思了.
- return false;
- }
5、释放后的回弹效果
有些时候, 当释放的时候, 需要将View回到原来的位置;
- //释放的时候, 会回调下面的方法
- @Override
- public void onViewReleased(View releasedChild, float xvel, float yvel) {
- //调用这个方法,就可以设置releasedChild回弹得位置.
- mViewDragHelper.settleCapturedViewAt(0, 100);//参数就是x,y的坐标
- postInvalidate();//注意一定要调用这个方法,否则没效果.
- }
- //以下2个方法最终调用的都是forceSettleCapturedViewAt().
- mViewDragHelper.settleCapturedViewAt(0, 100);
- mViewDragHelper.smoothSlideViewTo(getChildAt(1), 0, 100);
- //所以...发挥你的想象力,看看有什么妙用!!!
- //如果你还没有忘记的话...前文应该有说过,涉及到scroll,需要重写view的此方法.
- //此方法一定要重写,否则没效果
- @Override
- public void computeScroll() {
- //固定写法
- if (mViewDragHelper.continueSettling(true)) {
- postInvalidate();//注意此处.
- }
- }
- 通过上面2个方法的设置, 当手指释放的时候, View就会自动滑动到指定的位置...(不是一下子就到指定的位置哦,有一个滑动的过程.)
- 注意:如果需要滑动的View,会消耗touch事件,比如:Button,那么需要重写以下方法.
- @Override
- public int getViewHorizontalDragRange(View child) {
- return child.getMeasuredWidth();//只要返回大于0的值就行
- }
- @Override
- public int getViewVerticalDragRange(View child) {
- return child.getMeasuredHeight();//只要返回大于0的值就行
- }
6、简单api介绍
ViewDragHelper的API
- ViewDragHelper create(ViewGroup forParent, Callback cb);
- 一个静态的创建方法,
- 参数1:出入的是相应的ViewGroup
- 参数2:是一个回掉
- shouldInterceptTouchEvent(MotionEvent ev)
- 处理事件分发的(主要是将ViewGroup的事件分发,委托给ViewDragHelper进行处理)
- 参数1:MotionEvent ev 主要是ViewGroup的事件
- processTouchEvent(MotionEvent event) 处理相应TouchEvent的方法,这里要注意一个问题,处理相应的TouchEvent的时候要将结果返回为true,消费本次事件!否则将无法使用ViewDragHelper处理相应的拖拽事件!
ViewDragHelper.Callback的API
- tryCaptureView(View child, int pointerId)
- 这是一个抽象类,必须去实现,也只有在这个方法返回true的时候下面的方法才会生效;
- 参数1:捕获的View(也就是你拖动的这个View)
- onViewDragStateChanged(int state)
- 当状态改变的时候回调,返回相应的状态(这里有三种状态)
- STATE_IDLE 闲置状态
- STATE_DRAGGING 正在拖动
- STATE_SETTLING 放置到某个位置
- onViewPositionChanged(View changedView, int left, int top, int dx, int dy)
- 当你拖动的View位置发生改变的时候回调
- 参数1:你当前拖动的这个View
- 参数2:距离左边的距离
- 参数3:距离右边的距离
- 参数4:x轴的变化量
- 参数5:y轴的变化量
- onViewCaptured(View capturedChild, int activePointerId)
- 捕获View的时候调用的方法
- 参数1:捕获的View(也就是你拖动的这个View)
- onViewReleased(View releasedChild, float xvel, float yvel)
- 当View停止拖拽的时候调用的方法,一般在这个方法中重置一些参数,比如回弹什么的
- 参数1:你拖拽的这个View
- 参数2:x轴的速率
- 参数3:y轴的速率
- clampViewPositionVertical(View child, int top, int dy)
- 竖直拖拽的时候回调的方法
- 参数1:拖拽的View
- 参数2:距离顶部的距离
- 参数3:变化量
- clampViewPositionHorizontal(View child, int left, int dx)
- 水平拖拽的时候回调的方法
- 参数1:拖拽的View
- 参数2:距离左边的距离
- 参数3:变化量
二、简单的实现demo
下面是简单实现的demo,可以直接复制使用的
1、BottomView的ViewDragHelper实现
- public class BottomView extends LinearLayout {
- private ViewDragHelper mDragHelper;
- private View view;
- private int mDragBorder, verticalRange, mDragState, peekHeight, mDragHeight;
- private final double AUTO_OPEN_SPEED_LIMIT = 800.0;
- private boolean inflate = false, isExpanded = false, isDragHeightSet = false;
- private MotionEvent globalEvent;
- View try_view;
- public BottomView(Context context) {
- super(context);
- }
- public BottomView(Context context, @Nullable AttributeSet attrs) {
- super(context, attrs);
- initView(context, attrs);
- }
- public BottomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- initView(context, attrs);
- }
- void initView(Context context, AttributeSet attrs) {
- peekHeight = 300;
- }
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- }
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mDragHelper = ViewDragHelper.create(this, 1.0f, new DragHelperCallback());
- view = getChildAt(0);
- try_view = findViewById(R.id.ll_try_view);
- }
- @Override
- protected void onLayout(boolean b, int left, int top, int right, int bottom) {
- verticalRange = getMeasuredHeight() - peekHeight;
- if (!inflate) {
- mDragBorder = verticalRange;
- inflate = true;
- }
- view.layout(left, mDragBorder, right, bottom + mDragBorder);
- }
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- int action = MotionEventCompat.getActionMasked(ev);
- if ((action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) && !isDraggingAllowed(ev)) {
- mDragHelper.cancel();
- return false;
- }
- return mDragHelper.shouldInterceptTouchEvent(ev);
- }
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- if (isDraggingAllowed(event) || isMoving()) {
- mDragHelper.processTouchEvent(event);
- return true;
- }
- return super.onTouchEvent(event);
- }
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- globalEvent = ev;
- return super.dispatchTouchEvent(ev);
- }
- boolean isDraggingAllowed(MotionEvent event) {
- int[] viewLocations = new int[2];
- view.getLocationOnScreen(viewLocations);
- int upperLimit = viewLocations[1] + (isDragHeightSet ? mDragHeight : peekHeight);
- int lowerLimit = viewLocations[1];
- int y = (int) event.getRawY();
- return (y > lowerLimit && y < upperLimit);
- }
- boolean isMoving() {
- return (mDragState == ViewDragHelper.STATE_DRAGGING ||
- mDragState == ViewDragHelper.STATE_SETTLING);
- }
- class DragHelperCallback extends ViewDragHelper.Callback {
- @Override
- public boolean tryCaptureView(View child, int pointerId) {
- return true;
- }
- @Override
- public int clampViewPositionVertical(View child, int top, int dy) {
- return top;
- }
- @Override
- public void onViewDragStateChanged(int state) {
- super.onViewDragStateChanged(state);
- mDragState = state;
- }
- @Override
- public int getViewVerticalDragRange(View child) {
- return verticalRange;
- }
- @Override
- public void onViewReleased(View releasedChild, float xvel, float yvel) {
- super.onViewReleased(releasedChild, xvel, yvel);
- boolean settleToOpen = false;
- if (yvel > AUTO_OPEN_SPEED_LIMIT && xvel < yvel) {
- settleToOpen = true;
- } else if (yvel < -AUTO_OPEN_SPEED_LIMIT && xvel > yvel) {
- settleToOpen = false;
- } else if (mDragBorder > (2 * verticalRange / 3)) {
- settleToOpen = true;
- } else if (mDragBorder < (verticalRange / 3)) {
- settleToOpen = false;
- }
- final int settleDestY = settleToOpen ? verticalRange : 0;
- isExpanded = settleToOpen ? false : true;
- if (mDragHelper.settleCapturedViewAt(releasedChild.getLeft(), settleDestY)) {
- ViewCompat.postInvalidateOnAnimation(BottomView.this);
- }
- }
- @Override
- public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
- super.onViewPositionChanged(changedView, left, top, dx, dy);
- mDragBorder = top < 0 ? 0 : top > verticalRange ? verticalRange : top;
- float offset = 1 - ((float) mDragBorder / verticalRange);
- // if (listener != null) listener.onDrag(offset);
- requestLayout();
- }
- }
- @Override
- public void computeScroll() {
- super.computeScroll();
- if (mDragHelper.continueSettling(true)) {
- ViewCompat.postInvalidateOnAnimation(this);
- }
- }
- public boolean expandOnTouchView() {
- if (isDraggingAllowed(globalEvent) && mDragHelper.smoothSlideViewTo(view, 0, 0)) {
- isExpanded = true;
- ViewCompat.postInvalidateOnAnimation(BottomView.this);
- return true;
- }
- return false;
- }
- public boolean expandView() {
- if (mDragHelper.smoothSlideViewTo(view, 0, 0)) {
- isExpanded = true;
- ViewCompat.postInvalidateOnAnimation(BottomView.this);
- return true;
- }
- return false;
- }
- public boolean collapseOnTouchView() {
- if (isDraggingAllowed(globalEvent) && mDragHelper.smoothSlideViewTo(view, 0, verticalRange)) {
- isExpanded = false;
- ViewCompat.postInvalidateOnAnimation(BottomView.this);
- return true;
- }
- return false;
- }
- public boolean collapseView() {
- if (mDragHelper.smoothSlideViewTo(view, 0, verticalRange)) {
- isExpanded = false;
- ViewCompat.postInvalidateOnAnimation(BottomView.this);
- return true;
- }
- return false;
- }
- public boolean isViewExpanded() {
- return isExpanded;
- }
- public void setPeekHeight(int peekHeight) {
- this.peekHeight = peekHeight;
- requestLayout();
- }
- public void dragHeight(int mDragHeight) {
- isDragHeightSet = true;
- this.mDragHeight = mDragHeight;
- }
- }
2、布局文件
总结
面对不懂的知识点,不要害怕,勇敢面对;一起加油
本文转载自微信公众号「Android开发编程」