首页 >> 大全

2behavior原理解析

2023-10-03 大全 32 作者:考证青年

在上篇文章中,我们简单介绍了一下,今天对他的原理做进一步分析。主要介绍如何自定义,的构造,,fab为何随变化的相关的知识。

自定义

先看个例子,上篇文章主要是重点分析了下,为什么出现和消失的时候,fab会做出相应变化,那我们能否修改这种变化呢?

比如我想要出现的时候,fab往上移动100,消失的时候fab再往上移动100,能否实现呢?

当然可以,代码也很简单,自定义一个,注意必须加入一个的构造器,带有和参数,原因后文会说。

public class MyBehavior extends CoordinatorLayout.Behavior<View> {//此构造函数必须加入public MyBehavior(Context context, AttributeSet attrs) {super(context,attrs);}//child就是绑定此behavior的view,dependency是发送变化的view@Overridepublic boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {return dependency instanceof Snackbar.SnackbarLayout;}@Overridepublic boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {//此处child 就是fab,dependency是被依赖的viewif (dependency instanceof Snackbar.SnackbarLayout) {//SnackbarLayout 变化了,A该如何变化在这里写child.setTranslationY(child.getTranslationY() - 100);return true;}return false;}@Overridepublic void onDependentViewRemoved(CoordinatorLayout parent, View child, View dependency) {if (dependency instanceof Snackbar.SnackbarLayout) {//SnackbarLayout 变化了,fab该如何变化在这里写child.setTranslationY(child.getTranslationY() - 100);}}
}

然后在xml内配置,其实就是加入了一行代码 app:=”com.fish.a2.”

    <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" />

看效果

所以说自定义也是很简单的事情

原理分析 到底是什么

从上边的代码看来,像是view的一个属性,其实他是view的的一个属性,就像宽高一样。当然不是任何一个view的都有这个属性的,只有为.....才有这个属性,说白了,就是只有的子view的可以设置。我们看看.的代码,可以发现变量的确在里面。

  public static class LayoutParams extends ViewGroup.MarginLayoutParams {Behavior mBehavior;boolean mBehaviorResolved = false;...final Rect mLastChildRect = new Rect();}

再来看看app:=”com.fish.a2.”这行代码是怎么导致内的被赋值的

我们知道的时候,会根据xml去构造,所以我们看.的构造函数

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的时,走到L7,查一下是否存在值,如果存在,那就并且赋值给。就是把字符串com.fish.a2.变成一个对象,用反射的方法。主要代码如下所示

// 这里是指定的Behavior构造器的参数类型
static final Class[] CONSTRUCTOR_PARAMS = new Class[] {Context.class,AttributeSet.class
};...static Behavior parseBehavior(Context context, AttributeSet attrs, String name) {...try {Map> constructors = sConstructors.get();if (constructors == null) {constructors = new HashMap<>();sConstructors.set(constructors);}Constructor c = constructors.get(fullName);if (c == null) {final Class clazz = (Class) 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.的,就是一个,一个,为什么我们开头的时候说自定义必须带一个这种类型的构造器的,现在应该有答案了。

那么现在问题来了,在上一篇文章中,我们根本就没有设置,那这个里面的是在哪里复制的呢?

我们再来看看, 有个注解,.(..class),在这里指定了默认的

@CoordinatorLayout.DefaultBehavior(FloatingActionButton.Behavior.class)
public class FloatingActionButton extends VisibilityAwareImageButton 

在的的时候会调用,进而调用ams,在ams里会把注解里的默认赋值给,主要代码如下

    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的有2种方式,xml内指定,或者注解里指定,xml优先级高。xml内指定的话,是在的时候对赋值的,在注解里指定的话,是在内赋值的,稍有不同。

如何发挥作用

前面说了如何给view配置,那配了又有什么用呢?为何能够监测到另一个view的变化情况,这都是的功劳。

我们再来看看的代码,主要看和r

    @Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {prepareChildren();ensurePreDrawListener();。。。

先看

    private void prepareChildren() {//清空mDependencySortedChildrenmDependencySortedChildren.clear();for (int i = 0, count = getChildCount(); i < count; i++) {final View child = getChildAt(i);final LayoutParams lp = getResolvedLayoutParams(child);lp.findAnchorView(this, child);//加入childmDependencySortedChildren.add(child);}// We need to use a selection sort here to make sure that every item is compared// against each other//排序selectionSort(mDependencySortedChildren, mLayoutDependencyComparator);}

内做了什么,主要是搞出来一个ldren,根据依赖关系对child进行排序。首先L3把ldren clear,然后遍历子view,全部加入到ldren内,最后对ldren进行排序。注意每次都会调用来搞出一个ldren。

我们在看看排序的代码(用的冒泡),看就行了,看下边代码可以知道,被依赖的view放前面,比如我们fab依赖于,那么必然放在fab的前边。这么排序有什么用?其实是提高一点效率,后文会说的。

    final Comparator mLayoutDependencyComparator = new Comparator() {@Overridepublic 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;}}};

再看r

在确定ldren之后,会执行r,在这里写判断下的子view是否存在依赖关系,如果存在的话就为true,后边会加入。

    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) {//加入PreDrawListeneraddPreDrawListener();} else {removePreDrawListener();}}}

是什么?看下边代码,简单,就是在的时候调用。

    void addPreDrawListener() {if (mIsAttachedToWindow) {// Add the listenerif (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 {@Overridepublic boolean onPreDraw() {dispatchOnDependentViewChanged(false);return true;}}

这个回调和类似的,他们的对象是,而不是某个view。在即将绘制的时候,会调用.(),然后分发到各个,在回调的。简单的说,就是在重绘之前,会调用。我们在里面调用了,这个函数是非常重要的函数。的主要行为都是写在这里面的。我们先总结下r做了什么,判断子view是否有依赖行为,如果有的话注册一个监听

这里传进来的为false,遍历ldren,查一下每个view的rect是否发生了变化,如果发生了变化(假设变化的view为A),就遍历后边的view,判断后边view是否依赖于A(L33),如果依赖就做出相应变化(L36)。看到L33和L36,终于舒了口气,和里的行为扯上了关系。

    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 anchorfor (int j = 0; j < i; j++) {final View checkChild = mDependencySortedChildren.get(j);if (lp.mAnchorDirectChild == checkChild) {offsetChildToAnchor(child, layoutDirection);}}// Did it change? if not continuefinal 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 changefor (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的layoutDependsOnif (b != null && b.layoutDependsOn(this, checkChild, child)) {。。。//这里调用了behavior的onDependentViewChangedfinal boolean handled = b.onDependentViewChanged(this, checkChild, child);...}}}}

这里再说几点,怎么知道哪些view发生了变化,代码如下,就是看和 是否一致,就是获取view的当前rect,而是获取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);

看看的代码,原来的里面存储了。看上边的L8可以知道,会记录到里。

    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后边的代码就可以了,为什么?看看前文的ldren的排序规则就知道了,B,C绝对是在A的后边。可以省去找前面的view,这就是ldren排序的作用。

再后边代码就是,先判断下b.是否返回true,然后执行b.ed

                if (b != null && b.layoutDependsOn(this, checkChild, child)) {。。。//这里调用了behavior的onDependentViewChangedfinal boolean handled = b.onDependentViewChanged(this, checkChild, child);...}}

好了,的原理基本分析完了。有点绕,但不复杂。

我个人认为,这个实现过程还可以优化一下,比如上文第一个for循环,是遍历了所有的子view,实际上只要遍历被依赖的子view 就好了。而第二个for循环,是遍历了child(rect变化的view)之后的所有子view,其实也没这个必要,因为依赖关系是早就定好的,可以建一个数组存储哪些view依赖了child,这样只要遍历这个数组就可以了。

如果是我来写,我会给每个view设计一个依赖者数组,比如Aview的依赖者数组内有B,C,就代表B依赖于A,C依赖于A。 那第一个for循环遍历依赖者数组非空的view即可,而第二个for循环遍历依赖者数组就好。

还有一点,view的rect发生变化肯定在之后就知道了,如果在里把发生变化的view记录下来,那么第一个for循环就可以更简单了,也没必要在里面设计一个了。

以上是我的个人想法,如有不对,欢迎指正,可能代码认为反正的子view很小,所以没必要搞那么复杂。

ed

我们开篇自定布局的时候还写了ed,那这个ed是在哪里被调用的呢?

内有个,view结构发生变化时会触发tener回调。

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);}

再看内自己定义了一个ner,在的时候会调用,这个ner在构造函数内set。所以有view被调的时候回回调到

private class HierarchyChangeListener implements OnHierarchyChangeListener {@Overridepublic void onChildViewAdded(View parent, View child) {if (mOnHierarchyChangeListener != null) {mOnHierarchyChangeListener.onChildViewAdded(parent, child);}}@Overridepublic void onChildViewRemoved(View parent, View child) {dispatchDependentViewRemoved(child);if (mOnHierarchyChangeListener != null) {mOnHierarchyChangeListener.onChildViewRemoved(parent, child);}}}

的代码也很简单,会根据需要触发ed

   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 dependentviewSeen = 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被而加的,那为什么增加一个view的时候没这么麻烦,删除一个view就这么麻烦呢。因为增加了一个view,那这个view,必然在ldren内,而删除了一个view,这个view在ldren就找不到了,所以加了这一堆代码

泛型类

要知道其实是个泛型类

    public static abstract class Behavior<V extends View> 

所以自定义可以这么写,这样更优雅,免去了强转

    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;@Overridepublic boolean layoutDependsOn(CoordinatorLayout parent,FloatingActionButton child, View dependency) {// We're dependent on all SnackbarLayouts (if enabled)return SNACKBAR_BEHAVIOR_ENABLED && dependency instanceof Snackbar.SnackbarLayout;}@Overridepublic 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 AppBarLayoutupdateFabVisibility(parent, (AppBarLayout) dependency, child);}return false;}@Overridepublic void onDependentViewRemoved(CoordinatorLayout parent, FloatingActionButton child,View dependency) {if (dependency instanceof Snackbar.SnackbarLayout) {updateFabTranslationForSnackbar(parent, child, dependency);}}

总结

1、view的有2种方式,xml内指定,或者注解里指定,xml优先级高。xml内指定的话,是在的时候对赋值的,在注解里指定的话,是在内赋值的,稍有不同。

2、能够检测到view的尺寸变化以及view被

3、内的ldren里,被依赖的view放前面,比如我们fab依赖于,那么必然放在fab的前边。

关于我们

最火推荐

小编推荐

联系我们


版权声明:本站内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 88@qq.com 举报,一经查实,本站将立刻删除。备案号:桂ICP备2021009421号
Powered By Z-BlogPHP.
复制成功
微信号:
我知道了