Behavior 是 CoordinatorLayout为其 <b>子视图</b> 提供的一种交互行为插件。
它实现了用户可以操作子视图的一种或多种交互行为,包括包括拖拽,Fling(滑翔)或任何其他手势。
直接子类
CoordinatorLayout、AppBarLayout、SwipeDismissBehavior等使用方法
自定义
<p>自定义Behavior,这里分为两类:
- <b>Dependent机制</b>:
layoutDependsOn
和onDependentViewChanged
作为一组。<b>Dependent机制</b>最常见的案例就是FloatingActionButton
和SnackBar
的交互行为。如图 - <b>Nested机制</b>
onStartNestedScroll
和onNestedScroll
作为一组。<b>Nested机制</b>要求CoordinatorLayout
包含了一个实现了NestedScrollingChild
接口的滚动视图控件,比如v7包中的RecyclerView
,设置Behavior
属性的Child View
会随着这个控件的滚动而发生变化。如图
如下只标注了Behavior
部分方法。开发者可以根据自身业务需求有选择的复写。
<b>部分方法</b>
-
layoutDependsOn
此方法用于判断给定的View和同级View是否作为布局依赖关系。
/**
* @param parent
* @param child 给定的View,即应用了layout_behavior的View
* @param dependency 任何与child同级的View
* @return 如果返回true,那么parent将做两件事:
* 1.将忽略View的顺序,总是先去布局dependency,之后布局child。
* 2.当dependency视图的布局或位置发生改变时,调用onDependentViewChanged方法。
*/
public boolean layoutDependsOn(CoordinatorLayout parent,
View child,
View dependency) {
//判断dependency是否是需要的依赖项,如果是,则返回true
return false;
}
-
onDependentViewChanged
此方法用于对依赖视图的改变做出响应。开发者可以复写此方法从而改变child的大小和位置,并返回true。
public boolean onDependentViewChanged(CoordinatorLayout parent,
View child,
View dependency) {
//处理child的位置或大小,并返回true
return true;
}
-
onStartNestedScroll
此方法用于判断是否进行嵌套滚动。与CoordinatorLayout的任何直接子项相关联的任何Behavior都可以响应此事件。如果返回true,表明CoordinatorLayout应该充当此滚动的嵌套滚动父项。只有返回true,才会执行后续的嵌套滚动方法。
/**
* @param coordinatorLayout
* @param child 关联Behavior的CoordinatorLayout的子View
* @param directTargetChild CoordinatorLayout的子View或包含嵌套滚动操作的View。比如RecycleView外层的RelativeLayout
* @param target 嵌套滚动的View
* @param nestedScrollAxes 嵌套滚动的坐标轴。SCROLL_AXIS_HORIZONTAL, SCROLL_AXIS_VERTICAL
* @return
*/
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout,
View child,
View directTargetChild,
View target,
int nestedScrollAxes) {
return false;
}
-
onNestedScroll
此方法用于处理嵌套滚动。
每次嵌套滚动由嵌套滚动子元素更新时,onNestedScroll被调用,滚动的消费组件和未消费组件以像素提供。
/**
*
* @param coordinatorLayout
* @param child
* @param target
* @param dxConsumed 水平方向滚动增量,
* @param dyConsumed 垂直方向滚动增量,如果大于0,手指上滑中;如果小于0,手指下滑中。
* @param dxUnconsumed 同dyUnconsumed描述
* @param dyUnconsumed 正常情况下,始终为0,当View处于最顶部或最底部,用户仍然强制下滑或上滑时,dy则不为0
*/
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout,
View child,
View target,
int dxConsumed,
int dyConsumed,
int dxUnconsumed,
int dyUnconsumed) {
if (dyConsumed > 0 && dyUnconsumed == 0) {
System.out.println("上滑中。。。");
}
if (dyConsumed == 0 && dyUnconsumed > 0) {
System.out.println("到边界了还在上滑。。。");
}
if (dyConsumed < 0 && dyUnconsumed == 0) {
System.out.println("下滑中。。。");
}
if (dyConsumed == 0 && dyUnconsumed < 0) {
System.out.println("到边界了,还在下滑。。。");
}
}
原理分析
-
Behavior所属
通过查看CoordinatorLayout
源码可以知道,它有一个内部类LayoutParams
,用于存储CoordinatorLayout
的所有子View
布局参数,其中Behavior
也是LayoutParams
的一个属性值。因此和文章开始的描述保持了一致,Behavior
只能设置到CoordinatorLayout
的子View
上。
/**
* Parameters describing the desired layout for a child of a {@link CoordinatorLayout}.
*/
public static class LayoutParams extends ViewGroup.MarginLayoutParams {
/**
* A {@link Behavior} that the child view should obey.
*/
Behavior mBehavior;
//省略n多代码
}
-
设置Behavior两种方式
-
app:layout_behavior布局属性
在布局中设置,值为自定义 Behavior类的名字字符串(包含路径),有两种写法,包含包名的全路径和以”.”开头的省略项目包名的路径。
app:layout_behavior="com.yolo.myapplication.MyBehavior"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
-
@CoordinatorLayout.DefaultBehavior类注解
在需要使用 Behavior的控件源码定义中添加该注解,然后通过反射机制获取。系统的 AppBarLayout、 FloatingActionButton都采用了这种方式,所以无需在布局中重复设置。
-
Behavior实例化
在LayoutParams构造方法中,调用了parseBehavior(context, attrs, a.getString( R.styleable.CoordinatorLayout_Layout_layout_behavior))
方法。判断名称,然后通过反射机制实例化Behavior
。从而回调Behaivor
的其他方法。<p>
注意:在自定义 Behavior
时,一定要重写第二个带参数的构造函数,否则这个 Behavior
是不会起作用的。
static Behavior parseBehavior(Context context, AttributeSet attrs, String name) {
if (TextUtils.isEmpty(name)) {
return null;
}
final String fullName;
if (name.startsWith(".")) {
//如果behavior的值以 . 开头,则自动补全包名信息
// 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 {
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) {
//通过反射实例化Behavior
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);
}
}