Android属性动画与自定义View——实现vivo x6更新系统的动画效果
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了Android属性动画与自定义View——实现vivo x6更新系统的动画效果,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含16139字,纯文字阅读大概需要24分钟。
内容图文
晚上好,现在是凌晨两点半,然后我还在写代码。电脑里播放着《凌晨两点半》,晚上写代码,脑子更清醒,思路更清晰。
今天聊聊属性动画和自定义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;
privatestaticfinalint OFFSET_Y = 0;
// 第一条水波移动速度privatestaticfinalint TRANSLATE_X_SPEED_ONE = 7;
// 第二条水波移动速度privatestaticfinalint TRANSLATE_X_SPEED_TWO = 5;
// 第三条水波移动速度privatestaticfinalint TRANSLATE_X_SPEED_Three = 10;
privatefloat mCycleFactorW;
privateint mTotalWidth, mTotalHeight;
privatefloat[] mYPositions;
privatefloat[] mResetOneYPositions;
privatefloat[] mResetTwoYPositions;
privatefloat[] mResetThreeYPositions;
privateint mXOffsetSpeedOne;
privateint mXOffsetSpeedTwo;
privateint mXOffsetSpeedThree;
privateint mXOneOffset;
privateint mXTwoOffset;
privateint mXThreeOffset;
privateint 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"));
}
@OverrideprotectedvoidonSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// 记录下view的宽高
mTotalWidth = w;
mTotalHeight = h;
// 用于保存原始波纹的y值
mYPositions = newfloat[mTotalWidth];
// 用于保存波纹一的y值
mResetOneYPositions = newfloat[mTotalWidth];
// 用于保存波纹二的y值
mResetTwoYPositions = newfloat[mTotalWidth];
// 用于保存波纹三的y值
mResetThreeYPositions = newfloat[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
<RelativeLayoutxmlns: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;
privateint size = 0;
private ProgressBar bar;
privateint height;
private WaterRipple ripple;
private TextView text_state;
private LinearLayout layout1, layout2;
private RelativeLayout layout;
@OverrideprotectedvoidonCreate(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() {
publicvoidhandleMessage(android.os.Message msg) {
switch (msg.what) {
case1:
if (size < 81) {
bar.setProgress(size);
text_state.setText("下载进度:"
+ (float) (((float) (size) / 81) * 100) + "%");
size++;
} else {
startAnim();
layout2.setVisibility(View.GONE);
}
break;
case2:
ripple.setHeight(height2);
ViewGroup.LayoutParams params = layout.getLayoutParams();
params.height = height - height2;
layout.setLayoutParams(params);
height2++;
break;
case3:
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;
}
};
};
publicvoidonClick(View v) {
new Thread(new Runnable() {
publicvoidrun() {
while (size < 81) {
SystemClock.sleep(100);
handler.sendEmptyMessage(1);
}
}
}).start();
layout1.setVisibility(View.GONE);
layout2.setVisibility(View.VISIBLE);
}
publicvoidstartAnim() {
new Thread(new Runnable() {
publicvoidrun() {
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) {
finalfloat scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
/**
* 根据手机的分辨率从 px(像素) 的单位 转成为 dp
*/publicstaticintpx2dip(Context context, float pxValue) {
finalfloat 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;
privatestaticfinalint OFFSET_Y = 0;
// 第一条水波移动速度privatestaticfinalint TRANSLATE_X_SPEED_ONE = 7;
// 第二条水波移动速度privatestaticfinalint TRANSLATE_X_SPEED_TWO = 5;
// 第三条水波移动速度privatestaticfinalint TRANSLATE_X_SPEED_Three = 10;
privatefloat mCycleFactorW;
privateint mTotalWidth, mTotalHeight;
privatefloat[] mYPositions;
privatefloat[] mResetOneYPositions;
privatefloat[] mResetTwoYPositions;
privatefloat[] mResetThreeYPositions;
privateint mXOffsetSpeedOne;
privateint mXOffsetSpeedTwo;
privateint mXOffsetSpeedThree;
privateint mXOneOffset;
privateint mXTwoOffset;
privateint mXThreeOffset;
privateint height = 1200;
private Paint mWavePaint, mWavePaint2,mWavePaint3;
private DrawFilter mDrawFilter;
publicWaterRipple(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"));
}
@OverrideprotectedvoidonDraw(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重绘,空出时间片
}
privatevoidresetPositonY() {
// 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);
}
@OverrideprotectedvoidonSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// 记录下view的宽高
mTotalWidth = w;
mTotalHeight = h;
// 用于保存原始波纹的y值
mYPositions = newfloat[mTotalWidth];
// 用于保存波纹一的y值
mResetOneYPositions = newfloat[mTotalWidth];
// 用于保存波纹二的y值
mResetTwoYPositions = newfloat[mTotalWidth];
// 用于保存波纹三的y值
mResetThreeYPositions = newfloat[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);
}
}
publicvoidsetHeight(int height) {
this.height = height;
postInvalidate();
}
}
以上就是全部内容,有个bug,就是在转换的时候部分手机会出现卡顿,后面我加上插值器调整一下,先睡了。如果代码有什么不妥的地方欢迎指出。
原文:http://blog.csdn.net/qq_25193681/article/details/51930201
内容总结
以上是互联网集市为您收集整理的Android属性动画与自定义View——实现vivo x6更新系统的动画效果全部内容,希望文章能够帮你解决Android属性动画与自定义View——实现vivo x6更新系统的动画效果所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。