1. Présentation
Avant d'écrire du code, je dois poser quelques questions :
1. Quelles sont les responsabilités de ViewGroup ?
ViewGroup est équivalent à un conteneur pour placer des vues, et lorsque nous écrivons le XML de mise en page, nous indiquerons le conteneur (tous les attributs commençant par layout sont utilisés pour indiquer le conteneur), notre largeur (layout_width), notre hauteur (layout_height) , alignement (layout_gravity), etc. ; bien sûr, marge, etc. ; par conséquent, les fonctions de ViewGroup sont : calculer la largeur, la hauteur et le mode de mesure recommandés pour childView ; pourquoi s'agit-il uniquement de la largeur recommandée ; et la hauteur ? La hauteur, au lieu de la déterminer directement, n'oubliez pas que la largeur et la hauteur de childView peuvent être définies sur wrap_content, afin que seul childView puisse calculer sa propre largeur et hauteur.
2. Quelles sont les responsabilités de View ?
La responsabilité de View est de calculer sa propre largeur et hauteur en fonction du mode de mesure et de la largeur et de la hauteur recommandées données par ViewGroup. En même temps, il y a une responsabilité plus importante : dessiner les siennes dans la zone désignée par ; Formulaire ViewGroup.
3. Quelle est la relation entre ViewGroup et LayoutParams ?
Vous vous souvenez que lors de l'écriture de childView dans LinearLayout, vous pouvez écrire les attributs layout_gravity et layout_weight ; childView dans RelativeLayout a les attributs layout_centerInParent, mais n'a pas d'attribut layout_gravity et layout_weight. Pourquoi ? En effet, chaque ViewGroup doit spécifier un LayoutParams pour déterminer les propriétés prises en charge par childView. Par exemple, LinearLayout spécifie LinearLayout.LayoutParams, etc. Si vous regardez le code source de LinearLayout, vous constaterez que LinearLayout.LayoutParams est défini en interne. Dans cette classe, vous pouvez trouver le poids et la gravité.
2. Trois modes de mesure de View
Comme mentionné ci-dessus, ViewGroup spécifiera le mode de mesure pour childView Voici une brève introduction aux trois modes de mesure :
EXACTEMENT : Indique qu'une valeur précise. est défini, généralement lorsque childView définit sa largeur et sa hauteur sur des valeurs exactes et match_parent, ViewGroup le définira sur EXACTLY ; height à wrap_content, ViewGroup le définira sur AT_MOST ;
UNSPECIFIED : indique que le sous-layout peut être aussi grand qu'il le souhaite. Il apparaît généralement dans le heightMode de l'élément AdapterView et dans le heightMode de childView de ScrollView ; relativement rare.
Remarque : chaque ligne ci-dessus a un général, ce qui signifie que ce qui précède n'est pas absolu. Le réglage du mode de childView aura également une certaine relation avec le mode de mesure de ViewGroup, bien sûr, c'est le premier article à personnaliser ViewGroup ; . Et la plupart des cas sont basés sur les règles ci-dessus, donc par souci de simplicité, nous ne discuterons pas en profondeur des autres contenus pour le moment.
Les responsabilités de ViewGroup et View sont décrites ci-dessus, et ce qui suit est une brève analyse du point de vue de l'API.
View détermine sa largeur et sa hauteur en fonction des valeurs de mesure et des modes transmis par ViewGroup (complétés dans onMeasure), puis complète son propre dessin dans onDraw.
ViewGroup doit transmettre la valeur de mesure et le mode de la vue à la vue (complété dans onMeasure), et pour la disposition parent de ce ViewGroup, il doit également terminer la détermination de sa propre largeur et hauteur dans onMeasure. De plus, la position de son childView doit être spécifiée dans onLayout.
4. Exemple complet
Exigences : Nous définissons un ViewGroup, qui peut transmettre 0 à 4 childViews, qui sont affichées respectivement dans le coin supérieur gauche, le coin supérieur droit, le coin inférieur gauche et le coin inférieur droit.
1. Déterminez les LayoutParams du ViewGroup
Pour notre exemple, nous avons seulement besoin du ViewGroup pour prendre en charge la marge, puis nous utilisons directement les MarginLayoutParams
@Override public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { return new MarginLayoutParams(getContext(), attrs); }
2. onMeasure
Calculez la valeur de mesure et le mode de childView dans onMeasure, et définissez ses propres largeur et hauteur :
/** * 计算所有ChildView的宽度和高度 然后根据ChildView的计算结果,设置自己的宽和高 */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { /** * 获得此ViewGroup上级容器为其推荐的宽和高,以及计算模式 */ int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int sizeWidth = MeasureSpec.getSize(widthMeasureSpec); int sizeHeight = MeasureSpec.getSize(heightMeasureSpec); // 计算出所有的childView的宽和高 measureChildren(widthMeasureSpec, heightMeasureSpec); /** * 记录如果是wrap_content是设置的宽和高 */ int width = 0; int height = 0; int cCount = getChildCount(); int cWidth = 0; int cHeight = 0; MarginLayoutParams cParams = null; // 用于计算左边两个childView的高度 int lHeight = 0; // 用于计算右边两个childView的高度,最终高度取二者之间大值 int rHeight = 0; // 用于计算上边两个childView的宽度 int tWidth = 0; // 用于计算下面两个childiew的宽度,最终宽度取二者之间大值 int bWidth = 0; /** * 根据childView计算的出的宽和高,以及设置的margin计算容器的宽和高,主要用于容器是warp_content时 */ for (int i = 0; i < cCount; i++) { View childView = getChildAt(i); cWidth = childView.getMeasuredWidth(); cHeight = childView.getMeasuredHeight(); cParams = (MarginLayoutParams) childView.getLayoutParams(); // 上面两个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设置为我们计算的值 * 否则:直接设置为父容器计算的值 */ setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? sizeWidth : width, (heightMode == MeasureSpec.EXACTLY) ? sizeHeight : height); }
Ligne 17, définissez la largeur et la hauteur de tous ses enfants via la méthode MeasureChildren de ViewGroup. Une fois cette ligne exécutée, la largeur et la hauteur de childView ont été correctement calculées
Lignes 43 à 71, selon la. childView La largeur et la hauteur, ainsi que la marge, calculent la largeur et la hauteur de ViewGroup lors de wrap_content.
Lignes 80 à 82, si la valeur de l'attribut width et height est wrap_content, elle est définie sur la valeur calculée aux lignes 43 à 71, sinon il s'agit de la largeur et de la hauteur transmises par son conteneur parent.
3. onLayout positionne toutes ses childViews (définit la zone de dessin de la childView)
// 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根据其宽和高,以及margin进行布局 */ 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,根据childView的宽和高以及margin,然后分别将0,1,2,3位置的childView依次设置到左上、右上、左下、右下的位置。
如果是第一个View(index=0) :则childView.layout(cl, ct, cr, cb); cl为childView的leftMargin , ct 为topMargin , cr 为cl+ cWidth , cb为 ct + cHeight
如果是第二个View(index=1) :则childView.layout(cl, ct, cr, cb);
cl为getWidth() - cWidth - cParams.leftMargin- cParams.rightMargin;
ct 为topMargin , cr 为cl+ cWidth , cb为 ct + cHeight
剩下两个类似~
这样就完成了,我们的ViewGroup代码的编写,下面我们进行测试,分别设置宽高为固定值,wrap_content,match_parent
4、测试结果
布局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宽和高设置为固定值
效果图:
布局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的宽和高设置为wrap_content
<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的宽和高设置为match_parent
可以看到无论ViewGroup的宽和高的值如何定义,我们的需求都实现了预期的效果~~
四、使用ViewDragHelper自定义ViewGroup
1、概述
在自定义ViewGroup中,很多效果都包含用户手指去拖动其内部的某个View(eg:侧滑菜单等),针对具体的需要去写好onInterceptTouchEvent和onTouchEvent这两个方法是一件很不容易的事,需要自己去处理:多手指的处理、加速度检测等等。
好在官方在v4的支持包中提供了ViewDragHelper这样一个类来帮助我们方便的编写自定义ViewGroup。简单看一下它的注释:
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.
下面将重点介绍ViewDragHelper的使用,并且最终去实现一个类似DrawerLayout的一个自定义的ViewGroup。(ps:官方的DrawerLayout就是用此类实现)
2、入门小示例
首先我们通过一个简单的例子来看看其快捷的用法,分为以下几个步骤:
A、创建实例
B、触摸相关的方法的调用
C、ViewDragHelper.Callback实例的编写
(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; } }
可以看到,上面整个自定义ViewGroup的代码非常简洁,遵循上述3个步骤:
A、创建实例
mDragger = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() { });
创建实例需要3个参数,第一个就是当前的ViewGroup,第二个sensitivity,主要用于设置touchSlop:
helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity));
可见传入越大,mTouchSlop的值就会越小。第三个参数就是Callback,在用户的触摸过程中会回调相关方法,后面会细说。
B、触摸相关方法
@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)来决定我们是否应该拦截当前的事件。onTouchEvent中通过mDragger.processTouchEvent(event)处理事件。
C、实现ViewDragHelper.CallCack相关方法
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中拦截和处理事件时,需要会回调CallBack中的很多方法来决定一些事,比如:哪些子View可以移动、对个移动的View的边界的控制等等。
上面复写的3个方法:
tryCaptureView如何返回ture则表示可以捕获该view,你可以根据传入的第一个view参数决定哪些可以捕获
clampViewPositionHorizontal,clampViewPositionVertical可以在该方法中对child移动的边界进行控制,left , top 分别为即将移动到的位置,比如横向的情况下,我希望只在ViewGroup的内部移动,即:最小>=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; }
经过上述3个步骤,我们就完成了一个简单的自定义ViewGroup,可以自由的拖动子View。
简单看一下布局文件
(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中有三个TextView。
当前效果:
可以看到短短数行代码就可以玩起来了~~~
有了直观的认识以后,我们还需要对ViewDragHelper.CallBack里面的方法做下深入的理解。首先我们需要考虑的是:我们的ViewDragHelper不仅仅说只能够去让子View去跟随我们手指移动,我们继续往下学习其他的功能。
3、功能展示
ViewDragHelper还能做以下的一些操作:
边界检测、加速度检测(eg:DrawerLayout边界触发拉出)
回调Drag Release(eg:DrawerLayout部分,手指抬起,自动展开/收缩)
移动到某个指定的位置(eg:点击Button,展开/关闭Drawerlayout)
那么我们接下来对我们最基本的例子进行改造,包含上述的几个操作。
首先看一下我们修改后的效果:
简单的为每个子View添加了不同的操作:
第一个View,就是演示简单的移动
第二个View,演示除了移动后,松手自动返回到原本的位置。(注意你拖动的越快,返回的越快)
第三个View,边界移动时对View进行捕获。
好了,看完效果图,来看下代码的修改:
修改后的代码
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禁止直接移动 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; } //手指释放的时候回调 @Override public void onViewReleased(View releasedChild, float xvel, float yvel) { //mAutoBackView手指释放时可以自动回去 if (releasedChild == mAutoBackView) { mDragger.settleCapturedViewAt(mAutoBackOriginPos.x, mAutoBackOriginPos.y); invalidate(); } } //在边界拖动时回调 @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); } }
布局文件我们仅仅是换了下文本和背景色就不重复贴了。
第一个View基本没做任何修改。
第二个View,我们在onLayout之后保存了最开启的位置信息,最主要还是重写了Callback中的onViewReleased,我们在onViewReleased中判断如果是mAutoBackView则调用settleCapturedViewAt回到初始的位置。大家可以看到紧随其后的代码是invalidate();因为其内部使用的是mScroller.startScroll,所以别忘了需要invalidate()以及结合computeScroll方法一起。
第三个View,我们在onEdgeDragStarted回调方法中,主动通过captureChildView对其进行捕获,该方法可以绕过tryCaptureView,所以我们的tryCaptureView虽然并为返回true,但却不影响。注意如果需要使用边界检测需要添加上mDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);。
到此,我们已经介绍了Callback中常用的回调方法了,当然还有一些方法没有介绍,接下来我们修改下我们的布局文件,我们把我们的TextView全部加上clickable=true,意思就是子View可以消耗事件。再次运行,你会发现本来可以拖动的View不动了,(如果有拿Button测试的兄弟应该已经发现这个问题了,我希望你看到这了,而不是已经提问了,哈~)。
原因是什么呢?主要是因为,如果子View不消耗事件,那么整个手势(DOWN-MOVE*-UP)都是直接进入onTouchEvent,在onTouchEvent的DOWN的时候就确定了captureView。如果消耗事件,那么就会先走onInterceptTouchEvent方法,判断是否可以捕获,而在判断的过程中会去判断另外两个回调的方法:getViewHorizontalDragRange和getViewVerticalDragRange,只有这两个方法返回大于0的值才能正常的捕获。
所以,如果你用Button测试,或者给TextView添加了clickable = true ,都记得重写下面这两个方法:
@Override public int getViewHorizontalDragRange(View child) { return getMeasuredWidth()-child.getMeasuredWidth(); } @Override public int getViewVerticalDragRange(View child) { return getMeasuredHeight()-child.getMeasuredHeight(); }
方法的返回值应当是该childView横向或者纵向的移动的范围,当前如果只需要一个方向移动,可以只复写一个。
到此,我们列一下所有的Callback方法,看看还有哪些没用过的:
onViewDragStateChanged
当ViewDragHelper状态发生变化时回调(IDLE,DRAGGING,SETTING[自动滚动时]):
onViewPositionChanged
当captureview的位置发生改变时回调:
onViewCaptured
当captureview被捕获时回调:
onViewReleased 已用
onEdgeTouched
当触摸到边界时回调:
onEdgeLock
true的时候会锁住当前的边界,false则unLock。
onEdgeDragStarted 已用
getOrderedChildIndex
改变同一个坐标(x,y)去寻找captureView位置的方法。(具体在:findTopChildUnder方法中)
getViewHorizontalDragRange 已用
getViewVerticalDragRange 已用
tryCaptureView 已用
clampViewPositionHorizontal 已用
clampViewPositionVertical 已用
ok,至此所有的回调方法都有了一定的认识。
总结下,方法的大致的回调顺序:
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,上述是正常情况下大致的流程,当然整个过程可能会存在很多判断不成立的情况。
On peut également expliquer à partir de ce qui précède que lorsque nous n'avons pas écrit la méthode getViewHorizontalDragRange dans le cas précédent de TextView (clickable=false), elle peut être déplacée. Parce qu'il entre directement dans le DOWN de processTouchEvent, puis onViewCaptured, onViewDragStateChanged (entre dans l'état DRAGGING), puis MOVE directement dragTo.
Lorsque la vue enfant consomme un événement, elle doit accéder à ShouldInterceptTouchEvent. Lors de MOVE, elle passe par une série de jugements (getViewHorizontalDragRange, clampViewPositionVertical, etc.) avant de pouvoir accéder à tryCaptureView.
ok, c'est la fin de l'utilisation introductive de ViewDragHelper. Dans le prochain article, nous utiliserons ViewDragHelper pour implémenter nous-mêmes un DrawerLayout.
Ceux qui sont intéressés peuvent également l'implémenter selon cet article et le code source de DrawerLayout~
Pour plus de tutoriels et d'articles connexes sur la personnalisation des conteneurs de vue ViewGroup dans le développement d'applications Android, veuillez faire attention au PHP Site chinois !