Android高手進階之ViewDragHelper使用詳解以及拖動上下滑卡片實現
作者:Android開發編程
今天我們就來講解下ViewDragHelper;ViewDragHelper是針對 ViewGroup 中的拖拽和重新定位 views 操作時提供了一系列非常有用的方法和狀態追蹤。
前言
正好項目中有個頁面底部拖動上下滑的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開發編程」
責任編輯:姜華
來源:
Android開發編程