做程序猿这么久一直没有写博客,是不正常的,故以此为第一篇博客,开始我的博客生涯。
前不久,看到一篇博客,关于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,很方便很快捷。
最后,得感谢下“瓶子盖”,没有他,我的第一篇博客也不知道要到什么时候才会诞生。估计以后会写更多的博客,大家一块交流嘛~~
不积跬步,无以至千里。。。