android5.0協調佈局CoordinatorLayout(第二篇CollapsingToolbarLayout效果實現原理講解)原理
上一篇中已經講解了CoordinatorLayout、AppBarLayout、CollapsingToolbarLayout之間的關係,這一篇探索一下CollapsingToolbarLayout內部是怎麼實現的,不熟悉CoordinatorLayout、AppBarLayout、CollapsingToolbarLayout之間的關係的請先看上一篇文章android5.0協調佈局CoordinatorLayout(第一篇CoordinatorLayout、AppBarLayout、CollapsingToolbarLayout之間的關係詳解)原理
首先看一下CollapsingToolbarLayout的一些屬性說明,首先下面這些屬性是要寫在CollapsingToolbarLayout中的
app1:collapsedTitleGravity="center_horizontal":關閉後標題的位置 app1:contentScrim:完全摺疊後的背景顏色 app1:collapsedTitleTextAppearance:關閉後的標題顏色,存在兩個顏色值漸變效果 app1:statusBarScrim 摺疊完成後狀態列的顏色 app1:expandedTitleTextAppearance 展開後的tittle的顏色 app1:expandedTitleGravity展開後的標題位置 app1:expandedTitleMargin展開後的標題偏移量 app1:title:設定的標題名字 app:toolbarId:ToolBar的id必須設定,它通過id獲取物件操作ToolBar app1:titleEnabled 標題是否存在
下面這些屬性是寫在CollapsingToolbarLayout的子View中的
app1:layout_collapseMode 摺疊模式有兩個值 pin - 設定為這個模式時,當CollapsingToolbarLayout完全收縮後,Toolbar還可以保留在螢幕上。 parallax - 設定為這個模式時,在內容滾動時,CollapsingToolbarLayout中的View(比如ImageView)也可以同時滾動,實現視差滾動效果,通常和layout_collapseParallaxMultiplier(設定視差因子)搭配使用。 layout_collapseParallaxMultiplier(視差因子) - 設定視差滾動因子,值為:0~1
現在先以一個簡單的例子為入口點,先看一下效果圖
標題部分如下佈局程式碼
<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app1:collapsedTitleTextAppearance="@color/abc_primary_text_material_light"
app1:contentScrim="@android:color/holo_blue_light"
app1:title="6666" >
<!-- 視差值越小滾動越明顯 -->
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="0.7"
android:scaleType="centerCrop"
android:src="@drawable/tulips2" />
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:navigationIcon="@drawable/abc_ic_ab_back_mtrl_am_alpha" />
</android.support.design.widget.CollapsingToolbarLayout>
這裡設定了app1:contentScrim屬性,也就是最後圖片滑沒了的時候標題欄出現的顏色,背景圖片設定的是視差效果,然後底部放了一個ToolBar用了 app:layout_collapseMode="pin"懸浮的屬性,也就是說背景圖片隨著上移慢慢消失,而標題欄一直懸浮在上方。居然提到了這麼多的屬性,那麼就從屬性的獲取開始看起來,一般自定義屬性的話都是從建構函式開始獲取,不瞭解自定義屬性的童鞋,請先查一查這方面的知識。
public CollapsingToolbarLayout(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
ThemeUtils.checkAppCompatTheme(context);
mCollapsingTextHelper = new CollapsingTextHelper(this);
mCollapsingTextHelper
.setTextSizeInterpolator(AnimationUtils.DECELERATE_INTERPOLATOR);
// 設定了預設stytle,如果佈局裡面沒有設定的話,
// 預設 <item name="expandedTitleMargin">32dp</item>
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.CollapsingToolbarLayout, defStyleAttr,
R.style.Widget_Design_CollapsingToolbar);
// 獲得展開後的tittle的位置expandedTitleGravity,預設在左邊和底部
mCollapsingTextHelper.setExpandedTextGravity(a.getInt(
R.styleable.CollapsingToolbarLayout_expandedTitleGravity,
GravityCompat.START | Gravity.BOTTOM));
// 收縮後的tittle位置預設在左邊,垂直居中
mCollapsingTextHelper.setCollapsedTextGravity(a.getInt(
R.styleable.CollapsingToolbarLayout_collapsedTitleGravity,
GravityCompat.START | Gravity.CENTER_VERTICAL));
// 擴充套件後tittle的偏移量
mExpandedMarginLeft = mExpandedMarginTop = mExpandedMarginRight = mExpandedMarginBottom = a
.getDimensionPixelSize(
R.styleable.CollapsingToolbarLayout_expandedTitleMargin,
0);
final boolean isRtl = ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL;
if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleMarginStart)) {
final int marginStart = a
.getDimensionPixelSize(
R.styleable.CollapsingToolbarLayout_expandedTitleMarginStart,
0);
if (isRtl) {
mExpandedMarginRight = marginStart;
} else {
mExpandedMarginLeft = marginStart;
}
}
if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleMarginEnd)) {
final int marginEnd = a.getDimensionPixelSize(
R.styleable.CollapsingToolbarLayout_expandedTitleMarginEnd,
0);
if (isRtl) {
mExpandedMarginLeft = marginEnd;
} else {
mExpandedMarginRight = marginEnd;
}
}
if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleMarginTop)) {
mExpandedMarginTop = a.getDimensionPixelSize(
R.styleable.CollapsingToolbarLayout_expandedTitleMarginTop,
0);
}
if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleMarginBottom)) {
mExpandedMarginBottom = a
.getDimensionPixelSize(
R.styleable.CollapsingToolbarLayout_expandedTitleMarginBottom,
0);
}
// 收縮後的tittle是否顯示,預設顯示
mCollapsingTitleEnabled = a.getBoolean(
R.styleable.CollapsingToolbarLayout_titleEnabled, true);
setTitle(a.getText(R.styleable.CollapsingToolbarLayout_title));
// First load the default text appearances
mCollapsingTextHelper
.setExpandedTextAppearance(R.style.TextAppearance_Design_CollapsingToolbar_Expanded);
mCollapsingTextHelper
.setCollapsedTextAppearance(R.style.TextAppearance_AppCompat_Widget_ActionBar_Title);
// Now overlay any custom text appearances
// 展開後的tittle的顏色設定
if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleTextAppearance)) {
mCollapsingTextHelper
.setExpandedTextAppearance(a
.getResourceId(
R.styleable.CollapsingToolbarLayout_expandedTitleTextAppearance,
0));
}
// 收縮後的tittle顏色
if (a.hasValue(R.styleable.CollapsingToolbarLayout_collapsedTitleTextAppearance)) {
mCollapsingTextHelper
.setCollapsedTextAppearance(a
.getResourceId(
R.styleable.CollapsingToolbarLayout_collapsedTitleTextAppearance,
0));
}
setContentScrim(a
.getDrawable(R.styleable.CollapsingToolbarLayout_contentScrim));
setStatusBarScrim(a
.getDrawable(R.styleable.CollapsingToolbarLayout_statusBarScrim));
mToolbarId = a.getResourceId(
R.styleable.CollapsingToolbarLayout_toolbarId, -1);
a.recycle();
setWillNotDraw(false);
/**
* 設定處理狀態列或導航欄的監聽回撥
*/
ViewCompat.setOnApplyWindowInsetsListener(this,
new android.support.v4.view.OnApplyWindowInsetsListener() {
@Override
public WindowInsetsCompat onApplyWindowInsets(View v,
WindowInsetsCompat insets) {
mLastInsets = insets;
requestLayout();
return insets.consumeSystemWindowInsets();
}
});
}
這個構造方法雖然程式碼看起來多點,但是意思非常容易理解,就是獲取CollapsingToolbarLayout設定的屬性,然後進行相應操作,這裡設定的自身屬性就這三個
app1:collapsedTitleTextAppearance="@color/abc_primary_text_material_light"
app1:contentScrim="@android:color/holo_blue_light"
app1:title="6666"
先看看tittle屬性做了什麼,獲取完tittle屬性的值呼叫了這個方法setTitle(a.getText(R.styleable.CollapsingToolbarLayout_title));
public void setTitle(@Nullable CharSequence title) {
mCollapsingTextHelper.setText(title);
}
這個方法將我們的tittle值交給了mCollapsingTextHelper這個物件處理,基本上很多屬性都是交給CollapsingTextHelper類處理的,暫且叫它屬性幫助類,獲取完屬性之後呢,當然是測量了protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
ensureToolbar();
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
這裡呼叫父類的方法測量子View的大小,因為CollapsingToolbarLayout繼承與FrameLayout,所以最終測量由FrameLayout完成,這裡暫不關心FrameLayout怎麼測量的,再測量之前先呼叫了ensureToolbar方法,看英文名字的意思是確保ToolBar控制元件存在
private void ensureToolbar() {
if (!mRefreshToolbar) {
return;
}
Toolbar fallback = null, selected = null;
for (int i = 0, count = getChildCount(); i < count; i++) {
final View child = getChildAt(i);
if (child instanceof Toolbar) {
if (mToolbarId != -1) {
// There's a toolbar id set so try and find it...
if (mToolbarId == child.getId()) {
// We found the primary Toolbar, use it
selected = (Toolbar) child;
break;
}
if (fallback == null) {
// We'll record the first Toolbar as our fallback
fallback = (Toolbar) child;
}
} else {
// We don't have a id to check for so just use the first we
// come across
selected = (Toolbar) child;
break;
}
}
}
if (selected == null) {
// If we didn't find a primary Toolbar, use the fallback
selected = fallback;
}
mToolbar = selected;
updateDummyView();
mRefreshToolbar = false;
}
這個方法的意思就是迴圈遍歷子View,看哪一個子View是ToolBar,然後將mToolbarId 和mToolbar 附上值,還記得構造方法裡面有ToolBarI的的獲取mToolbarId = a.getResourceId(
R.styleable.CollapsingToolbarLayout_toolbarId, -1);
如果提供了該屬性的話,直接以選中的ToolBar為基準,否則則以最上面的ToolBar為基準,也就是我們可以設定多個ToolBar,但是CollapsingToolbarLayout必須依賴其中的一個ToolBar乾點事情,乾點什麼呢?那麼咱們接著往下看,倒數第二行呼叫了updateDummyView()方法,
private void updateDummyView() {
if (!mCollapsingTitleEnabled && mDummyView != null) {
// If we have a dummy view and we have our title disabled, remove it
// from its parent
final ViewParent parent = mDummyView.getParent();
if (parent instanceof ViewGroup) {
((ViewGroup) parent).removeView(mDummyView);
}
}
if (mCollapsingTitleEnabled && mToolbar != null) {
if (mDummyView == null) {
mDummyView = new View(getContext());
}
if (mDummyView.getParent() == null) {
mToolbar.addView(mDummyView, LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
}
}
}
tmd,就是在在ToolBar加上了一個子View
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
super.onLayout(changed, left, top, right, bottom);
// Update the collapsed bounds by getting it's transformed bounds. This
// needs to be done
// before the children are offset below
if (mCollapsingTitleEnabled && mDummyView != null) {
// We only draw the title if the dummy view is being displayed
// (Toolbar removes
// views if there is no space)
mDrawCollapsingTitle = mDummyView.isShown();
if (mDrawCollapsingTitle) {
ViewGroupUtils.getDescendantRect(this, mDummyView, mTmpRect);
//設定收縮後的Rect
mCollapsingTextHelper.setCollapsedBounds(mTmpRect.left, bottom
- mTmpRect.height(), mTmpRect.right, bottom);
// 設定展開後的Rect
mCollapsingTextHelper.setExpandedBounds(mExpandedMarginLeft,
mTmpRect.bottom + mExpandedMarginTop, right - left
- mExpandedMarginRight, bottom - top
- mExpandedMarginBottom);
// Now recalculate using the new bounds
mCollapsingTextHelper.recalculate();
}
}
//此處省略對狀態列欄距離的處理……
// Finally, set our minimum height to enable proper AppBarLayout
// collapsing
//如果沒有設定tittle屬性預設設定mToolbar的tittle
if (mToolbar != null) {
if (mCollapsingTitleEnabled
&& TextUtils.isEmpty(mCollapsingTextHelper.getText())) {
// If we do not currently have a title, try and grab it from the
// Toolbar
mCollapsingTextHelper.setText(mToolbar.getTitle());
}
//設定最小高度為toolbar的高度,也就是說自己設定的會失效
setMinimumHeight(mToolbar.getHeight());
}
}
這個方法主要做了對mDummyView 偏移ToolBar位置的計算,用來標記收縮完成後標題應該畫在哪個範圍內,將收縮後地 標題位置範圍記錄和展開後的標題位置範圍記錄交給CollapsingTextHelper,也就是說最終的標題會畫在在這個範圍內的,接下來判斷我們有沒有加入tittle這個屬性,如果沒有的話,將標題用ToolBar的標題代替,所以在應用的時候,可以省略掉這個屬性,接下來設定了CollapsingToolbarLayout的最小高度,也就是說不管屬性中設沒設定minHeight,最終最小高度都會被ToolBar的高度代替,所以AppBarLayout滾動到頂部的時候會留出ToolBar的高度。
接下來看一下畫圖的方法
public void draw(Canvas canvas) {
super.draw(canvas);
// If we don't have a toolbar, the scrim will be not be drawn in
// drawChild() below.
// Instead, we draw it here, before our collapsing text.
ensureToolbar();
// 到達多大位置的時候畫背景
if (mToolbar == null && mContentScrim != null && mScrimAlpha > 0) {
mContentScrim.mutate().setAlpha(mScrimAlpha);
mContentScrim.draw(canvas);
}
// Let the collapsing text helper draw it's text
// 畫摺疊後的標題
if (mCollapsingTitleEnabled && mDrawCollapsingTitle) {
mCollapsingTextHelper.draw(canvas);
}
// 最後滿足條件的話畫標題欄的背景色
if (mStatusBarScrim != null && mScrimAlpha > 0) {
final int topInset = mLastInsets != null ? mLastInsets
.getSystemWindowInsetTop() : 0;
if (topInset > 0) {
mStatusBarScrim.setBounds(0, -mCurrentOffset, getWidth(),
topInset - mCurrentOffset);
mStatusBarScrim.mutate().setAlpha(mScrimAlpha);
mStatusBarScrim.draw(canvas);
}
}
// Now draw the status bar scrim
}
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
// This is a little weird. Our scrim needs to be behind the Toolbar (if
// it is present),
// but in front of any other children which are behind it. To do this we
// intercept the
// drawChild() call, and draw our scrim first when drawing the toolbar
ensureToolbar();
if (child == mToolbar && mContentScrim != null && mScrimAlpha > 0) {
mContentScrim.mutate().setAlpha(mScrimAlpha);
mContentScrim.draw(canvas);
}
// Carry on drawing the child...
return super.drawChild(canvas, child, drawingTime);
}
CollapsingToolbarLayout類重寫了兩個畫圖的方法,draw方法和drawChild方法,表達的意思就是判斷mContentScrim是不是滿足條件畫,首先必須得設定contentScrim屬性
最後標題欄變得那個顏色就是通過它畫的
也就是圖中所示的顏色就相當於為ToolBar畫上了背景圖,當圖片剛要消失的時候會出現
是在draw方法裡的這個判斷畫的
// 到達多大位置的時候畫背景
if (mToolbar == null && mContentScrim != null && mScrimAlpha > 0) {
mContentScrim.mutate().setAlpha(mScrimAlpha);
mContentScrim.draw(canvas);
}
最後通過CollapsingTextHelper.draw(canvas)方法將標題畫到螢幕上。通過上一篇的講述CollapsingToolbarLayout的效果的變化都是根據AppBarLayout移動後的回撥方法通知而進行的子View響應狀態的變化,也就是說CollapsingToolbarLayout向AppBarLayout註冊了OnOffsetChangedListener 監聽方法,每次AppBarLayout的每次移動都會告訴
CollapsingToolbarLayout我現在的top或bottom在哪,由於是朝上移動的,那麼實際上是移動了AppBarLayout的距離父View的top位置,當然這個top位置一直是負值,也就是下面方法的verticalOffset變數
private class OffsetUpdateListener implements
AppBarLayout.OnOffsetChangedListener {
@Override
public void onOffsetChanged(AppBarLayout layout, int verticalOffset) {
// AppBarLayout的top或bottom的偏移量
mCurrentOffset = verticalOffset;
final int insetTop = mLastInsets != null ? mLastInsets
.getSystemWindowInsetTop() : 0;
final int scrollRange = layout.getTotalScrollRange();
for (int i = 0, z = getChildCount(); i < z; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final ViewOffsetHelper offsetHelper = getViewOffsetHelper(child);
switch (lp.mCollapseMode) {
// 如果是懸浮在頂部的時候
case LayoutParams.COLLAPSE_MODE_PIN:
// 父View朝上移動的時候,當前view的大小減去偏移量仍然大於懸浮的view的時候,那麼
if (getHeight() - insetTop + verticalOffset >= child
.getHeight()) {
// 父View移動多少,子View反向移動多少,看到的效果就是子View的位置視角看起來沒有改變
offsetHelper.setTopAndBottomOffset(-verticalOffset);
}
break;
// 如果是視差滾動
case LayoutParams.COLLAPSE_MODE_PARALLAX:
// mParallaxMult=1的朝下移動的效果越明顯,越小的話越接近fuView的移動位置,所以產生視差效果
// 反向移動子View
offsetHelper.setTopAndBottomOffset(Math
.round(-verticalOffset * lp.mParallaxMult));
break;
}
}
// Show or hide the scrims if needed
if (mContentScrim != null || mStatusBarScrim != null) {
// 讓contentScrim顯示
setScrimsShown(getHeight() + verticalOffset < getScrimTriggerOffset()
+ insetTop);
}
if (mStatusBarScrim != null && insetTop > 0) {
ViewCompat
.postInvalidateOnAnimation(CollapsingToolbarLayout.this);
}
// Update the collapsing text's fraction
final int expandRange = getHeight()
- ViewCompat.getMinimumHeight(CollapsingToolbarLayout.this)
- insetTop;
// 不斷改變偏移量
mCollapsingTextHelper.setExpansionFraction(Math.abs(verticalOffset)
/ (float) expandRange);
if (Math.abs(verticalOffset) == scrollRange) {
// If we have some pinned children, and we're offset to only
// show those views,
// we want to be elevate
ViewCompat.setElevation(layout, layout.getTargetElevation());
} else {
// Otherwise, we're inline with the content
ViewCompat.setElevation(layout, 0f);
}
}
}
這個方法遍歷子View判斷collapseMode屬性的值,佈局當中我們設定的ToolBar是pin方法,那麼每次fuView朝上走的話,子View就朝下走,那麼眼睛看起來,這個子View就像沒有動一樣,但是前提條件得滿足,有足夠的剩餘空間容納這個子View,如果屬性設定為parallax,那麼子View和CollapsingToolbarLayout走的相反的位置的時候需要乘以視差值,也就是設定的這個值 app:layout_collapseParallaxMultiplier="0.7",如果父View朝上走了100,那麼子View就朝下走70,看起來,子View只朝上走了30,那麼人眼看起來就形成了視差的效果。那麼接下來根據偏移量計算上面說的過渡的顏色是否可以畫,也就是
setScrimsShown(getHeight() + verticalOffset < getScrimTriggerOffset()
+ insetTop);
這個方法,當剩餘的可見高度為標題欄的二倍的時候,圖片將會被上面的背景色覆蓋,從而出現我們看到的效果,
private void setScrimAlpha(int alpha) {
if (alpha != mScrimAlpha) {
final Drawable contentScrim = mContentScrim;
if (contentScrim != null && mToolbar != null) {
ViewCompat.postInvalidateOnAnimation(mToolbar);
}
mScrimAlpha = alpha;
ViewCompat.postInvalidateOnAnimation(CollapsingToolbarLayout.this);
}
}
然後這個方法呼叫ViewCompat.postInvalidateOnAnimation方法進行重新繪製,最終呼叫屬性幫助類 setExpansionFraction方法將當前的進行的百分比設定進去mCollapsingTextHelper.setExpansionFraction(Math.abs(verticalOffset)
/ (float) expandRange);
也就是展開和收縮兩種狀態之下的百分比,完全展開的話百分比為0,完全收縮後百分比為1,也就是變數因子。進入setExpansionFraction這個方法
void setExpansionFraction(float fraction) {
fraction = MathUtils.constrain(fraction, 0f, 1f);
if (fraction != mExpandedFraction) {
mExpandedFraction = fraction;
calculateCurrentOffsets();
}
}
將變數因子設定給mExpandedFraction,然後呼叫calculateCurrentOffsets方法
private void calculateOffsets(final float fraction) {
interpolateBounds(fraction);
// 得到當前該畫的x位置
mCurrentDrawX = lerp(mExpandedDrawX, mCollapsedDrawX, fraction,
mPositionInterpolator);
// 當前該畫的y位置
mCurrentDrawY = lerp(mExpandedDrawY, mCollapsedDrawY, fraction,
mPositionInterpolator);
// 得到字型size的大小
setInterpolatedTextSize(lerp(mExpandedTextSize, mCollapsedTextSize,
fraction, mTextSizeInterpolator));
if (mCollapsedTextColor != mExpandedTextColor) {
// If the collapsed and expanded text colors are different, blend
// them based on the
// fraction
mTextPaint.setColor(blendColors(mExpandedTextColor,
mCollapsedTextColor, fraction));
} else {
mTextPaint.setColor(mCollapsedTextColor);
}
//如果設定了陰影屬性將畫上陰影,此處我們並沒有畫隱影
mTextPaint.setShadowLayer(
lerp(mExpandedShadowRadius, mCollapsedShadowRadius, fraction,
null),
lerp(mExpandedShadowDx, mCollapsedShadowDx, fraction, null),
lerp(mExpandedShadowDy, mCollapsedShadowDy, fraction, null),
blendColors(mExpandedShadowColor, mCollapsedShadowColor,
fraction));
//最後執行重畫邏輯
ViewCompat.postInvalidateOnAnimation(mView);
}
這個方法的邏輯也很簡單,意思就是根據插值器和變數因子,以及擴充套件前的tittle的x位置和收縮後的tittle的X距離就算出當前的tittle應該在什麼位置,y座標同理,然後用同樣的方法獲得此時字型大小應該是多少,假如收縮前和收縮後的字型大小不一樣,然後利用插值器計算當前tittle應該用什麼顏色,最後呼叫postInvalidateOnAnimation方法進行重畫。
位置計算,字型大小插值器如下
private static float lerp(float startValue, float endValue, float fraction,
Interpolator interpolator) {
if (interpolator != null) {
fraction = interpolator.getInterpolation(fraction);
}
return AnimationUtils.lerp(startValue, endValue, fraction);
}
如果沒有設定插值器的話,就用預設工具計算,如下程式碼
static float lerp(float startValue, float endValue, float fraction) {
return startValue + (fraction * (endValue - startValue));
}
就是一個線性變化嗎,也就是預設用的線性插值器,再看一下顏色插值器
private static int blendColors(int color1, int color2, float ratio) {
final float inverseRatio = 1f - ratio;
float a = (Color.alpha(color1) * inverseRatio)
+ (Color.alpha(color2) * ratio);
float r = (Color.red(color1) * inverseRatio)
+ (Color.red(color2) * ratio);
float g = (Color.green(color1) * inverseRatio)
+ (Color.green(color2) * ratio);
float b = (Color.blue(color1) * inverseRatio)
+ (Color.blue(color2) * ratio);
return Color.argb((int) a, (int) r, (int) g, (int) b);
}
這個演算法也很簡單,也就說展開和收縮的兩種狀態,離誰越近顏色值越接近與誰,這個效果,童鞋們仔細注意動態圖中顏色的變化,產生這個效果是靠了
app1:collapsedTitleTextAppearance屬性或者app1:expandedTitleTextAppearance,只要設定其一就可以產生效果,這一些列動作完成後就剩下畫標題了
public void draw(Canvas canvas) {
final int saveCount = canvas.save();
if (mTextToDraw != null && mDrawTitle) {
float x = mCurrentDrawX;
float y = mCurrentDrawY;
final boolean drawTexture = mUseTexture
&& mExpandedTitleTexture != null;
final float ascent;
final float descent;
// Update the TextPaint to the current text size
mTextPaint.setTextSize(mCurrentTextSize);
if (drawTexture) {
ascent = mTextureAscent * mScale;
descent = mTextureDescent * mScale;
} else {
ascent = mTextPaint.ascent() * mScale;
descent = mTextPaint.descent() * mScale;
}
if (DEBUG_DRAW) {
// Just a debug tool, which drawn a Magneta rect in the text
// bounds
canvas.drawRect(mCurrentBounds.left, y + ascent,
mCurrentBounds.right, y + descent, DEBUG_DRAW_PAINT);
}
if (drawTexture) {
y += ascent;
}
if (mScale != 1f) {
canvas.scale(mScale, mScale, x, y);
}
if (drawTexture) {
// If we should use a texture, draw it instead of text
canvas.drawBitmap(mExpandedTitleTexture, x, y, mTexturePaint);
} else {
// 畫標題
canvas.drawText(mTextToDraw, 0, mTextToDraw.length(), x, y,
mTextPaint);
}
}
canvas.restoreToCount(saveCount);
}
根據計算的位置和基線的計算將文字畫在該有的位置,畫標題的時候,這其中還包括畫布的縮放,當然縮放值為當前字型的大小和展開的字型大小或收縮的字型大小做對比
如下方法進行計算
private void calculateUsingTextSize(final float textSize) {
if (mText == null)
return;
final float availableWidth;
final float newTextSize;
boolean updateDrawText = false;
/**
* 假如當前textSize接近mCollapsedTextSize縮放值mScale=1
*/
if (isClose(textSize, mCollapsedTextSize)) {
availableWidth = mCollapsedBounds.width();
newTextSize = mCollapsedTextSize;
mScale = 1f;
if (mCurrentTypeface != mCollapsedTypeface) {
mCurrentTypeface = mCollapsedTypeface;
updateDrawText = true;
}
} else {
availableWidth = mExpandedBounds.width();
newTextSize = mExpandedTextSize;
if (mCurrentTypeface != mExpandedTypeface) {
mCurrentTypeface = mExpandedTypeface;
updateDrawText = true;
}
// 當前textSize接近mExpandedTextSize的時候縮放值也等於1,否則縮放值等於textSize /
// mExpandedTextSize
if (isClose(textSize, mExpandedTextSize)) {
// If we're close to the expanded text size, snap to it and use
// a scale of 1
mScale = 1f;
} else {
// Else, we'll scale down from the expanded text size
mScale = textSize / mExpandedTextSize;
}
}
if (availableWidth > 0) {
updateDrawText = (mCurrentTextSize != newTextSize)
|| mBoundsChanged || updateDrawText;
mCurrentTextSize = newTextSize;
mBoundsChanged = false;
}
if (mTextToDraw == null || updateDrawText) {
mTextPaint.setTextSize(mCurrentTextSize);
mTextPaint.setTypeface(mCurrentTypeface);
// If we don't currently have text to draw, or the text size has
// changed, ellipsize...
final CharSequence title = TextUtils.ellipsize(mText, mTextPaint,
availableWidth, TextUtils.TruncateAt.END);
if (!TextUtils.equals(title, mTextToDraw)) {
mTextToDraw = title;
mIsRtl = calculateIsRtl(mTextToDraw);
}
}
}
這個方法的大體意思就是說如果當前textSize接近收縮的textSize的話或接近展開textSize的話不進行縮放,如果都不滿足進行textSize / mExpandedTextSize縮放,在構造方法中
CollapsingToolbarLayout通過這兩個方法分別設定了mExpandedTextSize和mCollapsedTextSize,這裡採用的是用預設的樣式賦的值
mCollapsingTextHelper
.setExpandedTextAppearance(R.style.TextAppearance_Design_CollapsingToolbar_Expanded);
mCollapsingTextHelper
.setCollapsedTextAppearance(R.style.TextAppearance_AppCompat_Widget_ActionBar_Title);
仔細觀察上面的動態圖的話發現tittle在上移的時候是不斷縮放的,直到接近收縮的字型停止縮放,以上原始碼正好證明了這個情況,也就是說越朝上滑動,當前的字型越小,在沒接近收縮時,比值會越來越小,那麼mScale 會越來越小,那麼畫布就會有縮放效果。
到此CollapsingToolbarLayout效果實現的機制介紹完畢。