先來看最終效果圖
整個(gè)效果圖包括了
Share Element Transition
CollapsingToolbarLayout
AppbarLayout
自定義的動(dòng)畫效果
先來分析自定義動(dòng)畫煤蚌,用兩個(gè)TextView來分別顯示Title(動(dòng)物世界)和SubTitle(春天到了...)。SubTitle的動(dòng)畫需要自己手動(dòng)來畫髓窜,可以直接沿著TextView的周長(zhǎng)來畫線巢株。
我們需要重寫View的onDraw方法赡突,然后在方法體里來畫我們的圖像,然后通過調(diào)用invalidate來刷新View讓畫面動(dòng)起來。
線框的繪畫其實(shí)就是由5條線組合在一起赘被,這里用我用bottom,left,right,topLeft,topRight來代表這5條線悴侵,我們簡(jiǎn)單先給topLeft定義一個(gè)確定的長(zhǎng)度length 等于TextView的寬度1/4瞧剖。
為了讓這些白線有一定的粗細(xì),我們用canvas.drawRect來畫線而不是用drawLine可免,設(shè)定線的寬度stokeWidth抓于。
所以線框的完全體就是這樣
length = getWidth()/4;
stokeWidth = 2;
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//bottom
canvas.drawRect(0, getHeight() - stokeWidth, getWidth(), getHeight(), paint);
//left
canvas.drawRect(0, 0, stokeWidth, getHeight(), paint);
//right
canvas.drawRect(getWidth() - stokeWidth, 0, getWidth(), getHeight(), paint);
//topLeft
canvas.drawRect(0, 0, length, stokeWidth, paint);
//topRight
canvas.drawRect(getWidth() - length, 0, getWidth(), stokeWidth, paint);
}
然后實(shí)現(xiàn)動(dòng)畫:
計(jì)算線框的總長(zhǎng)度(或者說繪制完成時(shí)的長(zhǎng)度) maxRound
動(dòng)畫過程中線框長(zhǎng)度 currentRound
設(shè)置繪制比例值 percent
通過比較currentRound的值畫出對(duì)應(yīng)幀的線框圖像
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float currentRound = maxRound * percent;
if (currentRound <= stokeWidth) {
return;
}
//currentRound長(zhǎng)度小于bottom時(shí),只畫出底部線條
if (currentRound <= getWidth()) {
canvas.drawRect((getWidth() - currentRound) / 2, getHeight() - stokeWidth, (getWidth() + currentRound) / 2, getHeight(), paint);
//blr=bottom+left+right浇借,當(dāng)currentRound小于底邊和左右兩邊加起來的時(shí)候捉撮,畫出對(duì)應(yīng)的線條
} else if (currentRound <= blr) {
canvas.drawRect(0, getHeight() - stokeWidth, getWidth(), getHeight(), paint);
float y = getHeight() - (currentRound - getWidth()) / 2;
canvas.drawRect(0, y, stokeWidth, getHeight(), paint);
canvas.drawRect(getWidth() - stokeWidth, y, getWidth(), getHeight(), paint);
} else {
//bottom
canvas.drawRect(0, getHeight() - stokeWidth, getWidth(), getHeight(), paint);
//left
canvas.drawRect(0, 0, stokeWidth, getHeight(), paint);
//right
canvas.drawRect(getWidth() - stokeWidth, 0, getWidth(), getHeight(), paint);
//blr為bottom,left妇垢,right加起來的長(zhǎng)度
float r = (currentRound - blr) / 2;
//topLeft
canvas.drawRect(0, 0, r, stokeWidth, paint);
//topRight
canvas.drawRect(getWidth() - r, 0, getWidth(), stokeWidth, paint);
}
}
//通過屬性動(dòng)畫來畫出幀動(dòng)畫
public void animate(float f) {
ValueAnimator valueAnimator = new ValueAnimator();
valueAnimator.setFloatValues(percent, f);
valueAnimator.setDuration(500);
valueAnimator.addUpdateListener(this);
valueAnimator.start();
}
//監(jiān)聽屬性動(dòng)畫回調(diào)改變繪畫的線框百分比percent
@Override
public void onAnimationUpdate(ValueAnimator animation) {
percent= (float) animation.getAnimatedValue();
setTextColor(Color.argb((int) (percent * 255), 255, 255, 255));
invalidate();
}
順手給subTitle文字加個(gè)漸變巾遭,效果大概就是這樣
接下來是讓動(dòng)畫和AppbarLayout的滑動(dòng)事件關(guān)聯(lián)起來
xml布局
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="150dp"
app:elevation="0dp">
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/collapsingToolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:expandedTitleMarginEnd="64dp"
app:expandedTitleMarginStart="48dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/bgImageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="0.5"
tools:background="#cccccc" />
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#55000000" />
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:contentInsetLeft="0dp"
app:contentInsetStart="0dp"
app:layout_collapseMode="pin" />
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="48dp"
android:maxLines="1"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:textColor="@color/white"
android:textSize="18dp"
android:textStyle="bold"
app:layout_collapseMode="parallax"
tools:text="走進(jìn)科學(xué)" />
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<etong.lineanimation.SubTitleView
android:id="@+id/subTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="58dp"
android:gravity="center"
android:maxLines="2"
android:paddingBottom="12dp"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:paddingTop="20dp"
android:textColor="@color/white"
android:textSize="14dp"
tools:text="春天到了肉康,又到了交配的季節(jié)。" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="show"
android:layout_gravity="bottom"/>
</android.support.design.widget.CoordinatorLayout>
設(shè)置AppBarLayout監(jiān)聽
appBar.addOnOffsetChangedListener(this);
根據(jù)滑動(dòng)距離來和最大滑動(dòng)距離來計(jì)算線框繪制的百分比
@Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
if (!TextUtils.isEmpty(subTitle)) {
float value = appBar.getHeight() - toolbar.getHeight() * 2;
float p = (value + verticalOffset) / value;
if (p < 0) {
p = 0;
}
subTitleView.setPercent(p);
}
}
這里線框的位置是固定的灼舍,所以在appBarLayout滑動(dòng)的時(shí)候?yàn)榱吮苊饩€框出界吼和,需要線框提前一點(diǎn)消失,所以本來最大滑動(dòng)距離為
appBar.getHeight()-toolbar.getHeight()
改為
appBar.getHeight() - toolbar.getHeight() * 2
效果
接下來需要處理一下SubTtileView本身高寬和主標(biāo)題Title所在TextView的寬度問題骑素。
- Title最大行數(shù)為一行炫乓,SubTitle最大行數(shù)為兩行
- SubTtileView本身的最大寬度也必須小于屏幕寬度,而且需要有最小的左右邊距 padding
- SubTitleView的topLeft和topRight必須留有一點(diǎn)的長(zhǎng)度献丑,才能形成一個(gè)完整的線框 minLength
- Title文字過多的時(shí)候會(huì)占滿屏幕末捣,所以必須限制Title的最大寬度
maxTitleWidth = screenWidth - padding * 2 - minLength * 2 - SubTitleView的寬度必須大于Title的寬度,所以我們要根據(jù)Title的寬度手動(dòng)計(jì)算SubTitleView的寬度
minSubTitleViewWidth = titleWidth - minLenght * 2 - ............
subtitleMargin = getResources().getDimensionPixelSize(R.dimen.subtitle_margin);
minLength = getResources().getDimensionPixelSize(R.dimen.min_length);
maxTitleWidth = ScreenUtils.screenWidth - subtitleMargin * 2 - minLength * 2;
public void calculateSubTitle() {
int titleWidth = titleTextView.getWidth();
if (titleWidth > maxTitleWidth) {
titleTextView.getLayoutParams().width = maxTitleWidth;
subtitleView.getLayoutParams().width = maxTitleWidth + minLength * 2;
subtitleView.setLength(minLength * 2);
subtitleView.requestLayout();
titleTextView.requestLayout();
} else if (subtitleView.getWidth() > ScreenUtils.screenWidth - subtitleMargin * 2) {
subtitleView.getLayoutParams().width = ScreenUtils.screenWidth - subtitleMargin * 2;
subtitleView.setLength(subtitleView.getLayoutParams().width - titleWidth);
subtitleView.requestLayout();
} else if (subtitleView.getWidth() < titleWidth + minLength * 2) {
subtitleView.getLayoutParams().width = titleWidth + minLength * 2;
subtitleView.setLength(subtitleView.getLayoutParams().width - titleWidth);
subtitleView.requestLayout();
} else {
subtitleView.setLength(subtitleView.getLayoutParams().width - titleWidth);
subtitleView.requestLayout();
}
}
到此所有的自定義部分就寫完了创橄,剩下的過場(chǎng)動(dòng)畫由于是Android支持庫(kù)的api就不多解釋箩做。
最后,源碼鏈接 https://github.com/RoyWallace/LineAnimation
如果發(fā)現(xiàn)有錯(cuò)誤的地方或者不明白之處歡迎加群討論 qq群:295456349