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
- Android自定义控件——手把手教你写出Google样式的ProgressBar
- Android:自定义ProgressBar的样式
- Android自定义ProgressBar的样式
- Android自定义EditText:手把手教你做一款含一键删除&自定义样式的SuperEditText
- Android自定义控件——手把手教你实现SlidingMenu(一)
- Android自定义控件——手把手教你实现SlidingMenu(二)
- Android 自定义progressBar样式
- Android自定义ProgressBar样式
- Android 自定义ProgressBar样式
- Android中自定义ProgressBar的样式
- 自定义ProgressBar的样式
- 自定义样式的progressBAR
- 自定义ProgressBar的样式
- Android 常用控件自定义样式RadioButton、CheckBox、ProgressBar、
- Swing:LookAndFeel 教程第一篇——手把手教你写出自己的 LookAndFeel
- 安卓学习之—自定义ProgressBar的样式
- 【转】android progressbar 自定义样式
- android 自定义横向progressbar样式
- 统计硬币
- 自学数据挖掘
- 一个关于“权限正常,但是就是在该文件夹下创建文件失败”
- Opencv腐蚀-erode函数
- lintcode-最近公共祖先-88
- Android自定义控件——手把手教你写出Google样式的ProgressBar
- apriori算法
- 字符流与字节流的区别
- 装饰器模式
- Opencv膨胀-dilate函数
- 【C#】基础知识—初识C#与.Net
- HDU 5446 2015长春站网络赛1010(数论模板题)
- 天天学设计模式1--重新认识面向对象
- android网络游戏开发——网络通信——回调函数例子