100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > Android属性动画与自定义View——实现vivo x6更新系统的动画效果

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

时间:2022-03-29 07:55:13

相关推荐

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

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

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

先看看图:

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

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

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

首先要介绍的,就是高中的时候学过的余弦函数,还记得表达式吗?

y = Asin(wx+b)+h

这个公式里:w影响周期,A影响振幅,h影响y位置,b为初相;

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

// y = Asin(wx+b)+hprivate 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"));}@Overrideprotected 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方法,代码如下:

@Overrideprotected 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="/apk/res/android"xmlns:tools="/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" ><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content" ><TextViewandroid: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><TextViewandroid: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" /><TextViewandroid: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" ><TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:gravity="center_vertical"android:text="更新版本:vivo rev 1.14.1(81MB)" /><Viewandroid:layout_width="match_parent"android:layout_height="1px"android:layout_marginBottom="10dip"android:layout_marginTop="10dip"android:background="#eeeeee" /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="系统"android:textStyle="bold" /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="更新了部分第三方应用图标" /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="优化了系统与一些第三方应用的兼容性" /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="优化了应用安装体验" /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="发现威胁会有弹窗提醒" /><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_margin="10dip"android:background="#eeeeee"android:padding="1px" ><TextViewandroid: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" ><TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:textSize="25sp"android:text="当前版本:2.354785" /><TextViewandroid:id="@+id/text_state"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="状态" /><ProgressBarandroid: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;@Overrideprotected 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)+hprivate 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"));}@Overrideprotected 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);}@Overrideprotected 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,就是在转换的时候部分手机会出现卡顿,后面我加上插值器调整一下,先睡了。如果代码有什么不妥的地方欢迎指出。

源码地址

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。