A super-powered FrameLayout—协调布局CoordinatorLayout(二):Behavior

上篇文章介绍了CoordinatorLayout的基本使用 — A super-powered FrameLayout—协调布局CoordinatorLayout(一):深度基本了解

本篇文章说一说Behavior

  • 是什么
  • 怎么写
  • 用在哪

一、Behavior官方解释

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

CoordinatorLayout直接子view之间相互作用行为的插件.

行为实现了一个或多个用户可以在子view上进行的交互.
这些交互可能包括拖动、滑动、甩动或任何其他手势.

@param <V> 此行为操作的view类型.

二、自定义Behavior

2.1 初识 CoordinatorLayout.Behavior

方法是真的多,就这还删除了被标记@Deprecated的方法,后面会挑捡些重要的方法着重说明

public static abstract class Behavior<V extends View> {

    public Behavior() {}

    public Behavior(Context context, AttributeSet attrs) {}

    public void onAttachedToLayoutParams(@NonNull CoordinatorLayout.LayoutParams params) {}

    public void onDetachedFromLayoutParams() {}

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

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

    @ColorInt
    public int getScrimColor(@NonNull CoordinatorLayout parent, @NonNull V child) { return Color.BLACK; }

    @FloatRange(from = 0, to = 1)
    public float getScrimOpacity(@NonNull CoordinatorLayout parent, @NonNull V child) { return 0.f; }

    public boolean blocksInteractionBelow(@NonNull CoordinatorLayout parent, @NonNull V child) { return getScrimOpacity(parent, child) > 0.f; }

    public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull V child, @NonNull View dependency) { return false; }

    public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent, @NonNull V child, @NonNull View dependency) { return false; }

    public void onDependentViewRemoved(@NonNull CoordinatorLayout parent, @NonNull V child, @NonNull View dependency) { }

    public boolean onMeasureChild(@NonNull CoordinatorLayout parent, @NonNull V child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { return false; }

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

    public static void setTag(@NonNull View child, @Nullable Object tag) { }

    public static Object getTag(@NonNull View child) { }
    
    public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View directTargetChild, @NonNull View target, @ScrollAxis int axes, @NestedScrollType int type) { }

    public void onNestedScrollAccepted(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View directTargetChild, @NonNull View target,@ScrollAxis int axes, @NestedScrollType int type) { }

    public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target, @NestedScrollType int type) { }

    public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @NestedScrollType int type, @NonNull int[] consumed) { }
    
    public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target, int dx, int dy, @NonNull int[] consumed, @NestedScrollType int type) { }

    public boolean onNestedFling(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target, float velocityX, float velocityY, boolean consumed) { return false; }

    public boolean onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target, float velocityX, float velocityY) { return false; }
    
    public WindowInsetsCompat onApplyWindowInsets(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull WindowInsetsCompat insets) { return insets; }

    public boolean onRequestChildRectangleOnScreen(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull Rect rectangle, boolean immediate) { return false; }

    public void onRestoreInstanceState(@NonNull CoordinatorLayout parent, @NonNull V child, @NonNull Parcelable state) {// no-op}

    public Parcelable onSaveInstanceState(@NonNull CoordinatorLayout parent, @NonNull V child) { return BaseSavedState.EMPTY_STATE; }

    public boolean getInsetDodgeRect(@NonNull CoordinatorLayout parent, @NonNull V child, @NonNull Rect rect) { return false; }
}

2.2 CoordinatorLayout.Behavior<V extends View>的泛型、方法说明

2.2.1 泛型说明

官方释义:此Behavior操作的view类型.
这个Behavior是用在哪种类型的View上,TextViewImageViewButton等等都可以,如果确定了某一种类型,就需要用在对应的那一种类型上;或者直接使用View作为泛型类型,通吃。

2.2.2 方法说明

  • Behavior()无参构造方法用于手动实例化
  • Behavior(Context context, AttributeSet attrs)2个参构造方法如需再xml中设置,需要重写
  • void onAttachedToLayoutParams(CoordinatorLayout.LayoutParams params)当 Behavior 已附加到 LayoutParams 实例时调用
    -void onDetachedFromLayoutParams()当 Behavior 与其持有的 LayoutParams 实例分离时调用
  • boolean onInterceptTouchEvent(CoordinatorLayout parent,V child,MotionEvent ev)拦截CoordinatorLayout 触摸事件,在CoordinatorLayout 触摸事件分发到子view之前响应,如果此行为想要拦截并接管事件流,则为 true。默认返回 false
  • boolean onTouchEvent(CoordinatorLayout parent, V child,MotionEvent ev)一旦 Behavior 拦截到触摸事件,则事件流将被发送到此方法;如果Behavior处理此触摸事件并希望继续接收此流中的事件,则为 true。默认返回 false
  • boolean layoutDependsOn(CoordinatorLayout parent, V child,View dependency)设置此Behavior的view需要依赖的view
  • boolean onDependentViewChanged(CoordinatorLayout parent, V child,View dependency)当依赖view的大小或位置在发生变化时,调用此方法。
  • void onDependentViewRemoved(CoordinatorLayout parent, V child, View dependency)当依赖的view从父视图中删除后调用此方法
  • onMeasureChild(CoordinatorLayout parent, V child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed)当父 CoordinatorLayout 即将测量给定的子视图时调用,此方法可用于对子视图执行自定义或修改的测量,以代替默认的子测量行为
  • boolean onLayoutChild(CoordinatorLayout parent, V child,int layoutDirection)此方法可用于执行子视图的自定义或修改布局,以代替默认的子布局行为
  • boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout,V child, View directTargetChild, View target, @ScrollAxis int axes, @NestedScrollType int type) 嵌套滑动开始,如果behavior消费此次嵌套滑动,返回 true
  • void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout,V child, View directTargetChild, View target,@ScrollAxis int axes, @NestedScrollType int type)当 CoordinatorLayout 接受嵌套滚动时调用
  • void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target, @NestedScrollType int type)当嵌套滚动结束时调用
  • void onNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @NestedScrollType int type, @NonNull int[] consumed)嵌套滑动进行中,要监听的子 View的滑动事件已经被消费
  • void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dx, int dy, int[] consumed, @NestedScrollType int type)嵌套滑动进行中,要监听的子 View将要滑动,滑动事件即将被消费
  • boolean onNestedFling(CoordinatorLayout coordinatorLayout,V child, View target, float velocityX, float velocityY,boolean consumed)当嵌套滚动子项开始投掷或将是“投掷”的动作时调用
  • boolean onNestedPreFling( CoordinatorLayout coordinatorLayout,V child, View target, float velocityX, float velocityY)当嵌套滚动子项即将开始“投掷”时调用

2.3 自定义CoordinatorLayout.Behavior<V extends View>

2.3.1 明确需求,自定义Behavior一般情况下分为

  1. 此view随着依赖view的状态(位置等)变化而变化


class CustomBehavior : CoordinatorLayout.Behavior<View> {

    companion object {
        private const val TAG = "mr_gu"
    }

    // 列表顶部和title底部重合时,列表的滑动距离。
    private var deltaY = 0f

    constructor()
    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)

    /**
     * 实现Behavior必须要要复写的方法
     */
    override fun layoutDependsOn(parent: CoordinatorLayout, child: View, dependency: View): Boolean {
        Log.i(TAG, "layoutDependsOn: ")
        return dependency is NestedScrollView
    }

    /**
     * 当依赖的view状态发生变化时,设置此behavior的view应当作出的变化
     *
     * 这里实现的是,当NestedScrollView的位置变化时,设置此behavior的view的随着变化
     */
    override fun onDependentViewChanged(parent: CoordinatorLayout, child: View, dependency: View): Boolean {
        if (deltaY == 0f) {
            deltaY = dependency.y - child.height
        }
        var dy = dependency.y - child.height
        dy = if (dy < 0) 0f else dy
        val y = -(dy / deltaY) * child.height
        child.translationY = y

        Log.i(TAG, "onDependentViewChanged: ")

        return true
    }
} 
  1. 此view随着依赖view的滑动状态变化而变化


// 滑动状态透明度为0.5f,停止滑动透明度1f
class CustomBehavior : CoordinatorLayout.Behavior<View> {

    companion object {
        private const val TAG = "mr_gu"
    }

    // 列表顶部和title底部重合时,列表的滑动距离。
    private var deltaY = 0f

    constructor()
    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)

    /**
     * 实现Behavior必须要要复写的方法
     */
    override fun layoutDependsOn(parent: CoordinatorLayout, child: View, dependency: View): Boolean {
        Log.i(TAG, "layoutDependsOn: ")
        return dependency is NestedScrollView
    }
    
    override fun onStartNestedScroll(coordinatorLayout: CoordinatorLayout, child: View,
                                     directTargetChild: View, target: View, axes: Int, type: Int): Boolean {
        Log.i(TAG, "onStartNestedScroll: ")
        // 只分发垂直滑动
        return axes == ViewCompat.SCROLL_AXIS_VERTICAL
    }

    override fun onStopNestedScroll(coordinatorLayout: CoordinatorLayout, child: View, target: View, type: Int) {
        super.onStopNestedScroll(coordinatorLayout, child, target, type)
        child.visibility = View.VISIBLE
        child.alpha = 1f
        Log.i(TAG, "onStopNestedScroll: ")
    }

    override fun onNestedScroll(coordinatorLayout: CoordinatorLayout, child: View, target: View, dxConsumed: Int,
        dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int, type: Int, consumed: IntArray) {
        super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed,
            dxUnconsumed, dyUnconsumed, type, consumed)
        Log.i(TAG, "onNestedScroll: ")
         // 也可以拿到当次滑动的距离
        child.alpha = 0.5f
    }
}
  1. 1 + 2


三、总结与思考

使用Behavior可以较为方便的实现页面中view间相互作用的效果,文章中的例子都是最简单的效果,面对设计小姐姐天马行空的设计和想法,Behavior可能会帮助你很好的应对,不至于扎耳挠腮,骂骂咧咧,还容易掉头发

Behavior是底层逻辑是什么样的,为什么在xml中简单的配置个Behavior属性就可以实现,答案都在源码中,read the fucking resource code ,can help you grow quickly

【本文参考】
SheHuan —— Android CoordinatorLayout之自定义Behavior


如果文章对你有帮助,点个赞再走呗

如果文章中存在错误,还望评论区指出

一起成长,共同进步

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,717评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,501评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,311评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,417评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,500评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,538评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,557评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,310评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,759评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,065评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,233评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,909评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,548评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,172评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,420评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,103评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,098评论 2 352

推荐阅读更多精彩内容