Android高手進(jìn)階之ViewDragHelper使用詳解以及拖動(dòng)上下滑卡片實(shí)現(xiàn)
前言
正好項(xiàng)目中有個(gè)頁(yè)面底部拖動(dòng)上下滑的UI;
今天我們就來(lái)講解下ViewDragHelper;
這幾天項(xiàng)目比較忙,文章更新會(huì)慢,各位老鐵可以看歷史記錄;
一、viewDragHleper詳解
ViewDragHelper是針對(duì) ViewGroup 中的拖拽和重新定位 views 操作時(shí)提供了一系列非常有用的方法和狀態(tài)追蹤;
1、ViewDragHelper初始化
- public class ViewDragTest extends LinearLayout {
- ViewDragHelper mViewDragHelper;
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- //中間參數(shù)表示靈敏度,比如滑動(dòng)了多少像素才視為觸發(fā)了滑動(dòng).值越大越靈敏.
- mViewDragHelper = ViewDragHelper.create(this, 1f, new DragCallback());
- }
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- //固定寫(xiě)法
- 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) {
- //固定寫(xiě)法
- mViewDragHelper.processTouchEvent(event);
- return true;
- }
- @Override
- public void computeScroll() {
- //固定寫(xiě)法
- //此方法用于自動(dòng)滾動(dòng),比如自動(dòng)回滾到默認(rèn)位置.
- if (mViewDragHelper.continueSettling(true)) {
- ViewCompat.postInvalidateOnAnimation(this);
- }
- }
- }
2、ViewDragHelper.Callback
- //這個(gè)類的回調(diào)方法,才是ViewDragHelper的重點(diǎn)
- private class ViewDragCallback extends ViewDragHelper.Callback{
- @Override
- public boolean tryCaptureView(View child, int pointerId) {
- //child 表示想要滑動(dòng)的view
- //pointerId 表示觸摸點(diǎn)的id, 比如多點(diǎn)按壓的那個(gè)id
- //返回值表示,是否可以capture,也就是是否可以滑動(dòng).可以根據(jù)不同的child決定是否可以滑動(dòng)
- return true;
- }
- @Override
- public int clampViewPositionHorizontal(View child, int left, int dx) {
- //child 表示當(dāng)前正在移動(dòng)的view
- //left 表示當(dāng)前的view正要移動(dòng)到左邊距為left的地方
- //dx 表示和上一次滑動(dòng)的距離間隔
- //返回值就是child要移動(dòng)的目標(biāo)位置.可以通過(guò)控制返回值,從而控制child只能在ViewGroup的范圍中移動(dòng).
- return left;
- }
- @Override
- public int clampViewPositionVertical(View child, int top, int dy) {
- //child 表示當(dāng)前正在移動(dòng)的view
- //top 表示當(dāng)前的view正要移動(dòng)到上邊距為top的地方
- //dx 表示和上一次滑動(dòng)的距離間隔
- return top;
- }
- }
重寫(xiě)以上3個(gè)方法,可以正常工作了.子View就可以被任意拖動(dòng)了;
3、控制child的移動(dòng)范圍在父view中
- //控制child只能在ViewGroup的橫向中移動(dòng)
- @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的縱向中移動(dòng)
- @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、開(kāi)啟邊界滑動(dòng)
- //開(kāi)啟4個(gè)邊
- mViewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_ALL);
- //各個(gè)邊
- 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;
- //當(dāng)開(kāi)啟邊界滑動(dòng)之后, 此方法就會(huì)回調(diào)
- @Override
- public void onEdgeTouched(int edgeFlags, int pointerId) {
- //通常開(kāi)啟邊界之后, 都需要手動(dòng)capture view.之后就可以滑動(dòng)view了.
- mViewDragHelper.captureChildView(getChildAt(1), pointerId);
- }
- @Override
- public boolean tryCaptureView(View child, int pointerId) {
- //開(kāi)啟邊界之后, 這個(gè)方法的返回值可能需要進(jìn)一步處理.要不然開(kāi)邊界就沒(méi)啥意思了.
- return false;
- }
5、釋放后的回彈效果
有些時(shí)候, 當(dāng)釋放的時(shí)候, 需要將View回到原來(lái)的位置;
- //釋放的時(shí)候, 會(huì)回調(diào)下面的方法
- @Override
- public void onViewReleased(View releasedChild, float xvel, float yvel) {
- //調(diào)用這個(gè)方法,就可以設(shè)置releasedChild回彈得位置.
- mViewDragHelper.settleCapturedViewAt(0, 100);//參數(shù)就是x,y的坐標(biāo)
- postInvalidate();//注意一定要調(diào)用這個(gè)方法,否則沒(méi)效果.
- }
- //以下2個(gè)方法最終調(diào)用的都是forceSettleCapturedViewAt().
- mViewDragHelper.settleCapturedViewAt(0, 100);
- mViewDragHelper.smoothSlideViewTo(getChildAt(1), 0, 100);
- //所以...發(fā)揮你的想象力,看看有什么妙用!!!
- //如果你還沒(méi)有忘記的話...前文應(yīng)該有說(shuō)過(guò),涉及到scroll,需要重寫(xiě)view的此方法.
- //此方法一定要重寫(xiě),否則沒(méi)效果
- @Override
- public void computeScroll() {
- //固定寫(xiě)法
- if (mViewDragHelper.continueSettling(true)) {
- postInvalidate();//注意此處.
- }
- }
- 通過(guò)上面2個(gè)方法的設(shè)置, 當(dāng)手指釋放的時(shí)候, View就會(huì)自動(dòng)滑動(dòng)到指定的位置...(不是一下子就到指定的位置哦,有一個(gè)滑動(dòng)的過(guò)程.)
- 注意:如果需要滑動(dòng)的View,會(huì)消耗touch事件,比如:Button,那么需要重寫(xiě)以下方法.
- @Override
- public int getViewHorizontalDragRange(View child) {
- return child.getMeasuredWidth();//只要返回大于0的值就行
- }
- @Override
- public int getViewVerticalDragRange(View child) {
- return child.getMeasuredHeight();//只要返回大于0的值就行
- }
6、簡(jiǎn)單api介紹
ViewDragHelper的API
- ViewDragHelper create(ViewGroup forParent, Callback cb);
- 一個(gè)靜態(tài)的創(chuàng)建方法,
- 參數(shù)1:出入的是相應(yīng)的ViewGroup
- 參數(shù)2:是一個(gè)回掉
- shouldInterceptTouchEvent(MotionEvent ev)
- 處理事件分發(fā)的(主要是將ViewGroup的事件分發(fā),委托給ViewDragHelper進(jìn)行處理)
- 參數(shù)1:MotionEvent ev 主要是ViewGroup的事件
- processTouchEvent(MotionEvent event) 處理相應(yīng)TouchEvent的方法,這里要注意一個(gè)問(wèn)題,處理相應(yīng)的TouchEvent的時(shí)候要將結(jié)果返回為true,消費(fèi)本次事件!否則將無(wú)法使用ViewDragHelper處理相應(yīng)的拖拽事件!
ViewDragHelper.Callback的API
- tryCaptureView(View child, int pointerId)
- 這是一個(gè)抽象類,必須去實(shí)現(xiàn),也只有在這個(gè)方法返回true的時(shí)候下面的方法才會(huì)生效;
- 參數(shù)1:捕獲的View(也就是你拖動(dòng)的這個(gè)View)
- onViewDragStateChanged(int state)
- 當(dāng)狀態(tài)改變的時(shí)候回調(diào),返回相應(yīng)的狀態(tài)(這里有三種狀態(tài))
- STATE_IDLE 閑置狀態(tài)
- STATE_DRAGGING 正在拖動(dòng)
- STATE_SETTLING 放置到某個(gè)位置
- onViewPositionChanged(View changedView, int left, int top, int dx, int dy)
- 當(dāng)你拖動(dòng)的View位置發(fā)生改變的時(shí)候回調(diào)
- 參數(shù)1:你當(dāng)前拖動(dòng)的這個(gè)View
- 參數(shù)2:距離左邊的距離
- 參數(shù)3:距離右邊的距離
- 參數(shù)4:x軸的變化量
- 參數(shù)5:y軸的變化量
- onViewCaptured(View capturedChild, int activePointerId)
- 捕獲View的時(shí)候調(diào)用的方法
- 參數(shù)1:捕獲的View(也就是你拖動(dòng)的這個(gè)View)
- onViewReleased(View releasedChild, float xvel, float yvel)
- 當(dāng)View停止拖拽的時(shí)候調(diào)用的方法,一般在這個(gè)方法中重置一些參數(shù),比如回彈什么的
- 參數(shù)1:你拖拽的這個(gè)View
- 參數(shù)2:x軸的速率
- 參數(shù)3:y軸的速率
- clampViewPositionVertical(View child, int top, int dy)
- 豎直拖拽的時(shí)候回調(diào)的方法
- 參數(shù)1:拖拽的View
- 參數(shù)2:距離頂部的距離
- 參數(shù)3:變化量
- clampViewPositionHorizontal(View child, int left, int dx)
- 水平拖拽的時(shí)候回調(diào)的方法
- 參數(shù)1:拖拽的View
- 參數(shù)2:距離左邊的距離
- 參數(shù)3:變化量
二、簡(jiǎn)單的實(shí)現(xiàn)demo
下面是簡(jiǎn)單實(shí)現(xiàn)的demo,可以直接復(fù)制使用的
1、BottomView的ViewDragHelper實(shí)現(xiàn)
- 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、布局文件
總結(jié)
面對(duì)不懂的知識(shí)點(diǎn),不要害怕,勇敢面對(duì);一起加油
本文轉(zhuǎn)載自微信公眾號(hào)「Android開(kāi)發(fā)編程」