Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android屬性動畫與自定義View——實現vivo x6更新系統的動畫效果

Android屬性動畫與自定義View——實現vivo x6更新系統的動畫效果

編輯:關於Android編程

晚上好,現在是凌晨兩點半,然後我還在寫代碼。電腦裡播放著《凌晨兩點半》,晚上寫代碼,腦子更清醒,思路更清晰。

今天聊聊屬性動畫和自定義View搭配使用,前面都講到自定義View和屬性動畫,但是一起用的還是不多,剛巧今晚手機提示我更新系統,我看到那個更新的動畫還不錯,仔細的分析了一下,於是我也決定寫一個,不是一模一樣的,但是效果和原理是一樣的。

先看看圖:

這裡寫圖片描述vc/7yqehozwvcD4NCjxwPsv50tTE0bXj0rLKx9Ta1eK49rKowMvP38nPoaPV4rj2sqjAy8/fwODLxtPa0ru49suusqjOxqOs0rLT0LrctuC1xLTzyfHQtLn9y66yqM7GtcTQp7n7o6zL+dLUtbHO0sPH1qq1wNXiuPbKx8qyw7S1xMqxuvKjrL7Nsci9z8jd0tfKtc/W1eK49rmmxNzBy6GjPC9wPg0KPHA+1eLSu7TOo6zPyL+0v7TTw7W9wcvKssO0udi8/LXEvLzK9aGjPC9wPg0KPHA+ytfPyNKqvenJ3LXEo6y+zcrHuN/W0LXEyrG68tGnuf21xNPgz9K6r8r9o6y7ubzHtcOx7bTvyr3C8KO/PGJyIC8+DQp5ID0gQXNpbih3eCtiKStoPGJyIC8+DQrV4rj2uavKvcDvo7p307DP7NbcxtqjrEHTsM/s1fG3+aOsaNOwz+x5zrvWw6OsYs6qs/XP4KO7PC9wPg0KPHA+vdPPwsC0vs2/tL+01eLSu7TO08O1vcHLxMTQqbHkwb+jujwvcD4NCjxwcmUgY2xhc3M9"brush:java;"> // y = Asin(wx+b)+h private static final float STRETCH_FACTOR_A = 50; private static final int OFFSET_Y = 0; // 第一條水波移動速度 private static final int TRANSLATE_X_SPEED_ONE = 7; // 第二條水波移動速度 private static final int TRANSLATE_X_SPEED_TWO = 5; // 第三條水波移動速度 private static final int TRANSLATE_X_SPEED_Three = 10; private float mCycleFactorW; private int mTotalWidth, mTotalHeight; private float[] mYPositions; private float[] mResetOneYPositions; private float[] mResetTwoYPositions; private float[] mResetThreeYPositions; private int mXOffsetSpeedOne; private int mXOffsetSpeedTwo; private int mXOffsetSpeedThree; private int mXOneOffset; private int mXTwoOffset; private int mXThreeOffset; private int height = 1200; private Paint mWavePaint, mWavePaint2,mWavePaint3; private DrawFilter mDrawFilter;

都是常規的,像畫筆、高寬等等。

然後看看初始化的操作,在構造函數裡面:

    public WaterRipple(Context context, AttributeSet attrs) {
        super(context, attrs);
        // 將dp轉化為px,用於控制不同分辨率上移動速度基本一致
        mXOffsetSpeedOne = DensityUtil.dip2px(context, TRANSLATE_X_SPEED_ONE);
        mXOffsetSpeedTwo = DensityUtil.dip2px(context, TRANSLATE_X_SPEED_TWO);
        mXOffsetSpeedThree = DensityUtil.dip2px(context, TRANSLATE_X_SPEED_Three);
        // 初始繪制波紋的畫筆
        mWavePaint = new Paint();
        // 去除畫筆鋸齒
        mWavePaint.setAntiAlias(true);
        // 設置風格為實線
        mWavePaint.setStyle(Style.FILL);
        // 設置畫筆顏色
        mWavePaint.setColor(Color.parseColor("#ffffff"));
        mDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG
                | Paint.FILTER_BITMAP_FLAG);

        // 初始繪制波紋的畫筆
        mWavePaint2 = new Paint();
        // 去除畫筆鋸齒
        mWavePaint2.setAntiAlias(true);
        // 設置風格為實線
        mWavePaint2.setStyle(Style.FILL);
        // 設置畫筆顏色
        mWavePaint2.setColor(Color.parseColor("#eeeeee"));

        // 初始繪制波紋的畫筆
        mWavePaint3 = new Paint();
        // 去除畫筆鋸齒
        mWavePaint3.setAntiAlias(true);
        // 設置風格為實線
        mWavePaint3.setStyle(Style.FILL);
        // 設置畫筆顏色
        mWavePaint3.setColor(Color.parseColor("#ffffff"));
    }


    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        // 記錄下view的寬高
        mTotalWidth = w;
        mTotalHeight = h;
        // 用於保存原始波紋的y值
        mYPositions = new float[mTotalWidth];
        // 用於保存波紋一的y值
        mResetOneYPositions = new float[mTotalWidth];
        // 用於保存波紋二的y值
        mResetTwoYPositions = new float[mTotalWidth];
        // 用於保存波紋三的y值
        mResetThreeYPositions = new float[mTotalWidth];
        // 將周期定為view總寬度
        mCycleFactorW = (float) (2 * Math.PI / mTotalWidth);

        // 根據view總寬度得出所有對應的y值
        for (int i = 0; i < mTotalWidth; i++) {
            mYPositions[i] = (float) (STRETCH_FACTOR_A
                    * Math.sin(mCycleFactorW * i) + OFFSET_Y);
        }
    }

這個初始化倒是不用怎麼解釋,重點說一下下面的方法:

private void resetPositonY() {
        // mXOneOffset代表當前第一條水波紋要移動的距離
        int yOneInterval = mYPositions.length - mXOneOffset;
        // 使用System.arraycopy方式重新填充第一條波紋的數據
        // 第一個參數就是源數組,第二個參數是要復制的源數組中的起始位置,
        // 第三個參數是目標數組,第四個參數是要復制到的目標數組的起始位置,第五個參數是要復制的元素的長度
        System.arraycopy(mYPositions, mXOneOffset, mResetOneYPositions, 0,
                yOneInterval);
        System.arraycopy(mYPositions, 0, mResetOneYPositions, yOneInterval,
                mXOneOffset);

        int yTwoInterval = mYPositions.length - mXTwoOffset;

        System.arraycopy(mYPositions, mXTwoOffset, mResetTwoYPositions, 0,
                yTwoInterval);
        System.arraycopy(mYPositions, 0, mResetTwoYPositions, yTwoInterval,
                mXTwoOffset);

        int yThreeInterval = mYPositions.length - mXThreeOffset;

        System.arraycopy(mYPositions, mXThreeOffset, mResetThreeYPositions, 0,
                yThreeInterval);
        System.arraycopy(mYPositions, 0, mResetThreeYPositions, yThreeInterval,
                mXThreeOffset);

    }

這裡面用到了一個System.arraycopy的方法,這個在我們平時的時候可能用得比較少,但是確實非常好用,建議大家可以看看這個的具體用法。我這裡也是盡量的寫滿注釋,以便更好的理解。

接下來就是最關鍵的onDraw方法,代碼如下:

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 從canvas層面去除繪制時鋸齒
        canvas.setDrawFilter(mDrawFilter);
        resetPositonY();
        for (int i = 0; i < mTotalWidth; i++) {
            // 繪制第一條水波紋
            canvas.drawLine(i, mTotalHeight - mResetOneYPositions[i] - height,
                    i, mTotalHeight, mWavePaint);

            // 繪制第二條水波紋
            canvas.drawLine(i, mTotalHeight - mResetTwoYPositions[i] - height
                    + 200, i, mTotalHeight, mWavePaint2);

            // 繪制第三條水波紋
            canvas.drawLine(i, mTotalHeight - mResetThreeYPositions[i] - height
                    + 400, i, mTotalHeight, mWavePaint3);

        }

        // 改變兩條波紋的移動點
        mXOneOffset += mXOffsetSpeedOne;
        mXTwoOffset += mXOffsetSpeedTwo;
        mXThreeOffset += mXOffsetSpeedThree;

        // 如果已經移動到結尾處,則重新記錄
        if (mXOneOffset >= mTotalWidth) {
            mXOneOffset = 0;
        }
        if (mXTwoOffset > mTotalWidth) {
            mXTwoOffset = 0;
        }
        if (mXThreeOffset > mTotalWidth) {
            mXThreeOffset = 0;
        }
    }

我這裡繪制了三個波浪線,以及處理了一些在繪制中的數據。

最後暴露給外面一個方法,用於動態的調整高度。


    public void setHeight(int height) {
        this.height = height;
        postInvalidate();
    }

最重要的View已經繪制出來了,下面看看其他的代碼:

首先是布局文件,activity_main.xml



    

    

        

            

                
            

            

            
        

        

            

            

            

            

            

            

            

            

                
            
        

        

            

            

            
        
    

這一次的布局寫的比較多,也是用到了多層的嵌套。所以在閱讀的時候要很注意細節。

接下來是MainActivity.java:

public class MainActivity extends Activity {
    private int height2 = 1200;
    private int size = 0;
    private ProgressBar bar;
    private int height;
    private WaterRipple ripple;
    private TextView text_state;
    private LinearLayout layout1, layout2;
    private RelativeLayout layout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);
        ripple = (WaterRipple) findViewById(R.id.hehe);
        bar = (ProgressBar) findViewById(R.id.progressBar1);
        layout1 = (LinearLayout) findViewById(R.id.content_linear);
        layout2 = (LinearLayout) findViewById(R.id.progress_linear);
        text_state = (TextView) findViewById(R.id.text_state);
        layout = (RelativeLayout) findViewById(R.id.relativi);
        WindowManager wm = this.getWindowManager();
        height = wm.getDefaultDisplay().getHeight();
        height2 = (int)(height*0.7);
        ripple.setHeight(height2);
        bar.setMax(81);
    }

    Handler handler = new Handler() {
        public void handleMessage(android.os.Message msg) {

            switch (msg.what) {
            case 1:
                if (size < 81) {
                    bar.setProgress(size);
                    text_state.setText("下載進度:"
                            + (float) (((float) (size) / 81) * 100) + "%");
                    size++;

                } else {
                    startAnim();
                    layout2.setVisibility(View.GONE);
                }
                break;
            case 2:
                ripple.setHeight(height2);

                ViewGroup.LayoutParams params = layout.getLayoutParams();
                params.height = height - height2;
                layout.setLayoutParams(params);
                height2++;
                break;
            case 3:
                ObjectAnimator animator = ObjectAnimator.ofFloat(ripple,
                        "alpha", 1f, 0f,0.5f);
                ObjectAnimator animator2 = ObjectAnimator.ofFloat(layout,
                        "alpha", 1f, 0f,0.5f);
                AnimatorSet animatorSet = new AnimatorSet();
                animatorSet.playTogether(animator, animator2);
                animatorSet.setDuration(300);
                animatorSet.start();
                break;
            default:
                break;
            }

        };
    };

    public void onClick(View v) {
        new Thread(new Runnable() {
            public void run() {
                while (size < 81) {
                    SystemClock.sleep(100);
                    handler.sendEmptyMessage(1);
                }
            }
        }).start();
        layout1.setVisibility(View.GONE);
        layout2.setVisibility(View.VISIBLE);
    }

    public void startAnim() {
        new Thread(new Runnable() {
            public void run() {
                while (height2 < height) {
                    SystemClock.sleep(100);
                    handler.sendEmptyMessage(2);
                }
                handler.sendEmptyMessage(3);
            }
        }).start();
    }

}

這裡我有兩個線程,主要是控制下載的進度展示和水波紋效果。具體的參數,使用者可以根據自己的需要,再作調整。

這裡面也用了一個工具類DensityUtil.java:

public class DensityUtil {

    /**
     * 根據手機的分辨率從 dp 的單位 轉成為 px(像素)
     */
    public static int dip2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    /**
     * 根據手機的分辨率從 px(像素) 的單位 轉成為 dp
     */
    public static int px2dip(Context context, float pxValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (pxValue / scale + 0.5f);
    }
}

還有那個控件WaterRipple.java:

public class WaterRipple extends View {

    // y = Asin(wx+b)+h
    private static final float STRETCH_FACTOR_A = 50;
    private static final int OFFSET_Y = 0;
    // 第一條水波移動速度
    private static final int TRANSLATE_X_SPEED_ONE = 7;
    // 第二條水波移動速度
    private static final int TRANSLATE_X_SPEED_TWO = 5;
    // 第三條水波移動速度
    private static final int TRANSLATE_X_SPEED_Three = 10;
    private float mCycleFactorW;

    private int mTotalWidth, mTotalHeight;
    private float[] mYPositions;
    private float[] mResetOneYPositions;
    private float[] mResetTwoYPositions;
    private float[] mResetThreeYPositions;
    private int mXOffsetSpeedOne;
    private int mXOffsetSpeedTwo;
    private int mXOffsetSpeedThree;

    private int mXOneOffset;
    private int mXTwoOffset;
    private int mXThreeOffset;

    private int height = 1200;

    private Paint mWavePaint, mWavePaint2,mWavePaint3;
    private DrawFilter mDrawFilter;

    public WaterRipple(Context context, AttributeSet attrs) {
        super(context, attrs);
        // 將dp轉化為px,用於控制不同分辨率上移動速度基本一致
        mXOffsetSpeedOne = DensityUtil.dip2px(context, TRANSLATE_X_SPEED_ONE);
        mXOffsetSpeedTwo = DensityUtil.dip2px(context, TRANSLATE_X_SPEED_TWO);
        mXOffsetSpeedThree = DensityUtil.dip2px(context, TRANSLATE_X_SPEED_Three);
        // 初始繪制波紋的畫筆
        mWavePaint = new Paint();
        // 去除畫筆鋸齒
        mWavePaint.setAntiAlias(true);
        // 設置風格為實線
        mWavePaint.setStyle(Style.FILL);
        // 設置畫筆顏色
        mWavePaint.setColor(Color.parseColor("#ffffff"));
        mDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG
                | Paint.FILTER_BITMAP_FLAG);

        // 初始繪制波紋的畫筆
        mWavePaint2 = new Paint();
        // 去除畫筆鋸齒
        mWavePaint2.setAntiAlias(true);
        // 設置風格為實線
        mWavePaint2.setStyle(Style.FILL);
        // 設置畫筆顏色
        mWavePaint2.setColor(Color.parseColor("#eeeeee"));

        // 初始繪制波紋的畫筆
        mWavePaint3 = new Paint();
        // 去除畫筆鋸齒
        mWavePaint3.setAntiAlias(true);
        // 設置風格為實線
        mWavePaint3.setStyle(Style.FILL);
        // 設置畫筆顏色
        mWavePaint3.setColor(Color.parseColor("#ffffff"));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 從canvas層面去除繪制時鋸齒
        canvas.setDrawFilter(mDrawFilter);
        resetPositonY();
        for (int i = 0; i < mTotalWidth; i++) {

            // 減400只是為了控制波紋繪制的y的在屏幕的位置,大家可以改成一個變量,然後動態改變這個變量,從而形成波紋上升下降效果
            // 繪制第一條水波紋
            canvas.drawLine(i, mTotalHeight - mResetOneYPositions[i] - height,
                    i, mTotalHeight, mWavePaint);

            // 繪制第二條水波紋
            canvas.drawLine(i, mTotalHeight - mResetTwoYPositions[i] - height
                    + 200, i, mTotalHeight, mWavePaint2);

            // 繪制第三條水波紋
            canvas.drawLine(i, mTotalHeight - mResetThreeYPositions[i] - height
                    + 400, i, mTotalHeight, mWavePaint3);

        }

        // 改變兩條波紋的移動點
        mXOneOffset += mXOffsetSpeedOne;
        mXTwoOffset += mXOffsetSpeedTwo;
        mXThreeOffset += mXOffsetSpeedThree;

        // 如果已經移動到結尾處,則重頭記錄
        if (mXOneOffset >= mTotalWidth) {
            mXOneOffset = 0;
        }
        if (mXTwoOffset > mTotalWidth) {
            mXTwoOffset = 0;
        }
        if (mXThreeOffset > mTotalWidth) {
            mXThreeOffset = 0;
        }

        // 引發view重繪,一般可以考慮延遲20-30ms重繪,空出時間片

    }

    private void resetPositonY() {
        // mXOneOffset代表當前第一條水波紋要移動的距離
        int yOneInterval = mYPositions.length - mXOneOffset;
        // 使用System.arraycopy方式重新填充第一條波紋的數據
        // 第一個參數就是源數組,第二個參數是要復制的源數組中的起始位置,
        // 第三個參數是目標數組,第四個參數是要復制到的目標數組的起始位置,第五個參數是要復制的元素的長度
        System.arraycopy(mYPositions, mXOneOffset, mResetOneYPositions, 0,
                yOneInterval);
        System.arraycopy(mYPositions, 0, mResetOneYPositions, yOneInterval,
                mXOneOffset);

        int yTwoInterval = mYPositions.length - mXTwoOffset;

        System.arraycopy(mYPositions, mXTwoOffset, mResetTwoYPositions, 0,
                yTwoInterval);
        System.arraycopy(mYPositions, 0, mResetTwoYPositions, yTwoInterval,
                mXTwoOffset);

        int yThreeInterval = mYPositions.length - mXThreeOffset;

        System.arraycopy(mYPositions, mXThreeOffset, mResetThreeYPositions, 0,
                yThreeInterval);
        System.arraycopy(mYPositions, 0, mResetThreeYPositions, yThreeInterval,
                mXThreeOffset);

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        // 記錄下view的寬高
        mTotalWidth = w;
        mTotalHeight = h;
        // 用於保存原始波紋的y值
        mYPositions = new float[mTotalWidth];
        // 用於保存波紋一的y值
        mResetOneYPositions = new float[mTotalWidth];
        // 用於保存波紋二的y值
        mResetTwoYPositions = new float[mTotalWidth];
        // 用於保存波紋三的y值
        mResetThreeYPositions = new float[mTotalWidth];
        // 將周期定為view總寬度
        mCycleFactorW = (float) (2 * Math.PI / mTotalWidth);

        // 根據view總寬度得出所有對應的y值
        for (int i = 0; i < mTotalWidth; i++) {
            mYPositions[i] = (float) (STRETCH_FACTOR_A
                    * Math.sin(mCycleFactorW * i) + OFFSET_Y);
        }
    }

    public void setHeight(int height) {
        this.height = height;
        postInvalidate();
    }

}

以上就是全部內容,有個bug,就是在轉換的時候部分手機會出現卡頓,後面我加上插值器調整一下,先睡了。如果代碼有什麼不妥的地方歡迎指出。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved