做程序猿这么久一直没有写博客,是不正常的,故以此为第一篇博客,开始我的博客生涯。
前不久,看到一篇博客,关于X6闪充动画的效果,是一个叫什么“瓶子盖子”写的,暂时就叫这个名字吧,具体名字没记清(sorry)。跑了一下他的代码,发现各种卡顿。。。一查他的代码发现,在死循环里面不停地new Message(),不卡有鬼了。。。当然,我并没有对他有不敬的意思,只是想改良一下效果。
先上vivo X6闪充真机效果图:
充电时会不停地旋转,效果绚丽。
下面讲讲实现原理:
该动画实际上是由四条弧线,和中间的带线条的圆形组成。android里面给了画弧线的方法canvas.drawArc()。这个不难,难的是里面的圆圈带线条。其实“瓶子盖子”的那个方案很不错(给他个赞),画72条线,通过画面的旋转,使得各条线达到对应的位置,下面是代码:
/**
* 绘制内切圆和锯齿,通过canvas的旋转,画出对应的锯齿线
* @param canvas
*/
private void drawCircle(Canvas canvas) {
float radius = HEIGHT - (HEIGHT * 0.3f) * 2 - (WIDTH * 0.17f);
canvas.drawCircle(WIDTH / 2, HEIGHT / 2, radius, mInCrilePaint);
canvas.save();
for (int i = 0; i < 72; i++) {
if (i >= mProgress) {
mInLine.setColor(Color.parseColor("#555555"));
} else {
mInLine.setColor(Color.parseColor("#00ff00"));
}
canvas.drawLine(WIDTH / 2, HEIGHT / 3.7f, WIDTH / 2, HEIGHT / 3.7f + HEIGHT * 0.05f , mInLine);
canvas.rotate(5, getWidth() / 2, getHeight() / 2);
}
}
静态的显示效果出来了,但是还缺少动画效果。“瓶子盖子”的跑动画是通过一个死循环,去跑动画,实现完全可以不用这么做。
安卓里面已经提供了Animator来帮助我们实现动画效果。
事实上,我们可以通过Animator的setRepartCount(-1)来实现无限循环。我猜想,vivo的攻城狮也大致是这样写的吧。
下面贴下代码:
mAnimator = ValueAnimator.ofFloat(0, 1);
mAnimator.setDuration(1500);
mAnimator.setInterpolator(new LinearInterpolator());
mAnimator.setRepeatCount(Animation.INFINITE);
mAnimator.addUpdateListener(this);
整体思路:
1.首先创建一个新的类FlashChargeView继承View,实现动画的监听,以监听到动画变化。在该view的构造方法里,创建一个ValueAnimator,初始化该Animator,使其无限循环播放。提供给外部的只有三个方法:开始播放动画、停止播放动画、设置当前电量。具体代码:
/**
* Created by Jxr33 on 2016/8/23.
*/
public class FlashChargeView extends View implements ValueAnimator.AnimatorUpdateListener {
/** 细线,粗线,圆,圆内进度,文字的画笔 */
private Paint mPaintSmall, mPaintBig, mInCrilePaint, mInLine, mTextPaint;
/** 控件的高宽 */
private static float WIDTH, HEIGHT;
/** 动画 */
private ValueAnimator mAnimator;
/** 圆弧起始角度 */
private float startAngle = 0;
/** 圆弧旋转角度 */
private float offset = 0;
/** 当前电量 */
private int mCurrPower = 70;
/** 当前电量的进度 */
private float mProgress;
public FlashChargeView(Context context) {
super(context);
init();
}
public FlashChargeView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public FlashChargeView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPaintSmall = new Paint();
mPaintSmall.setAntiAlias(true);
mPaintSmall.setStrokeWidth(5);
mPaintSmall.setStyle(Paint.Style.STROKE);
mPaintSmall.setColor(Color.parseColor("#12ADFF"));
mPaintBig = new Paint();
mPaintBig.setAntiAlias(true);
mPaintBig.setStrokeWidth(20);
mPaintBig.setStyle(Paint.Style.STROKE);
mPaintBig.setColor(Color.parseColor("#12ADFF"));
mInCrilePaint = new Paint();
mInCrilePaint.setAntiAlias(true);
mInCrilePaint.setStrokeWidth(.5f);
mInCrilePaint.setStyle(Paint.Style.STROKE);
mInCrilePaint.setColor(Color.parseColor("#eeeeee"));
mInLine = new Paint();
mInLine.setAntiAlias(true);
mInLine.setStrokeWidth(3);
mInLine.setColor(Color.parseColor("#00ff00"));
mTextPaint = new Paint();
mTextPaint.setAntiAlias(true);
mTextPaint.setStrokeWidth(3);
mTextPaint.setTextSize(80);
mTextPaint.setColor(Color.parseColor("#ffffff"));
mAnimator = ValueAnimator.ofFloat(0, 1);
mAnimator.setDuration(1500);
mAnimator.setInterpolator(new LinearInterpolator());
mAnimator.setRepeatCount(Animation.INFINITE);
mAnimator.addUpdateListener(this);
this.mProgress = mCurrPower * 72.0f / 100;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
WIDTH = w;
HEIGHT = h;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawOutArc1(canvas);
drawOutArc2(canvas);
drawOutArc3(canvas);
drawOutArc4(canvas);
drawCircle(canvas);
drawCircleIn(canvas);
drawText(canvas);
}
/**
* 绘制文字
* @param canvas
*/
private void drawText(Canvas canvas) {
float textSize = mTextPaint.measureText(mCurrPower + "%");
float x = WIDTH / 2 - textSize / 2;
float y = HEIGHT / 2 + textSize / 5;
canvas.drawText(mCurrPower + "%", x, y, mTextPaint);
}
/**
* 绘制最里面的圆
* @param canvas
*/
private void drawCircleIn(Canvas canvas) {
float radius = (float) (HEIGHT - (HEIGHT * 0.3) * 2 - (WIDTH * 0.22));
canvas.drawCircle(WIDTH / 2, HEIGHT / 2, radius, mInCrilePaint);
canvas.save();
}
/**
* 绘制内切圆和锯齿,通过canvas的旋转,画出对应的锯齿线
* @param canvas
*/
private void drawCircle(Canvas canvas) {
float radius = HEIGHT - (HEIGHT * 0.3f) * 2 - (WIDTH * 0.17f);
canvas.drawCircle(WIDTH / 2, HEIGHT / 2, radius, mInCrilePaint);
canvas.save();
for (int i = 0; i < 72; i++) {
if (i >= mProgress) {
mInLine.setColor(Color.parseColor("#555555"));
} else {
mInLine.setColor(Color.parseColor("#00ff00"));
}
canvas.drawLine(WIDTH / 2, HEIGHT / 3.7f, WIDTH / 2, HEIGHT / 3.7f + HEIGHT * 0.05f , mInLine);
canvas.rotate(5, getWidth() / 2, getHeight() / 2);
}
}
/**
* 绘制最外层弧线
* @param canvas
*/
private void drawOutArc1(Canvas canvas) {
RectF mRectF = new RectF(WIDTH * 0.1f, WIDTH * 0.1f, WIDTH * 0.9f, WIDTH * 0.9f);
canvas.drawArc(mRectF, startAngle + offset, 200, false, mPaintSmall);
}
/**
* 绘制外层的第二条弧线
* @param canvas
*/
private void drawOutArc2(Canvas canvas) {
RectF mRectF = new RectF(WIDTH * 0.14f, WIDTH * 0.14f, WIDTH * 0.85f, WIDTH * 0.85f);
canvas.drawArc(mRectF, -(startAngle + offset), 150, false, mPaintBig);
}
/**
* 绘制外层第三条弧线
* @param canvas
*/
private void drawOutArc3(Canvas canvas) {
RectF mRectF = new RectF(WIDTH * 0.22f, WIDTH * 0.22f, WIDTH * 0.795f, WIDTH * 0.795f);
canvas.drawArc(mRectF, startAngle + offset - 90, 110, false, mPaintSmall);
}
/**
* 绘制外层第四条弧线
* @param canvas
*/
private void drawOutArc4(Canvas canvas) {
RectF mRectF = new RectF(WIDTH * 0.255f, WIDTH * 0.255f, WIDTH * 0.75f, WIDTH * 0.75f);
canvas.drawArc(mRectF, -(startAngle + offset + 150), 150, false, mPaintBig);
}
/**
* 开始播放闪充动画
*/
public void startChargeAnimator() {
if (mAnimator != null) {
mAnimator.start();
}
}
/**
* 停止闪充动画
*/
public void endChargeAnimator() {
if (mAnimator != null) {
mAnimator.cancel();
}
}
/**
* 设置当前电量
* @param power
*/
public void setPower(int power) {
this.mCurrPower = power;
this.mProgress = power * 72.0f / 100;
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float fraction = animation.getAnimatedFraction();
this.offset = 360 * fraction;
this.invalidate();
}
}
2.在布局文件layout里,引用该View:
<?xml version="1.0" encoding="utf-8"?>
<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="#000000"
tools:context="com.vivo.flashcharge.MainActivity">
<com.vivo.flashcharge.FlashChargeView
android:id="@+id/chargeView"
android:layout_width="180dip"
android:layout_height="180dip"
android:layout_centerInParent="true"/>
</RelativeLayout>
3.Activity里引用该layout,并开始播放动画,设置当前电量。(这里用到了ButterKnife插件,该插件封装了findViewById等方法,具体以下再讲):
public class MainActivity extends AppCompatActivity {
@BindView(R.id.chargeView)
FlashChargeView chargeView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
chargeView.startChargeAnimator();
chargeView.setPower(90);
}
}
好了,具体效果开发完成了,运行了一下,一点都不卡顿。这里我用的开发工具是Android Studio,很方便很快捷。
最后,得感谢下“瓶子盖”,没有他,我的第一篇博客也不知道要到什么时候才会诞生。估计以后会写更多的博客,大家一块交流嘛~~
不积跬步,无以至千里。。。