Android 展開/收回動畫效果思路與實現

要實現什麼效果?

在這裏插入圖片描述
我們就是要實現如圖所示的動畫效果,在開始之前我們先了解一下實現這個動畫的相關知識。

屬性動畫相關知識

動畫執行的邏輯

邏輯大概流程如下:

  1. 爲 ValueAnimator 設置動畫的時長,以及對應屬性的始 & 末值
  2. 設置屬性在 始 & 末值 間的變化邏輯
  • TimeInterpolator實現類:插值器-描述動畫的變化速率
  • TypeEvaluator實現類:估值器-描述 屬性值 變化的具體數值
  1. 根據2中的邏輯更新當前值
  2. 獲取3中更新的值 ,修改目標屬性值
  3. 刷新視圖
  4. 重複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);
    }

總結

至此,我們已經實現了這個簡單的展開收回動畫,簡單易懂,如果可以的話可以幫我

點個贊嗎?

如果有不動的也可以在評論裏問我,下次見拜拜!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章