碼迷,mamicode.com
首頁 > 移動開發 > 詳細

Android 屬性動畫框架 ObjectAnimator、ValueAnimator ,這一篇就夠了

時間:2019-06-19 10:52:14      閱讀:26      評論:0      收藏:0      [點我收藏+]

標簽:animation   let   隱藏   部分   通過   prope   沒有   int end   類型   

前言

我們都知道 Android 自帶了 Roate Scale Translate Alpha 多種框架動畫,我們可以通過她們實現豐富的動畫效果,但是這些寬家動畫卻有一個致命的弱點,它們只是改變了 View 顯示的大小,而沒有改變 View 的響應區域。這時以 ObjectAnimator、ValueAnimator 為代表的屬性動畫也就應運而生了。


簡單效果

技術圖片


工作原理

屬性動畫字如其名,是通過改變 View 的屬性值來改變控件的形態,說白了就是通過反射技術來獲取控件的一些屬性如寬度、高度等的 get 和 set 方法,從而實現所謂的動畫效果。所以,這就需要我們的 View (如自定義 View 中)具有 set 和 get 方法,如果沒有則會導致程序的 Clash 。
具體步驟

  1. 首先,系統通過 get 方法獲得屬性值
  2. 系統在時間插值器的作用下,更變屬性值
  3. 系統調用 set 方法,將屬性值重新賦予控件

由此也可以看出:屬性動畫直接改變了控件的屬性,所以動畫結束后控件也就發生了永久性的變化。


使用 ObjectAnimator 實現四種動畫

這里我打算通過使用 ObjectAnimator 實現四大動畫框架:

  1. alpha
  2. scaleX/scaleY
  3. translateX/translateY
  4. rotation

給大家講解下 ObjectAnimator 使用

    private void iniAnimation(){
        // 透明度動畫
        ObjectAnimator.ofFloat(mAlphaImage, "alpha", 1, 0, 1)
                .setDuration(4000)
                .start();
        
        // 縮放
        final AnimatorSet animatorSet = new AnimatorSet();
        mScaleImage.setPivotX(mScaleImage.getWidth()+250);
        mScaleImage.setPivotY(mScaleImage.getHeight()+250);
        animatorSet.playTogether(
                ObjectAnimator.ofFloat(mScaleImage, "scaleX", 1, 0)
                        .setDuration(2000),
                ObjectAnimator.ofFloat(mScaleImage, "scaleY", 1, 0)
                        .setDuration(2000)
        );
        animatorSet.start();
        
        // 平移 translation
        final AnimatorSet translationAnimatorSet = new AnimatorSet();
        translationAnimatorSet.playTogether(
                ObjectAnimator.ofFloat(mTranslationImage, "translationX", 20, 100)
                        .setDuration(2000),
                ObjectAnimator.ofFloat(mTranslationImage, "translationY", 20,100)
                        .setDuration(2000)
        );
        translationAnimatorSet.start();
        
        // 利用 ObjectAnimator 實現旋轉動畫
        final AnimatorSet rotateAnimationSet = new AnimatorSet();
        rotateAnimationSet.playTogether(
                ObjectAnimator.ofFloat(mRotationImage, "rotation",0, 360)
                        .setDuration(2000)
        );
        rotateAnimationSet.start();
    }

以上代碼就通過了 ObjectAnimator 實現了,四大效果,實現過程基本可以歸納為

  1. 創建 AnimatorSet 對象
  2. 設置,變化發生的軸心(部分需要)
  3. 設置所需要發生改變的動畫(通常在 playTogether() 方法中)
  4. 開啟動畫

最后的運行效果如開頭動畫所示
同樣的,我們可以在一個 playTogether 方法中添加多個動畫,這樣就能實現多動畫組合的效果。這里就不在贅述了,大家可以自己試試看(我 GIF 圖中,右下角的動畫,就是旋轉 + 透明度)


使用 ValueAnimator 實現屬性動畫

ValueAnimator 是 ObjectAnimator 的父類,他兩之間的區別是,ObjectAnimator 在ValueAnimator 的基礎上,通過反射技術實現了動畫功能,也就像我剛剛所舉的例子,子要給了 ObjectAnimator 兩個值(from,to),在確定動畫類型(“scale,translate”),他就能自動生成動畫。
與之形成區別,雖然我們同樣需要給 ValueAnimator 傳遞起始和最終兩個值,但是 ValueAnimator 并不會自動去執行什么,而是會通過 addUpdateListener 的監聽方法,在時間插值器的作用下,有序的返回一連串數值,然后我們就可以通過這些數值,對控件進行設置。
技術圖片

實例

最近看各大廠商,似乎都迷上了對 FloatingActionButton 進行操作,我就也來趁波熱點。
技術圖片


實現方法

這個效果既可以通過動畫框架實現,也可通過屬性動畫實現,這里我給大家講下實現的方法。
首先是思路
由于這里我們是采用 ValueAnimator 實現的,所以更具 ValueAnimator 的特性,在我們對其設定完時間插值器之后,它會規律的返回一系列數。所以我們只要更具這一系列數對控件的屬性進行設置即可。

    private FloatingActionButton fab;
    private ImageView imageView;
    private int buttonSize = 0, imageSize = 0;
    private float startY = 0;
    private float endY   = 0;
    
    private ValueAnimator createValueAnimate(final View view, int start, int end){
        ValueAnimator valueAnimator = ValueAnimator.ofInt(start, end);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                ViewGroup.LayoutParams params = view.getLayoutParams();
                params.height = (int) animation.getAnimatedValue();
                params.width  = (int) animation.getAnimatedValue();
                view.setLayoutParams(params);
            }
        });
        return valueAnimator;
    }

可以看到我們傳入三個參數,這里我做的是縮放動畫,所以給的分別是控件,控件當前大小和控件目標大小。然后根據監聽器返回的值進行設置即可。
調用方面
我這里實現的是上拉隱藏和下拉顯示,所以我們需要判斷下 Y軸 滑動方向:

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_UP:
                startY = event.getY();
                if ((startY - endY) < 0){
                    // 縮小
                    animationDown(fab, buttonSize);
                    animationDown(imageView, imageSize);
                }else if ((startY - endY) > 0){
                    // 放大
                    animationUp(fab, buttonSize);
                    animationUp(imageView, imageSize);
                }
                break;

            case MotionEvent.ACTION_DOWN:
                endY   = event.getY();
                break;
        }
        return super.onTouchEvent(event);
    }

    private void animationDown(final View view, int originalSize){
        ValueAnimator animator = createValueAnimate(view, originalSize, 0);
        animator.addListener(new AnimatorListenerAdapter(){
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                view.setVisibility(View.GONE);
            }
        });
        animator.setInterpolator(new BounceInterpolator());
        animator.setDuration(500).start();
    }

    private void animationUp(final View view, int originalSize){
        view.setVisibility(View.VISIBLE);
        ValueAnimator animator = createValueAnimate(view, 0, originalSize);
        animator.setInterpolator(new BounceInterpolator());
        animator.setDuration(500).start();
    }

這里我們會發現,由于是屬性動畫,所以改變的直接就是控件的大小,這就導致了一個問題,如果是實時的獲取控件大小。那么我們在執行完多小動畫,也就是 animationDown 后,就無法在獲得控件原始大小了。
所以這里我們在 onResume 方法中獲取控件大小:

    @Override
    protected void onResume() {
        super.onResume();
        fab.post(new Runnable() {
            @Override
            public void run() {
                buttonSize = fab.getHeight();
            }
        });
        imageView.post(new Runnable() {
            @Override
            public void run() {
                imageSize = imageView.getHeight();
            }
        });
    }

實戰演練

屬性動畫可以作為 ViewGroup 增加活減少控件是的動畫,是的界面的變換不是那么的突兀,其實細心的同學可能有發現,android 是自帶切換效果的,但是形式比較單一,所以這里我通過自定義 ObjectAnimator 的方法。

最后效果

技術圖片
實現過程其實非常簡單:

  1. 首先 實例化出來一個 LayoutTransition 對象
  2. 接著 通過 ObjectAnimator.ofPropertyValuesHolder() 實例化出來一個用于載入動畫的
  3. ObjectAnimator 對象
  4. 然后 在 ObjectAnimator.ofPropertyValuesHolder() 中設置一系列的動畫效果
  5. 用 setAnimation 方法將該 ObjectAnimator 對象設置為 transition 的動畫
  6. 為 ObjectAnimator 對象設置 Duration 執行時間
  7. 設置動畫延時 setStartDelay

用同樣的方法設置 remove 動畫

        LayoutTransition transition = new LayoutTransition();

        ObjectAnimator appendAnimator = ObjectAnimator.ofPropertyValuesHolder(
                (ImageView) null,
                PropertyValuesHolder.ofFloat("scaleX", 0.0f, 1.0f),
                PropertyValuesHolder.ofFloat("scaleY", 0.0f, 1.0f),
                PropertyValuesHolder.ofFloat("alpha" , 0.0f, 1.0f)
        );
        appendAnimator.setInterpolator(new BounceInterpolator());
        transition.setAnimator(LayoutTransition.APPEARING, appendAnimator);
        transition.setDuration(LayoutTransition.APPEARING, transition.getDuration(LayoutTransition.APPEARING));
        transition.setStartDelay(LayoutTransition.APPEARING, transition.getStartDelay(LayoutTransition.APPEARING));

        ObjectAnimator removeAnimator = ObjectAnimator.ofPropertyValuesHolder(
                (ImageView) null,
                PropertyValuesHolder.ofFloat("scaleX", 1.0f, 0.0f),
                PropertyValuesHolder.ofFloat("scaleY", 1.0f, 0.0f),
                PropertyValuesHolder.ofFloat("alpha", 1.0f, 0.0f)
        );
        removeAnimator.setInterpolator(new BounceInterpolator());
        transition.setAnimator(LayoutTransition.DISAPPEARING, removeAnimator);
        transition.setDuration(LayoutTransition.DISAPPEARING, transition.getDuration(LayoutTransition.DISAPPEARING));
        transition.setStartDelay(LayoutTransition.DISAPPEARING, transition.getStartDelay(LayoutTransition.DISAPPEARING));

最后通過 setLayoutTransition 將這個 LayoutTransition 對象付給你的 ViewGroup 即可

        layout = findViewById(R.id.layout);
        layout.setLayoutTransition(transition);

測試環節

測試是分為添加控件和移除控件,功能在活動中動態的執行:
添加方法

  1. 首先創建好一個控件,這里我拿的是 (ImageView 舉例)
  2. 將其 大小、內容等屬性設置完成
  3. 調用 LinearLayout 的 addView 方法添加控件到布局的指定位置

移除方法

  1. 首先判斷該線下布局中是否有控件
  2. 再有的情況下定點移除控件
    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_add_image:
                ImageView imageView = new ImageView(ExtendActivity.this);
                imageView.setScaleType(ImageView.ScaleType.FIT_XY);
                imageView.setImageResource(R.drawable.heart);
                ViewGroup.LayoutParams params = new LinearLayout.LayoutParams(200, 200);
                imageView.setLayoutParams(params);
                layout.addView(imageView,0);
                break;

            case R.id.btn_remove_image:
                int count = layout.getChildCount();
                if (count > 0){
                    layout.removeViewAt(0);
                }
                break;
        }
    }

項目 Demo 點擊前往https://github.com/FishInWater-1999/android_view_user_defined_first
到此為止所有屬性動畫的使用基本介紹完畢
由于是個人學習的總結,如果有問題或是我個人疏漏,希望大家在評論區給我留言
祝大家編程愉快,少碼 bug ,哈哈哈

Android 屬性動畫框架 ObjectAnimator、ValueAnimator ,這一篇就夠了

標簽:animation   let   隱藏   部分   通過   prope   沒有   int end   類型   

原文地址:https://www.cnblogs.com/yuanhao-1999/p/11049875.html

(0)
(0)
   
舉報
評論 一句話評論(0
0條  
登錄后才能評論!
? 2014 mamicode.com 版權所有 京ICP備13008772號-2
迷上了代碼!
公式规律下期单双