ViewGragHelper完全解析實現自定義ViewGroup
來自: http://www.lcode.org/神器viewgraghelper完全解析,媽媽再也不擔心我自定義viewgroup啦/
(一).前言:
這幾天正在更新錄制實戰項目,整體框架是采用仿照QQ5.X側滑效果的。那么我們一般的做法就是自定義ViewGroup或者采用開源項目MenuDrawer或者Google提供的控件DrawerLayout等方式來實現。這些的控件的很多效果基本上都是采用實現onInterceptTouchEvent和onTouchEvent這兩個方法進行實現,而且都是根據要實現的效果做自定義處理例如:多點觸控處理,加速度方面檢測以及控制等等。一般這樣做作于普通開發人員來講也是需要很強的程序與邏輯開發能力,幸好Android開發框架給我們提供了一個組件ViewDragHelper。那么今天我們來具體實現和分析一下ViewDragHelper
具體代碼已經上傳到下面的項目中,歡迎各位去star和fork一下。
FastDev4Android框架項目地址: https://github.com/jiangqqlmj/FastDev4Android
(二).ViewDragHelper基本介紹:
1.官方介紹如下:
對于自定義ViewGroup而言這邊的ViewDragHelper是一個很不錯的實用程序類。它給我們提供一系列的方法和相關狀態,讓我們可以進行拖拽移動或者重新定位ViewGroup中子視圖View。ViewGragHelper是一個簡化View的拖拽操作的幫助類,使用起來比較簡單與方便,一般我們只需要實現幾個方法和一個CallBack類就可以實現拖動的View。
2.在使用過程中我們一般會通過ViewDragHelper.Callback來進行連接ViewDragHelper和View。ViewDragHelper提供了一個靜態方法讓我們來創建實例,使用ViewDragHelper我們可以控制拖動的方向以及檢測是否已經拖動到屏幕的邊緣等相關操作。
3.ViewGragHelper.Callback中的相關方法說明:
Callback中的方法如下,其他包括一個抽象方法tryCaptureView()和十二個一般方法組成
tryCaptureView(View,int) | 傳遞當前觸摸的子View實例,如果當前的子View需要進行拖拽移動返回true |
clampViewPositionHorizontal | 決定拖拽的View在水平方向上面移動到的位置 |
clampViewPositionVertical | 決定拖拽的View在垂直方向上面移動到的位置 |
getViewHorizontalDragRange | 返回一個大于0的數,然后才會在水平方向移動 |
getViewVerticalDragRange | 返回一個大于0的數,然后才會在垂直方向移動 |
onViewDragStateChanged | 拖拽狀態發生變化回調 |
onViewPositionChanged |
當拖拽的View的位置發生變化的時候回調(特指capturedview) |
onViewCaptured | 捕獲captureview的時候回調 |
onViewReleased | 當拖拽的View手指釋放的時候回調 |
onEdgeTouched |
當觸摸屏幕邊界的時候回調 |
onEdgeLock |
是否鎖住邊界 |
onEdgeDrageStarted |
在邊緣滑動的時候可以設置滑動另一個子View跟著滑動 |
getOrderedChildIndex |
上面簡單講解了其中的一些方法說明,下面我們就需要使用這些方法以及ViewDragHelper本身提供的初始化方法以及設置方法來簡要說明使用ViewDragHelper使用流程。
(三).ViewDragHelper流程實例:
下面我們開始具體來使用ViewDragHelper了。
1.獲取ViewGragHelper實例:我們通過使用ViewDragHelper的一個靜態方法來創建實例:
public static ViewDragHelper create(ViewGroup forParent, float sensitivity, Callback cb) { final ViewDragHelper helper = create(forParent, cb); helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity)); return helper; }
- 參數1.一個ViewGroup,也就是拖動子View的父控件(ViewGroup)
- 參數2.靈敏度一般設置成1.0f,表示靈敏度最敏感
- 參數3.拖拽回調,用來處理拖動的位置等相關操作
具體使用如下:
mDragHelper = ViewDragHelper.create(this, 1.0f, new DragHelperCallback());
2.繼承ViewGragHelper.Callback類實現一個抽象方法:tryCaptureView()然后在內部進行處理需要捕獲的子View(用于拖拽操作和移動)
/** * 進行捕獲攔截,那些View可以進行drag操作 * @param child * @param pointerId * @return 直接返回true,攔截所有的VIEW */ @Override public boolean tryCaptureView(View child, int pointerId) { return true; }
這樣表示捕捉所有的子View,表示所有的子View都可以進行拖拽操作。
3.重寫onInterceptTouchView和onTouchEvent方法來攔截事件以及讓ViewDragHelper來進行處理攔截到得事件。因為ViewDragHelper的內部是根據觸摸等相關事件來實現拖拽的。
/** * 事件分發 * @param ev * @return */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return mDragHelper.shouldInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent ev) { mDragHelper.processTouchEvent(ev); return true; }
4.拖動行為處理,例如我們現在需要處理橫向的拖拽的,那么我們需要實現clampViewPositionHorizontal方法,并且返回一個適當的數值表示橫向拖拽的效果。 一般返回第二個參數即可,不過需要對邊界值做一下判斷這樣子View不要拖出屏幕。
【注】要實現橫向滑動這個方法必須要重寫,因為ViewGragHelper內部該方法的時候直接返回了0,看下內部實現代碼:
public int clampViewPositionHorizontal(View child, int left, int dx) { return 0; } 我們重寫的代碼如下: /** * 水平滑動 控制left * @param child * @param left * @param dx * @return */ @Override public int clampViewPositionHorizontal(View child, int left, int dx) { Log.d("DragLayout", "clampViewPositionHorizontal " + left + "," + dx); final int leftBound = getPaddingLeft(); final int rightBound = getWidth() - view_one.getWidth(); final int newLeft = Math.min(Math.max(left, leftBound), rightBound); return newLeft; }
同樣對于垂直方向拖拽的重寫clampViewPositionVertical()基本方法也差不多如下:
/** * 垂直滑動,控制top * @param child * @param top * @param dy * @return */ @Override public int clampViewPositionVertical(View child, int top, int dy) { Log.d("DragLayout", "clampViewPositionVertical " + top + "," + dy); final int topBound = getPaddingTop(); final int bottomBound = getHeight() - view_one.getHeight(); final int newTop = Math.min(Math.max(top, topBound), bottomBound); return newTop; }
5.基本功能實現核心代碼已經做完了,下面我們來實現一個布局文件:自定義組件ViewGragOne內部放入了兩個TextView子View,到時候我們主要拖拽這兩個子View即可。
< ?xml version="1.0" encoding="utf-8"?>
5.自定義組件ViewGragOnew繼承自LinerLayout,然后在內部采用ViewGragHelper做相關操作,具體包括以上核心操作的代碼如下:
public class ViewGragOne extends LinearLayout{ private View view_one,view_two; private ViewDragHelper mDragHelper; public ViewGragOne(Context context) { this(context, null); } public ViewGragOne(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ViewGragOne(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mDragHelper = ViewDragHelper.create(this, 1.0f, new DragHelperCallback()); } class DragHelperCallback extends Callback { /** * 進行捕獲攔截,那些View可以進行drag操作 * @param child * @param pointerId * @return 直接返回true,攔截所有的VIEW */ @Override public boolean tryCaptureView(View child, int pointerId) { return true; } /** * 水平滑動 控制left * @param child * @param left * @param dx * @return */ @Override public int clampViewPositionHorizontal(View child, int left, int dx) { Log.d("DragLayout", "clampViewPositionHorizontal " + left + "," + dx); final int leftBound = getPaddingLeft(); final int rightBound = getWidth() - view_one.getWidth(); final int newLeft = Math.min(Math.max(left, leftBound), rightBound); return newLeft; } /** * 垂直滑動,控制top * @param child * @param top * @param dy * @return */ @Override public int clampViewPositionVertical(View child, int top, int dy) { Log.d("DragLayout", "clampViewPositionVertical " + top + "," + dy); final int topBound = getPaddingTop(); final int bottomBound = getHeight() - view_one.getHeight(); final int newTop = Math.min(Math.max(top, topBound), bottomBound); return newTop; } } /** * 事件分發 * @param ev * @return */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return mDragHelper.shouldInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent ev) { mDragHelper.processTouchEvent(ev); return true; } @Override protected void onFinishInflate() { super.onFinishInflate(); view_one=getChildAt(0); view_two=getChildAt(1); } }
6.運行效果如下:
(四).ViewDragHelper進階講解:
1.tryCaptureView該方法可以進行選擇性攔截可以拖拽的子View,那么我們這邊選擇只攔截第一個View,那么第二個View就無法進行拖拽啦,具體實現方法如下:
@Override public boolean tryCaptureView(View child, int pointerId) { return child==view_one; }
實現效果如下:
2.滑動邊緣相關處理方法setEdgeTrackingEnabled(),onEdgeTouch()以及onEdgeDragStart()。
我們可以給ViewDragHelper加入滑動的邊緣設置,組件給我們提供了如下邊緣控制值:EDGE_LEFT,EDGE_RIGHT,EDGE_TOP,EDGE_BOTTOM和EDGE_ALL,分別控制上下左右以及全部邊緣控制。我們這邊設置左邊緣:
mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
那么根據上面我們的方法介紹,我們可以重寫onEdgeTouched()方法來攔截觸摸到邊緣的動作。
@Override public void onEdgeTouched(int edgeFlags, int pointerId) { super.onEdgeTouched(edgeFlags, pointerId); Toast.makeText(getContext(), "edgeTouched", Toast.LENGTH_SHORT).show(); }
如果要實現我們的手指在邊緣進行滑動的時候,同時根據滑動的距離來滑動另外一個View,我們可以重寫onEdgeDragStared()方法,在內部調用caturedChildView()方法實現即可。(這個效果我們就可以聯想到側滑界面效果,滑動時候可以打開主布局界面),具體實現代碼如下:
/** * 在邊界滑動的時候 同時滑動dragView2 * @param edgeFlags * @param pointerId */ @Override public void onEdgeDragStarted(int edgeFlags, int pointerId) { mDragHelper.captureChildView(view_two, pointerId); }
運行效果如下:
3.除了以上的方法之外,我們還有一個手指觸摸釋放之后回調的方法,onViewReleased()。這個好比側滑組件中我們點擊一個位置,然后界面自動打開或者關閉的效果。
/** * 當手指松開的時候回調方法 * @param releasedChild 滑動手指松開的View * @param xvel * @param yvel */ @Override public void onViewReleased(View releasedChild, float xvel, float yvel) { super.onViewReleased(releasedChild, xvel, yvel); Log.d("zttjiangqq","onViewReleased"); }
當我們拖拽一個子View,然后手指釋放之后運行效果如下:
4.ViewGragOne全部實例代碼如下:
package com.chinaztt.fda.test.ViewGragHelper; import android.content.Context; import android.support.v4.view.MotionEventCompat; import android.support.v4.widget.ViewDragHelper; import android.support.v4.widget.ViewDragHelper.Callback; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.widget.LinearLayout; import android.widget.Toast; import com.chinaztt.fda.utils.Log; /** * 當前類注釋: * 項目名:FastDev4Android * 包名:com.chinaztt.fda.test.ViewGragHelper * 作者:江清清 on 15/11/24 20:29 * 郵箱:jiangqqlmj@163.com * QQ: 781931404 * 公司:江蘇中天科技軟件技術有限公司 */ public class ViewGragOne extends LinearLayout{ private View view_one,view_two; private ViewDragHelper mDragHelper; public ViewGragOne(Context context) { this(context, null); } public ViewGragOne(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ViewGragOne(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mDragHelper = ViewDragHelper.create(this, 1.0f, new DragHelperCallback()); mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT); } class DragHelperCallback extends Callback { /** * 進行捕獲攔截,那些View可以進行drag操作 * @param child * @param pointerId * @return 直接返回true,攔截所有的VIEW */ @Override public boolean tryCaptureView(View child, int pointerId) { return true; } /** * 水平滑動 控制left * @param child * @param left * @param dx * @return */ @Override public int clampViewPositionHorizontal(View child, int left, int dx) { Log.d("DragLayout", "clampViewPositionHorizontal " + left + "," + dx); final int leftBound = getPaddingLeft(); final int rightBound = getWidth() - view_one.getWidth(); final int newLeft = Math.min(Math.max(left, leftBound), rightBound); return newLeft; } /** * 垂直滑動,控制top * @param child * @param top * @param dy * @return */ @Override public int clampViewPositionVertical(View child, int top, int dy) { Log.d("DragLayout", "clampViewPositionVertical " + top + "," + dy); final int topBound = getPaddingTop(); final int bottomBound = getHeight() - view_one.getHeight(); final int newTop = Math.min(Math.max(top, topBound), bottomBound); return newTop; } @Override public void onEdgeTouched(int edgeFlags, int pointerId) { super.onEdgeTouched(edgeFlags, pointerId); Toast.makeText(getContext(), "edgeTouched", Toast.LENGTH_SHORT).show(); } /** * 在邊界滑動的時候 同時滑動dragView2 * @param edgeFlags * @param pointerId */ @Override public void onEdgeDragStarted(int edgeFlags, int pointerId) { mDragHelper.captureChildView(view_two, pointerId); } /** * 當手指松開的時候回調方法 * @param releasedChild 滑動手指松開的View * @param xvel * @param yvel */ @Override public void onViewReleased(View releasedChild, float xvel, float yvel) { super.onViewReleased(releasedChild, xvel, yvel); Log.d("zttjiangqq","onViewReleased"); } } /** * 事件分發 * @param ev * @return */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return mDragHelper.shouldInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent ev) { mDragHelper.processTouchEvent(ev); return true; } @Override protected void onFinishInflate() { super.onFinishInflate(); view_one=getChildAt(0); view_two=getChildAt(1); } }
(五).最后總結
今天我們對于ViewGragHelper的基本使用方法和相關流程做了詳解,下一篇我們會通過ViewDragHelper來講解實現一個類似QQ5.x側滑效果的組件以及ViewGragHelper源代碼解析。
本次具體實例注釋過的全部代碼已經上傳到FastDev4Android項目中了。同時歡迎大家去Github站點進行clone或者下載瀏覽:
https://github.com/jiangqqlmj/FastDev4Android 同時歡迎大家star和fork整個開源快速開發框架項目~
尊重原創,轉載請注明:From Sky丶清( http://www.lcode.org/ ) 侵權必究!
關注我的訂閱號(codedev123),每天分享移動開發技術(Android/IOS),項目管理以及博客文章!(歡迎關注,第一時間推送精彩文章)
關注我的微博,可以獲得更多精彩內容