上一篇,我们大体理解了 Behavior简单理解
具体代码可以见 https://github.com/2954722256/use_little_demo
对应 coordinator 的 Module
简单使用
知道大体作用以后,我们可以参考一下别人的文章
自己简单搜索后,找一篇自己觉得很好的文章
例如:
http://www.jianshu.com/p/a506ee4afecb
(大体讲解Behavior以及对应的反射注解实现)
其中,有一个View一直在另一个View的下方
是通过自定义View,传递id来实现的
http://www.jianshu.com/p/39fbc9f4f0c6
一些总结感觉写得挺好
比如,一些写法,一些分类
虽然没有什么图示
自己简单实现
先写对应的Behavior
这里简单一点,不用 自定义属性,传递id了
直接写死对应的 dependencyView
只需要把这个View的Y值, 设置为 dependencyView的Y值 + dependencyView的高度 即可
DodoBelowBehavior 类
package com.aohuan.dodo.coordinator.utils;
import android.content.Context;
import android.support.design.widget.CoordinatorLayout;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Button;
import com.aohuan.dodo.coordinator.view.DodoMoveView;
/**
* Created by dodo on 2016/11/1.
* qq: 2390183798
*
*
* 在Main View 下方
* 原理也简单, 只要是 Main View 为 DodoMoveView, 就设置 一起动的View的Y值为 自己的Y值 + MainView的Height
*/
public class DodoBelowBehavior extends CoordinatorLayout.Behavior {
public DodoBelowBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
child.setY(dependency.getY()+dependency.getHeight());
return true;
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof DodoMoveView;
}
}
对应的layout为:
layout中,将需要上下关联的View放在一个 CoordinatorLayout 中
在给跟着动的View设置Behavior即可
我们看一下效果:
简单总结
对应的总结,参考文章:
http://www.jianshu.com/p/39fbc9f4f0c6
前提: 上面只是简单使用了一部分,总结后,后续文章再一起探索
markdown 的二级和代码解析,有一些冲突, 和之前wiki排版差不多,由于不重要,所以自己不花时间去整理了, 有时间找到解决方法后,再做修改
自定义Behavior的通用流程
通常分为:
- 重写构造方法
- 绑定到View
- 判断依赖对象
事件流
通常分为:(为了好记,自己名字可能不太一样)
- 触摸事件
- 计算和布局事件
- CoordinatorLayout关联事件
- 嵌套滑动事件
自定义Behavior的通用流程
对应的Behavior的路程大体为:
- 1. 重写构造方法
public class CustomBehavior extends CoordinatorLayout.Behavior {
public CustomBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
}
- **2. 绑定到View**
- 一定要重写这个构造方法,因为当你在XML中设置该Behavior时,
- 在 CoordinatorLayout中会反射调用该方法,并生成该 Behavior 实例。
- **绑定的方法有三种:**
- 在 XML 文件中,设置任意 View 的属性
- ```
app:layout_behavior="你的Behavior的包路径和类名"
- 或者在代码中:
- ```
(CoordinatorLayout.LayoutParams)child.getLayoutParams().setBehavior();
- 或者在你的自定义View类上添加@DefaultBehavior(你的Behavior.class)
- ```
@DefaultBehavior(CustomBehavior.class)
public class CustomView extends View {}
- 3. 判断依赖对象
- 过程:
- 当 CoordinatorLayout 收到某个 view 的变化或者嵌套滑动事件时
- CoordinatorLayout就会尝试把事件下发给Behavior
- 绑定了该 Behavior 的 view 就会对事件做出响应
- 判断关系的几种方式
-
根据id
-
- 过程:
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
· return dependency.getId() == R.id.xxx;
}
- **根据类型**
-
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
· return dependency instanceof CustomView;
}
- **自定义View的id传递**
- 自定义属性
-
- layout中传递id
-
- Behavior中获取对象
-
public class FollowBehavior extends CoordinatorLayout.Behavior {
private int targetId;
public FollowBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Follow);
for (int i = 0; i < a.getIndexCount(); i++) {
int attr = a.getIndex(i);
if(a.getIndex(i) == R.styleable.Follow_target){
targetId = a.getResourceId(attr, -1);
}
}
a.recycle();
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
return true;
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency.getId() == targetId;
}
}
事件流
为了便于记忆,对应的事件,大体分为下面几种
- 触摸事件
-
我们知道View的事件分发中有这2个方法:(dispatchEvent先忽略)
-
public boolean onInterceptTouchEvent(MotionEvent ev)
public boolean onTouchEvent(MotionEvent ev)
-
CoordinatorLayout
会尝试调用其Child View
拥有的Behavior
中的同名方法
public boolean onInterceptTouchEvent(CoordinatorLayout parent, View child, MotionEvent ev)
public boolean onTouchEvent(CoordinatorLayout parent, View child, MotionEvent ev)
- 如果
Behavior
对触摸事件进行了拦截,就不会再分发到Child View
自身拥有的触摸事件中- 这就意味着:在不知道具体View的情况下,就可以重写它的触摸事件
-
onTouch
事件是CoordinatorLayout
分发下来的,所以这里的onTouchEvent并不是我们控件自己的onTouch事件,也就是说,你假如手指不在我们的控件上滑动,也会触发onTouchEvent -
需要在
onTouchEvent
方法中的MotionEvent.ACTION_DOWN
下添加:
ox = ev.getX();
oy = ev.getY();
if (oy < child.getTop() || oy > child.getBottom() || ox < child.getLeft() || ox > child.getRight()) {
· return true;
}
- 手势过滤,以后自己再单独找资料学习
- 计算和布局事件
-
View的计算和布局有这2个方法
-
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
protected void onLayout(boolean changed, int l, int t, int r, int b)
-
在
CoordinatorLayout
也会尝试调用其Child View
拥有的Behavior
中对应的同名方法
public boolean onMeasureChild(CoordinatorLayout parent, V child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed)
public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection)
- 同样地,CoordinatorLayout 会优先处理 Behavior 中所重写的布局事件
- CoordinatorLayout关联事件
- 这个上面也用过部分类似的,应该会有点印象
- 这个
关联事件
是指View
的位置、尺寸发生了变化 - 在
CoordinatorLayout
的onDraw
方法中,会遍历全部的Child View
尝试寻找是否有相互关联的对象 - 确定是否关联的方式有两种:
- 1. Behavior中定义
- 通过 Behavior 的 layoutDependsOn 方法来判断是否有依赖关系
- 这个前面的例子中,已经用过很多次了
- 判断是dependency是否是当前behavior需要的对象
-
parent
CoordinatorLayout -
child
该Behavior对应的那个View -
dependency
dependency 要检查的View -
return
true 依赖, false 不依赖
- 大体常见的3种方式,可以参考前面的说明
- 这个前面的例子中,已经用过很多次了
- 如果有就继续调用 onDependentViewChanged
- 这个上面也详细说明过
- 当改变dependency的尺寸或者位置时被调用
-
parent
CoordinatorLayout -
child
该Behavior对应的那个View -
dependency
child依赖dependency -
return
true 处理了, false 没处理
-
- 在layoutDependsOn返回true的基础上之后,
onDependentViewRemoved
通知dependency被移除了-
parent
CoordinatorLayout -
child
该Behavior对应的那个View -
dependency
child依赖dependency
-
- 通过 Behavior 的 layoutDependsOn 方法来判断是否有依赖关系
- 2. XML中设置属性
- 通过 XML 中设置的 layout_anchor
- 关联设置 layout_anchor 的 Child View 与 layout_anchor
-
dependency View
随后调用offsetChildToAnchor(child, layoutDirection)
- 其实就是调整两者的位置,让它们可以一起变化
- 1. Behavior中定义
app:layout_anchor="@id/dependencyView.id"
- 嵌套滑动事件
-
大体分为 子控件 和 父控件, 也就是 被触发的 和 触发的
- 具体大致就是 NestedScrollingChild , NestedScrollingParent , Behavior子类
- 一些理解,可以参考 鸿洋的一篇博客:
- (主要讲解NestedScrollingChild NestedScrollingParent)
- Android NestedScrolling机制完全解析 带你玩转嵌套滑动
- 自己的理解:
- 实现NestedScrollingChild接口,获得事件,准备传递给 NestedScrollingParent
- 实现NestedScrollingParent接口,获取传递的事件,消费或者传递给Behavior子类消费
- 继承抽象类Behavior,获得事件,进行消费。完成对应View动作
-
1. 实现NestedScrollingChild
- 如果一个View想向外界传递滑动事件,即通知 NestedScrollingParent ,就必须实现此接口
- 而 Child 与 Parent 的具体交互逻辑, NestedScrollingChildHelper 辅助类基本已经帮我们封装好了,所以我们只需要调用对应的方法即可
- NestedScrollingChild接口的一般实现:
-
public class CustomNestedScrollingChildView extends View implements NestedScrollingChild {
private NestedScrollingChildHelper mChildHelper = new NestedScrollingChildHelper(this);
/**
* 设置当前View能否滑动
* @param enabled
*/
@Override
public void setNestedScrollingEnabled(boolean enabled) {
mChildHelper.setNestedScrollingEnabled(enabled);
}
/**
* 判断当前View能否滑动
* @return
*/
@Override
public boolean isNestedScrollingEnabled() {
return mChildHelper.isNestedScrollingEnabled();
}
/**
* 启动嵌套滑动事件流
* 1. 寻找可以接收 NestedScroll 事件的 parent view,即实现了 NestedScrollingParent 接口的 ViewGroup
* 2. 通知该 parent view,现在我要把滑动的参数传递给你
* @param axes
* @return
*/
@Override
public boolean startNestedScroll(int axes) {
return mChildHelper.startNestedScroll(axes);
}
/**
* 停止嵌套滑动事件流
*/
@Override
public void stopNestedScroll() {
mChildHelper.stopNestedScroll();
}
/**
* 是否存在接收 NestedScroll 事件的 parent view
* @return
*/
@Override
public boolean hasNestedScrollingParent() {
return mChildHelper.hasNestedScrollingParent();
}
/**
* 在滑动之后,向父view汇报滚动情况,包括child view消费的部分和child view没有消费的部分。
* @param dxConsumed x方向已消费的滑动距离
* @param dyConsumed y方向已消费的滑动距离
* @param dxUnconsumed x方向未消费的滑动距离
* @param dyUnconsumed y方向未消费的滑动距离
* @param offsetInWindow 如果parent view滑动导致child view的窗口发生了变化(child View的位置发生了变化)
* 该参数返回x(offsetInWindow[0]) y(offsetInWindow[1])方向的变化
* 如果你记录了手指最后的位置,需要根据参数offsetInWindow计算偏移量,
* 才能保证下一次的touch事件的计算是正确的。
* @return 如果parent view接受了它的滚动参数,进行了部分消费,则这个函数返回true,否则为false。
*/
@Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
int dyUnconsumed, int[] offsetInWindow) {
return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
offsetInWindow);
}
/**
* 在滑动之前,先问一下 parent view 是否需要滑动,
* 即child view的onInterceptTouchEvent或onTouchEvent方法中调用。
* 1. 如果parent view滑动了一定距离,你需要重新计算一下parent view滑动后剩下给你的滑动距离剩余量,
* 然后自己进行剩余的滑动。
* 2. 该方法的第三第四个参数返回parent view消费掉的滑动距离和child view的窗口偏移量,
* 如果你记录了手指最后的位置,需要根据第四个参数offsetInWindow计算偏移量,
* 才能保证下一次的touch事件的计算是正确的。
* @param dx x方向的滑动距离
* @param dy y方向的滑动距离
* @param consumed 如果不是null, 则告诉child view现在parent view滑动的情况,
* consumed[0]parent view告诉child view水平方向滑动的距离(dx)
* consumed[1]parent view告诉child view垂直方向滑动的距离(dy)
* @param offsetInWindow 可选 length=2 的数组,
* 如果parent view滑动导致child View的窗口发生了变化(子View的位置发生了变化)
* 该参数返回x(offsetInWindow[0]) y(offsetInWindow[1])方向的变化
* 如果你记录了手指最后的位置,需要根据参数offsetInWindow计算偏移量,
* 才能保证下一次的touch事件的计算是正确的。
* @return 如果parent view对滑动距离进行了部分消费,则这个函数返回true,否则为false。
*/
@Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
}
/**
* 在嵌套滑动的child view快速滑动之后再调用该函数向parent view汇报快速滑动情况。
* @param velocityX 水平方向的速度
* @param velocityY 垂直方向的速度
* @param consumed true 表示child view快速滑动了, false 表示child view没有快速滑动
* @return true 表示parent view快速滑动了, false 表示parent view没有快速滑动
*/
@Override
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
}
/**
* 在嵌套滑动的child view快速滑动之前告诉parent view快速滑动的情况。
* @param velocityX 水平方向的速度
* @param velocityY 垂直方向的速度
* @return true 表示parent view快速滑动了, false 表示parent view没有快速滑动
*/
@Override
public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
return mChildHelper.dispatchNestedPreFling(velocityX, velocityY);
}
}
- 2. 实现NestedScrollingParent
- 如果一个View Group想接收来自 NestedScrollingChild 的滑动事件,就需要实现该接口。
- 同样有一个 NestedScrollingParentHelper 辅助类,帮我们封装好了
parent view
与child view
之间的具体交互逻辑。 - 由
NestedScrollingChild
主动发出滑动事件传递给NestedScrollingParent
,NestedScrollingParent
做出响应 - 之间的调用关系如下表所示:
Child View | Parent View |
---|---|
startNestedScroll | onStartNestedScroll、onNestedScrollAccepted |
dispatchNestedPreScroll | onNestedPreScroll |
dispatchNestedScroll | onNestedScroll |
stopNestedScroll | onStopNestedScroll |
dispatchNestedFling | onNestedFling |
dispatchNestedPreFling | onNestedPreFling |
- 3. Behavior子类获得事件,对应View变化
-
Parent View
自身并不会消费滑动距离,都是传递给Behavior
- 拥有这个
Behavior
的Child View
才是真正消费滑动距离的实例 -
Behavior
拥有与NestedScrollingParent
接口完全同名的方法。在每一个NestedScrollingParent
的方法中都会调用Behavior
中的同名方法 - 特殊方法的说明:
-
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes)
- 开始嵌套滑动的时候被调用
- 需要判断滑动的方向是否是我们需要的
-
nestedScrollAxes == ViewCompat.SCROLL_AXIS_HORIZONTAL
表示是水平方向的滑动 -
nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL
表示是竖直方向的滑动
-
- 对应的返回值:
- 返回 true 表示继续接收后续的滑动事件,
- 返回 false 表示不再接收后续滑动事件
-
public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed)
- 滑动中调用
- 1 正在上滑: dyConsumed > 0 && dyUnconsumed == 0
- 2 已经到顶部了还在上滑: dyConsumed == 0 && dyUnconsumed > 0
- 3 正在下滑: dyConsumed < 0 && dyUnconsumed == 0
- 4 已经打底部了还在下滑: dyConsumed == 0 && dyUnconsumed < 0
-
public boolean onNestedFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY, boolean consumed)
- 快速滑动中调用
-
-
事件流总结
前面有写,通常分为:
- 事件来自外部父view
- 触摸事件
-
Behavior
的onInterceptTouchEvent + onTouchEvent
-
- 计算和布局事件
-
Behavior
的onMeasureChild + onLayoutChild
-
- 触摸事件
- 事件来自内部子view
- CoordinatorLayout关联事件
-
Behavior
的layoutDependsOn
+onDependentViewChanged
+onDependentViewRemoved
-
- 嵌套滑动事件
-
Behavior
的onStartNestedScroll
+onNestedScrollAccepted
+onStopNestedScroll
+onNestedScroll
+onNestedPreScroll
+onNestedFling
+onNestedPreFling
-
- CoordinatorLayout关联事件
其他参考
- CoordinatorLayout与Behavior的一己之见
- sidhu眼中的CoordinatorLayout.Behavior(一)
- sidhu眼中的CoordinatorLayout.Behavior(二)
- sidhu眼中的CoordinatorLayout.Behavior(三)
- Material Design系列,自定义Behavior支持所有View
- CoordinatorLayout的使用如此简单
简单回顾
最开始的demo和后面的流程关系不大
开始的demo大体也可以理解成:
- 自己定义的View,相当于NestedScrollingChild,获得事件,传递给 NestedScrollingParent
- CoordinatorLayout 实现NestedScrollingParent接口,获取传递的事件,传递给Behavior子类消费
- 自定义的Behavior继承抽象类Behavior,获得事件,进行消费。完成对应View动作
这篇文章内容很少,也很多
只有1个demo,但是总结的很多
其他的内容,后续一起学习
具体代码,可以见
https://github.com/2954722256/use_little_demo
对应 coordinator 的 Module
其他总结,后续的文章,接着参考分析,和大家一起学习。
下一篇我们可以了解
NestedScrollView & 嵌套滑动事件