Behavior 是 CoordinatorLayout為其 <b>子視圖</b> 提供的一種交互行為插件。
它實(shí)現(xiàn)了用戶可以操作子視圖的一種或多種交互行為灯荧,包括包括拖拽礁击,F(xiàn)ling(滑翔)或任何其他手勢(shì)。
直接子類
CoordinatorLayout逗载、AppBarLayout哆窿、SwipeDismissBehavior等使用方法
自定義
<p>自定義Behavior,這里分為兩類:
- <b>Dependent機(jī)制</b>:
layoutDependsOn
和onDependentViewChanged
作為一組厉斟。<b>Dependent機(jī)制</b>最常見(jiàn)的案例就是FloatingActionButton
和SnackBar
的交互行為挚躯。如圖 - <b>Nested機(jī)制</b>
onStartNestedScroll
和onNestedScroll
作為一組。<b>Nested機(jī)制</b>要求CoordinatorLayout
包含了一個(gè)實(shí)現(xiàn)了NestedScrollingChild
接口的滾動(dòng)視圖控件擦秽,比如v7包中的RecyclerView
码荔,設(shè)置Behavior
屬性的Child View
會(huì)隨著這個(gè)控件的滾動(dòng)而發(fā)生變化漩勤。如圖
如下只標(biāo)注了Behavior
部分方法。開(kāi)發(fā)者可以根據(jù)自身業(yè)務(wù)需求有選擇的復(fù)寫(xiě)缩搅。
<b>部分方法</b>
-
layoutDependsOn
此方法用于判斷給定的View和同級(jí)View是否作為布局依賴關(guān)系越败。
/**
* @param parent
* @param child 給定的View,即應(yīng)用了layout_behavior的View
* @param dependency 任何與child同級(jí)的View
* @return 如果返回true誉己,那么parent將做兩件事:
* 1.將忽略View的順序眉尸,總是先去布局dependency,之后布局child巨双。
* 2.當(dāng)dependency視圖的布局或位置發(fā)生改變時(shí)噪猾,調(diào)用onDependentViewChanged方法。
*/
public boolean layoutDependsOn(CoordinatorLayout parent,
View child,
View dependency) {
//判斷dependency是否是需要的依賴項(xiàng)筑累,如果是袱蜡,則返回true
return false;
}
-
onDependentViewChanged
此方法用于對(duì)依賴視圖的改變做出響應(yīng)。開(kāi)發(fā)者可以復(fù)寫(xiě)此方法從而改變child的大小和位置慢宗,并返回true坪蚁。
public boolean onDependentViewChanged(CoordinatorLayout parent,
View child,
View dependency) {
//處理child的位置或大小,并返回true
return true;
}
-
onStartNestedScroll
此方法用于判斷是否進(jìn)行嵌套滾動(dòng)。與CoordinatorLayout的任何直接子項(xiàng)相關(guān)聯(lián)的任何Behavior都可以響應(yīng)此事件镜沽。如果返回true敏晤,表明CoordinatorLayout應(yīng)該充當(dāng)此滾動(dòng)的嵌套滾動(dòng)父項(xiàng)。只有返回true缅茉,才會(huì)執(zhí)行后續(xù)的嵌套滾動(dòng)方法嘴脾。
/**
* @param coordinatorLayout
* @param child 關(guān)聯(lián)Behavior的CoordinatorLayout的子View
* @param directTargetChild CoordinatorLayout的子View或包含嵌套滾動(dòng)操作的View。比如RecycleView外層的RelativeLayout
* @param target 嵌套滾動(dòng)的View
* @param nestedScrollAxes 嵌套滾動(dòng)的坐標(biāo)軸蔬墩。SCROLL_AXIS_HORIZONTAL, SCROLL_AXIS_VERTICAL
* @return
*/
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout,
View child,
View directTargetChild,
View target,
int nestedScrollAxes) {
return false;
}
-
onNestedScroll
此方法用于處理嵌套滾動(dòng)译打。
每次嵌套滾動(dòng)由嵌套滾動(dòng)子元素更新時(shí),onNestedScroll被調(diào)用拇颅,滾動(dòng)的消費(fèi)組件和未消費(fèi)組件以像素提供奏司。
/**
*
* @param coordinatorLayout
* @param child
* @param target
* @param dxConsumed 水平方向滾動(dòng)增量,
* @param dyConsumed 垂直方向滾動(dòng)增量樟插,如果大于0韵洋,手指上滑中;如果小于0,手指下滑中黄锤。
* @param dxUnconsumed 同dyUnconsumed描述
* @param dyUnconsumed 正常情況下麻献,始終為0,當(dāng)View處于最頂部或最底部猜扮,用戶仍然強(qiáng)制下滑或上滑時(shí),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所屬
通過(guò)查看CoordinatorLayout
源碼可以知道充易,它有一個(gè)內(nèi)部類LayoutParams
梗脾,用于存儲(chǔ)CoordinatorLayout
的所有子View
布局參數(shù),其中Behavior
也是LayoutParams
的一個(gè)屬性值盹靴。因此和文章開(kāi)始的描述保持了一致炸茧,Behavior
只能設(shè)置到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多代碼
}
-
設(shè)置Behavior兩種方式
-
app:layout_behavior布局屬性
在布局中設(shè)置稿静,值為自定義 Behavior類的名字字符串(包含路徑)梭冠,有兩種寫(xiě)法,包含包名的全路徑和以”.”開(kāi)頭的省略項(xiàng)目包名的路徑改备。
app:layout_behavior="com.yolo.myapplication.MyBehavior"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
-
@CoordinatorLayout.DefaultBehavior類注解
在需要使用 Behavior的控件源碼定義中添加該注解控漠,然后通過(guò)反射機(jī)制獲取。系統(tǒng)的 AppBarLayout绍妨、 FloatingActionButton都采用了這種方式润脸,所以無(wú)需在布局中重復(fù)設(shè)置。
-
Behavior實(shí)例化
在LayoutParams構(gòu)造方法中他去,調(diào)用了parseBehavior(context, attrs, a.getString( R.styleable.CoordinatorLayout_Layout_layout_behavior))
方法毙驯。判斷名稱,然后通過(guò)反射機(jī)制實(shí)例化Behavior
灾测。從而回調(diào)Behaivor
的其他方法爆价。<p>
注意:在自定義 Behavior
時(shí),一定要重寫(xiě)第二個(gè)帶參數(shù)的構(gòu)造函數(shù)媳搪,否則這個(gè) Behavior
是不會(huì)起作用的铭段。
static Behavior parseBehavior(Context context, AttributeSet attrs, String name) {
if (TextUtils.isEmpty(name)) {
return null;
}
final String fullName;
if (name.startsWith(".")) {
//如果behavior的值以 . 開(kāi)頭,則自動(dòng)補(bǔ)全包名信息
// 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) {
//通過(guò)反射實(shí)例化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);
}
}