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

上一篇中已经讲解了CoordinatorLayout、AppBarLayout、CollapsingToolbarLayout之间的关系,这一篇探索一下CollapsingToolbarLayout内部是怎么实现的,不熟悉CoordinatorLayout、AppBarLayout、CollapsingToolbarLayout之间的关系的请先看上一篇文章android5.0协调布局CoordinatorLayout(第一篇CoordinatorLayout、AppBarLayout、CollapsingToolbarLayout之间的关系详解)原理java

首先看一下CollapsingToolbarLayout的一些属性说明,首先下面这些属性是要写在CollapsingToolbarLayout中的android


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


如今先以一个简单的例子为入口点,先看一下效果图算法




标题部分以下布局代码canvas

<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的高度。

接下来看一下画图的方法app

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画上了背景图,当图片刚要消失的时候会出现
ide



是在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);
post

这个方法,当剩余的可见高度为标题栏的二倍的时候,图片将会被上面的背景色覆盖,从而出现咱们看到的效果,


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效果实现的机制介绍完毕。