码迷,mamicode.com
首页 > 移动开发 > 详细

Android属性动画与自定义View——实现vivo x6更新系统的动画效果

时间:2016-07-17 16:02:10      阅读:432      评论:0      收藏:0      [点我收藏+]

标签:

晚上好,现在是凌晨两点半,然后我还在写代码。电脑里播放着《凌晨两点半》,晚上写代码,脑子更清醒,思路更清晰。

今天聊聊属性动画和自定义View搭配使用,前面都讲到自定义View和属性动画,但是一起用的还是不多,刚巧今晚手机提示我更新系统,我看到那个更新的动画还不错,仔细的分析了一下,于是我也决定写一个,不是一模一样的,但是效果和原理是一样的。

先看看图:

技术分享

这是一张静态的图,这里有三个波浪线,当下载完之后,波浪线会往上活动,一直到消失。

所以难点也是在这个波浪线上。这个波浪线类似于一个水波纹,也有很多的大神写过水波纹的效果,所以当我们知道这个是什么的时候,就比较容易实现这个功能了。

这一次,先看看用到了什么关键的技术。

首先要介绍的,就是高中的时候学过的余弦函数,还记得表达式吗?
y = Asin(wx+b)+h
这个公式里:w影响周期,A影响振幅,h影响y位置,b为初相;

接下来就看看这一次用到了哪些变量:

// 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

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#12ADFF"
    tools:context=".MainActivity" >

    <com.ltl.waterripple.WaterRipple
        android:id="@+id/hehe"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <RelativeLayout
        android:id="@+id/relativi"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical" >

            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content" >

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_alignParentRight="true"
                    android:layout_margin="5dip"
                    android:text="设置"
                    android:textColor="#ffffff"
                    android:textSize="20sp" />
            </RelativeLayout>

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_margin="5dip"
                android:gravity="center"
                android:text="Funtouch OS"
                android:textColor="#ffffff"
                android:textSize="40sp" />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_margin="5dip"
                android:gravity="center"
                android:text="Power For Android"
                android:textColor="#ffffff"
                android:textSize="20sp" />
        </LinearLayout>

        <LinearLayout
            android:id="@+id/content_linear"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:orientation="vertical"
            android:padding="20dip" >

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center_vertical"
                android:text="更新版本:vivo rev 1.14.1(81MB)" />

            <View
                android:layout_width="match_parent"
                android:layout_height="1px"
                android:layout_marginBottom="10dip"
                android:layout_marginTop="10dip"
                android:background="#eeeeee" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="系统"
                android:textStyle="bold" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="更新了部分第三方应用图标" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="优化了系统与一些第三方应用的兼容性" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="优化了应用安装体验" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="发现威胁会有弹窗提醒" />

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_margin="10dip"
                android:background="#eeeeee"
                android:padding="1px" >

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:background="#ffffff"
                    android:clickable="true"
                    android:gravity="center"
                    android:onClick="onClick"
                    android:padding="5dip"
                    android:text="下载并安装"
                    android:textSize="20sp"
                    android:textStyle="bold" />
            </LinearLayout>
        </LinearLayout>

        <LinearLayout
            android:id="@+id/progress_linear"
            android:layout_width="match_parent"
            android:layout_height="200dip"
            android:layout_alignParentBottom="true"
            android:gravity="center|bottom"
            android:orientation="vertical"
            android:padding="20dip"
            android:visibility="gone" >

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:textSize="25sp"
                android:text="当前版本:2.354785" />

            <TextView
                android:id="@+id/text_state"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="状态" />

            <ProgressBar
                android:id="@+id/progressBar1"
                style="?android:attr/progressBarStyleHorizontal"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />
        </LinearLayout>
    </RelativeLayout>

</RelativeLayout>

这一次的布局写的比较多,也是用到了多层的嵌套。所以在阅读的时候要很注意细节。

接下来是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,就是在转换的时候部分手机会出现卡顿,后面我加上插值器调整一下,先睡了。如果代码有什么不妥的地方欢迎指出。

源码地址

Android属性动画与自定义View——实现vivo x6更新系统的动画效果

标签:

原文地址:http://blog.csdn.net/qq_25193681/article/details/51930201

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!