高階UI特效之酷炫搶紅包金幣下落動畫

codeGoogle發表於2017-08-01

最近專案需求要求做一個搶紅包UI特效。效果如下:

專案效果
專案效果

從這張效果圖中我們這可看出要包括功能:

  • 實現是個彈框:
  • 金幣下落功能

  • 開啟金幣按鈕的翻轉效果

分析

  1. 實現是個彈框:
    可以用thime為Dialog的Activity
    或者 之談彈出一個Dialog,或者彈出一個PopupWindow

  2. 金幣下落功能: 可用自定義View+自定義屬性動畫

  3. 金幣的翻轉效果:可硬用幀動畫或者自定義View+ScheduledExecutorService傳送runnable

Markdown
Markdown

利用PopupWindow彈出一些紅包介面:

    private PopupWindow showPopWindows(View v, String moneyStr, boolean show) {
            View view = this.getLayoutInflater().inflate(R.layout.view_login_reward, null);
            TextView tvTips = (TextView) view.findViewById(R.id.tv_tip);
            TextView money = (TextView) view.findViewById(R.id.tv_money);
            tvTips.setText("連續登陸3天,送您" + moneyStr + "個愛心幣");
            money.setText(moneyStr);
            final LinearLayout container = (LinearLayout) view.findViewById(R.id.container);
            container.removeAllViews();
            //將flakeView 新增到佈局中
            container.addView(flakeView);
            //設定背景
            this.getWindow().setBackgroundDrawable(new ColorDrawable(Color.BLACK));
            //設定同時出現在螢幕上的金幣數量  建議64以內 過多會引起卡頓
            flakeView.addFlakes(8);
            /**
             * 繪製的型別
             * @see View.LAYER_TYPE_HARDWARE
             * @see View.LAYER_TYPE_SOFTWARE
             * @see View.LAYER_TYPE_NONE
             */
            flakeView.setLayerType(View.LAYER_TYPE_NONE, null);
            iv_onclick = (BofangView) view.findViewById(R.id.iv_onclick);
           // iv_onclick.setBackgroundResource(R.drawable.open_red_animation_drawable);
            iv_onclick.startAnation();
            iv_onclick.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (container!=null){
                        container.removeAllViews();
                    }
                    pop.dismiss();
                    GetToast.useString(getBaseContext(),"恭喜您,搶到紅包");
                }
            });
            pop = new PopupWindow(view, FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);
            ColorDrawable dw = new ColorDrawable(getResources().getColor(R.color.half_color));
            pop.setBackgroundDrawable(dw);
            pop.setOutsideTouchable(true);
            pop.setFocusable(true);
            pop.showAtLocation(v, Gravity.CENTER, 0, 0);

            /**
             * 移除動畫
             */
            final Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        //設定2秒後
                        Thread.sleep(2000);
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                container.removeAllViews();
                            }
                        });
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
            });
            if (!show)
                thread.start();
            //ivOpen指的是需要播放動畫的ImageView控制元件
    //        AnimationDrawable animationDrawable = (AnimationDrawable)iv_onclick.getBackground();
    //        animationDrawable.start();//啟動動畫
            MediaPlayer player = MediaPlayer.create(this, R.raw.shake);
            player.start();
            return pop;
        }複製程式碼

在Java中萬物皆物件,一個金幣就是一個物件,
擁有自己bitmap,寬高,大小,還有自己的座標 利用屬性動畫進行改變每一個小金幣的屬性值
這裡將每一個金幣看作為一個類

    /**
     * 類功能描述:</br>
     *紅包金幣仿雨滴下落效果
     * @author yuyahao
     * @version 1.0 </p> 修改時間:</br> 修改備註:</br>
     */
    public class FlakeView extends View {

        Bitmap droid;
        int numFlakes = 0;
        ArrayList<Flake> flakes = new ArrayList<Flake>(); // List of current flakes
        public ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
        long startTime, prevTime; // Used to track elapsed time for animations and fps
        int frames = 0;     // Used to track frames per second
        Paint textPaint;    // Used for rendering fps text
        float fps = 0;      // frames per second
        Matrix m = new Matrix(); // Matrix used to translate/rotate each flake during rendering
        String fpsString = "";
        String numFlakesString = "";
        /**
         * 利用屬性動畫進行改變每一個小金幣的屬性值
         * 這裡是將每一個金幣看作為一個類
         * 在Java中萬物皆物件,一個金幣就是一個物件,
         * 擁有自己bitmap,寬高,大小,還有自己的座標
         * the animator
         */
        public FlakeView(Context context) {
            super(context);
            droid = BitmapFactory.decodeResource(getResources(), R.drawable.b);
            textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            textPaint.setColor(Color.WHITE);
            textPaint.setTextSize(24);
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator arg0) {
                    long nowTime = System.currentTimeMillis();
                    float secs = (float) (nowTime - prevTime) / 100f;
                    prevTime = nowTime;
                    for (int i = 0; i < numFlakes; ++i) {
                        Flake flake = flakes.get(i);
                        flake.y += (flake.speed * secs);
                        if (flake.y > getHeight()) {
                            // If a flake falls off the bottom, send it back to the top
                            flake.y = 0 - flake.height;
                        }
                        flake.rotation = flake.rotation + (flake.rotationSpeed * secs);
                    }
                    // Force a redraw to see the flakes in their new positions and orientations
                    invalidate();
                }
            });
            animator.setRepeatCount(ValueAnimator.INFINITE);
            animator.setDuration(3000);
        }



        private void setNumFlakes(int quantity) {
            numFlakes = quantity;
            numFlakesString = "numFlakes: " + numFlakes;
        }

        /**
         *增加每一個小金幣屬性
         */
        public void addFlakes(int quantity) {
            for (int i = 0; i < quantity; ++i) {
                flakes.add(Flake.createFlake(getWidth(), droid,getContext()));
            }
            setNumFlakes(numFlakes + quantity);
        }

        /**
         * 減去指定數量的金幣,其他的金幣屬性保持不變
         */
        void subtractFlakes(int quantity) {
            for (int i = 0; i < quantity; ++i) {
                int index = numFlakes - i - 1;
                flakes.remove(index);
            }
            setNumFlakes(numFlakes - quantity);
        }

        /**
         * nSizeChanged()實在佈局發生變化時的回撥函式,間接回去呼叫onMeasure, onLayout函式重新佈局
         * @param w
         * @param h
         * @param oldw
         * @param oldh
         */
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            // Reset list of droidflakes, then restart it with 8 flakes
            flakes.clear();
            numFlakes = 0;
            addFlakes(16);
            // Cancel animator in case it was already running
            animator.cancel();
            // Set up fps tracking and start the animation
            startTime = System.currentTimeMillis();
            prevTime = startTime;
            frames = 0;
            animator.start();
        }

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            for (int i = 0; i < numFlakes; ++i) {
                Flake flake = flakes.get(i);
                m.setTranslate(-flake.width / 2, -flake.height / 2);
                m.postRotate(flake.rotation);
                m.postTranslate(flake.width / 2 + flake.x, flake.height / 2 + flake.y);
                canvas.drawBitmap(flake.bitmap, m, null);
            }
            ++frames;
            long nowTime = System.currentTimeMillis();
            long deltaTime = nowTime - startTime;
            if (deltaTime > 1000) {
                float secs = (float) deltaTime / 1000f;
                fps = (float) frames / secs;
                startTime = nowTime;
                frames = 0;
            }
        }
        /**
         * 生命週期 pause
         */
        public void pause() {
            animator.cancel();
        }
        /**
         * 生命週期 resume
         */
        public void resume() {
            animator.start();
        }
    }複製程式碼

利用PropertyValuesHolder實現圖片的左右晃動效果

PropertyValuesHolder的作用:

PropertyValuesHolder這個類可以先將動畫屬性和值暫時的儲存起來,後一起執行,在有些時候可以使用替換掉AnimatorSet,減少程式碼量

public static void doWaggleAnimation(View view) {
        PropertyValuesHolder rotation = PropertyValuesHolder.ofFloat("Rotation", 0f, -20f, 20f, -20f, 20f, -20f, 20f, -20f, 0f);
        PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("ScaleX", 0.8f, 0.85f, 0.9f, 1f);
        PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("ScaleY", 0.8f, 0.85f, 0.9f, 1f);
        ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view, rotation, scaleX, scaleY);
        animator.setDuration(1000);
        animator.setRepeatMode(ValueAnimator.REVERSE);
        animator.setRepeatCount(ValueAnimator.INFINITE);
        animator.start();
    }複製程式碼

本文用於自定義動畫比較多一些。

自定義動畫總結

Android提供了幾種動畫型別:

  • View Animation
  • Drawable Animation
  • Property Animation

View Animation相當簡單,不過只能支援簡單的縮放、平移、旋轉、透明度基本的動畫,且有一定的侷限性。比如:你希望View有一個顏色的切換動畫;你希望可以使用3D旋轉動畫;你希望當動畫停止時,View的位置就是當前的位置;這些View Animation都無法做到

Property Animation故名思議就是通過動畫的方式改變物件的屬性了,我們首先需要了解幾個屬性:

  • Duration動畫的持續時間,預設300ms。

  • TimeInterpolation:定義動畫變化速率的介面,所有插值器都必須實現此介面,如線性、非線性插值器;

  • TypeEvaluator:用於定義屬性值計算方式的介面,有int、float、color型別,根據屬性的起始、結束值和插值一起計算出當前時間的屬性值;

  • Animation sets:動畫集合,即可以同時對一個物件應用多個動畫,這些動畫可以同時播放也可以對不同動畫設定不同的延遲;

  • Frame refreash delay:多少時間重新整理一次,即每隔多少時間計算一次屬性值,預設為10ms,最終重新整理時間還受系統程式排程與硬體的影響;

  • Repeat Country and behavoir:重複次數與方式,如播放3次、5次、無限迴圈,可以讓此動畫一直重複,或播放完時向反向播放;

  • Frame refresh delay:幀重新整理延遲,對於你的動畫,多久重新整理一次幀;預設為10ms,但最終依賴系統的當前狀態;基本不用管。

    相關的類

  • ObjectAnimator 動畫的執行類

    ObjectAnimator是其中比較容易使用的一個動畫類,它繼承自ValueAnimator,

    說比較容易使用是因為它在動畫啟動後自動監視屬性值的變化並把值賦給物件屬性,

    而ValueAnimator則只監視屬性值的變化,但不會自動在屬性中應用該值,因此我們需要手動應用這些值

  • ValueAnimator 動畫的執行類

    ValueAnimtor動畫的建立基本上和ObjectAnimator一樣,只是我們需要手動應用屬性值

  • AnimatorSet 用於控制一組動畫的執行:線性,一起,每個動畫的先後執行等。

  • AnimatorInflater 使用者載入屬性動畫的xml檔案

  • TypeEvaluator 型別估值,主要用於設定動畫操作屬性的值。

  • TimeInterpolator 時間插值

ValueAnimator和ObjectAnimator之間的關係:

自定義屬性詳解
自定義屬性詳解

掌握了這邊可以輕鬆的寫一個自定義屬性動畫了

PropertyValuesHolder的作用:

PropertyValuesHolder這個類可以先將動畫屬性和值暫時的儲存起來,後一起執行,在有些時候可以使用替換掉AnimatorSet,減少程式碼量

專案中效果圖:

抖動特效
抖動特效

最後獻上整個專案的原始碼:

專案GitHub連結地址:

github.com/androidstar…

專案csdn連結地址:

download.csdn.net/detail/andr…

部落格地址:

blog.csdn.net/androidstar…

相信自己,沒有做不到的,只有想不到的

如果你覺得此文對您有所幫助,歡迎入群 QQ交流群 :232203809
微信公眾號:終端研發部

技術+職場
技術+職場

    (歡迎關注學習和交流)複製程式碼

相關文章