Behavior是Android新出的Design库里新增的布局概念。Behavior只有是CoordinatorLayout的直接子View才有意义。可以为任何View添加一个Behavior。
Behavior是一系列回调。让你有机会以非侵入的为View添加动态的依赖布局,和处理父布局(CoordinatorLayout)滑动手势的机会。如果我们想实现控件之间任意的交互效果,完全可以通过自定义 Behavior 的方式达到。

Behavior是CoordinatorLayout的一个抽象内部类

public abstract static class Behavior<V extends View> {public Behavior() {}public Behavior(Context context, AttributeSet attrs) {}...
}

1,官方BottomSheetBehavior使用

        app:behavior_hideable="true"app:behavior_peekHeight="0dp"app:layout_behavior="@string/bottom_sheet_behavior">public void onCl(View view) {BottomSheetBehavior behavior = BottomSheetBehavior.from(findViewById(R.id.scroll));if(behavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {behavior.setState(BottomSheetBehavior.STATE_COLLAPSED);}else {behavior.setState(BottomSheetBehavior.STATE_EXPANDED);}}// BottomSheetDialog /* BottomSheetDialog sheetDialog = new BottomSheetDialog();sheetDialog.setContentView(view);sheetDialog.show();*///setBottomSheetCallback/*   behavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {@Overridepublic void onStateChanged(@NonNull View bottomSheet, int newState) {}@Overridepublic void onSlide(@NonNull View bottomSheet, float slideOffset) {}});*/

2,自定义Behavior
我们可以按照两种目的来实现自己的Behavior,当然也可以两种都实现啦

某个view监听另一个view的状态变化,例如大小、位置、显示状态等

某个view监听CoordinatorLayout内的NestedScrollingChild的接口实现类的滑动状态

第一种情况需要重写layoutDependsOn和onDependentViewChanged方法

第二种情况需要重写onStartNestedScroll和onNestedPreScroll系列方法

如果你的自定义View默认使用一个Behavior。
在你的自定义View类上添加@DefaultBehavior(你的Behavior.class)这句注解。
你的View就默认使用这个Behavior。就像AppBarLayout一样。

@DefaultBehavior(AppBarLayout.Behavior.class)
public class AppBarLayout extends LinearLayout {}

第一种情况

现在我们就来根据第一种情况尝试自定义一个Behavior,这里我们实现一个简单的效果,让一个View根据另一个View上下移动。

public class DependencyBehavior extends CoordinatorLayout.Behavior<View> {public DependencyBehavior(Context context, AttributeSet attrs) {super(context, attrs);}@Overridepublic boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {return dependency instanceof Button;}@Overridepublic boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {int offset = dependency.getTop();ViewCompat.offsetTopAndBottom(child, -offset);return true;}
}

我们覆写了两个方法layoutDependsOn和onDependentViewChanged,这两个方法的参数都是一样的,解释一下,第一个不用说,就是当前的CoordinatorLayout,第二个参数是我们设置这个Behavior的View,第三个是我们关心的那个View。如何知道关心的哪个呢?layoutDependsOn的返回值决定了一切!

return dependency instanceof Button; 关心所有Button的变化。
return dependency.getId()==R.id.Btn; 关心id为R.id.Btn的变化。

现在设置好了关心谁,接下来就是在这个View状态发生变化的时候,我们现在的View该做些什么了,这里肯定是在onDependentViewChanged做工作了。我们的任务就是获取dependency距离顶部的距离,并且设置给child。

@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {int offset = dependency.getTop();ViewCompat.offsetTopAndBottom(child, -offset);return true;
}

在XML中使用 app:layout_behavior=”com.lava.toolbar.behave.DependencyBehavior”

<?xml version="1.0" encoding="utf-8"?>
<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" android:fitsSystemWindows="true"tools:context="com.lava.toolbar.behave.MainActivity"><Button
        android:id="@+id/depentent"android:layout_width="100dp"android:layout_height="100dp"android:background="@color/colorAccent"android:gravity="center"android:textColor="@android:color/white"android:layout_gravity="top|left"android:text="depentent"/><Button
        android:layout_width="100dp"android:layout_height="100dp"android:background="@color/colorPrimary"android:gravity="center"android:textColor="@android:color/white"android:layout_gravity="bottom|right"app:layout_behavior="com.lava.toolbar.behave.DependencyBehavior"android:text="auto"/></android.support.design.widget.CoordinatorLayout>
    final Button depentent = (Button) findViewById(R.id.depentent);depentent.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {ViewCompat.offsetTopAndBottom(v, 5);}});

第二种情况

public class ScrollBehavior extends CoordinatorLayout.Behavior<View> {public ScrollBehavior(Context context, AttributeSet attrs) {super(context, attrs);}@Overridepublic boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {return super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);}@Overridepublic void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);}@Overridepublic boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY) {return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);}

需要重写上述方法

首先来看看onStartNestedScroll,这里的返回值表明这次滑动我们要不要关心,我们要关心什么样的滑动?当然是y轴方向上的。

 @Overridepublic boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;}

demo代码

public class ScrollBehavior extends CoordinatorLayout.Behavior<View> {public ScrollBehavior(Context context, AttributeSet attrs) {super(context, attrs);}@Overridepublic boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;}@Overridepublic void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);int leftScrolled = target.getScrollY();child.setScrollY(leftScrolled);}@Overridepublic boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY) {((NestedScrollView) child).fling((int)velocityY);return true;}
}
<android.support.design.widget.CoordinatorLayoutxmlns: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" android:fitsSystemWindows="true"tools:context="com.lava.toolbar.behave.MainActivity"><android.support.v4.widget.NestedScrollViewandroid:layout_gravity="left"android:layout_width="wrap_content"android:background="@color/colorAccent"android:layout_height="match_parent"><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:orientation="vertical">...<TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:paddingTop="50dp"android:paddingBottom="50dp"android:textColor="@android:color/white"android:text="Left"/>...</LinearLayout></android.support.v4.widget.NestedScrollView><android.support.v4.widget.NestedScrollViewandroid:layout_gravity="right"android:layout_width="wrap_content"android:background="@color/colorPrimary"android:layout_height="match_parent"app:layout_behavior="com.lava.toolbar.behave.ScrollBehavior"><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:orientation="vertical">....<TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:paddingTop="50dp"android:paddingBottom="50dp"android:textColor="@android:color/white"android:text="Right"/>....</LinearLayout></android.support.v4.widget.NestedScrollView>

CoordinatorLayout的源码之Behavior

简单来看看Behavior是如何从xml中解析的,通过检测xxx:behavior属性,通过全限定名或者相对路径的形式指定路径,最后通过反射来新建实例,默认的构造器是Behavior(Context context, AttributeSet attrs),如果你需要配置额外的参数,可以在外部构造好Behavior并通过setter的方式注入到LayoutParams或者获取到解析好的Behavior进行额外的参数设定
实例化Behavior

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); //通过apps: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(".")) {// Relative to the app package. Prepend the app package name.fullName = context.getPackageName() + name;} else if (name.indexOf('.') >= 0) {// Fully qualified package name.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 {// 我们在自定义Behavior的时候,必须要有这种类型的构造方法if (c == null) {final Class<Behavior> clazz = (Class<Behavior>) Class.forName(fullName, true, context.getClassLoader());c = clazz.getConstructor(CONSTRUCTOR_PARAMS);c.setAccessible(true);//...}return c.newInstance(context, attrs);} catch (Exception e) {throw new RuntimeException("Could not inflate Behavior subclass " + fullName, e);}
}

layoutDependsOn和onDependentViewChanged调用过程
在CoordinatorLayout中注册了一个ViewTreeObserver,我们可以从这里入手,因为它可以监听到view的各种状态变化,

@Override
public void onAttachedToWindow() {super.onAttachedToWindow();resetTouchBehaviors();if (mNeedsPreDrawListener) {if (mOnPreDrawListener == null) {// 实例化了OnPreDrawListener// 并在下面注册到了ViewTreeObserver中mOnPreDrawListener = new OnPreDrawListener();}final ViewTreeObserver vto = getViewTreeObserver();vto.addOnPreDrawListener(mOnPreDrawListener);}if (mLastInsets == null && ViewCompat.getFitsSystemWindows(this)) {// We're set to fitSystemWindows but we haven't had any insets yet...// We should request a new dispatch of window insetsViewCompat.requestApplyInsets(this);}mIsAttachedToWindow = true;
}
在onAttachedToWindow向ViewTreeObserver注册了一个监听draw变化的Observer,那在这里Observer中到底干了嘛呢?class OnPreDrawListener implements ViewTreeObserver.OnPreDrawListener {@Overridepublic boolean onPreDraw() {dispatchOnDependentViewChanged(false);return true;}
}

就两行代码,调用了dispatchOnDependentViewChanged方法

void dispatchOnDependentViewChanged(final boolean fromNestedScroll) {final int layoutDirection = ViewCompat.getLayoutDirection(this);final int childCount = mDependencySortedChildren.size();// 遍历所有的子viewfor (int i = 0; i < childCount; i++) {final View child = mDependencySortedChildren.get(i);final LayoutParams lp = (LayoutParams) child.getLayoutParams();...// 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;}// Update any behavior-dependent views for the change// 这里从下一个子view开始//mDependencySortedChildren有一个排序规则// selectionSort// 感兴趣的可以看一下mDependencySortedChildren部分。for (int j = i + 1; j < childCount; j++) {final View checkChild = mDependencySortedChildren.get(j);final LayoutParams checkLp = (LayoutParams) checkChild.getLayoutParams();// 获取到Behaviorfinal Behavior b = checkLp.getBehavior();// 这里调用Behavior的layoutDependsOn来判断我们的带有behavior的view是不是依赖这个viewif (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;}// 这里调用了Behavior的onDependentViewChangedfinal boolean handled = b.onDependentViewChanged(this, checkChild, child);...}}}
}

dispatchOnDependentViewChanged方法有一个布尔类型的参数,上面我们传递的是false, 这里主要是区分是view引起的状态变化还是布局引起的,在一些的scroll中也会调用dispatchOnDependentViewChanged这个方法。

onStartNestedScroll和onNestedPreScroll
大体的流程就是:

NestedScrollView.onInterceptTouchEvent->NestedScrollingChildHelper.onStartNestedScroll->CoordinatorLayout.onStartNestedScroll

CoordinatorLayout中:

public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {boolean handled = false;final int childCount = getChildCount();for (int i = 0; i < childCount; i++) {final View view = getChildAt(i);final LayoutParams lp = (LayoutParams) view.getLayoutParams();final Behavior viewBehavior = lp.getBehavior();if (viewBehavior != null) {// 调用遍历出来的这个子view的onStartNestedScroll方法final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child, target,nestedScrollAxes);handled |= accepted;lp.acceptNestedScroll(accepted);} else {lp.acceptNestedScroll(false);}}return handled;

这里还是去遍历了所有子view,然后去调用它的onStartNestedScroll方法,它的返回值,决定了NestedScrollingChildHelper.onStartNestedScroll是不是要继续遍历,如果我们的子view对这个view的滑动感兴趣,就返回true,它的遍历就会结束掉。

Android Behavior详解相关推荐

  1. 【转】Android菜单详解——理解android中的Menu--不错

    原文网址:http://www.cnblogs.com/qingblog/archive/2012/06/08/2541709.html 前言 今天看了pro android 3中menu这一章,对A ...

  2. Android菜单详解——理解android中的Menu

    前言 今天看了pro android 3中menu这一章,对Android的整个menu体系有了进一步的了解,故整理下笔记与大家分享. PS:强烈推荐<Pro Android 3>,是我至 ...

  3. Android LayoutInflater详解

    Android LayoutInflater详解 在实际开发中LayoutInflater这个类还是非常有用的,它的作用类 似于findViewById().不同点是LayoutInflater是用来 ...

  4. android Fragments详解

    android Fragments详解一:概述 android Fragments详解二:创建Fragment 转载于:https://my.oschina.net/liangzhenghui/blo ...

  5. android WebView详解,常见漏洞详解和安全源码(下)

    上篇博客主要分析了 WebView 的详细使用,这篇来分析 WebView 的常见漏洞和使用的坑.  上篇:android WebView详解,常见漏洞详解和安全源码(上)  转载请注明出处:http ...

  6. android WebView详解,常见漏洞详解和安全源码(上)

    这篇博客主要来介绍 WebView 的相关使用方法,常见的几个漏洞,开发中可能遇到的坑和最后解决相应漏洞的源码,以及针对该源码的解析.  由于博客内容长度,这次将分为上下两篇,上篇详解 WebView ...

  7. android子视图无菜单,Android 菜单详解

    Android中菜单分为三种,选项菜单(OptionMenu),上下文菜单(ContextMenu),子菜单(SubMenu) 选项菜单 可以通过两种办法增加选项菜单,一是在menu.xml中添加,该 ...

  8. Android StateFlow详解

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/121913352 本文出自[赵彦军的博客] 文章目录 系列文章 一.冷流还是热流 S ...

  9. Android SharedFlow详解

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/121911675 本文出自[赵彦军的博客] 文章目录 系列文章 什么是SharedF ...

  10. Android菜单详解(三)——SubMenu和IconMenu

    我们在上一篇介绍了如何在Android中创建和响应选项菜单,今天我们将探索子菜单和图标菜单. 子菜单Sub Menu 子菜单提供了一种自然的组织菜单项的方式,它被大量地运用在windows和其他OS的 ...

最新文章

  1. 重磅 ! Redis+Nginx+JVM+设计模式+Spring全家桶+Dubbo
  2. 问题:Unable to find a 'userdata.img' file for ABI armeabi to copy into the AVD folder.
  3. 太赞了!2020年全网最新Java面试题(附答案)免费下载!超全!!
  4. war3必须安装的游戏组件_在单独的WAR组件中对SPA资源和API实现进行分区
  5. Java虚拟机专题之类加载机制
  6. 关于 Flutter Layout(转载)
  7. C#3.0新特性 扩展方法
  8. HDU-4313-Matrix(离线并查集)
  9. 超市收银程序_思迅天店星耀版收银系统助力超市商品建档!
  10. Java基础2讲义四千字总结---黑马刘意
  11. 我的五年百度博客文章列表(带链接版)
  12. 虚拟机不能清空回收站_回收站不能清空怎么办?清空回收站无反应的解决方法...
  13. python-2.找出数组中重复的数字
  14. 游戏建模入门教程:绝地求生—PUBG的游戏模型制作流程
  15. 找靓机用计算机表白,找靓机怎么样-2300元的鼠标用起来怎么样?Finalmouse Ultralight Phantom体验...
  16. 离线语音智能家居控制
  17. 【容器云】Calico 组件架构
  18. ERA5-Land hourly data数据直接计算出来数据量偏大,monthly单位等
  19. 动手改善Google PageSpeed
  20. 【零知ESP8266教程】blynk控制RGB LED

热门文章

  1. java制作视频播放器
  2. .har 文件解析工具
  3. fortran 学习记录2
  4. Spring 揭秘 12.1
  5. 企业IT架构转型之道(书)总结以及反思
  6. LayaBox1.7.16 TiledMap 销毁的问题,TiledMap销毁后屏幕变灰,不能显示
  7. java语句以什么结尾_[JAVA] 关于语句的结尾
  8. matlab遗传算法工具箱介绍和详细使用方法【matlab优化算法工具箱】
  9. SAP针对中国市场推出双轨制医疗计划
  10. 【软件相关】Proteus 8入门教程