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


现在先以一个简单的例子为入口点,先看一下效果图


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


标题部分如下布局代码

 
            
            
            
        

这里设置了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,如果布局里面没有设置的话,
		// 默认 32dp
		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属性

最后标题栏变得那个颜色就是通过它画的
android5.0协调布局CoordinatorLayout(第二篇CollapsingToolbarLayout效果实现原理讲解)原理_第2张图片

也就是图中所示的颜色就相当于为ToolBar画上了背景图,当图片刚要消失的时候会出现


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

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

你可能感兴趣的:(android高级组件原理篇)