2behavior原理解析
来源:互联网 发布:c语言逻辑表达式符号 编辑:程序博客网 时间:2024/06/05 19:23
在上篇文章中,我们简单介绍了一下Behavior,今天对他的原理做进一步分析。主要介绍behavior如何自定义,behavior的构造,onPreDraw,fab为何随snackbar变化的相关的知识。
自定义behavior
先看个例子,上篇文章主要是重点分析了下,为什么snackbar出现和消失的时候,fab会做出相应变化,那我们能否修改这种变化呢?
比如我想要snackbar出现的时候,fab往上移动100,snackbar消失的时候fab再往上移动100,能否实现呢?
当然可以,代码也很简单,自定义一个MyBehavior,注意必须加入一个MyBehavior的构造器,带有Context和AttributeSet参数,原因后文会说。
public class MyBehavior extends CoordinatorLayout.Behavior<View> { //此构造函数必须加入 public MyBehavior(Context context, AttributeSet attrs) { super(context,attrs); } //child就是绑定此behavior的view,dependency是发送变化的view @Override public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) { return dependency instanceof Snackbar.SnackbarLayout; } @Override public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) { //此处child 就是fab,dependency是被依赖的view if (dependency instanceof Snackbar.SnackbarLayout) { //SnackbarLayout 变化了,A该如何变化在这里写 child.setTranslationY(child.getTranslationY() - 100); return true; } return false; } @Override public void onDependentViewRemoved(CoordinatorLayout parent, View child, View dependency) { if (dependency instanceof Snackbar.SnackbarLayout) { //SnackbarLayout 变化了,fab该如何变化在这里写 child.setTranslationY(child.getTranslationY() - 100); } }}
然后在xml内配置behavior,其实就是加入了一行代码 app:layout_behavior=”com.fish.a2.MyBehavior”
<android.support.design.widget.FloatingActionButton android:id="@+id/fab" app:layout_behavior="com.fish.a2.MyBehavior" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" android:layout_margin="@dimen/fab_margin" android:src="@android:drawable/ic_dialog_email" />
看效果
所以说自定义Behavior也是很简单的事情
原理分析
behavior到底是什么
从上边的代码看来,behavior像是view的一个属性,其实他是view的LayoutParam的一个属性,就像宽高一样。当然不是任何一个view的LayoutParam都有这个属性的,只有LayoutParam为android.support.design.widget.CoordinatorLayout.LayoutParams才有这个属性,说白了,就是只有CoordinatorLayout的子view的LayoutParam可以设置behavior。我们看看CoordinatorLayout.LayoutParams的代码,可以发现Behavior变量的确在里面。
public static class LayoutParams extends ViewGroup.MarginLayoutParams { Behavior mBehavior; boolean mBehaviorResolved = false; ... final Rect mLastChildRect = new Rect(); }
再来看看app:layout_behavior=”com.fish.a2.MyBehavior”这行代码是怎么导致LayoutParams内的mBehavior被赋值的
我们知道infate的时候,会根据xml去构造LayoutParams,所以我们看CoordinatorLayout.LayoutParams的构造函数
LayoutParams(Context context, AttributeSet attrs) { super(context, attrs); final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CoordinatorLayout_LayoutParams); ... mBehaviorResolved = a.hasValue( R.styleable.CoordinatorLayout_LayoutParams_layout_behavior); if (mBehaviorResolved) { mBehavior = parseBehavior(context, attrs, a.getString( R.styleable.CoordinatorLayout_LayoutParams_layout_behavior)); } a.recycle();}
当我们构造fab的LayoutParams时,走到L7,查一下是否存在layout_behavior值,如果存在,那就parseBehavior并且赋值给mBehavior。parseBehavior就是把字符串com.fish.a2.MyBehavior变成一个对象,用反射的方法。主要代码如下所示
// 这里是指定的Behavior构造器的参数类型static final Class<?>[] CONSTRUCTOR_PARAMS = new Class<?>[] { Context.class, AttributeSet.class};...static Behavior parseBehavior(Context context, AttributeSet attrs, String name) { ... try { Map<String, Constructor<Behavior>> constructors = sConstructors.get(); if (constructors == null) { constructors = new HashMap<>(); sConstructors.set(constructors); } Constructor<Behavior> c = constructors.get(fullName); if (c == null) { final Class<Behavior> clazz = (Class<Behavior>) Class.forName(fullName, true, context.getClassLoader()); //获取特定参数的构造器 c = clazz.getConstructor(CONSTRUCTOR_PARAMS); c.setAccessible(true); constructors.put(fullName, c); } return c.newInstance(context, attrs); } catch (Exception e) { throw new RuntimeException("Could not inflate Behavior subclass " + fullName, e); }}
主要L23,这里是用特定参数的构造器来c.newInstance的,CONSTRUCTOR_PARAMS就是一个Context,一个AttributeSet,为什么我们开头的时候说自定义Behavior必须带一个这种类型的构造器的,现在应该有答案了。
那么现在问题来了,在上一篇文章中,我们根本就没有设置layout_behavior,那这个LayoutParams里面的mBehavior是在哪里复制的呢?
我们再来看看, FloatingActionButton有个注解,CoordinatorLayout.DefaultBehavior(FloatingActionButton.Behavior.class),在这里指定了默认的Behavior
@CoordinatorLayout.DefaultBehavior(FloatingActionButton.Behavior.class)public class FloatingActionButton extends VisibilityAwareImageButton
在CoordinatorLayout的onMeasure的时候会调用prepareChildren,进而调用getResolvedLayoutParams,在getResolvedLayoutParams里会把注解里的默认Behavior赋值给mBehavior,主要代码如下
LayoutParams getResolvedLayoutParams(View child) { final LayoutParams result = (LayoutParams) child.getLayoutParams(); //如果xml内写了behavior,此时result.mBehaviorResolved就为true,不会进去 if (!result.mBehaviorResolved) { Class<?> childClass = child.getClass(); DefaultBehavior defaultBehavior = null; while (childClass != null && (defaultBehavior = childClass.getAnnotation(DefaultBehavior.class)) == null) { childClass = childClass.getSuperclass(); } if (defaultBehavior != null) { try { result.setBehavior(defaultBehavior.value().newInstance()); } catch (Exception e) { Log.e(TAG, "Default behavior class " + defaultBehavior.value().getName() + " could not be instantiated. Did you forget a default constructor?", e); } } result.mBehaviorResolved = true; } return result; }
所以到了现在,我们知道设置一个view的behavior有2种方式,xml内指定,或者注解里指定,xml优先级高。xml内指定的话,是在inflate的时候对mBehavior赋值的,在注解里指定的话,是在onMeasure内赋值的,稍有不同。
behavior如何发挥作用
前面说了如何给view配置behavior,那配了behavior又有什么用呢?为何behavior能够监测到另一个view的变化情况,这都是CoordinatorLayout的功劳。
onMeasure
我们再来看看onMeasure的代码,主要看prepareChildren和ensurePreDrawListener
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { prepareChildren(); ensurePreDrawListener(); 。。。
先看prepareChildren
private void prepareChildren() { //清空mDependencySortedChildren mDependencySortedChildren.clear(); for (int i = 0, count = getChildCount(); i < count; i++) { final View child = getChildAt(i); final LayoutParams lp = getResolvedLayoutParams(child); lp.findAnchorView(this, child); //加入child mDependencySortedChildren.add(child); } // We need to use a selection sort here to make sure that every item is compared // against each other //排序 selectionSort(mDependencySortedChildren, mLayoutDependencyComparator); }
prepareChildren内做了什么,主要是搞出来一个mDependencySortedChildren,根据依赖关系对child进行排序。首先L3把mDependencySortedChildren clear,然后遍历子view,全部加入到mDependencySortedChildren内,最后对mDependencySortedChildren进行排序。注意每次measure都会调用prepareChildren来搞出一个mDependencySortedChildren。
我们在看看排序的代码(用的冒泡),看mLayoutDependencyComparator就行了,看下边代码可以知道,被依赖的view放前面,比如我们fab依赖于snackbar,那么snackbar必然放在fab的前边。这么排序有什么用?其实是提高一点效率,后文会说的。
final Comparator<View> mLayoutDependencyComparator = new Comparator<View>() { @Override public int compare(View lhs, View rhs) { if (lhs == rhs) { return 0; } else if (((LayoutParams) lhs.getLayoutParams()).dependsOn( CoordinatorLayout.this, lhs, rhs)) { return 1; } else if (((LayoutParams) rhs.getLayoutParams()).dependsOn( CoordinatorLayout.this, rhs, lhs)) { return -1; } else { return 0; } } };
再看ensurePreDrawListener
在prepareChildren确定mDependencySortedChildren之后,会执行ensurePreDrawListener,在这里写判断下CoordinatorLayout的子view是否存在依赖关系,如果存在的话就hasDependencies为true,后边会加入PreDrawListener。
void ensurePreDrawListener() { //判断是否存在依赖关系 boolean hasDependencies = false; final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); if (hasDependencies(child)) { hasDependencies = true; break; } } if (hasDependencies != mNeedsPreDrawListener) { if (hasDependencies) { //加入PreDrawListener addPreDrawListener(); } else { removePreDrawListener(); } } }
PreDrawListener是什么?看下边代码,简单,就是在onPreDraw的时候调用dispatchOnDependentViewChanged。
void addPreDrawListener() { if (mIsAttachedToWindow) { // Add the listener if (mOnPreDrawListener == null) { mOnPreDrawListener = new OnPreDrawListener(); } final ViewTreeObserver vto = getViewTreeObserver(); vto.addOnPreDrawListener(mOnPreDrawListener); } // Record that we need the listener regardless of whether or not we're attached. // We'll add the real listener when we become attached. mNeedsPreDrawListener = true; }
class OnPreDrawListener implements ViewTreeObserver.OnPreDrawListener { @Override public boolean onPreDraw() { dispatchOnDependentViewChanged(false); return true; } }
onPreDraw这个回调和onGlobalLayout类似的,他们的对象是ViewTreeObserver,而不是某个view。在即将绘制的时候,会调用mTreeObserver.dispatchOnPreDraw(),然后分发到各个OnPreDrawListener,在回调onPreDraw的。简单的说,就是在重绘之前,会调用onPreDraw。我们在onPreDraw里面调用了dispatchOnDependentViewChanged,这个函数是CoordinatorLayout非常重要的函数。Behavior的主要行为都是写在这里面的。我们先总结下ensurePreDrawListener做了什么,判断子view是否有依赖行为,如果有的话注册一个onPreDraw监听
dispatchOnDependentViewChanged
这里传进来的fromNestedScroll为false,遍历mDependencySortedChildren,查一下每个view的rect是否发生了变化,如果发生了变化(假设变化的view为A),就遍历后边的view,判断后边view是否依赖于A(L33),如果依赖就做出相应变化(L36)。看到L33和L36,终于舒了口气,和Behavior里的行为扯上了关系。
void dispatchOnDependentViewChanged(final boolean fromNestedScroll) { final int layoutDirection = ViewCompat.getLayoutDirection(this); final int childCount = mDependencySortedChildren.size(); for (int i = 0; i < childCount; i++) { final View child = mDependencySortedChildren.get(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); // Check child views before for anchor for (int j = 0; j < i; j++) { final View checkChild = mDependencySortedChildren.get(j); if (lp.mAnchorDirectChild == checkChild) { offsetChildToAnchor(child, layoutDirection); } } // Did it change? if not continue final Rect oldRect = mTempRect1; final Rect newRect = mTempRect2; getLastChildRect(child, oldRect); getChildRect(child, true, newRect); if (oldRect.equals(newRect)) { continue; } recordLastChildRect(child, newRect); // Update any behavior-dependent views for the change for (int j = i + 1; j < childCount; j++) { final View checkChild = mDependencySortedChildren.get(j); final LayoutParams checkLp = (LayoutParams) checkChild.getLayoutParams(); final Behavior b = checkLp.getBehavior(); //这里调用了behavior的layoutDependsOn if (b != null && b.layoutDependsOn(this, checkChild, child)) { 。。。 //这里调用了behavior的onDependentViewChanged final boolean handled = b.onDependentViewChanged(this, checkChild, child); ... } } } }
这里再说几点,怎么知道哪些view发生了变化,代码如下,就是看oldRect和 newRect 是否一致,getChildRect就是获取view的当前rect,而getLastChildRect是获取view的旧的rect,这个比较奇怪,居然知道旧的rect。
final Rect oldRect = mTempRect1; final Rect newRect = mTempRect2; getLastChildRect(child, oldRect); getChildRect(child, true, newRect); if (oldRect.equals(newRect)) { continue; } recordLastChildRect(child, newRect);
看看getLastChildRect的代码,原来CoordinatorLayout的LayoutParams里面存储了mLastChildRect。看上边的L8可以知道,会记录newRect到LayoutParams里。
void getLastChildRect(View child, Rect out) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); out.set(lp.getLastChildRect()); }
还有个问题,比如我们知道子view A发生了变化,可能有B依赖于A,C依赖于A,怎么去找B,C呢,看上边L28,只要遍历A后边的代码就可以了,为什么?看看前文的mDependencySortedChildren的排序规则就知道了,B,C绝对是在A的后边。可以省去找前面的view,这就是mDependencySortedChildren排序的作用。
再后边代码就是,先判断下b.layoutDependsOn是否返回true,然后执行b.onDependentViewChanged
if (b != null && b.layoutDependsOn(this, checkChild, child)) { 。。。 //这里调用了behavior的onDependentViewChanged final boolean handled = b.onDependentViewChanged(this, checkChild, child); ... } }
好了,behavior的原理基本分析完了。有点绕,但不复杂。
我个人认为,这个实现过程还可以优化一下,比如上文第一个for循环,是遍历了所有的子view,实际上只要遍历被依赖的子view 就好了。而第二个for循环,是遍历了child(rect变化的view)之后的所有子view,其实也没这个必要,因为依赖关系是早就定好的,可以建一个数组存储哪些view依赖了child,这样只要遍历这个数组就可以了。
如果是我来写,我会给每个view设计一个依赖者数组,比如Aview的依赖者数组内有B,C,就代表B依赖于A,C依赖于A。 那第一个for循环遍历依赖者数组非空的view即可,而第二个for循环遍历依赖者数组就好。
还有一点,view的rect发生变化肯定在onLayout之后就知道了,如果在onLayout里把发生变化的view记录下来,那么第一个for循环就可以更简单了,也没必要在LayoutParam里面设计一个mLastChildRect了。
以上是我的个人想法,如有不对,欢迎指正,可能代码google认为反正CoordinatorLayout的子view很小,所以没必要搞那么复杂。
onDependentViewRemoved
我们开篇自定布局的时候还写了onDependentViewRemoved,那这个onDependentViewRemoved是在哪里被调用的呢?
ViewGroup内有个mOnHierarchyChangeListener,view结构发生变化时会触发OnHierarchyChangeListener回调。
protected OnHierarchyChangeListener mOnHierarchyChangeListener; public interface OnHierarchyChangeListener { /** * Called when a new child is added to a parent view. * * @param parent the view in which a child was added * @param child the new child view added in the hierarchy */ void onChildViewAdded(View parent, View child); /** * Called when a child is removed from a parent view. * * @param parent the view from which the child was removed * @param child the child removed from the hierarchy */ void onChildViewRemoved(View parent, View child); }
再看CoordinatorLayout内自己定义了一个HierarchyChangeListener,在onChildViewRemoved的时候会调用dispatchDependentViewRemoved,这个HierarchyChangeListener在构造函数内set。所以有view被remove调的时候回回调到
dispatchDependentViewRemoved。
private class HierarchyChangeListener implements OnHierarchyChangeListener { @Override public void onChildViewAdded(View parent, View child) { if (mOnHierarchyChangeListener != null) { mOnHierarchyChangeListener.onChildViewAdded(parent, child); } } @Override public void onChildViewRemoved(View parent, View child) { dispatchDependentViewRemoved(child); if (mOnHierarchyChangeListener != null) { mOnHierarchyChangeListener.onChildViewRemoved(parent, child); } } }
dispatchDependentViewRemoved的代码也很简单,会根据需要触发onDependentViewRemoved
void dispatchDependentViewRemoved(View view) { final int childCount = mDependencySortedChildren.size(); boolean viewSeen = false; for (int i = 0; i < childCount; i++) { final View child = mDependencySortedChildren.get(i); if (child == view) { // We've seen our view, which means that any Views after this could be dependent viewSeen = true; continue; } if (viewSeen) { CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) child.getLayoutParams(); CoordinatorLayout.Behavior b = lp.getBehavior(); if (b != null && lp.dependsOn(this, child, view)) { b.onDependentViewRemoved(this, child, view); } } } }
上述代码都是为了监听某个view被remove而加的,那为什么增加一个view的时候没这么麻烦,删除一个view就这么麻烦呢。因为增加了一个view,那这个view,必然在mDependencySortedChildren内,而删除了一个view,这个view在mDependencySortedChildren就找不到了,所以加了这一堆代码
泛型类Behavior
要知道Behavior其实是个泛型类
public static abstract class Behavior<V extends View>
所以自定义Behavior可以这么写,这样更优雅,免去了强转
public static class Behavior extends CoordinatorLayout.Behavior<FloatingActionButton> { // We only support the FAB <> Snackbar shift movement on Honeycomb and above. This is // because we can use view translation properties which greatly simplifies the code. private static final boolean SNACKBAR_BEHAVIOR_ENABLED = Build.VERSION.SDK_INT >= 11; private ValueAnimatorCompat mFabTranslationYAnimator; private float mFabTranslationY; private Rect mTmpRect; @Override public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton child, View dependency) { // We're dependent on all SnackbarLayouts (if enabled) return SNACKBAR_BEHAVIOR_ENABLED && dependency instanceof Snackbar.SnackbarLayout; } @Override public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child, View dependency) { if (dependency instanceof Snackbar.SnackbarLayout) { updateFabTranslationForSnackbar(parent, child, dependency); } else if (dependency instanceof AppBarLayout) { // If we're depending on an AppBarLayout we will show/hide it automatically // if the FAB is anchored to the AppBarLayout updateFabVisibility(parent, (AppBarLayout) dependency, child); } return false; } @Override public void onDependentViewRemoved(CoordinatorLayout parent, FloatingActionButton child, View dependency) { if (dependency instanceof Snackbar.SnackbarLayout) { updateFabTranslationForSnackbar(parent, child, dependency); } }
总结
1、view的behavior有2种方式,xml内指定,或者注解里指定,xml优先级高。xml内指定的话,是在inflate的时候对mBehavior赋值的,在注解里指定的话,是在onMeasure内赋值的,稍有不同。
2、behavior能够检测到view的尺寸变化以及view被remove
3、CoordinatorLayout内的mDependencySortedChildren里,被依赖的view放前面,比如我们fab依赖于snackbar,那么snackbar必然放在fab的前边。
- 2behavior原理解析
- coordinatorlayout和behavior解析
- 源码看CoordinatorLayout.Behavior原理
- 源码看CoordinatorLayout.Behavior原理
- Behavior,Trigger,TriggerAction深度解析
- Android Behavior之相关解析
- Material之Behavior(2)
- ThinkPHP3.2 Behavior随记
- behavior
- BEHAVIOR
- Behavior
- Behavior
- CoordinatorLayout源码解析之初识Behavior
- 安卓behavior详解2--自定义behavior简单案例
- Iwfu-CoordinatorLayout(2)自定义Behavior
- yii2 controller behavior函数的beforeAction实现原理
- 16个behavior question 的面试官解析及tips
- CoordinatorLayout源码解析,探索Behavior机制的奥秘
- tomcat部署weblogic服务器项目到本地执行的总结整理
- PHP常用字符串函数详解
- 写在前面
- ThinkPHP中同时上传视频和图片的简单处理方法
- cocos2djs游戏android sdk接入
- 2behavior原理解析
- RGB24转yuv420 高效率 且颜色没有失真
- HTML5分组元素(20160804-0004)
- Instant Run(App加壳)
- Spring 注解学习手札(六) 测试
- java操作csv文件
- 获取App启动时间
- JS倒数计时器
- Git分支管理策略