这是一个很简单的动画效果,使用属性动画便可实现,但愿对读者学习动画能达到抛砖引玉的效果
一.自定义动画效果——Loading效果
如上是咱们须要作的一个Loading动画。Loading效果是很常见的一种动画,最简单的实现让设计画个动态图便可,或者画个静态图而后使用帧动画也能够实现。可是今天咱们用纯代码实现,不用任何图片资源。面试
大体思路
咱们自定义一个View,继承View类,而后画两个不一样半径的弧形,转动不一样的角度便可实现。
绘制两个不一样半径的弧形
首先初始化外圆和内园的Recf();canvas
private RectF mOuterCircleRectF = new RectF(); private RectF mInnerCircleRectF = new RectF();
而后在onDraw方法绘制圆弧:性能优化
//获取View的中心 float centerX = getWidth() / 2; float centerY = getHeight() / 2; if (lineWidth > centerX) { throw new IllegalArgumentException("lineWidth值太大了"); } //外圆半径,由于咱们的弧形是有宽度的,因此计算半径的时候应该把这部分减去,否则会有切割的效果 float outR = centerX - lineWidth; //小圆半径 float inR = outR * 0.6f - lineWidth; //设置弧形的距离上下左右的距离,也就是包围园的矩形。 mOuterCircleRectF.set(centerX - outR, centerY - outR, centerX + outR, centerY + outR); mInnerCircleRectF.set(centerX - inR, centerY - inR, centerX + inR, centerY + inR); //绘制外圆 canvas.drawArc(mOuterCircleRectF, mRotateAngle % 360, OUTER_CIRCLE_ANGLE, false, mStrokePaint); //绘制内圆 canvas.drawArc(mInnerCircleRectF, 270 - mRotateAngle % 360, INTER_CIRCLE_ANGLE, false, mStrokePaint);
代码很简单,就像注释同样:架构
绘制圆的过程应该放在onDraw方法中,这样咱们能够不断的重绘,也能够获取view的真实的宽高
固然,咱们还需设置一个画笔来画咱们的圆ide
mStrokePaint = new Paint(); mStrokePaint.setStyle(Paint.Style.STROKE); mStrokePaint.setStrokeWidth(lineWidth); mStrokePaint.setColor(color); mStrokePaint.setAntiAlias(true); mStrokePaint.setStrokeCap(Paint.Cap.ROUND); mStrokePaint.setStrokeJoin(Paint.Join.ROUND);
这个设置很简单,设置值得范围,这是无线循环,设置动画执行的时间,这只动画循环时延迟的时间,设置插值器。性能
弧形动起来
让弧形动起来的原理,就是监听值属性动画的值变化,而后在这个变化的过程当中不断的改变弧形的角度,而后让它重绘便可。
咱们让咱们的loadview实现ValueAnimator.AnimatorUpdateListener接口,而后在onAnimationUpdate监听动画的变化。咱们初始化值属性动画的时候设置了值得范围为float型,因此这里能够获取这个变化的值。而后利用这个值能够改变绘制圆的角度大小,再调用重绘方法,便可实现:学习
@Override public void onAnimationUpdate(ValueAnimator animation) { mRotateAngle = 360 * (float)animation.getAnimatedValue(); invalidate(); }
整个思路大体就是这样。优化
完整代码以下:动画
public class LoadingView extends View implements Animatable, ValueAnimator.AnimatorUpdateListener { private static final long ANIMATION_START_DELAY = 200; private static final long ANIMATION_DURATION = 1000; private static final int OUTER_CIRCLE_ANGLE = 270; private static final int INTER_CIRCLE_ANGLE = 90; private ValueAnimator mFloatValueAnimator; private Paint mStrokePaint; private RectF mOuterCircleRectF; private RectF mInnerCircleRectF; private float mRotateAngle; public LoadingView (Context context) { this(context, null); } public LoadingView (Context context, @Nullable AttributeSet attrs) { this(context, attrs, -1); } public LoadingView (Context context, @Nullable AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, -1); } public LoadingView (Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); initView(context, attrs); } float lineWidth; private void initView(Context context, AttributeSet attrs) { TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyCustomLoadingView); lineWidth = typedArray.getFloat(R.styleable.MyCustomLoadingView_lineWidth, 10.0f); int color = typedArray.getColor(R.styleable.MyCustomLoadingView_viewColor, context.getColor(R.color.colorAccent)); typedArray.recycle(); initAnimators(); mOuterCircleRectF = new RectF(); mInnerCircleRectF = new RectF(); //初始化画笔 initPaint(lineWidth, color); //旋转角度 mRotateAngle = 0; } private void initAnimators() { mFloatValueAnimator = ValueAnimator.ofFloat(0.0f, 1.0f); mFloatValueAnimator.setRepeatCount(Animation.INFINITE); mFloatValueAnimator.setDuration(ANIMATION_DURATION); mFloatValueAnimator.setStartDelay(ANIMATION_START_DELAY); mFloatValueAnimator.setInterpolator(new AccelerateDecelerateInterpolator()); } /** * 初始化画笔 */ private void initPaint(float lineWidth, int color) { mStrokePaint = new Paint(); mStrokePaint.setStyle(Paint.Style.STROKE); mStrokePaint.setStrokeWidth(lineWidth); mStrokePaint.setColor(color); mStrokePaint.setAntiAlias(true); mStrokePaint.setStrokeCap(Paint.Cap.ROUND); mStrokePaint.setStrokeJoin(Paint.Join.ROUND); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); float centerX = getWidth() / 2; float centerY = getHeight() / 2; //最大尺寸 if (lineWidth > centerX) { throw new IllegalArgumentException("lineWidth值太大了"); } float outR = centerX - lineWidth; //小圆尺寸 float inR = outR * 0.6f; mOuterCircleRectF.set(centerX - outR, centerY - outR, centerX + outR, centerY + outR); mInnerCircleRectF.set(centerX - inR, centerY - inR, centerX + inR, centerY + inR); //先保存画板的状态 canvas.save(); //外圆 canvas.drawArc(mOuterCircleRectF, mRotateAngle % 360, OUTER_CIRCLE_ANGLE, false, mStrokePaint); //内圆 canvas.drawArc(mInnerCircleRectF, 270 - mRotateAngle % 360, INTER_CIRCLE_ANGLE, false, mStrokePaint); //恢复画板的状态 canvas.restore(); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); startLoading(); } public void startLoading() { start(); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); stopLoading(); } public void stopLoading() { stop(); } @Override public void onAnimationUpdate(ValueAnimator animation) { mRotateAngle = 360 * (float)animation.getAnimatedValue(); invalidate(); } protected void computeUpdateValue(float animatedValue) { mRotateAngle = (int) (360 * animatedValue); } @Override public void start() { if (mFloatValueAnimator.isStarted()) { return; } mFloatValueAnimator.addUpdateListener(this); mFloatValueAnimator.setRepeatCount(Animation.INFINITE); mFloatValueAnimator.setDuration(ANIMATION_DURATION); mFloatValueAnimator.start(); } @Override public void stop() { mFloatValueAnimator.removeAllUpdateListeners(); mFloatValueAnimator.removeAllListeners(); mFloatValueAnimator.setRepeatCount(0); mFloatValueAnimator.setDuration(0); mFloatValueAnimator.end(); } @Override public boolean isRunning() { return mFloatValueAnimator.isRunning(); } }
attr文件代码以下:this
<declare-styleable name="LoadingView"> <attr name="lineWidth" format="float" /> <attr name="viewColor" format="color" /> </declare-styleable>
最后送福利了,如今关注我而且加入群聊能够获取包含源码解析,自定义View,动画实现,架构分享等。
内容难度适中,篇幅精炼,天天只需花上十几分钟阅读便可。
你们能够跟我一块儿探讨,欢迎加群探讨,有flutter—性能优化—移动架构—资深UI工程师 —NDK相关专业人员和视频教学资料,还有更多面试题等你来拿~
群号:925019412