重要的ui组件——Behavior

v7包下的组件类似CoordinatorLayout推出也有一段时间了,大家使用的时候应该会体会到其中很多的便利,今天这篇文章带大家来了解一个比较重要的ui组件——Behavior。从字面意思上就可以看出它的作用,就是用来规定某些组件的行为的,那它到底是什么,又该怎么用呢?看完这篇文章希望大家会有自己的收获~

前言

写这篇文章的起因是因为我无意中在GitHub上发现了Jake Wharton大神新建了一个Repo,内容是JakeWharton/DrawerBehavior。有兴趣的同学可以去看看,其实就是通过Behavior去构造一个类似于DrawerLayout的布局。想了想已经挺长时间没有搞ui方面的代码了,所以趁着这个机会复习了一下,顺便写一篇文章巩固,也给想要了解这方面内容的同学一个平台吧。

Behavior是什么

在文章的开始,我们先要了解什么是Behavior。

123456789101112131415161718192021
/** * Interaction behavior plugin for child views of {@link CoordinatorLayout}. * * <p>A Behavior implements one or more interactions that a user can take on a child view. * These interactions may include drags, swipes, flings, or any other gestures.</p> * * @param <V> The View type that this Behavior operates on */public static abstract class Behavior<V extends View> {    ...........

    public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) 	   {        return false;    }

    public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) {        return false;    }

    ...........}

它是CoordinatorLayout的内部类,从它的注释和其中的方法可以看出来,它其实就是给CoordinatorLayout的子View提供了一些交互的方法,用来规范它们的交互行为,比如上面出现的onTouchEvent可以用来规范子View的触摸事件,onLayoutChild可以用来规范子View的布局。

说到这里,大家可能会有一个问题,CoordinatorLayout又是个什么东西?

12
public class CoordinatorLayout extends ViewGroup implements NestedScrollingParent {}

可以看出,它其实就是一个ViewGroup,实现了NestedScrollingParent用来执行嵌套滑动。至于嵌套滑动的机制大家可以看我博客的第一篇文章,这不是我们这篇文章的重点。

既然CoordinatorLayout仅仅只是一个ViewGroup,它又为什么能展示出它在xml布局中展示的威力呢?其中的秘密就是在Behavior中。我们可以这么说,CoordinatorLayout利用了Behavior作为一个代理,去控制管理其下的子View做到各种布局和动画效果。那为什么要使用Behavior呢?我想原因大概就是解耦吧,如果把所有的逻辑都写死在CoordinatorLayout中,一来不利于维护,二来我们就没有做一些自定义的事情,会显得非常的笨重。

为什么要用Behavior

这里我们举一个非常简单的例子。首先来看看我们的布局文件。

12345678910111213141516171819202122232425262728293031
<?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"    android:id="@+id/main_fragment_container"    android:layout_width="match_parent"    android:layout_height="match_parent">

    <android.support.design.widget.AppBarLayout        android:id="@+id/app_bar"        android:layout_width="match_parent"        android:layout_height="48dp">

        <TextView            android:background="#ff0000"            android:layout_width="match_parent"            android:layout_height="match_parent"            android:text="title"            android:textColor="#00ff00"            android:gravity="center"/>    </android.support.design.widget.AppBarLayout>

    <android.support.v7.widget.RecyclerView        android:id="@+id/list"        android:layout_width="match_parent"        android:layout_height="match_parent"        app:layout_behavior="@string/appbar_scrolling_view_behavior">

    </android.support.v7.widget.RecyclerView>

</android.support.design.widget.CoordinatorLayout>

非常简单有木有,CoordinatorLayout作为根布局,里面一个AppBarLayout一个RecyclerView。让我们看看界面是怎么样的。

可以看到显示是正确的。但是如果我把xml里RecyclerView的那行layout_behavior删掉呢?就像这样。

123456789101112131415161718192021222324252627282930
<?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"    android:id="@+id/main_fragment_container"    android:layout_width="match_parent"    android:layout_height="match_parent">

    <android.support.design.widget.AppBarLayout        android:id="@+id/app_bar"        android:layout_width="match_parent"        android:layout_height="48dp">

        <TextView            android:background="#ff0000"            android:layout_width="match_parent"            android:layout_height="match_parent"            android:text="title"            android:textColor="#00ff00"            android:gravity="center"/>    </android.support.design.widget.AppBarLayout>

    <android.support.v7.widget.RecyclerView        android:id="@+id/list"        android:layout_width="match_parent"        android:layout_height="match_parent">

    </android.support.v7.widget.RecyclerView>

</android.support.design.widget.CoordinatorLayout>

最终界面的展示就像这样,RecyclerView把AppBarLayout给覆盖了。这里其实很好理解,如刚才的代码所示,CoordinatorLayout其实只是一个ViewGroup,它不像LinearLayout那样具有特定的布局特点,甚至可以说它内部的逻辑和FrameLayout是没什么差别的,所以如果你不设置对应的Behavior的话,布局就会有问题。从这里也可以反映出Behavior的作用,就是规范子View的显示和交互。

原理&系统是怎么用Behavior的

说完了Behavior的作用,那该怎么用它呢?这一小节让我们来讲讲Behavior的原理以及系统是如何使用它的。

首先先看原理。我们知道Behavior是用来帮助CoordinatorLayout的,所以我们要从CoordinatorLayout中寻找答案。首先,我们可以看到CoordinatorLayout中有一个LayoutParams,它的子View的LayoutParams都是这个,其中它的构造函数如下。

1234567891011
LayoutParams(Context context, AttributeSet attrs) {    super(context, attrs);

    .........    if (mBehaviorResolved) {        mBehavior = parseBehavior(context, attrs, a.getString(                R.styleable.CoordinatorLayout_LayoutParams_layout_behavior));    }

    a.recycle();}

可以看到它通过parseBehavior去得到了对应子View的Behavior。大家可以试试用RecyclerView的getLayoutParams方法去获取LayoutParams并且调用getBehavior方法,可以得到的就是我们在xml文件中设置的那个Behavior。

知道了如何将Behavior设置进去,那它是如何发挥作用的呢?让我们来看看onLayout函数。

1234567891011121314
@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {    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();        final Behavior behavior = lp.getBehavior();

        if (behavior == null || !behavior.onLayoutChild(this, child, layoutDirection)) {            onLayoutChild(child, layoutDirection);        }    }}

可以看到的是其中会先调用behavior.onLayoutChild(this, child, layoutDirection)。也就是说,Behavior的逻辑要优先于CoordinatorLayout自己的逻辑。其实不止是onLayout,我们还可以看看onTouchEvent这个函数。

123456789101112131415161718192021
public boolean onTouchEvent(MotionEvent ev) {    boolean handled = false;    boolean cancelSuper = false;    MotionEvent cancelEvent = null;

    final int action = MotionEventCompat.getActionMasked(ev);

    if (mBehaviorTouchView != null || (cancelSuper = performIntercept(ev, TYPE_ON_TOUCH))) {        // Safe since performIntercept guarantees that        // mBehaviorTouchView != null if it returns true        final LayoutParams lp = (LayoutParams) mBehaviorTouchView.getLayoutParams();        final Behavior b = lp.getBehavior();        if (b != null) {            handled = b.onTouchEvent(this, mBehaviorTouchView, ev);        }    }

    .........

    return handled;}

可以看到也是调用了Behavior的onTouchEvent,我们可以下判断说Behavior中的那些方法在CoordinatorLayout中都会在合适的时机去调用。这也证明了我们刚才的那句话:[Behavior就是CoordinatorLayout的代理,帮助它去管理子View]。

我们做一个总结,Behavior可以代理哪些行为呢?

1.Measure和Layout的布局行为。

2.onTouchEvent和onInterceptTouchEvent的触摸行为。比如design包中的SwipeDismissBehavior就是通过这样的方式完成的。

3.嵌套滑动行为(NestedScrollingParent和NestedScrollingChild中的逻辑)。

4.子View间的依赖行为。

对于第四点我们这里可以细说一下,什么叫子View的依赖行为呢?这里我们举个例子,我们都知道如果在CoordinatorLayout中使用了FAB并且点击展示SnackbarLayout的话,FAB会在Snackbar显示的时候对应的上移,这是因为FAB依赖了SnackbarLayout。

12345678910111213141516171819202122232425
public static class Behavior extends CoordinatorLayout.Behavior<FloatingActionButton> {

    ........

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

    ........}

这是FAB中的Behavior,可以看到它重写了layoutDependsOn和onDependentViewChanged,里面的逻辑很简单的就可以看明白。这里我们[将代码翻译成语言]就是说FAB要依赖的组件是SnackbarLayout,所以在之后的操作里当DependentView(SnackbarLayout)发生了改变,自己(FAB)也会相应的做出改变。

值得一提的是,onDependentViewChanged这个函数的调用时机并不是在onLayout之前,而是在onPreDraw中,具体代码如下:

1234567
class OnPreDrawListener implements ViewTreeObserver.OnPreDrawListener {    @Override    public boolean onPreDraw() {        dispatchOnDependentViewChanged(false);        return true;    }}

如此简单的处理View间的依赖,可见Behavior配合CoordinatorLayout是有多强大。下面我们可以再举一个例子来讲讲Behavior的作用。还记得我们上面说的吗?RecyclerView设置了一个Behavior它就可以和AppBarLayout很好的展示出来。这个Behavior的名字是:

123
app:layout_behavior="@string/appbar_scrolling_view_behavior"

<string name="appbar_scrolling_view_behavior" translatable="false">android.support.design.widget.AppBarLayout$ScrollingViewBehavior</string>

可以看到它是AppBarLayout里的一个内部类,让我们看看它做了什么。

123456789101112131415161718192021222324252627
@Overridepublic boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {    // We depend on any AppBarLayouts    return dependency instanceof AppBarLayout;}

@Overridepublic boolean onDependentViewChanged(CoordinatorLayout parent, View child,        View dependency) {    offsetChildAsNeeded(parent, child, dependency);    return false;}

private void offsetChildAsNeeded(CoordinatorLayout parent, View child, View dependency) {    final CoordinatorLayout.Behavior behavior =            ((CoordinatorLayout.LayoutParams) dependency.getLayoutParams()).getBehavior();    if (behavior instanceof Behavior) {        // Offset the child, pinning it to the bottom the header-dependency, maintaining        // any vertical gap, and overlap        final Behavior ablBehavior = (Behavior) behavior;        final int offset = ablBehavior.getTopBottomOffsetForScrollingSibling();        child.offsetTopAndBottom((dependency.getBottom() - child.getTop())                + ablBehavior.mOffsetDelta                + getVerticalLayoutGap()                - getOverlapPixelsForOffset(dependency));    }}

我们知道,如果不设置这个Behavior的话,RecyclerView会覆盖AppBarLayout。而上面这段代码里的逻辑就可以很好的解释这个原因了。值得一提的是,在offsetChildAsNeeded方法中有这么一段:

12345
final CoordinatorLayout.Behavior behavior = ((CoordinatorLayout.LayoutParams) dependency.getLayoutParams()).getBehavior();if (behavior instanceof Behavior) {    // Offset the child, pinning it to the bottom the header-dependency, maintaining    // any vertical gap, and overlap    final Behavior ablBehavior = (Behavior) behavior;

这里dependency就是AppBarLayout,所以我们可以知道,AppBarLayout中有两个Behavior,一个是我们前面提到的ScrollingViewBehavior,用来处理它和其他滑动View的关系,另外一个就是Behavior,用来处理自己的逻辑,比如Layout。通过这种巧妙的方式,我们就可以做到非常简便的控制View本身和View之间的逻辑。

如何自定义Behavior

本来想写个demo给大家看一看的,不过感觉还是不要重复造轮子了,还是没用的轮子。推荐大家看SwipeDismissBehavior用法及实现原理这篇文章和一开始提到的Jake大神的新作DrawerBehavior。如果你把这两个东西搞懂,那么Behavior你可以说已经完全没问题了~

后记

最近一段时间都在搞hotPatch和插件化相关的东西,看了很多Framework层的源码,要做的东西也做的七七八八,希望快点解决最后的几个bug并且之后能开源和大家见面吧

时间: 2024-08-02 10:36:51

重要的ui组件——Behavior的相关文章

Android UI组件进阶(2)——仿Windows对话框

Android UI组件进阶(2)--仿Windows对话框 在开始本章前先祝大家中秋节快乐哈,相信很多上班的朋友都是放三天假的哈! 有时间的话回家陪陪父母吧!树欲静而风不止,子欲养而亲不待!岁月不饶人! 好了,道理和祝福语就说到这里了,今天给大家准备的是模仿Windows风格对话框! 效果图: 相信大部分的AlertDialog都是下面这个样子的: 今天给大家讲解的对话框是下面这样的: 对比两种对话框,站在用户的角度,相信你更加钟情于第二种颜色鲜明的对话框 好了下面就开始讲解如何制作模仿win

React-Native入门指南(五)——UI组件

React-Native入门指南 github:https://github.com/vczero/react-native-lession React-Native:用JavaScript开发你的原生应用,释放Native的UI体验,体验 Hybird开发效率. 最近一个星期写的文章如下: 第1篇hello react-native 第2篇认识代码结构 第3篇css和布局 第4篇学会react-native布局 第5篇ui组件 还有几篇需要这这周完成(这块会时刻更新): 第6篇JSX语法 第7

推荐使用Tiny Framework web开发UI组件

TINY FRAMEWORK 基于组件化的J2EE开发框架,from:http://www.tinygroup.org/ 名字 Tiny名称的来历 取名Tiny是取其微不足道,微小之意. Tiny的构建者认为,一个J2EE开发框架是非常复杂的,只有把框架分解成非常细小.可控的部分,并且对每个细小.可控的部分都有一个最优解或相对最优解, 那么整个方案也就可以非常不错的落地. 策略 Tiny框架的构建策略 Think big, start small, scale fast. 想法要宏伟,但是要从小

布局技巧1:创建可重用的UI组件(include标签)

Android平台提供了大量的UI构件,你可以将这些小的视觉块(构件)搭建在一起,呈现给用户复杂且有用的画面.然而,应用程序有时需要一些高级的视觉组件.为了满足这一需求,并且能高效的实现,你可以把多个标准的构件结合起来成为一个单独的.可重用的组件. 例如,你可以创建一个可重用的组件包含一个进度条和一个取消按钮,一个Panel包含两个按钮(确定和取消动作),一个Panel包含图标.标题和描述等等.简单的,你可以通过书写一个自定义的View来创建一个UI组件,但更简单的方式是仅使用XML来实现. 在

微信小程序UI组件、开发框架、实用库

UI组件 weui-wxss ★852 - 同微信原生视觉体验一致的基础样式库 Wa-UI ★122 - 针对微信小程序整合的一套UI库 wx-charts ★105 - 微信小程序图表工具 wemark ★85 - 微信小程序Markdown渲染库 WeZRender ★36 - 微信小程序Canvas增强组件 wetoast ★21 - 仿照微信小程序提供的showToast功能 wxapp-charts ★20 - 微信小程序图表charts组件 WeiXinProject ★18 - 列

推荐 11 款 React Native 开源移动 UI 组件

推荐 11 款 React Native 开源移动 UI 组件 oschina 发布于 10个月前,共有 14 条评论 本文推荐 11 个非常棒的 React Native 开源组件,希望能给移动应用开发者提供帮助. React Native 是近期 Facebook 基于 MIT 协议开源的原生移动应用开发框架,已经用于 Facebook 的生产环境.React Native 可以使用最近非常流行的 React.js 库来开发 iOS 和 Android 原生 APP. 1. iOS 表单处理

前端UI组件复用工具

"懒"是第一生产力. 代码复用总是程序员喜闻乐见的,前端组件化的最终目的就是复用,今天我们就将深入探讨如何实现UI组件的复用. 通常我们所说的组件往往是包含业务逻辑的前端组件,而这类组件实际上很难实现广义上的复用,顶多能在同一条业务线上复用一下,但UI组件就不一样了,没有了业务的约束,只在UI层面上实现复用,那想象空间就很大了,所以这里我们只讨论UI组件. 首先界定一下,UI组件就是一个web界面的前端代码片段,虽然说不包含业务,但基本的JS效果是可以有的,比如表单验证.轮播图效果.选

封装一个简单的UI组件

方法其实很简单,用一个函数把整个过程包起来.调用时用new,这样可以在一个页面使用多个改组件.这是一个非常简单的方法,后面还有很大改进的空间.下面是一个封装日历的示例. 现在我们的组件类似bootstrap,写好下面的html结构,然后引入ui.css和ui.js,就可以生成相应的UI组件了.效果如上图. <!doctype html> <html> <head> <meta charset="UTF-8"> <title>C

工作流,WEB框架,UI组件网络收集整理

工作流,WEB框架,UI组件网络收集整理 在博客园上逛了好多年,随手收录了一些工作流,WEB开发框架,UI组件,现在整理一下与大家分享. 由于个人能力与精力有限,望各位园友在评论中补充,我将全部整理到正文: ? 工作流篇 RoadFlow工作流(收费):                  http://www.cqroad.cn/WorkFlow 驰骋工作流引擎 ccflow                       https://www.oschina.net/p/ccflow YbSof