要實現什麼效果?
我們就是要實現如圖所示的動畫效果,在開始之前我們先了解一下實現這個動畫的相關知識。
屬性動畫相關知識
動畫執行的邏輯
邏輯大概流程如下:
- 爲 ValueAnimator 設置動畫的時長,以及對應屬性的始 & 末值
- 設置屬性在 始 & 末值 間的變化邏輯
- TimeInterpolator實現類:插值器-描述動畫的變化速率
- TypeEvaluator實現類:估值器-描述 屬性值 變化的具體數值
- 根據2中的邏輯更新當前值
- 獲取3中更新的值 ,修改目標屬性值
- 刷新視圖
- 重複4-5,直到 屬性值 == 末值
動畫工作的關鍵類
Java類 | 說明 |
---|---|
ValueAnimator | 動畫執行類,核心。負責動畫的整體協調 |
ObjectAnimator | 動畫執行類 |
TimeInterpolator | 時間插值(插值器接口),控制動畫變化率 |
TypeEvaluator | 類型估值(估值器接口),設置屬性值計算方式,根據屬性的始末值和插值計算出當前時間的屬性值 |
AnimatorSet | 動畫集 |
AnimatorInflater | 加載屬性動畫的XML文件 |
思路分析與實現代碼
首先我們實現這樣一個TextView用於展示,實現代碼如下:
<TextView
android:id="@+id/tv_left"
android:layout_width="wrap_content"
android:layout_height="60dp"
android:layout_gravity="end"
android:layout_marginTop="30dp"
android:background="@drawable/circleside"
android:gravity="center_vertical"
android:maxLines="1"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:text="青檸天氣提醒您,天冷注意添加衣"
android:textSize="17sp" />
其中的@drawable/circleside 代碼如下:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
<!-- 填充顏色 -->
<solid android:color="#40FF552E"></solid>
<!-- 矩形的圓角半徑 -->
<corners android:bottomLeftRadius="999dp" android:topLeftRadius="999dp"></corners>
</shape>
其實思路很簡單,就是先將View的寬度設置爲0,隨着時間的增多逐漸把View的寬度設置爲它應該有的寬度即可。
private ValueAnimator createDropAnimator(final View v, int start, int end) {
ValueAnimator animator = ValueAnimator.ofInt(start, end);
animator.addUpdateListener(arg0 -> {
int value = (int) arg0.getAnimatedValue();
ViewGroup.LayoutParams layoutParams = v.getLayoutParams();
layoutParams.width = value;
v.setLayoutParams(layoutParams);
});
return animator;
}
首先我們構建一個ValueAnimator,添加了一個UpdateLisenter的方法,這個方法是在動畫開始後返回值來讓我們進行處理的,這個方法我們傳入了三個參數分別是View,start,end,分別代表了我們創建的那個TextView,動畫開始的寬度,最終的寬度。我們在updateListener裏對view的寬度進行的修改。
private void show(View view, int tvWidth, long delay) {
view.setVisibility(View.VISIBLE);
ValueAnimator valueAnimator = createDropAnimator(view, 0, tvWidth);
valueAnimator.setDuration(500);
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
}
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
Timer timer = new Timer();
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
runOnUiThread(() -> disMiss(view, tvWidth));
}
};
timer.schedule(timerTask, delay);
}
});
valueAnimator.start();
}
這個是觸發動畫的方法,第三行調用了上述的ValueAnimator的構建方法,從0到width的一個變化過程。在打開動畫結束後我們又延時delay ms調用了dismiss方法來把view收起來。
private void disMiss(View view, int tvWidth) {
ValueAnimator valueAnimator = createDropAnimator(view, tvWidth, 0);
valueAnimator.setDuration(500);
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
}
@Override
public void onAnimationEnd(Animator animation) {
view.setVisibility(View.GONE);
}
});
valueAnimator.start();
}
dismiss方法的思路和show的差不多,只不過是從width到0的一個過程。
進過上述的3個方法我們的動畫基本上已經完成了
要踩的坑
1. show()方法何時調用(動畫)何時開始?
如果你在onCreate裏調用show方法,動畫播放的過程是這樣的。等你看見View的時候動畫已經播放到一半了,爲什麼會這樣?我們要了解Activity的生命週期。
onCreate和onStart調用之後我們還不能看見Activity的視圖,所以調用之後動畫已經繪製了一半了我們還沒有看見。那我們放在onResume裏再調用不就可以了嗎?這樣做可以,但是還是有問題,因爲我們進入Activity時是有動畫的,這個動畫是和我們的動畫一起執行的,所以還是會有可能看不全的(概率較小)。這裏我們就要引入一個監聽了,代碼如下:
ViewTreeObserver viewTreeObserver = getWindow().getDecorView().getViewTreeObserver();
viewTreeObserver.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
show(mTextView,tvWidth,1000);
getWindow().getDecorView().getViewTreeObserver().removeOnPreDrawListener(this::onPreDraw);
return true;
}
});
這個方法監聽了onDraw方法,在onDraw執行之前就會調用這個show方法保證我們的動畫播放完全。
2.爲什麼橫幅(View)有時候會閃一下?
有時候View會閃,就是因爲View本來就是VISIBLE的,動畫開始的時候回突然把view的寬度置爲0,所以會閃一下。如何解決這個問題呢?答案就一句話:
在XML裏把View的可見性設置爲INVISIBLE或者GONE
3.這個TVWidth(View的寬度)如何獲得呢?
衆所周知,在OnCreate裏是無法獲取View的真實寬高的,那我們要如何解決這個問題呢?有人要說了,我百度了,用第一個問題裏的監聽方法就可以獲取寬高。可是這個動畫裏你能獲取到嗎?答案是不能,第二個問題裏我們已經把View的可見性設置爲INVISIBLE或者GONE了,這兩個方法View是不會繪製的,所以你根本無法獲得View的寬高,那怎麼解決呢?答案就是兩個字:
計算
tvWidth = ScreenUtils.dip2px(this, (float) (paddingLeft + paddingRight)) +
ScreenUtils.sp2px(this, (float) ( 17))*s.length();
這裏我提供了一個簡單的思路,View的寬度就是paddingLeft + paddingRight + 字的寬度;看這個計算方法應該非常簡單了。下面是ScreenUtils的三個方法:
public static int dp2px(Context context, float dpValue) {
float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5F);
}
public static int px2dp(Context context, float pxValue) {
float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5F);
}
public static int sp2px(Context context, float spValue) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * fontScale + 0.5f);
}
總結
至此,我們已經實現了這個簡單的展開收回動畫,簡單易懂,如果可以的話可以幫我
點個贊嗎?
如果有不動的也可以在評論裏問我,下次見拜拜!