上文說了一個(gè)behavior的創(chuàng)建和也知道了依賴的產(chǎn)生原理异赫。這篇文章來說說兩點(diǎn):
1appBarlayout為何在狀態(tài)欄下面:
2..NestScrollView為何在appBarlayout的下面:
讓我們繼續(xù)看CoordinatorLayout的onMeasure方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
prepareChildren();
ensurePreDrawListener();
final int paddingLeft = getPaddingLeft();
final int paddingTop = getPaddingTop();
final int paddingRight = getPaddingRight();
final int paddingBottom = getPaddingBottom();
final int layoutDirection = ViewCompat.getLayoutDirection(this);
final boolean isRtl = layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL;
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
final int widthPadding = paddingLeft + paddingRight;
final int heightPadding = paddingTop + paddingBottom;
int widthUsed = getSuggestedMinimumWidth();
int heightUsed = getSuggestedMinimumHeight();
int childState = 0;
final boolean applyInsets = mLastInsets != null && ViewCompat.getFitsSystemWindows(this);
final int childCount = mDependencySortedChildren.size();
for (int i = 0; i < childCount; i++) {
final View child = mDependencySortedChildren.get(i);
if (child.getVisibility() == GONE) {
// If the child is GONE, skip...
continue;
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
int keylineWidthUsed = 0;
}
int childWidthMeasureSpec = widthMeasureSpec;
int childHeightMeasureSpec = heightMeasureSpec;
if (applyInsets && !ViewCompat.getFitsSystemWindows(child)) {
// We're set to handle insets but this child isn't, so we will measure the
// child as if there are no insets
final int horizInsets = mLastInsets.getSystemWindowInsetLeft()
+ mLastInsets.getSystemWindowInsetRight();
final int vertInsets = mLastInsets.getSystemWindowInsetTop()
+ mLastInsets.getSystemWindowInsetBottom();
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
widthSize - horizInsets, widthMode);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
heightSize - vertInsets, heightMode);
}
final Behavior b = lp.getBehavior();
if (b == null || !b.onMeasureChild(this, child, childWidthMeasureSpec, keylineWidthUsed,
childHeightMeasureSpec, 0)) {
onMeasureChild(child, childWidthMeasureSpec, keylineWidthUsed,
childHeightMeasureSpec, 0);
}
widthUsed = Math.max(widthUsed, widthPadding + child.getMeasuredWidth() +
lp.leftMargin + lp.rightMargin);
heightUsed = Math.max(heightUsed, heightPadding + child.getMeasuredHeight() +
lp.topMargin + lp.bottomMargin);
childState = View.combineMeasuredStates(childState, child.getMeasuredState());
}
final int width = View.resolveSizeAndState(widthUsed, widthMeasureSpec,
childState & View.MEASURED_STATE_MASK);
final int height = View.resolveSizeAndState(heightUsed, heightMeasureSpec,
childState << View.MEASURED_HEIGHT_STATE_SHIFT);
setMeasuredDimension(width, height);
}
我們可以很清楚的看到首先是獲取了父view的Measurespec的寬高,當(dāng)然這里會(huì)默認(rèn)進(jìn)行2次measure打印在模擬器上分別是1488和1704,很多人會(huì)覺得奇怪,其實(shí)任何自定義的view都會(huì)進(jìn)行2次measure,因?yàn)闀?huì)2次調(diào)用viewRootImp的測(cè)量方法,第一次是父view給的寬高是獲取屏幕密度產(chǎn)生的高度,第二次才是我們通常所說的高度,由于在1920的手機(jī)上不包含狀態(tài)欄和虛擬按鍵,所以1704就是coordinatorLayout的高度了挟秤,在代碼上,如果applyInsets不為空的話抄伍,高度就是1776也就說會(huì)加上狀態(tài)欄艘刚,那appBarlayout的話是不包含的,所以會(huì)減去狀態(tài)欄高度截珍,這在源碼里也有體現(xiàn)昔脯。
在測(cè)量子view的時(shí)候啄糙,這里用到了Behavior,分別測(cè)量了appBarlayout和NestScrollview,首先看appBarlayout
@Override
public boolean onMeasureChild(CoordinatorLayout parent, AppBarLayout child,
int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec,
int heightUsed) {
final CoordinatorLayout.LayoutParams lp =
(CoordinatorLayout.LayoutParams) child.getLayoutParams();
if (lp.height == CoordinatorLayout.LayoutParams.WRAP_CONTENT) {
// If the view is set to wrap on it's height, CoordinatorLayout by default will
// cap the view at the CoL's height. Since the AppBarLayout can scroll, this isn't
// what we actually want, so we measure it ourselves with an unspecified spec to
// allow the child to be larger than it's parent
parent.onMeasureChild(child, parentWidthMeasureSpec, widthUsed,
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), heightUsed);
return true;
}
// Let the parent handle it as normal
return super.onMeasureChild(parent, child, parentWidthMeasureSpec, widthUsed,
parentHeightMeasureSpec, heightUsed);
}
當(dāng)給他的高度是wrap_content時(shí),可以看到他允許子view比他更大云稚,也就是說是允許子view滑動(dòng)的隧饼,不是的話,也沒做特殊的處理静陈,直接調(diào)用其父類的了燕雁,再來看下nestScrollview在其HeaderScrollingViewBehavior里重寫的
@Override
public boolean onMeasureChild(CoordinatorLayout parent, View child,
int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec,
int heightUsed) {
final int childLpHeight = child.getLayoutParams().height;
if (childLpHeight == ViewGroup.LayoutParams.MATCH_PARENT
|| childLpHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
// If the menu's height is set to match_parent/wrap_content then measure it
// with the maximum visible height
final List<View> dependencies = parent.getDependencies(child);
final View header = findFirstDependency(dependencies);
if (header != null) {
if (ViewCompat.getFitsSystemWindows(header)
&& !ViewCompat.getFitsSystemWindows(child)) {
// If the header is fitting system windows then we need to also,
// otherwise we'll get CoL's compatible measuring
ViewCompat.setFitsSystemWindows(child, true);
if (ViewCompat.getFitsSystemWindows(child)) {
// If the set succeeded, trigger a new layout and return true
child.requestLayout();
return true;
}
}
int availableHeight = View.MeasureSpec.getSize(parentHeightMeasureSpec);
if (availableHeight == 0) {
// If the measure spec doesn't specify a size, use the current height
availableHeight = parent.getHeight();
}
final int height = availableHeight - header.getMeasuredHeight()
+ getScrollRange(header);
final int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height,
childLpHeight == ViewGroup.LayoutParams.MATCH_PARENT
? View.MeasureSpec.EXACTLY
: View.MeasureSpec.AT_MOST);
// Now measure the scrolling view with the correct height
parent.onMeasureChild(child, parentWidthMeasureSpec,
widthUsed, heightMeasureSpec, heightUsed);
return true;
}
}
return false;
}
重點(diǎn)在
final int height = availableHeight - header.getMeasuredHeight()
+ getScrollRange(header);
可以看到他的高度是coordinatorLayout的高度-appBarlayout的高度
+appBarlayou可滑動(dòng)的高度,當(dāng)然我們從appBarlayout里可以看到
public final int getTotalScrollRange() {
if (mTotalScrollRange != INVALID_SCROLL_RANGE) {
return mTotalScrollRange;
}
int range = 0;
for (int i = 0, z = getChildCount(); i < z; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int childHeight = child.getMeasuredHeight();
final int flags = lp.mScrollFlags;
if ((flags & LayoutParams.SCROLL_FLAG_SCROLL) != 0) {
// We're set to scroll so add the child's height
range += childHeight + lp.topMargin + lp.bottomMargin;
if ((flags & LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) != 0) {
// For a collapsing scroll, we to take the collapsed height into account.
// We also break straight away since later views can't scroll beneath
// us
range -= ViewCompat.getMinimumHeight(child);
break;
}
} else {
// As soon as a view doesn't have the scroll flag, we end the range calculation.
// This is because views below can not scroll under a fixed view.
break;
}
}
return mTotalScrollRange = Math.max(0, range - getTopInset());
}
他的高度和我們的scroll_flag屬性以及狀態(tài)欄有關(guān)
總的來說這個(gè)可滑動(dòng)高度是<=appBarlayout的高度的
看完了onMeasure再來看下onLayout方法
@Override
protected 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);
if (child.getVisibility() == GONE) {
// If the child is GONE, skip...
continue;
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final Behavior behavior = lp.getBehavior();
if (behavior == null || !behavior.onLayoutChild(this, child, layoutDirection)) {
onLayoutChild(child, layoutDirection);
}
}
}
也很簡(jiǎn)單是同樣的操作
這里appBarlayout的layoutchild首先會(huì)調(diào)用其父類的onlayoutChild
private void layoutChild(View child, int layoutDirection) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final Rect parent = acquireTempRect();
parent.set(getPaddingLeft() + lp.leftMargin,
getPaddingTop() + lp.topMargin,
getWidth() - getPaddingRight() - lp.rightMargin,
getHeight() - getPaddingBottom() - lp.bottomMargin);
if (mLastInsets != null && ViewCompat.getFitsSystemWindows(this)
&& !ViewCompat.getFitsSystemWindows(child)) {
// If we're set to handle insets but this child isn't, then it has been measured as
// if there are no insets. We need to lay it out to match.
parent.left += mLastInsets.getSystemWindowInsetLeft();
parent.top += mLastInsets.getSystemWindowInsetTop();
parent.right -= mLastInsets.getSystemWindowInsetRight();
parent.bottom -= mLastInsets.getSystemWindowInsetBottom();
}
final Rect out = acquireTempRect();
GravityCompat.apply(resolveGravity(lp.gravity), child.getMeasuredWidth(),
child.getMeasuredHeight(), parent, out, layoutDirection);
child.layout(out.left, out.top, out.right, out.bottom);
releaseTempRect(parent);
releaseTempRect(out);
}
可以看到如果父view寫了fitsysystemwindow為true而appBarlayout沒寫鲸拥,會(huì)把他的位置手動(dòng)移到其下面,
這里分析了為何appBarlayout會(huì)在狀態(tài)欄的下方
再說一下nestScollview
@Override
protected void layoutChild(final CoordinatorLayout parent, final View child,
final int layoutDirection) {
final List<View> dependencies = parent.getDependencies(child);
final View header = findFirstDependency(dependencies);
if (header != null) {
final CoordinatorLayout.LayoutParams lp =
(CoordinatorLayout.LayoutParams) child.getLayoutParams();
final Rect available = mTempRect1;
available.set(parent.getPaddingLeft() + lp.leftMargin,
header.getBottom() + lp.topMargin,
parent.getWidth() - parent.getPaddingRight() - lp.rightMargin,
parent.getHeight() + header.getBottom()
- parent.getPaddingBottom() - lp.bottomMargin);
final WindowInsetsCompat parentInsets = parent.getLastWindowInsets();
if (parentInsets != null && ViewCompat.getFitsSystemWindows(parent)
&& !ViewCompat.getFitsSystemWindows(child)) {
// If we're set to handle insets but this child isn't, then it has been measured as
// if there are no insets. We need to lay it out to match horizontally.
// Top and bottom and already handled in the logic above
available.left += parentInsets.getSystemWindowInsetLeft();
available.right -= parentInsets.getSystemWindowInsetRight();
}
final Rect out = mTempRect2;
GravityCompat.apply(resolveGravity(lp.gravity), child.getMeasuredWidth(),
child.getMeasuredHeight(), available, out, layoutDirection);
final int overlap = getOverlapPixelsForOffset(header);
child.layout(out.left, out.top - overlap, out.right, out.bottom - overlap);
mVerticalLayoutGap = out.top - header.getBottom();
} else {
// If we don't have a dependency, let super handle it
super.layoutChild(parent, child, layoutDirection);
mVerticalLayoutGap = 0;
}
}
可以看到available的位置是header.getBottom() + lp.topMargin也就是其top在appBarlayout的下面了(一開始就屈居人下),那有啥屬性可以改變呢拐格?可以看到getOverlapPixelsForOffset(head)也就是nestScrollview的Behivor的一個(gè)屬性O(shè)verlay可以改變位置(當(dāng)然滑動(dòng)的交互也會(huì)改了)
這里分析了為何NestScrollview會(huì)在appBarlayout的下方
總結(jié):
基本的布局位置都分析清楚了,主要是對(duì)onMeasure和onlayout進(jìn)行behavior的處理,下篇將討論滑動(dòng)的原理.