Android自定义控件——手把手教你写出Google样式的ProgressBar

来源:互联网 发布:p2p网络借贷论文 编辑:程序博客网 时间:2024/05/21 10:48

本文介绍一下如何实现一个Google样式ProgressBar

这里有个相对简单的热热身先        Android 自定义控件——Simple_Loading

然后我们换种思路来重新实现一下

看图先:



分析:

        根据前面链接中的重写方式,我们需要重写一个View,然后在View中通过计算,实现一个不断旋转的圆弧,我们回过头来想想,既然系统中已经有ProgressBar,并且它本身就继承子View,我们何不直接重写ProgressBar来实现了呢?带着这个问题开始探究。

        ProgressBar继承自View,ProgressBar中显示出来的旋转的动画其实就是在画布上画的Drawable,具体的方法是这个

setIndeterminateDrawable()
        要想ProgressBar有动态效果,Drawable本身就是动态变化着的并一直在重绘。所以我们要做的工作就是写一个动态的Drawable,想要让Drawable动态建议实现Animatable,Animatable是一个支持动画接口。

        集体的动画怎么计算呢?我这里使用了属性动画来计算值变化的过程,以及使用的插值器来是动画有加速和减速效果。

        动画1:mRotationAnimator   0-360度不断restart方式重复,

        动画2:mSweepAppearing   20-300给弧度增加度数的动画,30-300是自己定义的变化范围,你也可以自己定义

        动画3:mSweepDisappesring   300-20 给弧度减少度数的动画,与上一个正好相反。

        三个动画执行的顺序如下:

        动画1在一直重复不断的执行,从0-360,也就是说动画1负责转圈,当动画1开始执行时,动画2也开始执行了,动画2的值加速变化到300,也就是A-B弧长加速变长的效果,动画2在执行的时候A的速度保持原有的速度,当动画2结束之后,动画3开始执行,A-B的弧长又加速变短,同时A点的度数加速。所以动画2,3负责的是弧长由长到短,由短到长交替的工作,由长到短的时候A点的值加速增大,造成B点在变短的时候被没有倒退的现象。看起来像A一直在追B,但又追不上。

        


        

        这三张图就差不多表示一个周期的  初-中-结束  的状态。虽然画的有点丑。如果这样不好理解,你还可以在放在直线上理解

 

直线上啊从O点开始,有两个小人,在a和b路程上分别加速,和匀速交替跑,两个始终都追不上,现象就是两人的距离在一个最大值和一个最小值之间交替,就是上面园中所说的弧长。


下面看看代码是怎么实现,只贴出了关键代码,细节的地方还需要完善。

一直出于重绘状态的Drawable  

SimpleLoadingDrawable.java

public class SimpleLoadingDrawable extends Drawable implements Animatable {private final String TAG = "mingwei";//public interface OnEndListener {public void onEnd(Drawable drawable);}private OnEndListener mOnEndListener;private RectF mRectF;private Paint mPaint;private int mColor;private float mStrokeWidth = 8;//private Interpolator mEndInterpolator = new LinearInterpolator();private Interpolator mRotationInterpolator = new LinearInterpolator();private Interpolator mSweepInterpolator = new DecelerateInterpolator();//private boolean isRunning;private ValueAnimator mSweepAppearingAnimator;private ValueAnimator mSweepDisAppearingAnimator;private ValueAnimator mRotationAnimator;private ValueAnimator mEndAnimator;//private float mCurrentRotationAngle = 0;private float mCurrentSweepAngle;private float mCurrentRotationAngleOffset = 0;private float mCurrentEndRation = 1f;//private float mRotatonSpeed = 0.5f;private int mSweepAngleMin = 20;private int mSweepAngleMax = 300;//private int mRotationDuration = 2000;private int mSweepDuration = 600;private int mEndDuration = 200;//private boolean mFirstSweepAnimator;private boolean mModeAppearing;//public SimpleLoadingDrawable() {Log.i(TAG, "SimpleLoadingDrawable()");this.mPaint = new Paint();this.mPaint.setAntiAlias(true);this.mPaint.setStrokeWidth(mStrokeWidth);this.mPaint.setStyle(Paint.Style.STROKE);this.mPaint.setStrokeCap(Cap.ROUND);this.mColor = Color.RED;this.mPaint.setColor(mColor);startDeceAnimation();}private void reinitValues() {mFirstSweepAnimator = true;mCurrentEndRation = 1f;// mPaint.setColor(mColor);}private void setAppearing() {mModeAppearing = true;mCurrentRotationAngleOffset += mSweepAngleMin;}private void setDisAppearing() {mModeAppearing = false;mCurrentRotationAngleOffset = mCurrentRotationAngleOffset + (360 - mSweepAngleMax);}@Overridepublic void draw(Canvas canvas) {float startAngle = mCurrentRotationAngle - mCurrentRotationAngleOffset;float sweepAngle = mCurrentSweepAngle;if (!mModeAppearing) {startAngle = startAngle + (360 - sweepAngle);}startAngle %= 360;if (mCurrentEndRation < 1f) {float newSweepAngle = sweepAngle * mCurrentEndRation;startAngle = (startAngle + (sweepAngle - newSweepAngle)) % 360;sweepAngle = newSweepAngle;}canvas.drawArc(mRectF, startAngle, sweepAngle, false, mPaint);}private void startDeceAnimation() {mRotationAnimator = ValueAnimator.ofFloat(0f, 360f);mRotationAnimator.setDuration((long) (mRotationDuration / mRotatonSpeed));mRotationAnimator.setInterpolator(mRotationInterpolator);mRotationAnimator.setRepeatCount(ValueAnimator.INFINITE);mRotationAnimator.setRepeatMode(ValueAnimator.RESTART);mRotationAnimator.addUpdateListener(new AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {// float rotation = getAnimatedFraction(animation) * 360f;setCurrentRotationAngle((Float) animation.getAnimatedValue());}});//mSweepAppearingAnimator = ValueAnimator.ofFloat(mSweepAngleMin, mSweepAngleMax);mSweepAppearingAnimator.setDuration(mSweepDuration);mSweepAppearingAnimator.setInterpolator(mSweepInterpolator);mSweepAppearingAnimator.addUpdateListener(new AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {float anitationFraction = getAnimatedFraction(animation);float angle;if (mFirstSweepAnimator) {angle = anitationFraction * mSweepAngleMax;} else {angle = mSweepAngleMin + anitationFraction * (mSweepAngleMax - mSweepAngleMin);}setCurrentSweepAngle(angle);}});mSweepAppearingAnimator.addListener(new AnimatorListener() {boolean cancel = false;@Overridepublic void onAnimationStart(Animator animation) {cancel = false;mModeAppearing = true;}@Overridepublic void onAnimationRepeat(Animator animation) {}@Overridepublic void onAnimationEnd(Animator animation) {if (!cancel) {mFirstSweepAnimator = false;setDisAppearing();mSweepDisAppearingAnimator.start();}}@Overridepublic void onAnimationCancel(Animator animation) {cancel = true;}});//mSweepDisAppearingAnimator = ValueAnimator.ofFloat(mSweepAngleMax, mSweepAngleMin);mSweepDisAppearingAnimator.setInterpolator(mSweepInterpolator);mSweepDisAppearingAnimator.setDuration(mSweepDuration);mSweepDisAppearingAnimator.addUpdateListener(new AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {float floatFraction = getAnimatedFraction(animation);setCurrentSweepAngle(mSweepAngleMax - floatFraction * (mSweepAngleMax - mSweepAngleMin));long duration = animation.getDuration();long currentTime = animation.getCurrentPlayTime();float fraction = currentTime / duration;}});mSweepDisAppearingAnimator.addListener(new AnimatorListener() {boolean cancel;@Overridepublic void onAnimationStart(Animator animation) {cancel = false;}@Overridepublic void onAnimationRepeat(Animator animation) {}@Overridepublic void onAnimationEnd(Animator animation) {if (!cancel) {setAppearing();mSweepAppearingAnimator.start();}}@Overridepublic void onAnimationCancel(Animator animation) {cancel = true;}});//mEndAnimator = ValueAnimator.ofFloat(1f, 0f);mEndAnimator.setInterpolator(mEndInterpolator);mEndAnimator.setDuration(mEndDuration);mEndAnimator.addUpdateListener(new AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {float endRation = getAnimatedFraction(animation);initEndRation(1.0f - endRation);}});mEndAnimator.addListener(new AnimatorListener() {boolean cancel;@Overridepublic void onAnimationStart(Animator animation) {cancel = false;}@Overridepublic void onAnimationRepeat(Animator animation) {}@Overridepublic void onAnimationEnd(Animator animation) {initEndRation(0f);if (!cancel) {stop();}}@Overridepublic void onAnimationCancel(Animator animation) {cancel = false;}});}@Overridepublic void start() {if (isRunning()) {return;}isRunning = true;reinitValues();mRotationAnimator.start();mSweepAppearingAnimator.start();invalidateSelf();}@Overridepublic void stop() {if (!isRunning()) {return;}isRunning = false;stopAnimators();invalidateSelf();}private void stopAnimators() {mRotationAnimator.cancel();mSweepAppearingAnimator.cancel();mSweepDisAppearingAnimator.cancel();mEndAnimator.cancel();}@Overridepublic void setBounds(int left, int top, int right, int bottom) {super.setBounds(left, top, right, bottom);mRectF = new RectF(left + mStrokeWidth / 2f + 0.5f, top + mStrokeWidth / 2f + 0.5f, right - mStrokeWidth / 2f - 0.5f,bottom - mStrokeWidth / 2f - 0.5f);}protected void setCurrentRotationAngle(float rotationAngle) {mCurrentRotationAngle = rotationAngle;invalidateSelf();}protected void setCurrentSweepAngle(float sweepAngle) {mCurrentSweepAngle = sweepAngle;invalidateSelf();}private void initEndRation(float f) {mCurrentEndRation = f;invalidateSelf();}@Overridepublic void setAlpha(int alpha) {mPaint.setAlpha(alpha);}@Overridepublic void setColorFilter(ColorFilter cf) {mPaint.setColorFilter(cf);}@Overridepublic int getOpacity() {return PixelFormat.TRANSLUCENT;}public void progressiveStop() {progressiveStop(null);}private void progressiveStop(OnEndListener listener) {if (!isRunning() || mEndAnimator.isRunning()) {return;}mOnEndListener = listener;mEndAnimator.addListener(new AnimatorListener() {@Overridepublic void onAnimationStart(Animator animation) {}@Overridepublic void onAnimationRepeat(Animator animation) {}@Overridepublic void onAnimationEnd(Animator animation) {mEndAnimator.removeListener(this);if (mOnEndListener != null) {mOnEndListener.onEnd(SimpleLoadingDrawable.this);}}@Overridepublic void onAnimationCancel(Animator animation) {}});mEndAnimator.start();}@Overridepublic boolean isRunning() {return isRunning;}public static class Build {public Build() {}public SimpleLoadingDrawable builder() {return new SimpleLoadingDrawable();}}}

重写的ProgressBar,别忘记给加上一个ProgressBar的样式,否则没有绘制效果

SimpleLoading.java

public class SimpleLoading extends ProgressBar {public SimpleLoading(Context context) {this(context, null);}public SimpleLoading(Context context, AttributeSet attrs) {this(context, attrs, 0);}public SimpleLoading(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init(attrs);}private void init(AttributeSet attrs) {if (isInEditMode()) {setIndeterminateDrawable(new SimpleLoadingDrawable.Build().builder());}setIndeterminateDrawable(new SimpleLoadingDrawable.Build().builder());}}


样式
<resources>    <style name="LoadingBarStyle" parent="android:Widget.Holo.ProgressBar"></style></resources>

属性

<resources>    <declare-styleable name="Loading">        <attr name="style" format="reference" />        <attr name="color" format="color" />        <attr name="colors" format="reference" />        <attr name="stroke_width" format="dimension" />        <attr name="min_sweep_angle" format="integer" />        <attr name="max_sweep_angle" format="integer" />        <attr name="sweep_speed" format="float" />        <attr name="rotation_speed" format="float" />    </declare-styleable></resources>

在布局文件中使用,别忘记加样式style

<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:paddingBottom="@dimen/activity_vertical_margin"    android:paddingLeft="@dimen/activity_horizontal_margin"    android:paddingRight="@dimen/activity_horizontal_margin"    android:paddingTop="@dimen/activity_vertical_margin"    tools:context="com.mingwei.sampleloading2.MainActivity" >    <com.mingwei.sampleloading2.SimpleLoading        style="@style/LoadingBarStyle"        android:layout_width="wrap_content"        android:layout_height="wrap_content" /></RelativeLayout>

Activity中啥也没干就不贴出来了。


GitHub地址:https://github.com/Mingwei360/RotatonProgressBar


3 0
原创粉丝点击