Android應(yīng)用開發(fā)中自定義ViewGroup視圖容器的教程
一、概述
在寫代碼之前,我必須得問幾個(gè)問題:
1、ViewGroup的職責(zé)是啥?
ViewGroup相當(dāng)于一個(gè)放置View的容器,并且我們?cè)趯懖季謝ml的時(shí)候,會(huì)告訴容器(凡是以layout為開頭的屬性,都是為用于告訴容器的),我們的寬度(layout_width)、高度(layout_height)、對(duì)齊方式(layout_gravity)等;當(dāng)然還有margin等;于是乎,ViewGroup的職能為:給childView計(jì)算出建議的寬和高和測(cè)量模式 ;決定childView的位置;為什么只是建議的寬和高,而不是直接確定呢,別忘了childView寬和高可以設(shè)置為wrap_content,這樣只有childView才能計(jì)算出自己的寬和高。
2、View的職責(zé)是啥?
View的職責(zé),根據(jù)測(cè)量模式和ViewGroup給出的建議的寬和高,計(jì)算出自己的寬和高;同時(shí)還有個(gè)更重要的職責(zé)是:在ViewGroup為其指定的區(qū)域內(nèi)繪制自己的形態(tài)。
3、ViewGroup和LayoutParams之間的關(guān)系?
大家可以回憶一下,當(dāng)在LinearLayout中寫childView的時(shí)候,可以寫layout_gravity,layout_weight屬性;在RelativeLayout中的childView有l(wèi)ayout_centerInParent屬性,卻沒有l(wèi)ayout_gravity,layout_weight,這是為什么呢?這是因?yàn)槊總€(gè)ViewGroup需要指定一個(gè)LayoutParams,用于確定支持childView支持哪些屬性,比如LinearLayout指定LinearLayout.LayoutParams等。如果大家去看LinearLayout的源碼,會(huì)發(fā)現(xiàn)其內(nèi)部定義了LinearLayout.LayoutParams,在此類中,你可以發(fā)現(xiàn)weight和gravity的身影。
二、View的3種測(cè)量模式
上面提到了ViewGroup會(huì)為childView指定測(cè)量模式,下面簡(jiǎn)單介紹下三種測(cè)量模式:
EXACTLY:表示設(shè)置了精確的值,一般當(dāng)childView設(shè)置其寬、高為精確值、match_parent時(shí),ViewGroup會(huì)將其設(shè)置為EXACTLY;
AT_MOST:表示子布局被限制在一個(gè)最大值內(nèi),一般當(dāng)childView設(shè)置其寬、高為wrap_content時(shí),ViewGroup會(huì)將其設(shè)置為AT_MOST;
UNSPECIFIED:表示子布局想要多大就多大,一般出現(xiàn)在AadapterView的item的heightMode中、ScrollView的childView的heightMode中;此種模式比較少見。
注:上面的每一行都有一個(gè)一般,意思上述不是絕對(duì)的,對(duì)于childView的mode的設(shè)置還會(huì)和ViewGroup的測(cè)量mode有一定的關(guān)系;當(dāng)然了,這是第一篇自定義ViewGroup,而且絕大部分情況都是上面的規(guī)則,所以為了通俗易懂,暫不深入討論其他內(nèi)容。
三、從API角度進(jìn)行淺析
上面敘述了ViewGroup和View的職責(zé),下面從API角度進(jìn)行淺析。
View的根據(jù)ViewGroup傳人的測(cè)量值和模式,對(duì)自己寬高進(jìn)行確定(onMeasure中完成),然后在onDraw中完成對(duì)自己的繪制。
ViewGroup需要給View傳入view的測(cè)量值和模式(onMeasure中完成),而且對(duì)于此ViewGroup的父布局,自己也需要在onMeasure中完成對(duì)自己寬和高的確定。此外,需要在onLayout中完成對(duì)其childView的位置的指定。
四、完整的例子
需求:我們定義一個(gè)ViewGroup,內(nèi)部可以傳入0到4個(gè)childView,分別依次顯示在左上角,右上角,左下角,右下角。
1、決定該ViewGroup的LayoutParams
對(duì)于我們這個(gè)例子,我們只需要ViewGroup能夠支持margin即可,那么我們直接使用系統(tǒng)的MarginLayoutParams
@Override public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { return new MarginLayoutParams(getContext(), attrs); }
重寫父類的該方法,返回MarginLayoutParams的實(shí)例,這樣就為我們的ViewGroup指定了其LayoutParams為MarginLayoutParams。
2、onMeasure
在onMeasure中計(jì)算childView的測(cè)量值以及模式,以及設(shè)置自己的寬和高:
/** * 計(jì)算所有ChildView的寬度和高度 然后根據(jù)ChildView的計(jì)算結(jié)果,設(shè)置自己的寬和高 */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { /** * 獲得此ViewGroup上級(jí)容器為其推薦的寬和高,以及計(jì)算模式 */ int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int sizeWidth = MeasureSpec.getSize(widthMeasureSpec); int sizeHeight = MeasureSpec.getSize(heightMeasureSpec); // 計(jì)算出所有的childView的寬和高 measureChildren(widthMeasureSpec, heightMeasureSpec); /** * 記錄如果是wrap_content是設(shè)置的寬和高 */ int width = 0; int height = 0; int cCount = getChildCount(); int cWidth = 0; int cHeight = 0; MarginLayoutParams cParams = null; // 用于計(jì)算左邊兩個(gè)childView的高度 int lHeight = 0; // 用于計(jì)算右邊兩個(gè)childView的高度,最終高度取二者之間大值 int rHeight = 0; // 用于計(jì)算上邊兩個(gè)childView的寬度 int tWidth = 0; // 用于計(jì)算下面兩個(gè)childiew的寬度,最終寬度取二者之間大值 int bWidth = 0; /** * 根據(jù)childView計(jì)算的出的寬和高,以及設(shè)置的margin計(jì)算容器的寬和高,主要用于容器是warp_content時(shí) */ for (int i = 0; i < cCount; i++) { View childView = getChildAt(i); cWidth = childView.getMeasuredWidth(); cHeight = childView.getMeasuredHeight(); cParams = (MarginLayoutParams) childView.getLayoutParams(); // 上面兩個(gè)childView if (i == 0 || i == 1) { tWidth += cWidth + cParams.leftMargin + cParams.rightMargin; } if (i == 2 || i == 3) { bWidth += cWidth + cParams.leftMargin + cParams.rightMargin; } if (i == 0 || i == 2) { lHeight += cHeight + cParams.topMargin + cParams.bottomMargin; } if (i == 1 || i == 3) { rHeight += cHeight + cParams.topMargin + cParams.bottomMargin; } } width = Math.max(tWidth, bWidth); height = Math.max(lHeight, rHeight); /** * 如果是wrap_content設(shè)置為我們計(jì)算的值 * 否則:直接設(shè)置為父容器計(jì)算的值 */ setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? sizeWidth : width, (heightMode == MeasureSpec.EXACTLY) ? sizeHeight : height); }
10-14行,獲取該ViewGroup父容器為其設(shè)置的計(jì)算模式和尺寸,大多情況下,只要不是wrap_content,父容器都能正確的計(jì)算其尺寸。所以我們自己需要計(jì)算如果設(shè)置為wrap_content時(shí)的寬和高,如何計(jì)算呢?那就是通過其childView的寬和高來(lái)進(jìn)行計(jì)算。
17行,通過ViewGroup的measureChildren方法為其所有的孩子設(shè)置寬和高,此行執(zhí)行完成后,childView的寬和高都已經(jīng)正確的計(jì)算過了
43-71行,根據(jù)childView的寬和高,以及margin,計(jì)算ViewGroup在wrap_content時(shí)的寬和高。
80-82行,如果寬高屬性值為wrap_content,則設(shè)置為43-71行中計(jì)算的值,否則為其父容器傳入的寬和高。
3、onLayout對(duì)其所有childView進(jìn)行定位(設(shè)置childView的繪制區(qū)域)
// abstract method in viewgroup @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int cCount = getChildCount(); int cWidth = 0; int cHeight = 0; MarginLayoutParams cParams = null; /** * 遍歷所有childView根據(jù)其寬和高,以及margin進(jìn)行布局 */ for (int i = 0; i < cCount; i++) { View childView = getChildAt(i); cWidth = childView.getMeasuredWidth(); cHeight = childView.getMeasuredHeight(); cParams = (MarginLayoutParams) childView.getLayoutParams(); int cl = 0, ct = 0, cr = 0, cb = 0; switch (i) { case 0: cl = cParams.leftMargin; ct = cParams.topMargin; break; case 1: cl = getWidth() - cWidth - cParams.leftMargin - cParams.rightMargin; ct = cParams.topMargin; break; case 2: cl = cParams.leftMargin; ct = getHeight() - cHeight - cParams.bottomMargin; break; case 3: cl = getWidth() - cWidth - cParams.leftMargin - cParams.rightMargin; ct = getHeight() - cHeight - cParams.bottomMargin; break; } cr = cl + cWidth; cb = cHeight + ct; childView.layout(cl, ct, cr, cb); } }
代碼比較容易懂:遍歷所有的childView,根據(jù)childView的寬和高以及margin,然后分別將0,1,2,3位置的childView依次設(shè)置到左上、右上、左下、右下的位置。
如果是第一個(gè)View(index=0) :則childView.layout(cl, ct, cr, cb); cl為childView的leftMargin , ct 為topMargin , cr 為cl+ cWidth , cb為 ct + cHeight
如果是第二個(gè)View(index=1) :則childView.layout(cl, ct, cr, cb);
cl為getWidth() - cWidth - cParams.leftMargin- cParams.rightMargin;
ct 為topMargin , cr 為cl+ cWidth , cb為 ct + cHeight
剩下兩個(gè)類似~
這樣就完成了,我們的ViewGroup代碼的編寫,下面我們進(jìn)行測(cè)試,分別設(shè)置寬高為固定值,wrap_content,match_parent
4、測(cè)試結(jié)果
布局1:
<com.example.zhy_custom_viewgroup.CustomImgContainer xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="200dp" android:layout_height="200dp" android:background="#AA333333" > <TextView android:layout_width="50dp" android:layout_height="50dp" android:background="#FF4444" android:gravity="center" android:text="0" android:textColor="#FFFFFF" android:textSize="22sp" android:textStyle="bold" /> <TextView android:layout_width="50dp" android:layout_height="50dp" android:background="#00ff00" android:gravity="center" android:text="1" android:textColor="#FFFFFF" android:textSize="22sp" android:textStyle="bold" /> <TextView android:layout_width="50dp" android:layout_height="50dp" android:background="#ff0000" android:gravity="center" android:text="2" android:textColor="#FFFFFF" android:textSize="22sp" android:textStyle="bold" /> <TextView android:layout_width="50dp" android:layout_height="50dp" android:background="#0000ff" android:gravity="center" android:text="3" android:textColor="#FFFFFF" android:textSize="22sp" android:textStyle="bold" /> </com.example.zhy_custom_viewgroup.CustomImgContainer>
ViewGroup寬和高設(shè)置為固定值
效果圖:
布局2:
<com.example.zhy_custom_viewgroup.CustomImgContainer xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="#AA333333" > <TextView android:layout_width="150dp" android:layout_height="150dp" android:background="#E5ED05" android:gravity="center" android:text="0" android:textColor="#FFFFFF" android:textSize="22sp" android:textStyle="bold" /> <TextView android:layout_width="50dp" android:layout_height="50dp" android:background="#00ff00" android:gravity="center" android:text="1" android:textColor="#FFFFFF" android:textSize="22sp" android:textStyle="bold" /> <TextView android:layout_width="50dp" android:layout_height="50dp" android:background="#ff0000" android:gravity="center" android:text="2" android:textColor="#FFFFFF" android:textSize="22sp" android:textStyle="bold" /> <TextView android:layout_width="50dp" android:layout_height="50dp" android:background="#0000ff" android:gravity="center" android:text="3" android:textColor="#FFFFFF" android:textSize="22sp" android:textStyle="bold" /> </com.example.zhy_custom_viewgroup.CustomImgContainer>
ViewGroup的寬和高設(shè)置為wrap_content
效果圖:
布局3:
<com.example.zhy_custom_viewgroup.CustomImgContainer xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#AA333333" > <TextView android:layout_width="150dp" android:layout_height="150dp" android:background="#E5ED05" android:gravity="center" android:text="0" android:textColor="#FFFFFF" android:textSize="22sp" android:textStyle="bold" /> <TextView android:layout_width="50dp" android:layout_height="50dp" android:background="#00ff00" android:gravity="center" android:text="1" android:textColor="#FFFFFF" android:textSize="22sp" android:textStyle="bold" /> <TextView android:layout_width="50dp" android:layout_height="50dp" android:background="#ff0000" android:gravity="center" android:text="2" android:textColor="#FFFFFF" android:textSize="22sp" android:textStyle="bold" /> <TextView android:layout_width="150dp" android:layout_height="150dp" android:background="#0000ff" android:gravity="center" android:text="3" android:textColor="#FFFFFF" android:textSize="22sp" android:textStyle="bold" /> </com.example.zhy_custom_viewgroup.CustomImgContainer>
ViewGroup的寬和高設(shè)置為match_parent
可以看到無(wú)論ViewGroup的寬和高的值如何定義,我們的需求都實(shí)現(xiàn)了預(yù)期的效果~~
四、使用ViewDragHelper自定義ViewGroup
1、概述
在自定義ViewGroup中,很多效果都包含用戶手指去拖動(dòng)其內(nèi)部的某個(gè)View(eg:側(cè)滑菜單等),針對(duì)具體的需要去寫好onInterceptTouchEvent和onTouchEvent這兩個(gè)方法是一件很不容易的事,需要自己去處理:多手指的處理、加速度檢測(cè)等等。
好在官方在v4的支持包中提供了ViewDragHelper這樣一個(gè)類來(lái)幫助我們方便的編寫自定義ViewGroup。簡(jiǎn)單看一下它的注釋:
ViewDragHelper is a utility class for writing custom ViewGroups. It offers a number
of useful operations and state tracking for allowing a user to drag and reposition
views within their parent ViewGroup.
下面將重點(diǎn)介紹ViewDragHelper的使用,并且最終去實(shí)現(xiàn)一個(gè)類似DrawerLayout的一個(gè)自定義的ViewGroup。(ps:官方的DrawerLayout就是用此類實(shí)現(xiàn))
2、入門小示例
首先我們通過一個(gè)簡(jiǎn)單的例子來(lái)看看其快捷的用法,分為以下幾個(gè)步驟:
A、創(chuàng)建實(shí)例
B、觸摸相關(guān)的方法的調(diào)用
C、ViewDragHelper.Callback實(shí)例的編寫
(1) 自定義ViewGroup
package com.zhy.learn.view; import android.content.Context; import android.support.v4.widget.ViewDragHelper; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.widget.LinearLayout; /** * Created by zhy on 15/6/3. */ public class VDHLayout extends LinearLayout { private ViewDragHelper mDragger; public VDHLayout(Context context, AttributeSet attrs) { super(context, attrs); mDragger = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() { @Override public boolean tryCaptureView(View child, int pointerId) { return true; } @Override public int clampViewPositionHorizontal(View child, int left, int dx) { return left; } @Override public int clampViewPositionVertical(View child, int top, int dy) { return top; } }); } @Override public boolean onInterceptTouchEvent(MotionEvent event) { return mDragger.shouldInterceptTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { mDragger.processTouchEvent(event); return true; } }
可以看到,上面整個(gè)自定義ViewGroup的代碼非常簡(jiǎn)潔,遵循上述3個(gè)步驟:
A、創(chuàng)建實(shí)例
mDragger = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() { });
創(chuàng)建實(shí)例需要3個(gè)參數(shù),第一個(gè)就是當(dāng)前的ViewGroup,第二個(gè)sensitivity,主要用于設(shè)置touchSlop:
helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity));
可見傳入越大,mTouchSlop的值就會(huì)越小。第三個(gè)參數(shù)就是Callback,在用戶的觸摸過程中會(huì)回調(diào)相關(guān)方法,后面會(huì)細(xì)說。
B、觸摸相關(guān)方法
@Override public boolean onInterceptTouchEvent(MotionEvent event) { return mDragger.shouldInterceptTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { mDragger.processTouchEvent(event); return true; }
onInterceptTouchEvent中通過使用mDragger.shouldInterceptTouchEvent(event)來(lái)決定我們是否應(yīng)該攔截當(dāng)前的事件。onTouchEvent中通過mDragger.processTouchEvent(event)處理事件。
C、實(shí)現(xiàn)ViewDragHelper.CallCack相關(guān)方法
new ViewDragHelper.Callback() { @Override public boolean tryCaptureView(View child, int pointerId) { return true; } @Override public int clampViewPositionHorizontal(View child, int left, int dx) { return left; } @Override public int clampViewPositionVertical(View child, int top, int dy) { return top; } }
ViewDragHelper中攔截和處理事件時(shí),需要會(huì)回調(diào)CallBack中的很多方法來(lái)決定一些事,比如:哪些子View可以移動(dòng)、對(duì)個(gè)移動(dòng)的View的邊界的控制等等。
上面復(fù)寫的3個(gè)方法:
tryCaptureView如何返回ture則表示可以捕獲該view,你可以根據(jù)傳入的第一個(gè)view參數(shù)決定哪些可以捕獲
clampViewPositionHorizontal,clampViewPositionVertical可以在該方法中對(duì)child移動(dòng)的邊界進(jìn)行控制,left , top 分別為即將移動(dòng)到的位置,比如橫向的情況下,我希望只在ViewGroup的內(nèi)部移動(dòng),即:最小>=paddingleft,最大<=ViewGroup.getWidth()-paddingright-child.getWidth。就可以按照如下代碼編寫:
@Override public int clampViewPositionHorizontal(View child, int left, int dx) { final int leftBound = getPaddingLeft(); final int rightBound = getWidth() - mDragView.getWidth() - leftBound; final int newLeft = Math.min(Math.max(left, leftBound), rightBound); return newLeft; }
經(jīng)過上述3個(gè)步驟,我們就完成了一個(gè)簡(jiǎn)單的自定義ViewGroup,可以自由的拖動(dòng)子View。
簡(jiǎn)單看一下布局文件
(2) 布局文件
<com.zhy.learn.view.VDHLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:orientation="vertical" android:layout_height="match_parent" > <TextView android:layout_margin="10dp" android:gravity="center" android:layout_gravity="center" android:background="#44ff0000" android:text="I can be dragged !" android:layout_width="100dp" android:layout_height="100dp"/> <TextView android:layout_margin="10dp" android:layout_gravity="center" android:gravity="center" android:background="#44ff0000" android:text="I can be dragged !" android:layout_width="100dp" android:layout_height="100dp"/> <TextView android:layout_margin="10dp" android:layout_gravity="center" android:gravity="center" android:background="#44ff0000" android:text="I can be dragged !" android:layout_width="100dp" android:layout_height="100dp"/> </com.zhy.learn.view.VDHLayout>
我們的自定義ViewGroup中有三個(gè)TextView。
當(dāng)前效果:
可以看到短短數(shù)行代碼就可以玩起來(lái)了~~~
有了直觀的認(rèn)識(shí)以后,我們還需要對(duì)ViewDragHelper.CallBack里面的方法做下深入的理解。首先我們需要考慮的是:我們的ViewDragHelper不僅僅說只能夠去讓子View去跟隨我們手指移動(dòng),我們繼續(xù)往下學(xué)習(xí)其他的功能。
3、功能展示
ViewDragHelper還能做以下的一些操作:
邊界檢測(cè)、加速度檢測(cè)(eg:DrawerLayout邊界觸發(fā)拉出)
回調(diào)Drag Release(eg:DrawerLayout部分,手指抬起,自動(dòng)展開/收縮)
移動(dòng)到某個(gè)指定的位置(eg:點(diǎn)擊Button,展開/關(guān)閉Drawerlayout)
那么我們接下來(lái)對(duì)我們最基本的例子進(jìn)行改造,包含上述的幾個(gè)操作。
首先看一下我們修改后的效果:
簡(jiǎn)單的為每個(gè)子View添加了不同的操作:
第一個(gè)View,就是演示簡(jiǎn)單的移動(dòng)
第二個(gè)View,演示除了移動(dòng)后,松手自動(dòng)返回到原本的位置。(注意你拖動(dòng)的越快,返回的越快)
第三個(gè)View,邊界移動(dòng)時(shí)對(duì)View進(jìn)行捕獲。
好了,看完效果圖,來(lái)看下代碼的修改:
修改后的代碼
package com.zhy.learn.view; import android.content.Context; import android.graphics.Point; import android.support.v4.widget.ViewDragHelper; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.widget.LinearLayout; /** * Created by zhy on 15/6/3. */ public class VDHLayout extends LinearLayout { private ViewDragHelper mDragger; private View mDragView; private View mAutoBackView; private View mEdgeTrackerView; private Point mAutoBackOriginPos = new Point(); public VDHLayout(Context context, AttributeSet attrs) { super(context, attrs); mDragger = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() { @Override public boolean tryCaptureView(View child, int pointerId) { //mEdgeTrackerView禁止直接移動(dòng) return child == mDragView || child == mAutoBackView; } @Override public int clampViewPositionHorizontal(View child, int left, int dx) { return left; } @Override public int clampViewPositionVertical(View child, int top, int dy) { return top; } //手指釋放的時(shí)候回調(diào) @Override public void onViewReleased(View releasedChild, float xvel, float yvel) { //mAutoBackView手指釋放時(shí)可以自動(dòng)回去 if (releasedChild == mAutoBackView) { mDragger.settleCapturedViewAt(mAutoBackOriginPos.x, mAutoBackOriginPos.y); invalidate(); } } //在邊界拖動(dòng)時(shí)回調(diào) @Override public void onEdgeDragStarted(int edgeFlags, int pointerId) { mDragger.captureChildView(mEdgeTrackerView, pointerId); } }); mDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT); } @Override public boolean onInterceptTouchEvent(MotionEvent event) { return mDragger.shouldInterceptTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { mDragger.processTouchEvent(event); return true; } @Override public void computeScroll() { if(mDragger.continueSettling(true)) { invalidate(); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); mAutoBackOriginPos.x = mAutoBackView.getLeft(); mAutoBackOriginPos.y = mAutoBackView.getTop(); } @Override protected void onFinishInflate() { super.onFinishInflate(); mDragView = getChildAt(0); mAutoBackView = getChildAt(1); mEdgeTrackerView = getChildAt(2); } }
布局文件我們僅僅是換了下文本和背景色就不重復(fù)貼了。
第一個(gè)View基本沒做任何修改。
第二個(gè)View,我們?cè)趏nLayout之后保存了最開啟的位置信息,最主要還是重寫了Callback中的onViewReleased,我們?cè)趏nViewReleased中判斷如果是mAutoBackView則調(diào)用settleCapturedViewAt回到初始的位置。大家可以看到緊隨其后的代碼是invalidate();因?yàn)槠鋬?nèi)部使用的是mScroller.startScroll,所以別忘了需要invalidate()以及結(jié)合computeScroll方法一起。
第三個(gè)View,我們?cè)趏nEdgeDragStarted回調(diào)方法中,主動(dòng)通過captureChildView對(duì)其進(jìn)行捕獲,該方法可以繞過tryCaptureView,所以我們的tryCaptureView雖然并為返回true,但卻不影響。注意如果需要使用邊界檢測(cè)需要添加上mDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);。
到此,我們已經(jīng)介紹了Callback中常用的回調(diào)方法了,當(dāng)然還有一些方法沒有介紹,接下來(lái)我們修改下我們的布局文件,我們把我們的TextView全部加上clickable=true,意思就是子View可以消耗事件。再次運(yùn)行,你會(huì)發(fā)現(xiàn)本來(lái)可以拖動(dòng)的View不動(dòng)了,(如果有拿Button測(cè)試的兄弟應(yīng)該已經(jīng)發(fā)現(xiàn)這個(gè)問題了,我希望你看到這了,而不是已經(jīng)提問了,哈~)。
原因是什么呢?主要是因?yàn)?,如果子View不消耗事件,那么整個(gè)手勢(shì)(DOWN-MOVE*-UP)都是直接進(jìn)入onTouchEvent,在onTouchEvent的DOWN的時(shí)候就確定了captureView。如果消耗事件,那么就會(huì)先走onInterceptTouchEvent方法,判斷是否可以捕獲,而在判斷的過程中會(huì)去判斷另外兩個(gè)回調(diào)的方法:getViewHorizontalDragRange和getViewVerticalDragRange,只有這兩個(gè)方法返回大于0的值才能正常的捕獲。
所以,如果你用Button測(cè)試,或者給TextView添加了clickable = true ,都記得重寫下面這兩個(gè)方法:
@Override public int getViewHorizontalDragRange(View child) { return getMeasuredWidth()-child.getMeasuredWidth(); } @Override public int getViewVerticalDragRange(View child) { return getMeasuredHeight()-child.getMeasuredHeight(); }
方法的返回值應(yīng)當(dāng)是該childView橫向或者縱向的移動(dòng)的范圍,當(dāng)前如果只需要一個(gè)方向移動(dòng),可以只復(fù)寫一個(gè)。
到此,我們列一下所有的Callback方法,看看還有哪些沒用過的:
onViewDragStateChanged
當(dāng)ViewDragHelper狀態(tài)發(fā)生變化時(shí)回調(diào)(IDLE,DRAGGING,SETTING[自動(dòng)滾動(dòng)時(shí)]):
onViewPositionChanged
當(dāng)captureview的位置發(fā)生改變時(shí)回調(diào):
onViewCaptured
當(dāng)captureview被捕獲時(shí)回調(diào):
onViewReleased 已用
onEdgeTouched
當(dāng)觸摸到邊界時(shí)回調(diào):
onEdgeLock
true的時(shí)候會(huì)鎖住當(dāng)前的邊界,false則unLock。
onEdgeDragStarted 已用
getOrderedChildIndex
改變同一個(gè)坐標(biāo)(x,y)去尋找captureView位置的方法。(具體在:findTopChildUnder方法中)
getViewHorizontalDragRange 已用
getViewVerticalDragRange 已用
tryCaptureView 已用
clampViewPositionHorizontal 已用
clampViewPositionVertical 已用
ok,至此所有的回調(diào)方法都有了一定的認(rèn)識(shí)。
總結(jié)下,方法的大致的回調(diào)順序:
shouldInterceptTouchEvent: DOWN: getOrderedChildIndex(findTopChildUnder) ->onEdgeTouched MOVE: getOrderedChildIndex(findTopChildUnder) ->getViewHorizontalDragRange & getViewVerticalDragRange(checkTouchSlop)(MOVE中可能不止一次) ->clampViewPositionHorizontal& clampViewPositionVertical ->onEdgeDragStarted ->tryCaptureView ->onViewCaptured ->onViewDragStateChanged processTouchEvent: DOWN: getOrderedChildIndex(findTopChildUnder) ->tryCaptureView ->onViewCaptured ->onViewDragStateChanged ->onEdgeTouched MOVE: ->STATE==DRAGGING:dragTo ->STATE!=DRAGGING: onEdgeDragStarted ->getOrderedChildIndex(findTopChildUnder) ->getViewHorizontalDragRange& getViewVerticalDragRange(checkTouchSlop) ->tryCaptureView ->onViewCaptured ->onViewDragStateChanged
ok,上述是正常情況下大致的流程,當(dāng)然整個(gè)過程可能會(huì)存在很多判斷不成立的情況。
從上面也可以解釋,我們?cè)谥癟extView(clickable=false)的情況下,沒有編寫getViewHorizontalDragRange方法時(shí),是可以移動(dòng)的。因?yàn)橹苯舆M(jìn)入processTouchEvent的DOWN,然后就onViewCaptured、onViewDragStateChanged(進(jìn)入DRAGGING狀態(tài)),接下來(lái)MOVE就直接dragTo了。
而當(dāng)子View消耗事件的時(shí)候,就需要走shouldInterceptTouchEvent,MOVE的時(shí)候經(jīng)過一系列的判斷(getViewHorizontalDragRange,clampViewPositionVertical等),才能夠去tryCaptureView。
ok,到此ViewDragHelper的入門用法我們就介紹結(jié)束了,下一篇,我們將使用ViewDragHelper去自己實(shí)現(xiàn)一個(gè)DrawerLayout。
有興趣的也可以根據(jù)本文,以及DrawerLayout的源碼去實(shí)現(xiàn)了~
- 從源碼解析Android中View的容器ViewGroup
- Android中標(biāo)簽容器控件的實(shí)例詳解
- Android自定義ViewGroup實(shí)現(xiàn)標(biāo)簽流容器FlowLayout
- Android自定義控件之繼承ViewGroup創(chuàng)建新容器
- Android中實(shí)現(xiàn)多行、水平滾動(dòng)的分頁(yè)的Gridview實(shí)例源碼
- android listview 水平滾動(dòng)和垂直滾動(dòng)的小例子
- Android使用RecyclerView實(shí)現(xiàn)水平滾動(dòng)控件
- Android實(shí)現(xiàn)Activity水平和垂直滾動(dòng)條的方法
- 詳解Android使GridView橫向水平滾動(dòng)的實(shí)現(xiàn)方式
- Android使用Recyclerview實(shí)現(xiàn)圖片水平自動(dòng)循環(huán)滾動(dòng)效果
- Android開發(fā)實(shí)現(xiàn)自定義水平滾動(dòng)的容器示例
相關(guān)文章
android二級(jí)listview列表實(shí)現(xiàn)代碼
今天來(lái)實(shí)現(xiàn)以下大眾點(diǎn)評(píng)客戶端的橫向listview二級(jí)列表,感興趣的朋友可以研究下2013-01-01Android RecyclerView實(shí)現(xiàn)下拉刷新和上拉加載
這篇文章主要介紹了Android RecyclerView實(shí)現(xiàn)下拉刷新和上拉加載的相關(guān)資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-05-05Android編程實(shí)現(xiàn)保存圖片到系統(tǒng)圖庫(kù)的方法示例
這篇文章主要介紹了Android編程實(shí)現(xiàn)保存圖片到系統(tǒng)圖庫(kù)的方法,結(jié)合實(shí)例形式分析了Android保存圖片到系統(tǒng)圖庫(kù)的常見操作方法、注意事項(xiàng)與相關(guān)問題解決技巧,需要的朋友可以參考下2017-08-08Android資源文件與層次式導(dǎo)航超詳細(xì)講解
這篇文章主要介紹了Android資源文件與層次式導(dǎo)航,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2022-12-12Android 通過jni返回Mat數(shù)據(jù)類型方法
今天小編就為大家分享一篇Android 通過jni返回Mat數(shù)據(jù)類型方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來(lái)看看吧2018-08-08一篇文章弄懂Android自定義viewgroup的相關(guān)難點(diǎn)
這篇文章主要給大家介紹了關(guān)于如何通過一篇文章弄懂Android中自定義viewgroup的一些相關(guān)難點(diǎn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-06-06Android自定義viewGroup實(shí)現(xiàn)點(diǎn)擊動(dòng)畫效果
這篇文章主要介紹了Android自定義viewGroup實(shí)現(xiàn)點(diǎn)擊動(dòng)畫效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12Android常用命令集錦(圖文并茂適應(yīng)于初學(xué)者)
大家好,今天我們要講的是android開發(fā)中,比較常用的名令集錦, 在我們開發(fā)中難免用到Android命令,有些確實(shí)命令確實(shí)很有用處,這也是我為什么總結(jié)這篇文章的原因了,希望對(duì)大家有所幫助2013-01-01Android實(shí)現(xiàn)qq列表式的分類懸浮提示
工作中遇到了一個(gè)需求,讓應(yīng)用中的一個(gè)列表按照分類顯示,并且能提示當(dāng)前是在哪個(gè)分類,度娘了一番,參考了前輩們的博客后實(shí)現(xiàn)了,現(xiàn)在分享給大家,有需要的可以參考借鑒。2016-09-09