CoordinatorLayout.Behavior

2023-12-24 20:58

本文主要是介绍CoordinatorLayout.Behavior,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

CoordinatorLayout我们可以将它理解为一个超级Fragment,它的布局方式是一层一层叠上去,而且它可以组织子View之间的协作。组织协作的方式需要使用最重要的对象Behavior
Behavior是CoordinatorLayout实现子View之间交互的插件,它可以实现用户的一个或多个交互行为,它们可能包括拖拽、滑动、或者其他一些手势。
我们在使用CoordinatorLayout的时候,在NestedScrollView的xml属性中总是能看到app:layout_behavior="@string/appbar_scrolling_view_behavior"。NestedScrollView要想与AppBarLayout有联动,那么NestedScrollView作为直接的子View,就必须设置这个behavior,当然这个behavior谷歌已经给默认设置好了。

常用的方法(暂不介绍嵌套滑动)

1 . onLayoutChild可以用于子View视图布局的更改,修改behavior默认设置子View的行为。需要调用parent.onLayoutChild

  /**** @param parent CoordinatorLayout* @param child   子View* @param layoutDirection  ViewCompat.LAYOUT_DIRECTION_LTR(水平布局从左到右)*                         ViewCompat.LAYOUT_DIRECTION_RTL(水平布局从右到左)* @return  false表示不改变,true改变View的视图*/@Overridepublic boolean onLayoutChild(CoordinatorLayout parent, ImageView child, int layoutDirection) {return super.onLayoutChild(parent, child, layoutDirection);}



2 . layoutDependsOn View的依赖关系在这里设置

/*** 表示是否给应用Behavior的View指定一个依赖的布局,一般当依赖的View布局发生变化时* 不管被被依赖View的顺序怎样,被依赖的View也会重新布局* @param parent CoordinatorLayout * @param child 绑定behavior 的View* @param dependency   依赖的view* @return 如果child是依赖的指定的View 返回true,否则返回false*/@Overridepublic boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {return super.layoutDependsOn(parent, child, dependency);}


3 . onDependentViewChanged

/*** 当依赖的视图状态位置、大小发生变化时,就会调用这个方法* @param parent* @param child* @param dependency* @return*/@Overridepublic boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {return super.onDependentViewChanged(parent, child, dependency);}

只是介绍这几个方法,可能我们还不太清楚,所以直接做几个练习,就能看到效果了。
我这里不准备做练习了,练习可以自己试,我这里要简单说一说源码里面这几个方法怎么调用的(我的水平只能简单说一说/(ㄒoㄒ)/~~)
我们一般会在XML中直接定义Behavior,我们在代码中来看看这个Behavior是怎么解析的。

LayoutParams(Context context, AttributeSet attrs) {super(context, attrs);...省略代码final TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.CoordinatorLayout_LayoutParams);...省略代码//通过检测app:behavior="xxx",获取路径。所以在定义behavior的时候,一定要写有//两个参数的构造方法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();
}static Behavior parseBehavior(Context context, AttributeSet attrs, String name) {if (TextUtils.isEmpty(name)) {return null;}final String fullName;if (name.startsWith(".")) {//相对路径fullName = context.getPackageName() + name;} else if (name.indexOf('.') >= 0) {//全限定名fullName = name;} else {// Assume stock behavior in this package (if we have one)fullName = !TextUtils.isEmpty(WIDGET_PACKAGE_NAME)? (WIDGET_PACKAGE_NAME + '.' + name): 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);}
}


在xml里面写的话,是在inflate的时候对Behavior赋值的
以注解方式写的话,是在onMeasure内赋值的。(后面会说为什么)


为View配置了Behavior,那么我们接着来看View之间是怎么依赖的。首先在LayoutParams中有关依赖的代码如下:

boolean dependsOn(CoordinatorLayout parent, View child, View dependency) {return dependency == mAnchorDirectChild|| (mBehavior != null && mBehavior.layoutDependsOn(parent, child, dependency));
}


返回两种结果,满足一种就是依赖的关系。一种是设置anchor ,另一种就是View的Behavior对另一个View有依赖。


如何处理这种依赖关系呢?既然Behavior能够监测另一个View的变化状况,那么肯定会有重新测量等操作。所以我们来看下面的代码

 @Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {prepareChildren();ensurePreDrawListener();...省略代码}

点进去prepareChildren();方法,看看里面写了什么…


private final List<View> mDependencySortedChildren = new ArrayList<View>();//根据依赖关系对child进行排序private void prepareChildren() {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);mDependencySortedChildren.add(child);}//排序,按依赖关系排序,被依赖的View排在前面,保证被依赖的View先被测量绘制selectionSort(mDependencySortedChildren, mLayoutDependencyComparator);}


可以看到mDependencySortedChildren这个集合按照依赖关系将View存储了起来,至于为什么要排序,这个有性能上的考虑。getResolvedLayoutParams(child)这里有判断和解析注解的:

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的层级变化了,所以在ensurePreDrawListener方法中先判断是否存在依赖关系,如果存在,直接注册相关的监听。

//添加或者删除绘制前的监听器 void ensurePreDrawListener() {boolean hasDependencies = false;final int childCount = getChildCount();//判断下CoordinatorLayout的子view是否存在依赖关系//如果存在的话就hasDependencies为truefor (int i = 0; i < childCount; i++) {final View child = getChildAt(i);if (hasDependencies(child)) {hasDependencies = true;break;}}if (hasDependencies != mNeedsPreDrawListener) {if (hasDependencies) {addPreDrawListener();} else {removePreDrawListener();}}}...省略代码void addPreDrawListener() {if (mIsAttachedToWindow) {// Add the listenerif (mOnPreDrawListener == null) {mOnPreDrawListener = new OnPreDrawListener();}final ViewTreeObserver vto = getViewTreeObserver();//在重绘之前,我们在onPreDraw里调用了dispatchOnDependentViewChanged方法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;}}


ViewTreeObserver 是用来注册一个观察者来监听视图树,当视图树的布局、焦点、绘制、滚动等发生改变时,ViewTreeObserver都会收到通知。ViewTreeObserver不能被实例化,可以调用View.getViewTreeObserver()来获得。


dispatchOnDependentViewChanged方法是核心的方法,它会遍历根据依赖关系排序好的子View集合,找到位置改变了的View,或者有锚定目标的View,并回调依赖这个View的Behavior的onDependentViewChanged方法

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();// 检查View设置了Anchor,然后处理for (int j = 0; j < i; j++) {final View checkChild = mDependencySortedChildren.get(j);if (lp.mAnchorDirectChild == checkChild) {//调整child,让孩子到正确的锚视图位置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;}//记录newRect到LayoutParams里recordLastChildRect(child, newRect);// 找到依赖当前View的Behavior来进行回调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();if (b != null && b.layoutDependsOn(this, checkChild, child)) {if (!fromNestedScroll && checkLp.getChangedAfterNestedScroll()) {// If this is not from a nested scroll and we have already been changed// from a nested scroll, skip the dispatch and reset the flagcheckLp.resetChangedAfterNestedScroll();continue;}final boolean handled = b.onDependentViewChanged(this, checkChild, child);if (fromNestedScroll) {// If this is from a nested scroll, set the flag so that we may skip// any resulting onPreDraw dispatch (if needed)checkLp.setChangedAfterNestedScroll(handled);}}}}}

监听提供依赖的View的添加和移除,HierarchyChangeListener在View的添加和移除都会回调

private class HierarchyChangeListener implements OnHierarchyChangeListener {...@Overridepublic void onChildViewRemoved(View parent, View child) {dispatchDependentViewRemoved(child);...}
}


然后回调给Behavior#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) {// 判断后续位置的View是否依赖当前View并回调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);}}}
}

这样Behavior中关于依赖的关系就是这个样子了。


下面我们写一个简单的demo来测试一下,上面的方法。简单暴力,直接贴代码,也没有什么好讲的。

  1. XML
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:ignore="RtlHardcoded"><android.support.design.widget.AppBarLayout
        android:id="@+id/main.appbar"android:layout_width="match_parent"android:layout_height="wrap_content"android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"><android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/main.collapsing"android:layout_width="match_parent"android:layout_height="wrap_content"app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"><ImageView
                android:id="@+id/main.imageview.placeholder"android:layout_width="match_parent"android:layout_height="300dp"android:scaleType="fitXY"android:src="@drawable/huo"android:tint="#11000000"app:layout_collapseMode="parallax"app:layout_collapseParallaxMultiplier="0.9"android:contentDescription=""tools:ignore="ContentDescription" /><FrameLayout
                android:id="@+id/main.framelayout.title"android:layout_width="match_parent"android:layout_height="100dp"android:layout_gravity="bottom|center_horizontal"android:background="@color/primary"android:orientation="vertical"app:layout_collapseMode="parallax"app:layout_collapseParallaxMultiplier="0.3"><LinearLayout
                    android:id="@+id/main.linearlayout.title"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:orientation="vertical"tools:ignore="UselessParent"><TextView
                        android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:gravity="bottom|center"android:text="@string/quila_name"android:textColor="@android:color/white"android:textSize="30sp" /><TextView
                        android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:layout_marginTop="4dp"android:text="@string/quila_tagline"android:textColor="@android:color/white" /></LinearLayout></FrameLayout></android.support.design.widget.CollapsingToolbarLayout></android.support.design.widget.AppBarLayout><android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"android:layout_height="match_parent"android:scrollbars="none"app:layout_behavior="@string/appbar_scrolling_view_behavior"><android.support.v7.widget.CardView
            android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_margin="8dp"app:cardElevation="8dp"app:contentPadding="16dp"><TextView
                android:layout_width="match_parent"android:layout_height="wrap_content"android:lineSpacingExtra="8dp"android:text="@string/lorem"android:textSize="18sp" /></android.support.v7.widget.CardView></android.support.v4.widget.NestedScrollView><android.support.v7.widget.Toolbar
        android:id="@+id/main.toolbar"android:layout_width="match_parent"android:layout_height="?attr/actionBarSize"android:background="@color/primary"app:layout_anchor="@id/main.framelayout.title"app:theme="@style/ThemeOverlay.AppCompat.Dark"app:title=""><RelativeLayout
            android:layout_width="match_parent"android:layout_height="match_parent"><TextView
                android:id="@+id/main.textview.title"android:layout_width="wrap_content"android:layout_centerInParent="true"android:layout_height="wrap_content"android:text="@string/quila_name2"android:textColor="@android:color/white"android:textSize="20sp" /></RelativeLayout></android.support.v7.widget.Toolbar><ImageView
        android:layout_width="@dimen/image_width"android:layout_height="@dimen/image_width"android:layout_gravity="center"app:layout_behavior="myapplication.ImageCameraBehavior"android:src="@drawable/ic_perm_camera_mic_black_48dp"/><ImageView
        android:layout_width="@dimen/image_width"android:layout_height="@dimen/image_width"android:layout_gravity="center"app:layout_behavior="myapplication.ImageHomeBehavior"android:src="@drawable/ic_home_black_48dp"/></android.support.design.widget.CoordinatorLayout>


这里请注意ToolBar设置了锚定于上面的FrameLayout


主要是让两个ImageView有交互,所以我找了两张图片。然后先设置ImageCameraBehavior

@SuppressWarnings("unused")
public class ImageCameraBehavior extends CoordinatorLayout.Behavior<ImageView> {private final static String TAG = "kim";private Context mContext;private int toolBarYPosition;private int currentImageX;private int finalYPosition = 150;private float changeBehaviorPoint;private float childX;private int imageHeight;public ImageCameraBehavior(Context context, AttributeSet attrs) {mContext = context;}@Overridepublic boolean layoutDependsOn(CoordinatorLayout parent, ImageView child, View dependency) {return dependency instanceof Toolbar;}@Overridepublic boolean onDependentViewChanged(CoordinatorLayout parent, ImageView child, View dependency) {InitProperties(child, dependency);final int maxScrollDistance = toolBarYPosition;//展开的百分比,初始是1.float expandedPercentageFactor = dependency.getY() / maxScrollDistance;if (expandedPercentageFactor < changeBehaviorPoint) {//折叠的百分比float heightFactor = (changeBehaviorPoint - expandedPercentageFactor) / changeBehaviorPoint;//这里直接设置150硬编码,为了方便,实际开发请从dimens中获取float distanceXToSubtract = ((currentImageX - 150) * heightFactor) + (child.getWidth() / 2);float distanceYToSubtract = (toolBarYPosition)* (1f - expandedPercentageFactor);float iX = currentImageX - distanceXToSubtract;float iY = toolBarYPosition - distanceYToSubtract;Log.e(TAG, "ix=" + iX + "iy=" + iY);child.setX(iX);child.setY(iY);float heightToSubtract = ((imageHeight - 200) * heightFactor);CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) child.getLayoutParams();lp.width = (int) (imageHeight - heightToSubtract);lp.height = (int) (imageHeight - heightToSubtract);child.setLayoutParams(lp);} else {float distanceYToSubtract = ((toolBarYPosition)* (1f - expandedPercentageFactor));child.setX(currentImageX);child.setY(toolBarYPosition - distanceYToSubtract);CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) child.getLayoutParams();lp.width = imageHeight;lp.height = imageHeight;child.setLayoutParams(lp);}return true;}//初始化需要用到的参数private void InitProperties(ImageView child, View dependency) {if (toolBarYPosition == 0)toolBarYPosition = (int) dependency.getY();
//        Log.e(TAG, "toolBarYPosition=" + toolBarYPosition);if (currentImageX == 0)currentImageX = (int) (child.getX() + child.getWidth());
//        Log.e(TAG, "currentImageX=" + currentImageX);if (finalYPosition == 0)finalYPosition = dependency.getHeight();//ToolBar高度
//        Log.e(TAG, "mFinalYPosition=" + finalYPosition);if (imageHeight == 0) {imageHeight = child.getHeight();}//设定一个阈值,滑动到设定的阈值范围之后,开始移动变化if (changeBehaviorPoint == 0)changeBehaviorPoint = child.getHeight() / (2f * (toolBarYPosition - finalYPosition));}}

代码比较好理解,就是一些移动与计算。另一个ImageView的Behavior也和这个差不多,就不贴了。而且这里面在计算上还有一些问题,没有时间去搞了。

那么看一眼效果图吧:
这里写图片描述


最近看到项目组的大佬们在重构代码,马上就要上线的项目,还要大改架构,真是不明白这是在干啥。之前用了1年半的时间,解决了将近4k个问题了,这样一搞,可能要重来一遍了。。。。。

幸亏和我没啥关系,大爷的。O(∩_∩)O

这篇关于CoordinatorLayout.Behavior的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/533102

相关文章

Behavior Retrieval: Few-Shot Imitation Learning by Querying Unlabeled Datasets

发表时间:13 May 2023 论文链接:https://readpaper.com/pdf-annotate/note?pdfId=1900983943467731200&noteId=2446646993511259136 作者单位:Stanford University Motivation:使机器人能够以数据有效的方式学习新的视觉运动技能仍然是一个未解决的问题,有无数的挑战。解决这

读论文《Behavior Pattern Mining-based Multi-Behavior Recommendation》

论文地址:arxiv.org/pdf/2408.12152v1 项目地址:GitHub - rookitkitlee/BPMR 基于行为模式挖掘的多行为推荐:论文提出了一种新颖的多行为推荐算法(BPMR),旨在通过分析用户和项目之间的复杂交互模式来提高推荐系统的有效性。这种方法特别关注于用户除了购买之外的其他行为,例如页面浏览和收藏等辅助行为,这些行为可以提供更丰富的用户交互数据,帮助更准确地

Verilog-Behavior Level 和 RTL Level 和 GATE Level的区别

硬件设计中对硬件的描述可以具有不同的抽象级别,以Verilog为例: Behavior Level。描述的是硬件的行为,当我们在看到如下关键字时就是行为级别的代码:#,wait,while,force,release等,行为级别的代码通常比较直观,但可能不可综合。RTL Level。RTL即Register Transfer Level寄存器传输级别,使用always和assign语句块组成的代

coordinatorLayout使用总结篇,看完这篇完全可以开发5.0的高级特效了 +Android 详细分析AppBarLayout的五种ScrollFlags

coordinatorLayout使用总结篇,看完这篇完全可以开发5.0的高级特效了   了解相关更多技术,可参考《沉浸式状态栏+ScrollView顶部伸缩+ActionBar渐变》,最近在用coordinatorLayout做一些特效,发现网上有好多人已经走完了坑,借此我们来总结一把。 一言不合就上个图,还是动态的  主要是找了半天,好多人说的都不够详细,而且有好多注意事项没有说

WINUI——Behavior(行为)小结

前言 在使用MVVM进行WINUI或WPF开发时,Command在某些时候并不能满足逻辑与UI分离的要求。这时肯定就需要其它技术的支持,Behavior就是一种。在WPF中是有Behavior直接支持的,转到WINUI后,相对有一些麻烦,于是在此记录之,以备忘。 开发环境 WIN11 VS2022 Nuget包:Microsoft.Xaml.Behaviors.WinUI.Manag

tp5行为(Behavior)

是应用在执行过程中的一个动作或者处理,既可以独立调用,也可以绑定到某个标签中进行侦听。在每个标签位置,可以配置多个行为定义,行为的执行顺序按照定义的顺序依次执行。除非前面的行为里面中断执行了(某些行为可能需要中断执行,例如检测机器人或者非法执行行为),否则会继续下一个行为的执行。用法只需以下几步1.定义定义执行入口方法runnamespace app\index\behavior;cl

Could not inflate Behavior subclass

为了处理CoordinatorLayout + AppBarLayout + ViewPager滑动出现回弹和卡顿的现象,自定义的一个AppBarLayoutBehavior,但在运行过程中会出现java.lang.RuntimeException: Could not inflate Behavior subclass 刚开始的布局如下: <android.support.design

RTPS协议之Behavior Module

目录 交互要求基本要求RTPS Writer 行为RTPS Reader行为 RTPS协议的实现与Reader匹配的Writer的行为涉及到的类型RTPS Writer实现RTPS WriterRTPS StatelessWriterRTPS ReaderLocatorRTPS StatefulWriterRTPS ReaderProxyRTPS ChangeForReader RTPS S

智能体之斯坦福AI小镇(Generative Agents: Interactive Simulacra of Human Behavior)

相关代码地址见文末 论文地址:Generative Agents: Interactive Simulacra of Human Behavior | Proceedings of the 36th Annual ACM Symposium on User Interface Software and Technology 1.概述         论文提出了一种多个智能体进行协同,进而模拟

Material design学习笔记-CoordinatorLayout,NestedScrollView,AppBarLayout,CollapsingToolbarLayout学习

MD学习大概效果实现后是这样: 这里的具体控件使用: 下面具体来看一下吧~ CoordinatorLayout CoordinatorLayout 实现了多种Material Design中提到的滚动效果,用layout_gravity设置内部相关控件的位置。一般会和AppBarLayout、NestedScrollView等一起使用。 可以实现的效果: 让浮动操作按钮上下滑