ViewDragHelper使用詳解

一、基本介紹

 

前輩們已經總結的很多了,所以從別人的博客裏直接複製的比較多,用到的源碼我也會經過修改。用到的博客我會在下方註釋。

 

最近發下一篇寫的不錯的ViewDragHelper的博客,比較清晰,調理,看完這一片再看下面的效果會更好點點擊跳轉

 

2013年穀歌i/o大會上介紹了兩個新的layout: SlidingPaneLayout和DrawerLayout,現在這倆個類被廣泛的運用,其實研究他們的源碼你會發現這兩個類都運用了ViewDragHelper來處理拖動。ViewDragHelper是framework中不爲人知卻非常有用的一個工具

ViewDragHelper解決了Android中手勢處理過於複雜的問題,在DrawerLayout出現之前,側滑菜單都是由第三方開源代碼實現的,其中著名的當屬MenuDrawer(現在用android.support.v4.widget.DrawerLayout)。

其實ViewDragHelper並不是第一個用於分析手勢處理的類,gesturedetector也是,但是在和拖動相關的手勢分析方面gesturedetector只能說是勉爲其難。

關於ViewDragHelper有如下幾點:

   1、ViewDragHelper.Callback是連接ViewDragHelper與view之間的橋樑(這個view一般是指擁子view的容器即parentView);

   2、ViewDragHelper的實例是通過靜態工廠方法創建的;

   3、你能夠指定拖動的方向;

   4、ViewDragHelper可以檢測到是否觸及到邊緣;

   5、ViewDragHelper並不是直接作用於要被拖動的View,而是使其控制的視圖容器中的子View可以被拖動,如果要指定某個子view的行爲,需要在Callback中想辦法;

  6、ViewDragHelper的本質其實是分析onInterceptTouchEventonTouchEvent的MotionEvent參數,然後根據分析的結果去改變一個容器中被拖動子View的位 通過offsetTopAndBottom(int offset)和offsetLeftAndRight(int offset)方法 ),他能在觸摸的時候判斷當前拖動的是哪個子View;

   7、雖然ViewDragHelper的實例方法 ViewDragHelper create(ViewGroup forParent, Callback cb) 可以指定一個被

ViewDragHelper處理拖動事件的對象 ,但ViewDragHelper類的設計決定了其適用於被包含在一個自定義ViewGroup之中,而不是對

任意一個佈局上的視圖容器使用ViewDragHelper

注意:ViewDragHelper是作用在一個ViewGroup上,也就是說他不能直接作用到被拖拽的view, 其實這也很好理

 

解,因爲view在佈局中的位置是父ViewGroup決定的。

 

 

如何使用ViewGroup實現一個可以拖動的view?

1、獲取ViewDragHelper的實例,注意,這裏不能直接new,而是使用ViewDragHelper的一個靜態方法:

ViewDragHelper.create(ViewGroup forParent, float sensitivity, ViewDragHelper.Callback cb);

參數1: 一個ViewGroup, 也就是ViewDragHelper將要用來拖拽誰下面的子view

參數2:靈敏度,一般設置爲1.0f就行

參數3:一個回調,用來處理拖動到位置

 

2、在Callback對象裏面,下面幾個方法非常重要。

 (1) public int clampViewPositionHorizontal(View child, int left, int dx)

          這個是返回被橫向移動的子控件child的左座標left,和移動距離dx,我們可以根據這些值來返回child的新的left。

          返回值該child現在的位置,  這個方法必須重寫,要不然就不能移動了。

(2)public int clampViewPositionVertical(View child, int top, int dy) 

         這個和上面的方法一個意思,就是換成了垂直方向的移動和top座標。

         如果有垂直移動,這個也必須重寫,要不默認返回0,也不能移動了。

(3)public abstract boolean tryCaptureView(View child, int pointerId) 

表示嘗試捕獲子view,這裏一定要返回true, 返回true表示允許。           

這個方法用來返回可以被移動的View對象,我們可以通過判斷child與我們想移動的View是的相等來控制誰能移動。

(4)public int getViewVerticalDragRange(View child)

          這個用來控制垂直移動的邊界範圍,單位是像素。

(5)public int getViewHorizontalDragRange(View child) 

          和上面一樣,就是是橫向的邊界範圍。

(6)public void onViewReleased(View releasedChild, float xvel, float yvel) 

           當releasedChild被釋放的時候,xvel和yvel是x和y方向的加速度

(7)public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) 

這個是當changedView的位置發生變化時調用,我們可以在這裏面控制View的顯示位置和移動。

    我們前面雖然獲取了ViewDragHelper的對象,但是現在我們還是不能接收到事件的,我們需要在onTouch()和onInterceptTouchEvent()裏面,將觸摸事件傳入到ViewDragHelper裏面,才能進行處理,就像下面這樣,注意需要在clampViewPositionHorizontal和clampViewPositionHorizontal中做一點工作,以防止view超出了邊界。

 

[java] view plain copy

  1. @Override    
  2. public boolean onInterceptTouchEvent(MotionEvent ev) {    
  3.     
  4.     …    
  5.     
  6.     return mDragHelper.shouldInterceptTouchEvent(ev);    
  7. }   

 

 

[java] view plain copy

  1. public boolean onTouchEvent(MotionEvent event) {    
  2.         
  3.     …    
  4.     mDragHelper.processTouchEvent(event);    
  5.     return true;    
  6. }  

 


   傳遞給ViewDragHelper之後,我們就可以在Callback的各個事件裏面進行處理了。

 

二、實戰1

例1:

 

 

[java] view plain copy

  1. public class CustomView extends LinearLayout {  
  2.     private ViewDragHelper mDragHelper;  
  3.   
  4.     public CustomView(Context context) {  
  5.         super(context);  
  6.         init();  
  7.     }  
  8.   
  9.     public CustomView(Context context, AttributeSet attrs) {  
  10.         super(context, attrs);  
  11.         init();  
  12.     }  
  13.   
  14.     public CustomView(Context context, AttributeSet attrs, int defStyle) {  
  15.         super(context, attrs, defStyle);  
  16.         init();  
  17.     }  
  18.   
  19.     private void init() {  
  20.         /** 
  21.          * @params ViewGroup forParent 必須是一個ViewGroup 
  22.          * @params float sensitivity 靈敏度 
  23.          * @params Callback cb 回調 
  24.          */  
  25.         mDragHelper = ViewDragHelper.create(this1.0f, new ViewDragCallback());  
  26.     }  
  27.       
  28.     private class ViewDragCallback extends ViewDragHelper.Callback {  
  29.         /** 
  30.          * 嘗試捕獲子view,一定要返回true 
  31.          * @param View child 嘗試捕獲的view 
  32.          * @param int pointerId 指示器id?  
  33.          * 這裏可以決定哪個子view可以拖動 
  34.          */  
  35.         @Override  
  36.         public boolean tryCaptureView(View view, int pointerId) {  
  37. //          return mCanDragView == view;  
  38.             return true;  
  39.         }  
  40.           
  41.         /** 
  42.          * 處理水平方向上的拖動 
  43.          * @param View child 被拖動到view 
  44.          * @param int left 移動到達的x軸的距離 
  45.          * @param int dx 建議的移動的x距離 
  46.          */  
  47.         @Override  
  48.         public int clampViewPositionHorizontal(View child, int left, int dx) {  
  49.             System.out.println("left = " + left + ", dx = " + dx);  
  50.               
  51.             // 兩個if主要是爲了讓viewViewGroup裏  
  52.             if(getPaddingLeft() > left) {  
  53.                 return getPaddingLeft();  
  54.             }  
  55.               
  56.             if(getWidth() - child.getWidth() < left) {  
  57.                 return getWidth() - child.getWidth();  
  58.             }  
  59.               
  60.             return left;  
  61.         }  
  62.           
  63.         /** 
  64.          *  處理豎直方向上的拖動 
  65.          * @param View child 被拖動到view 
  66.          * @param int top 移動到達的y軸的距離 
  67.          * @param int dy 建議的移動的y距離 
  68.          */  
  69.         @Override  
  70.         public int clampViewPositionVertical(View child, int top, int dy) {  
  71.             // 兩個if主要是爲了讓viewViewGroup裏  
  72.             if(getPaddingTop() > top) {  
  73.                 return getPaddingTop();  
  74.             }  
  75.               
  76.             if(getHeight() - child.getHeight() < top) {  
  77.                 return getHeight() - child.getHeight();  
  78.             }  
  79.               
  80.             return top;  
  81.         }  
  82.           
  83.         /** 
  84.          * 當拖拽到狀態改變時回調 
  85.          * @params 新的狀態 
  86.          */  
  87.         @Override  
  88.         public void onViewDragStateChanged(int state) {  
  89.             switch (state) {  
  90.             case ViewDragHelper.STATE_DRAGGING:  // 正在被拖動  
  91.                 break;  
  92.             case ViewDragHelper.STATE_IDLE:  // view沒有被拖拽或者 正在進行fling/snap  
  93.                 break;  
  94.             case ViewDragHelper.STATE_SETTLING: // fling完畢後被放置到一個位置  
  95.                 break;  
  96.             }  
  97.             super.onViewDragStateChanged(state);  
  98.         }  
  99.     }  
  100.       
  101.     @Override  
  102.     public boolean onInterceptTouchEvent(MotionEvent ev) {  
  103.         switch (ev.getAction()) {  
  104.         case MotionEvent.ACTION_CANCEL:  
  105.         case MotionEvent.ACTION_DOWN:  
  106.             mDragHelper.cancel(); // 相當於調用 processTouchEvent收到ACTION_CANCEL  
  107.             break;  
  108.         }  
  109.           
  110.         /** 
  111.          * 檢查是否可以攔截touch事件 
  112.          * 如果onInterceptTouchEvent可以return true 則這裏return true 
  113.          */  
  114.         return mDragHelper.shouldInterceptTouchEvent(ev);  
  115.     }  
  116.       
  117.     @Override  
  118.     public boolean onTouchEvent(MotionEvent event) {  
  119.         /** 
  120.          * 處理攔截到的事件 
  121.          * 這個方法會在返回前分發事件 
  122.          */  
  123.         mDragHelper.processTouchEvent(event);  
  124.         return true;  
  125.     }  
  126. }  

layout:

 

[java] view plain copy

  1. <span style="color:#999999;"><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:paddingBottom="@dimen/activity_vertical_margin"  
  6.     android:paddingLeft="@dimen/activity_horizontal_margin"  
  7.     android:paddingRight="@dimen/activity_horizontal_margin"  
  8.     android:paddingTop="@dimen/activity_vertical_margin"  
  9.     tools:context=".MainActivity" >  
  10.   
  11.     <com.bluemor.reddotface.view.CustomView  
  12.         android:layout_width="match_parent"  
  13.         android:layout_height="match_parent">  
  14.         <View  
  15.             android:layout_width="100dp"  
  16.             android:layout_height="100dp"  
  17.             android:background="#FFFF0000" />  
  18.     </com.bluemor.reddotface.view.CustomView>  
  19.   
  20. </RelativeLayout></span>  

 

 

三、實戰2

 
 

DragLayout.Java

 

[java] view plain copy

  1. public class DragLayout extends FrameLayout {  
  2.     private Context context;  
  3.     private GestureDetectorCompat gestureDetector;  
  4.     private ViewDragHelper dragHelper;  
  5.     private DragListener dragListener;  
  6.   
  7.     /** 水平可以滾動的範圍 */  
  8.     private int horizontalRange;  
  9.     /** 垂直可以滾動的範圍 */  
  10.     private int verticalRange;  
  11.     /** 默認滾動式水平的 */  
  12.     private Orientation orientation = Orientation.Horizontal;  
  13.   
  14.     private int viewWidth;  
  15.     private int viewHeight;  
  16.     private int distanceLeft;  
  17.     private int distanceTop;  
  18.   
  19.     private ViewGroup layoutMenu;  
  20.     private ViewGroup layoutContent;  
  21.   
  22.     private Status status = Status.Close;  
  23.   
  24.     public DragLayout(Context context) {  
  25.         this(context, null);  
  26.     }  
  27.   
  28.     public DragLayout(Context context, AttributeSet attrs) {  
  29.         this(context, attrs, 0);  
  30.         this.context = context;  
  31.     }  
  32.   
  33.     public DragLayout(Context context, AttributeSet attrs, int defStyle) {  
  34.         super(context, attrs, defStyle);  
  35.         gestureDetector = new GestureDetectorCompat(context,  
  36.                 new XYScrollDetector());  
  37.         dragHelper = ViewDragHelper.create(this, dragHelperCallback);  
  38.     }  
  39.   
  40.     class XYScrollDetector extends SimpleOnGestureListener {  
  41.         @Override  
  42.         public boolean onScroll(MotionEvent e1, MotionEvent e2, float dx,  
  43.                 float dy) {  
  44.             if (orientation == Orientation.Vertical) {  
  45.                 return Math.abs(dy) >= Math.abs(dx);  
  46.             }  
  47.             return Math.abs(dy) <= Math.abs(dx);  
  48.         }  
  49.     }  
  50.   
  51.     private ViewDragHelper.Callback dragHelperCallback = new ViewDragHelper.Callback() {  
  52.   
  53.         // 這個是返回被橫向移動的子控件child的左座標left,和移動距離dx,我們可以根據這些值來返回child的新的left。  
  54.         // 這個方法必須重寫,要不然就不能移動了。  
  55.         // 返回橫向座標左右邊界值  
  56.         @Override  
  57.         public int clampViewPositionHorizontal(View child, int left, int dx) {  
  58.   
  59.             if (orientation == Orientation.Vertical)  
  60.                 return 0;  
  61.             if (distanceLeft + dx < 0) {  
  62.                 // 右邊界  
  63.                 return 0;  
  64.             } else if (distanceLeft + dx > horizontalRange) {  
  65.                 // 左邊界  
  66.                 return horizontalRange;  
  67.             } else {  
  68.                 // 左右邊界範圍內  
  69.                 return left;  
  70.             }  
  71.         }  
  72.   
  73.         // 這個方法用來返回可以被移動的View對象,我們可以通過判斷child與我們想移動的View是的相等來控制誰能移動。  
  74.         @Override  
  75.         public boolean tryCaptureView(View child, int pointerId) {  
  76.             return true;  
  77.         }  
  78.   
  79.         // 橫向的邊界範圍  
  80.         @Override  
  81.         public int getViewHorizontalDragRange(View child) {  
  82.             return horizontalRange;  
  83.         }  
  84.   
  85.         public int clampViewPositionVertical(View child, int top, int dy) {  
  86.   
  87.             if (orientation == Orientation.Horizontal)  
  88.                 return 0;  
  89.   
  90.             if (distanceTop + dy < 0) {  
  91.                 return 0;  
  92.             } else if (distanceTop + dy > verticalRange) {  
  93.                 return verticalRange;  
  94.             } else {  
  95.                 return top;  
  96.             }  
  97.         }  
  98.   
  99.         public int getViewVerticalDragRange(View child) {  
  100.   
  101.             return verticalRange;  
  102.   
  103.         };  
  104.   
  105.         // ACTION_UP事件後調用其方法  
  106.         // 當releasedChild被釋放的時候,xvel和yvel是x和y方向的加速度  
  107.         @Override  
  108.         public void onViewReleased(View releasedChild, float xvel, float yvel) {  
  109.             super.onViewReleased(releasedChild, xvel, yvel);  
  110.             if (orientation == Orientation.Vertical) {  
  111.                 if (releasedChild == layoutMenu)  
  112.                     return;  
  113.                 if (yvel > 0) {  
  114.                     // 加速度向下  
  115.                     open();  
  116.                 } else if (yvel < 0) {  
  117.                     // 加速度向上  
  118.                     close();  
  119.                 } else if (releasedChild == layoutContent  
  120.                         && distanceTop > verticalRange * 0.3) {  
  121.                     // 如果釋放時,手指在內容區且內容區離左邊的距離是range * 0.3  
  122.                     open();  
  123.                 } else {  
  124.                     close();  
  125.                 }  
  126.   
  127.             } else {  
  128.                 if (xvel > 0) {  
  129.                     // 加速度向  
  130.                     open();  
  131.                 } else if (xvel < 0) {  
  132.                     // 加速度向左  
  133.                     close();  
  134.                 } else if (releasedChild == layoutContent  
  135.                         && distanceLeft > horizontalRange * 0.3) {  
  136.                     // 如果釋放時,手指在內容區且內容區離左邊的距離是range * 0.3  
  137.                     open();  
  138.                 } else if (releasedChild == layoutMenu  
  139.                         && distanceLeft > horizontalRange * 0.7) {  
  140.                     // 如果釋放時,手指在菜單區且內容區離左邊的距離是range * 0.7  
  141.                     open();  
  142.                 } else {  
  143.                     close();  
  144.                 }  
  145.             }  
  146.   
  147.         }  
  148.   
  149.         // view在拖動過程座標發生變化時會調用此方法,包括兩個時間段:手動拖動和自動滾動  
  150.         @Override  
  151.         public void onViewPositionChanged(View changedView, int left, int top,  
  152.                 int dx, int dy) {  
  153.   
  154.             if (orientation == Orientation.Horizontal) {  
  155.                 if (changedView == layoutContent) {  
  156.                     distanceLeft = left;  
  157.                 } else {  
  158.                     distanceLeft = distanceLeft + left;  
  159.                 }  
  160.                 if (distanceLeft < 0) {  
  161.                     distanceLeft = 0;  
  162.                 } else if (distanceLeft > horizontalRange) {  
  163.                     distanceLeft = horizontalRange;  
  164.                 }  
  165.                 layoutMenu.layout(00, viewWidth, viewHeight);  
  166.                 layoutContent.layout(distanceLeft, 0, distanceLeft + viewWidth,  
  167.                         viewHeight);  
  168.                 dispatchDragEvent(distanceLeft);  
  169.             } else {  
  170.                 distanceTop = top;  
  171.                 if (distanceTop < 0) {  
  172.                     distanceTop = 0;  
  173.                 } else if (distanceTop > verticalRange) {  
  174.                     distanceTop = verticalRange;  
  175.                 }  
  176.                 layoutMenu.layout(00, viewWidth, viewHeight);  
  177.                 layoutContent.layout(0, distanceTop, viewWidth, distanceTop  
  178.                         + viewHeight);  
  179.                 dispatchDragEvent(distanceTop);  
  180.             }  
  181.         }  
  182.     };  
  183.   
  184.     public interface DragListener {  
  185.         /** 已經打開 */  
  186.         public void onOpen();  
  187.   
  188.         /** 已經關閉 */  
  189.         public void onClose();  
  190.   
  191.         /** 真在拖拽 */  
  192.         public void onDrag(float percent);  
  193.     }  
  194.   
  195.     public void setDragListener(DragListener dragListener) {  
  196.         this.dragListener = dragListener;  
  197.     }  
  198.   
  199.     @Override  
  200.     protected void onFinishInflate() {  
  201.         super.onFinishInflate();  
  202.         layoutMenu = (ViewGroup) getChildAt(0);  
  203.         layoutContent = (ViewGroup) getChildAt(1);  
  204.         layoutMenu.setClickable(true);  
  205.         layoutContent.setClickable(true);  
  206.     }  
  207.   
  208.     @Override  
  209.     protected void onSizeChanged(int w, int h, int oldw, int oldh) {  
  210.         super.onSizeChanged(w, h, oldw, oldh);  
  211.         viewWidth = layoutMenu.getMeasuredWidth();  
  212.         viewHeight = layoutMenu.getMeasuredHeight();  
  213.         horizontalRange = (int) (viewWidth * 0.7);  
  214.         verticalRange = (int) (viewHeight * 0.9);  
  215.     }  
  216.   
  217.     @Override  
  218.     protected void onLayout(boolean changed, int left, int top, int right,  
  219.             int bottom) {  
  220.         layoutMenu.layout(00, viewWidth, viewHeight);  
  221.         if (orientation == Orientation.Horizontal) {  
  222.             layoutContent.layout(distanceLeft, 0, distanceLeft + viewWidth,  
  223.                     viewHeight);  
  224.         } else {  
  225.             layoutContent.layout(0, distanceTop, viewWidth, distanceTop  
  226.                     + viewHeight);  
  227.         }  
  228.   
  229.     }  
  230.   
  231.     @Override  
  232.     public boolean onInterceptTouchEvent(MotionEvent ev) {  
  233.   
  234.         if (orientation == Orientation.Vertical) {  
  235.             if ((Status.Open == getStatus() && ev.getY() < verticalRange)) {  
  236.                 return false;  
  237.             }  
  238.         }  
  239.         return dragHelper.shouldInterceptTouchEvent(ev)  
  240.                 && gestureDetector.onTouchEvent(ev);  
  241.   
  242.     }  
  243.   
  244.     @Override  
  245.     public boolean onTouchEvent(MotionEvent e) {  
  246.         try {  
  247.   
  248.             // 在processTouchEvent中對ACTION_DOWN、ACTION_MOVE和ACTION_UP事件進行了處理:  
  249.             // 1.在ACTION_DOWN中調用回調接口中的tryCaptureView方法,看當前touch的view是否允許拖動  
  250.             // 在此項目中的是直接return true,兩個view都是允許拖動的  
  251.             // 2.在ACTION_MOVE中,view的座標發生改變,調用回調接口中的onViewPositionChanged方法,  
  252.             // 根據座標信息對view進行layout,通過ViewHelper這個類中的setScaleX、setScaleY方法,實現在  
  253.             // 拖動的過程中view在XY座標上進行相應比例的縮放;  
  254.             // 3.在ACTION_UP後調用回調接口中的onViewReleased方法,此方法中一個重要的任務是在ACTION_UP事件  
  255.             dragHelper.processTouchEvent(e);  
  256.         } catch (Exception ex) {  
  257.             ex.printStackTrace();  
  258.         }  
  259.         return false;  
  260.     }  
  261.   
  262.     private void dispatchDragEvent(int mainLeft) {  
  263.         float percent;  
  264.         if (orientation == Orientation.Horizontal) {  
  265.             percent = mainLeft / (float) horizontalRange;  
  266.             animateView(percent);  
  267.         } else {  
  268.             percent = mainLeft / (float) verticalRange;  
  269.         }  
  270.   
  271.         Status lastStatus = status;  
  272.         if (dragListener == null)  
  273.             return;  
  274.         dragListener.onDrag(percent);  
  275.         if (lastStatus != getStatus() && status == Status.Close) {  
  276.             dragListener.onClose();  
  277.         } else if (lastStatus != getStatus() && status == Status.Open) {  
  278.             dragListener.onOpen();  
  279.         }  
  280.     }  
  281.   
  282.     private void animateView(float percent) {  
  283.         float f1 = 1 - percent * 0.3f;  
  284.   
  285.         ViewHelper.setScaleX(layoutContent, f1);  
  286.         ViewHelper.setScaleY(layoutContent, f1);  
  287.   
  288.         ViewHelper.setTranslationX(  
  289.                 layoutMenu,  
  290.                 -layoutMenu.getWidth() / 2.3f  
  291.                         + layoutMenu.getWidth() / 2.3f * percent);  
  292.         ViewHelper.setScaleX(layoutMenu, 0.5f + 0.5f * percent);  
  293.         ViewHelper.setScaleY(layoutMenu, 0.5f + 0.5f * percent);  
  294.         ViewHelper.setAlpha(layoutMenu, percent);  
  295.   
  296.         getBackground().setColorFilter(  
  297.                 evaluate(percent, Color.BLACK, Color.TRANSPARENT),  
  298.                 Mode.SRC_OVER);  
  299.     }  
  300.   
  301.     private Integer evaluate(float fraction, Object startValue, Integer endValue) {  
  302.         int startInt = (Integer) startValue;  
  303.         int startA = (startInt >> 24) & 0xff;  
  304.         int startR = (startInt >> 16) & 0xff;  
  305.         int startG = (startInt >> 8) & 0xff;  
  306.         int startB = startInt & 0xff;  
  307.         int endInt = (Integer) endValue;  
  308.         int endA = (endInt >> 24) & 0xff;  
  309.         int endR = (endInt >> 16) & 0xff;  
  310.         int endG = (endInt >> 8) & 0xff;  
  311.         int endB = endInt & 0xff;  
  312.         return (int) ((startA + (int) (fraction * (endA - startA))) << 24)  
  313.                 | (int) ((startR + (int) (fraction * (endR - startR))) << 16)  
  314.                 | (int) ((startG + (int) (fraction * (endG - startG))) << 8)  
  315.                 | (int) ((startB + (int) (fraction * (endB - startB))));  
  316.     }  
  317.   
  318.     @Override  
  319.     public void computeScroll() {  
  320.         if (dragHelper.continueSettling(true)) {  
  321.             ViewCompat.postInvalidateOnAnimation(this);  
  322.         }  
  323.     }  
  324.   
  325.     public enum Status {  
  326.         Drag, Open, Close  
  327.     }  
  328.   
  329.     public enum Orientation {  
  330.         Horizontal, Vertical;  
  331.     }  
  332.   
  333.     public Status getStatus() {  
  334.         if (orientation == Orientation.Horizontal) {  
  335.             if (distanceLeft == 0) {  
  336.                 status = Status.Close;  
  337.             } else if (distanceLeft == horizontalRange) {  
  338.                 status = Status.Open;  
  339.             } else {  
  340.                 status = Status.Drag;  
  341.             }  
  342.         } else {  
  343.             if (distanceTop == 0) {  
  344.                 status = Status.Close;  
  345.             } else if (distanceTop == verticalRange) {  
  346.                 status = Status.Open;  
  347.             } else {  
  348.                 status = Status.Drag;  
  349.             }  
  350.         }  
  351.   
  352.         return status;  
  353.     }  
  354.   
  355.     public ViewGroup getlayoutContent() {  
  356.         return layoutContent;  
  357.     }  
  358.   
  359.     public ViewGroup getlayoutMenu() {  
  360.         return layoutMenu;  
  361.     }  
  362.   
  363.     public void setOrientation(Orientation orientation) {  
  364.         this.orientation = orientation;  
  365.     }  
  366.   
  367.     public void open() {  
  368.         open(true);  
  369.     }  
  370.   
  371.     public void open(boolean animate) {  
  372.         if (animate) {  
  373.             if (orientation == Orientation.Horizontal) {  
  374.                 if (dragHelper.smoothSlideViewTo(layoutContent,  
  375.                         horizontalRange, 0)) {  
  376.                     ViewCompat.postInvalidateOnAnimation(this);  
  377.                 }  
  378.             } else {  
  379.                 if (dragHelper.smoothSlideViewTo(layoutContent, 0,  
  380.                         verticalRange)) {  
  381.                     ViewCompat.postInvalidateOnAnimation(this);  
  382.                 }  
  383.             }  
  384.   
  385.         } else {  
  386.             layoutContent.layout(horizontalRange, 0, horizontalRange  
  387.                     + viewWidth, viewHeight);  
  388.             dispatchDragEvent(horizontalRange);  
  389.         }  
  390.     }  
  391.   
  392.     public void close() {  
  393.         close(true);  
  394.     }  
  395.   
  396.     public void close(boolean animate) {  
  397.         if (animate) {  
  398.   
  399.             if (dragHelper.smoothSlideViewTo(layoutContent, 00)) {  
  400.                 ViewCompat.postInvalidateOnAnimation(this);  
  401.             }  
  402.   
  403.         } else {  
  404.             layoutContent.layout(00, viewWidth, viewHeight);  
  405.             dispatchDragEvent(0);  
  406.         }  
  407.     }  
  408.   
  409. }  

layout.xml

 

[java] view plain copy

  1. <com.bluemor.reddotface.view.DragLayout   
  2.     xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:id="@+id/dl"  
  4.     android:layout_width="match_parent"  
  5.     android:layout_height="match_parent"  
  6.     android:background="@drawable/bg" >  
  7.   
  8.     <RelativeLayout  
  9.         android:layout_width="match_parent"  
  10.         android:layout_height="match_parent"  
  11.         android:paddingBottom="30dp"  
  12.         android:paddingLeft="30dp"  
  13.         android:onClick="true"  
  14.         android:paddingTop="50dp" >  
  15.   
  16.         <LinearLayout  
  17.             android:id="@+id/ll1"  
  18.             android:layout_width="match_parent"  
  19.             android:layout_height="wrap_content"  
  20.             android:orientation="horizontal" >  
  21.   
  22.             <ImageView  
  23.                 android:id="@+id/iv_bottom"  
  24.                 android:layout_width="70dp"  
  25.                 android:layout_height="70dp"  
  26.                 android:src="@drawable/ic_launcher" />  
  27.   
  28.             <TextView  
  29.                 android:layout_width="wrap_content"  
  30.                 android:layout_height="wrap_content"  
  31.                 android:layout_gravity="center_vertical"  
  32.                 android:layout_marginLeft="20dp"  
  33.                 android:text="BlueMor"  
  34.                 android:textColor="#ffffff"  
  35.                 android:textSize="25sp" />  
  36.         </LinearLayout>  
  37.   
  38.         <ListView  
  39.             android:id="@+id/lv"  
  40.             android:layout_width="match_parent"  
  41.             android:layout_height="wrap_content"  
  42.             android:layout_below="@id/ll1"  
  43.             android:layout_marginBottom="30dp"  
  44.             android:layout_marginTop="20dp"  
  45.             android:cacheColorHint="#00000000"  
  46.             android:divider="@null"  
  47.             android:textColor="#ffffff" />  
  48.     </RelativeLayout>  
  49.   
  50.     <RelativeLayout  
  51.         android:layout_width="match_parent"  
  52.         android:layout_height="match_parent"  
  53.         android:background="#eeeeee" >  
  54.   
  55.         <RelativeLayout  
  56.             android:id="@+id/rl_title"  
  57.             android:layout_width="match_parent"  
  58.             android:layout_height="55dp"  
  59.             android:background="#009990" >  
  60.   
  61.             <ImageView  
  62.                 android:id="@+id/iv_icon"  
  63.                 android:layout_width="42dp"  
  64.                 android:layout_height="42dp"  
  65.                 android:layout_centerVertical="true"  
  66.                 android:layout_marginLeft="10dp"  
  67.                 android:scaleType="centerCrop"  
  68.                 android:src="@drawable/ic_launcher" />  
  69.   
  70.             <TextView  
  71.                 android:layout_width="wrap_content"  
  72.                 android:layout_height="wrap_content"  
  73.                 android:layout_centerInParent="true"  
  74.                 android:text="系統相冊"  
  75.                 android:textColor="#ffffff"  
  76.                 android:textSize="20sp" />  
  77.         </RelativeLayout>  
  78.   
  79.         <GridView  
  80.             android:id="@+id/gv_img"  
  81.             android:layout_width="match_parent"  
  82.             android:layout_height="wrap_content"  
  83.             android:layout_below="@id/rl_title"  
  84.             android:cacheColorHint="#00000000"  
  85.             android:numColumns="4"  
  86.             android:verticalSpacing="20dp" >  
  87.         </GridView>  
  88.   
  89.     </RelativeLayout>  
  90.   
  91. </com.bluemor.reddotface.view.DragLayout>  

 

 

 

 

activity

 

 

[java] view plain copy

  1.         dl = (DragLayout) findViewById(R.id.dl);  
  2.         dl.setOrientation(Orientation.Vertical);  
  3. //      dl.setDragListener(new DragListener() {  
  4. //          @Override  
  5. //          public void onOpen() {  
  6. //              lv.smoothScrollToPosition(new Random().nextInt(30));  
  7. //          }  
  8. //  
  9. //          @Override  
  10. //          public void onClose() {  
  11. //                
  12. //          }  
  13. //  
  14. //          @Override  
  15. //          public void onDrag(float percent) {  
  16. //                
  17. //          }  
  18. //      });  

 

 

 

 

 

 

 

 

 

 

 

四、實戰3

例3:

ViewDragHelper一般用在一個自定義ViewGroup的內部,比如下面自定義了一個繼承於LinearLayout的DragLayout,DragLayout內部有一個子viewmDragView作爲成員變量:

 

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片

  1. public class DragLayout extends LinearLayout {  
  2. private final ViewDragHelper mDragHelper;  
  3. private View mDragView;  
  4. public DragLayout(Context context) {  
  5.   this(context, null);  
  6. }  
  7. public DragLayout(Context context, AttributeSet attrs) {  
  8.   this(context, attrs, 0);  
  9. }  
  10. public DragLayout(Context context, AttributeSet attrs, int defStyle) {  
  11.   super(context, attrs, defStyle);  
  12. }  

 

 


創建一個帶有回調接口的ViewDragHelper

 

 

 

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片

  1. public DragLayout(Context context, AttributeSet attrs, int defStyle) {  
  2.   super(context, attrs, defStyle);  
  3.   mDragHelper = ViewDragHelper.create(this, 1.0f, new DragHelperCallback());  
  4. }  

 

 

 

 

 

 

其中1.0f是敏感度參數參數越大越敏感。第一個參數爲this,表示該類生成的對象,他是ViewDragHelper的拖動處理對象,必須爲ViewGroup

要讓ViewDragHelper能夠處理拖動需要將觸摸事件傳遞給ViewDragHelper,這點和gesturedetector是一樣的:

 

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片

  1. @Override  
  2. public boolean onInterceptTouchEvent(MotionEvent ev) {  
  3.   final int action = MotionEventCompat.getActionMasked(ev);  
  4.   if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {  
  5.       mDragHelper.cancel();  
  6.       return false;  
  7.   }  
  8.   return mDragHelper.shouldInterceptTouchEvent(ev);  
  9. }  
  10. @Override  
  11. public boolean onTouchEvent(MotionEvent ev) {  
  12.   mDragHelper.processTouchEvent(ev);  
  13.   return true;  

 

 

 

 

接下來,你就可以在回調中處理各種拖動行爲了。

 

 

2.拖動行爲的處理

 

處理橫向的拖動:

DragHelperCallback中實現clampViewPositionHorizontal方法, 並且返回一個適當的數值就能實現橫向拖動效果,clampViewPositionHorizontal的第二個參數是指當前拖動子view應該到達的x座標。所以按照常理這個方法原封返回第二個參數就可以了,但爲了讓被拖動的view遇到邊界之後就不在拖動,對返回的值做了更多的考慮。

 

 

  1. @Override  
  2. public int clampViewPositionHorizontal(View child, int left, int dx) {  
  3.   Log.d("DragLayout", "clampViewPositionHorizontal " + left + "," + dx);  
  4.   final int leftBound = getPaddingLeft();  
  5.   final int rightBound = getWidth() - mDragView.getWidth();  
  6.   final int newLeft = Math.min(Math.max(left, leftBound), rightBound);  
  7.   return newLeft;  
  8. }  

 

 

 

 

 

 

 

同上,處理縱向的拖動:

DragHelperCallback中實現clampViewPositionVertical方法,實現過程同clampViewPositionHorizontal

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片

  1. @Override  
  2. public int clampViewPositionVertical(View child, int top, int dy) {  
  3.   final int topBound = getPaddingTop();  
  4.   final int bottomBound = getHeight() - mDragView.getHeight();  
  5.   final int newTop = Math.min(Math.max(top, topBound), bottomBound);  
  6.   return newTop;  
  7. }  


 

clampViewPositionHorizontal 和 clampViewPositionVertical必須要重寫,因爲默認它返回的是0。事實上我們在這兩個方法中所能做的事情很有限。 個人覺得這兩個方法的作用就是給了我們重新定義目的座標的機會。

通過DragHelperCallback的tryCaptureView方法的返回值可以決定一個parentview中哪個子view可以拖動,現在假設有兩個子views (mDragView1和mDragView2)  ,如下實現tryCaptureView之後,則只有mDragView1是可以拖動的。

1

2

3

4

@Override

public boolean tryCaptureView(View child, int pointerId) {

  returnchild == mDragView1;

}

 

滑動邊緣:

分爲滑動左邊緣還是右邊緣:EDGE_LEFT和EDGE_RIGHT,下面的代碼設置了可以處理滑動左邊緣:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片

  1. mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);  

 

假如如上設置,onEdgeTouched方法會在左邊緣滑動的時候被調用,這種情況下一般都是沒有和子view接觸的情況。

 

 

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片

  1. @Override  
  2. public void onEdgeTouched(int edgeFlags, int pointerId) {  
  3.     super.onEdgeTouched(edgeFlags, pointerId);  
  4.     Toast.makeText(getContext(), "edgeTouched", Toast.LENGTH_SHORT).show();  
  5. }  

 

 

 

 

如果你想在邊緣滑動的時候根據滑動距離移動一個子view,可以通過實現onEdgeDragStarted方法,並在onEdgeDragStarted方法中手動指定要移動的子View

 

 

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片

  1. @Override  
  2. public void onEdgeDragStarted(int edgeFlags, int pointerId) {  
  3.     mDragHelper.captureChildView(mDragView2, pointerId);  
  4. }  

 

 

 

 

 

 

ViewDragHelper讓我們很容易實現一個類似於YouTube視頻瀏覽效果的控件,效果如下:

 

代碼中的關鍵點:

1.tryCaptureView返回了唯一可以被拖動的header view;

2.拖動範圍drag range的計算是在onLayout中完成的;

3.注意在onInterceptTouchEvent和onTouchEvent中使用的ViewDragHelper的若干方法;

4.在computeScroll中使用continueSettling方法(因爲ViewDragHelper使用了scroller)

5.smoothSlideViewTo方法來完成拖動結束後的慣性操作。

需要注意的是代碼仍然有很大改進空間。

activity_main.xml

 

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片

  1. <FrameLayout  
  2.         xmlns:android="http://schemas.android.com/apk/res/android"  
  3.         android:layout_width="match_parent"  
  4.         android:layout_height="match_parent">  
  5.     <ListView  
  6.             android:id="@+id/listView"  
  7.             android:layout_width="match_parent"  
  8.             android:layout_height="match_parent"  
  9.             android:tag="list"  
  10.             />  
  11.     <com.example.vdh.YoutubeLayout  
  12.             android:layout_width="match_parent"  
  13.             android:layout_height="match_parent"  
  14.             android:id="@+id/youtubeLayout"  
  15.             android:orientation="vertical"  
  16.             android:visibility="visible">  
  17.         <TextView  
  18.                 android:id="@+id/viewHeader"  
  19.                 android:layout_width="match_parent"  
  20.                 android:layout_height="128dp"  
  21.                 android:fontFamily="sans-serif-thin"  
  22.                 android:textSize="25sp"  
  23.                 android:tag="text"  
  24.                 android:gravity="center"  
  25.                 android:textColor="@android:color/white"  
  26.                 android:background="#AD78CC"/>  
  27.         <TextView  
  28.                 android:id="@+id/viewDesc"  
  29.                 android:tag="desc"  
  30.                 android:textSize="35sp"  
  31.                 android:gravity="center"  
  32.                 android:text="Loreum Loreum"  
  33.                 android:textColor="@android:color/white"  
  34.                 android:layout_width="match_parent"  
  35.                 android:layout_height="match_parent"  
  36.                 android:background="#FF00FF"/>  
  37.     </com.example.vdh.YoutubeLayout>  
  38. </FrameLayout>  

 

 


YoutubeLayout.javaml] view plaincopy在CODE上查看代碼片派生到我的代碼片

 

 

 

 

 

  1. public class YoutubeLayout extends ViewGroup {  
  2. private final ViewDragHelper mDragHelper;  
  3. private View mHeaderView;  
  4. private View mDescView;  
  5. private float mInitialMotionX;  
  6. private float mInitialMotionY;  
  7. private int mDragRange;  
  8. private int mTop;  
  9. private float mDragOffset;  
  10. public YoutubeLayout(Context context) {  
  11.   this(context, null);  
  12. }  
  13. public YoutubeLayout(Context context, AttributeSet attrs) {  
  14.   this(context, attrs, 0);  
  15. }  
  16. @Override  
  17. protected void onFinishInflate() {  
  18.     mHeaderView = findViewById(R.id.viewHeader);  
  19.     mDescView = findViewById(R.id.viewDesc);  
  20. }  
  21. public YoutubeLayout(Context context, AttributeSet attrs, int defStyle) {  
  22.   super(context, attrs, defStyle);  
  23.   mDragHelper = ViewDragHelper.create(this, 1f, new DragHelperCallback());  
  24. }  
  25. public void maximize() {  
  26.     smoothSlideTo(0f);  
  27. }  
  28. boolean smoothSlideTo(float slideOffset) {  
  29.     final int topBound = getPaddingTop();  
  30.     int y = (int) (topBound + slideOffset * mDragRange);  
  31.     if (mDragHelper.smoothSlideViewTo(mHeaderView, mHeaderView.getLeft(), y)) {  
  32.         ViewCompat.postInvalidateOnAnimation(this);  
  33.         return true;  
  34.     }  
  35.     return false;  
  36. }  
  37. private class DragHelperCallback extends ViewDragHelper.Callback {  
  38.   @Override  
  39.   public boolean tryCaptureView(View child, int pointerId) {  
  40.         return child == mHeaderView;  
  41.   }  
  42.     @Override  
  43.   public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {  
  44.       mTop = top;  
  45.       mDragOffset = (float) top / mDragRange;  
  46.         mHeaderView.setPivotX(mHeaderView.getWidth());  
  47.         mHeaderView.setPivotY(mHeaderView.getHeight());  
  48.         mHeaderView.setScaleX(1 - mDragOffset / 2);  
  49.         mHeaderView.setScaleY(1 - mDragOffset / 2);  
  50.         mDescView.setAlpha(1 - mDragOffset);  
  51.         requestLayout();  
  52.   }  
  53.   @Override  
  54.   public void onViewReleased(View releasedChild, float xvel, float yvel) {  
  55.       int top = getPaddingTop();  
  56.       if (yvel > 0 || (yvel == 0 && mDragOffset > 0.5f)) {  
  57.           top += mDragRange;  
  58.       }  
  59.       mDragHelper.settleCapturedViewAt(releasedChild.getLeft(), top);  
  60.   }  
  61.   @Override  
  62.   public int getViewVerticalDragRange(View child) {  
  63.       return mDragRange;  
  64.   }  
  65.   @Override  
  66.   public int clampViewPositionVertical(View child, int top, int dy) {  
  67.       final int topBound = getPaddingTop();  
  68.       final int bottomBound = getHeight() - mHeaderView.getHeight() - mHeaderView.getPaddingBottom();  
  69.       final int newTop = Math.min(Math.max(top, topBound), bottomBound);  
  70.       return newTop;  
  71.   }  
  72. }  
  73. @Override  
  74. public void computeScroll() {  
  75.   if (mDragHelper.continueSettling(true)) {  
  76.       ViewCompat.postInvalidateOnAnimation(this);  
  77.   }  
  78. }  
  79. @Override  
  80. public boolean onInterceptTouchEvent(MotionEvent ev) {  
  81.   final int action = MotionEventCompat.getActionMasked(ev);  
  82.   if (( action != MotionEvent.ACTION_DOWN)) {  
  83.       mDragHelper.cancel();  
  84.       return super.onInterceptTouchEvent(ev);  
  85.   }  
  86.   if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {  
  87.       mDragHelper.cancel();  
  88.       return false;  
  89.   }  
  90.   final float x = ev.getX();  
  91.   final float y = ev.getY();  
  92.   boolean interceptTap = false;  
  93.   switch (action) {  
  94.       case MotionEvent.ACTION_DOWN: {  
  95.           mInitialMotionX = x;  
  96.           mInitialMotionY = y;  
  97.             interceptTap = mDragHelper.isViewUnder(mHeaderView, (int) x, (int) y);  
  98.           break;  
  99.       }  
  100.       case MotionEvent.ACTION_MOVE: {  
  101.           final float adx = Math.abs(x - mInitialMotionX);  
  102.           final float ady = Math.abs(y - mInitialMotionY);  
  103.           final int slop = mDragHelper.getTouchSlop();  
  104.           if (ady > slop && adx > ady) {  
  105.               mDragHelper.cancel();  
  106.               return false;  
  107.           }  
  108.       }  
  109.   }  
  110.   return mDragHelper.shouldInterceptTouchEvent(ev) || interceptTap;  
  111. }  
  112. @Override  
  113. public boolean onTouchEvent(MotionEvent ev) {  
  114.   mDragHelper.processTouchEvent(ev);  
  115.   final int action = ev.getAction();  
  116.     final float x = ev.getX();  
  117.     final float y = ev.getY();  
  118.     boolean isHeaderViewUnder = mDragHelper.isViewUnder(mHeaderView, (int) x, (int) y);  
  119.     switch (action & MotionEventCompat.ACTION_MASK) {  
  120.       case MotionEvent.ACTION_DOWN: {  
  121.           mInitialMotionX = x;  
  122.           mInitialMotionY = y;  
  123.           break;  
  124.       }  
  125.       case MotionEvent.ACTION_UP: {  
  126.           final float dx = x - mInitialMotionX;  
  127.           final float dy = y - mInitialMotionY;  
  128.           final int slop = mDragHelper.getTouchSlop();  
  129.           if (dx * dx + dy * dy < slop * slop && isHeaderViewUnder) {  
  130.               if (mDragOffset == 0) {  
  131.                   smoothSlideTo(1f);  
  132.               } else {  
  133.                   smoothSlideTo(0f);  
  134.               }  
  135.           }  
  136.           break;  
  137.       }  
  138.   }  
  139.   return isHeaderViewUnder && isViewHit(mHeaderView, (int) x, (int) y) || isViewHit(mDescView, (int) x, (int) y);  
  140. }  
  141. private boolean isViewHit(View view, int x, int y) {  
  142.     int[] viewLocation = new int[2];  
  143.     view.getLocationOnScreen(viewLocation);  
  144.     int[] parentLocation = new int[2];  
  145.     this.getLocationOnScreen(parentLocation);  
  146.     int screenX = parentLocation[0] + x;  
  147.     int screenY = parentLocation[1] + y;  
  148.     return screenX >= viewLocation[0] && screenX < viewLocation[0] + view.getWidth() &&  
  149.             screenY >= viewLocation[1] && screenY < viewLocation[1] + view.getHeight();  
  150. }  
  151. @Override  
  152. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  153.     measureChildren(widthMeasureSpec, heightMeasureSpec);  
  154.     int maxWidth = MeasureSpec.getSize(widthMeasureSpec);  
  155.     int maxHeight = MeasureSpec.getSize(heightMeasureSpec);  
  156.     setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, 0),  
  157.             resolveSizeAndState(maxHeight, heightMeasureSpec, 0));  
  158. }  
  159. @Override  
  160. protected void onLayout(boolean changed, int l, int t, int r, int b) {  
  161.   mDragRange = getHeight() - mHeaderView.getHeight();  
  162.     mHeaderView.layout(  
  163.             0,  
  164.             mTop,  
  165.             r,  
  166.             mTop + mHeaderView.getMeasuredHeight());  
  167.     mDescView.layout(  
  168.             0,  
  169.             mTop + mHeaderView.getMeasuredHeight(),  
  170.             r,  
  171.             mTop  + b);  
  172. }  

 

 

代碼下載地址:https://github.com/flavienlaurent/flavienlaurent.com

 

 

 

 

不管是menudrawer 還是本文實現的DragLayout都體現了一種設計哲學,即可拖動的控件都是封裝在一個自定義的Layout中的,爲什麼這樣做?爲什麼不直接將ViewDragHelper.create(this, 1f, new DragHelperCallback())中的this替換成任何已經佈局好的容器,這樣這個容器中的子View就能被拖動了,而往往是單獨定義一個Layout來處理?個人認爲如果在一般的佈局中去拖動子view並不會出現什麼問題,只是原本規則的世界被打亂了,而單獨一個Layout來完成拖動,無非是說,他本來就沒有什麼規則可言,拖動一下也無妨。

 

參考:http://www.xuebuyuan.com/2225442.html

           http://blog.csdn.net/pi9nc/article/details/39583377

           https://software.intel.com/zh-cn/blogs/2015/03/05/android-zlistview-listview-0

 

本文轉載自:http://blog.csdn.net/l25000/article/details/46345051#reply;

 

發佈了32 篇原創文章 · 獲贊 45 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章