上一篇中已经讲解了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.CollapsingToolbarLayoutandroid: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" ><!-- 视差值越小滚动越明显 --><ImageViewandroid: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.Toolbarandroid: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 appearancesmCollapsingTextHelper.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() {@Overridepublic 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 itselected = (Toolbar) child;break;}if (fallback == null) {// We'll record the first Toolbar as our fallbackfallback = (Toolbar) child;}} else {// We don't have a id to check for so just use the first we// come acrossselected = (Toolbar) child;break;}}}if (selected == null) {// If we didn't find a primary Toolbar, use the fallbackselected = 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 parentfinal 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 belowif (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);
//设置收缩后的RectmCollapsingTextHelper.setCollapsedBounds(mTmpRect.left, bottom- mTmpRect.height(), mTmpRect.right, bottom);// 设置展开后的RectmCollapsingTextHelper.setExpandedBounds(mExpandedMarginLeft,mTmpRect.bottom + mExpandedMarginTop, right - left- mExpandedMarginRight, bottom - top- mExpandedMarginBottom);// Now recalculate using the new boundsmCollapsingTextHelper.recalculate();}}
//此处省略对状态栏栏距离的处理……// Finally, set our minimum height to enable proper AppBarLayout// collapsing//如果没有设置tittle属性默认设置mToolbar的tittleif (mToolbar != null) {if (mCollapsingTitleEnabled&& TextUtils.isEmpty(mCollapsingTextHelper.getText())) {// If we do not currently have a title, try and grab it from the// ToolbarmCollapsingTextHelper.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}
@Overrideprotected 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 toolbarensureToolbar();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 implementsAppBarLayout.OnOffsetChangedListener {@Overridepublic 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的移动位置,所以产生视差效果// 反向移动子ViewoffsetHelper.setTopAndBottomOffset(Math.round(-verticalOffset * lp.mParallaxMult));break;}}// Show or hide the scrims if neededif (mContentScrim != null || mStatusBarScrim != null) {// 让contentScrim显示setScrimsShown(getHeight() + verticalOffset < getScrimTriggerOffset()+ insetTop);}if (mStatusBarScrim != null && insetTop > 0) {ViewCompat.postInvalidateOnAnimation(CollapsingToolbarLayout.this);}// Update the collapsing text's fractionfinal 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 elevateViewCompat.setElevation(layout, layout.getTargetElevation());} else {// Otherwise, we're inline with the contentViewCompat.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// fractionmTextPaint.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 sizemTextPaint.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// boundscanvas.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 textcanvas.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 /// mExpandedTextSizeif (isClose(textSize, mExpandedTextSize)) {// If we're close to the expanded text size, snap to it and use// a scale of 1mScale = 1f;} else {// Else, we'll scale down from the expanded text sizemScale = 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效果实现的机制介绍完毕。

android5.0协调布局CoordinatorLayout(第二篇CollapsingToolbarLayout效果实现原理讲解)原理相关推荐

  1. Android开发笔记(一百三十四)协调布局CoordinatorLayout

    协调布局CoordinatorLayout Android自5.0之后对UI做了较大的提升,一个重大的改进是推出了MaterialDesign库,而该库的基础即为协调布局CoordinatorLayo ...

  2. android内容协调,理清Android协调布局CoordinatorLayout的摆放位置及特殊属性。

    先看代码 android:layout_width="match_parent" android:layout_height="match_parent"> ...

  3. android 实现磨砂效果_Android(Android5.0)下毛玻璃(磨砂)效果如何实现?

    技术调研,可以给一些优缺点的对比.,目前主流实现毛玻璃效果(高斯模糊)分大致三种方法: 一 利用RenderScript接口 利用现有Android结构,通过RenderScript调用底层接口实现高 ...

  4. Android5.0和6.0之后新增的控件说明

    Android自5.0后增加了不少新控件,帮助开发者实现了更酷更炫的UI效果.可是对于初学者来说,这些新控件的用法不像老控件那么简单,网上相关的使用介绍也不如老控件那样丰富,种种情况无疑加大了我们学习 ...

  5. 微信小程序第二篇实战

    title: 微信小程序第二篇实战 date: 2018-03-08 02:33:00 tags: WeChat category: WeChat description: 微信小程序第二篇实战 效果 ...

  6. 【.NET Core 跨平台 GUI 开发】第二篇:Gtk# 布局入门,初识HBox 和 VBox

    这是 Gtk# 系列博文的第二篇.在上一篇博文<编写你的第一个 Gtk# 应用>中,我们提到"一个 Gtk.Window 只能直接包含一个部件".这意味着,在不做其他额 ...

  7. bootstrapV4.6.0 图片宫格布局(案例篇)

    文章目录 官方文档 (代码参考): 效果图如下: 代码示下: 使用注意事项: 附件:CSS样式表 官方文档 (代码参考): 文档入口1:img图片布局 -> https://v4.bootcss ...

  8. Android5.0 Settings各个子模块跳转和布局实现

    前言 今天要很任性的研究一下Android5.0中Settings子模块的跳转实现. Settings应用的Launcher类 我们首先看一下Settings应用的Launcher类.查看packag ...

  9. android5.0后新特性修改标题头,Android5.0中Material Design的新特性

    Material Design简介 Material Design是谷歌新的设计语言,谷歌希望寄由此来统一各种平台上的用户体验,Material Design的特点是干净的排版和简单的布局,以此来突出 ...

最新文章

  1. 学习《Linux设备模型浅析之设备篇》笔记(三)
  2. 002.Heartbeat部署及httpd高可用
  3. 关于eigrp-FSM有限状态机的资料
  4. ROS系统 实现客户端Client和服务端Server
  5. DEDECMS整合DISCUZ的方法
  6. 向前的快捷键_枣院生活快捷键使用手册,你值得拥有
  7. android程序日历layout,Android使用GridLayout绘制自定义日历控件
  8. beego 快速入门
  9. 通过迭代(非递归)及递归将单链表逆序
  10. 感知算法论文(六):LEDNet(2019)
  11. 下载anaconda时出现“Please make sure you are connected to the internet”警告
  12. vs2012 entity framework mysql_MVC4,MVC3,VS2012+ entity framework Migration from Sqlserver
  13. SSM整理笔记1——SSM网站初步功能设计
  14. Android自定义ImageView(二)——实现双击放大与缩小图片
  15. linux服务器之间做ssh,Linux 服务器之间怎么样 SSH 不需密码
  16. 【符号修改】之修改静态库内部的符号
  17. Boost库编译安装
  18. Web服务器程序解释请求消息并作出响应
  19. LINUX信息命令查看大全
  20. 排序算法之 Slow Sort

热门文章

  1. Android4.3 Google Pinyin输入法UI定制
  2. div+css+jquery仿写HTML京东首页的练习及一些关于oo css的总结
  3. RedMonk最新编程语言排行榜;Spring 框架现 RCE 漏洞……|叨资讯
  4. css3边框圆角、背景
  5. 阿里云ACP云计算错题集71-100
  6. 小米2017校招面试经历
  7. 程序启动,遇到Process finished with exit code 1 解决方法
  8. beeline安装_Beeline使用
  9. 悲观锁和乐观锁的理解以及实现方式-学习笔记
  10. 动物识别Python