Android自定义view之(CSDN应用splash界面的倒计时View)
2016-08-08 21:54
429 查看
最近有点闲,准确来说是很闲,每天早上要在公车上度过一段时间,一般我就会打开csdn应用看看别人的博客,哎哟~! 看到CSDN欢迎页的倒计时view还不错哦,正好最近在学自定义View,所以就捣腾了一下,以前感觉遥不可及的东西真正做起来也不难哈,菜鸟一个,大牛勿喷^~^!!
先看一下效果图:
思路:
1、肯定是自定义个View了,偷懒下一个,为了不自己定义progress就直接继承ProgressBar,通过getProgress和getMax获取当前进度跟最大值。
2、重写onMeasure,测量View,自定义View的老套路了,测量的规则大概是这样的,如果明确了宽高就用定义的宽高,如果没有也就是wrap_content类型的时候,拿到文本也就是这里的“跳过”字的长度+一个自定义的offset偏移量的东西。
3、在ondraw里面不断的绘制,通过改变弧的startAngle绘制。
4、当达到最大值的时候开始回调(我这里为了测试通过监听动画结束来实现回调了)
atrr文件:
第一步:自定义一个CountDownView继承ProgressBar,照样是覆盖三个构造方法。
第二步:重写onMeasure方法告诉父控件大小
点进resolveSize方法的代码你就懂了,
是不是很熟悉。嘻嘻!~ 没事多ctrl+左键一下就可以了~
第三步:来到了最关键的代码了
附带sp2px,dp2px的方法
第四步:打工告成了,测试一下(我们这就偷偷懒,用属性动画测试了,不过开发中可以换子线程+Handler或者别的方式实现,因为属性动画不兼容低版本)
其实很简单吧,加起来才几十行代码,以前总感觉自定义view离我是那么的远,只要动手敲敲,摸索着前进,一定会有结果的~!!!
最后附上CountDownView代码:
先看一下效果图:
思路:
1、肯定是自定义个View了,偷懒下一个,为了不自己定义progress就直接继承ProgressBar,通过getProgress和getMax获取当前进度跟最大值。
2、重写onMeasure,测量View,自定义View的老套路了,测量的规则大概是这样的,如果明确了宽高就用定义的宽高,如果没有也就是wrap_content类型的时候,拿到文本也就是这里的“跳过”字的长度+一个自定义的offset偏移量的东西。
3、在ondraw里面不断的绘制,通过改变弧的startAngle绘制。
4、当达到最大值的时候开始回调(我这里为了测试通过监听动画结束来实现回调了)
atrr文件:
<!-- countdown view--> <attr name="text" format="string"></attr> <attr name="progress_color" format="color"></attr> <attr name="center_bg" format="color"></attr> <attr name="text_color" format="color"></attr> <attr name="progress_width" format="dimension"></attr> <declare-styleable name="count_down" > <attr name="text" /> <attr name="progress_color" /> <attr name="center_bg" /> <attr name="text_color" /> <attr name="text_size"/> <attr name="progress_width" /> </declare-styleable>
第一步:自定义一个CountDownView继承ProgressBar,照样是覆盖三个构造方法。
public class CountDownView extends ProgressBar { /** * 结束的位置 */ private int endAngle=270; private Paint mTextPaint; private Paint mBgPaint; /** * 绘画的半径 */ private int mRadius=dp2px(30); /** * 进度条color默认红色 */ private int mProgressColor = Color.RED; /** * 进度条宽度,默认2dp */ private int mProgressWidth = dp2px(2); /** * 文本 */ private String mText="跳过"; /** * 文本颜色 */ private int mTextColor=Color.WHITE; /** * 文本大小,默认14.5sp */ private int mTextSize=sp2px(14.5f); /** * 默认中心背景色 */ private int centerBg=Color.GRAY; /** * 当wrapcontent的时候 * 控件的宽高为文字的宽度加上offset */ private float offset=15; public CountDownView(Context context) { this(context, null); } public CountDownView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public CountDownView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); /** 获取参数和初始化Paint */ obtainStyleAttr(context, attrs, defStyleAttr); ////////////////测试 setMax(100); startAnim(); ///////////////测试 } /** * 获取参数 * @param context * @param attrs * @param defStyleAttr */ private void obtainStyleAttr(Context context, AttributeSet attrs, int defStyleAttr) { TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.count_down, defStyleAttr, 0); int count=a.getIndexCount(); for (int i = 0; i < count; i++) { int attr=a.getIndex(i); switch (attr){ case R.styleable.count_down_center_bg: centerBg=a.getColor(attr,centerBg); break; case R.styleable.count_down_text: mText=a.getString(attr); break; case R.styleable.count_down_progress_color: mProgressColor=a.getColor(attr,mProgressColor); break; case R.styleable.count_down_text_color: mTextColor=a.getColor(attr,mTextColor); break; case R.styleable.count_down_text_size: mTextSize=a.getDimensionPixelSize(attr,mTextSize); break; case R.styleable.count_down_progress_width: mProgressWidth=a.getDimensionPixelSize(attr,mProgressWidth); break; } } a.recycle(); mTextPaint=new Paint(Paint.ANTI_ALIAS_FLAG); mTextPaint.setDither(true); mTextPaint.setTextSize(mTextSize); mTextPaint.setColor(mTextColor); mTextPaint.setStyle(Paint.Style.FILL); mBgPaint=new Paint(Paint.ANTI_ALIAS_FLAG); mBgPaint.setDither(true); mBgPaint.setStyle(Paint.Style.STROKE); mBgPaint.setStrokeCap(Paint.Cap.ROUND); mBgPaint.setStrokeJoin(Paint.Join.ROUND); mBgPaint.setStrokeWidth(mProgressWidth); mBgPaint.setColor(mProgressColor); } }
第二步:重写onMeasure方法告诉父控件大小
@Override protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { /** * 当为wrap_content的时候,返回的大小为 (文本的长度+红色进度条的宽度*2+一个自定义的offset) */ int result= (int) (getPaddingLeft()+getPaddingRight()+mTextPaint.measureText(mText)+mProgressWidth*2)+dp2px(offset); /** * 传入一个result,当widthMode为MeasureSpec.UNSPECIFIED的时候返回result, * 否则为设定的值 */ MeasureSpec.UNSPECIFIED int width=resolveSize(result, widthMeasureSpec); int height=resolveSize(result,heightMeasureSpec); result=Math.min(width,height); setMeasuredDimension(result, result); mRadius=getMeasuredWidth()/2; }
点进resolveSize方法的代码你就懂了,
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) { final int specMode = MeasureSpec.getMode(measureSpec); final int specSize = MeasureSpec.getSize(measureSpec); final int result; switch (specMode) { case MeasureSpec.AT_MOST: if (specSize < size) { result = specSize | MEASURED_STATE_TOO_SMALL; } else { result = size; } break; case MeasureSpec.EXACTLY: result = specSize; break; case MeasureSpec.UNSPECIFIED: default: result = size; } return result | (childMeasuredState & MEASURED_STATE_MASK); }
是不是很熟悉。嘻嘻!~ 没事多ctrl+左键一下就可以了~
第三步:来到了最关键的代码了
@Override protected synchronized void onDraw(Canvas canvas) { //画中间的圈 mTextPaint.setColor(centerBg); canvas.drawCircle(mRadius, mRadius, mRadius-mProgressWidth/2, mTextPaint); //绘制文本 mTextPaint.setColor(mTextColor); int baseX= (int) (mRadius-mTextPaint.measureText(mText)/2); int baseY= (int) (mRadius+(mTextPaint.getFontMetrics().bottom-mTextPaint.getFontMetrics().top)/2 -mTextPaint.getFontMetrics().bottom); canvas.drawText(mText, baseX, baseY, mTextPaint); /** * 绘制进度条, * 比如从startAngle从-90度的位置开始,endAngle为270位置(y的负轴位置) * 需要绘制的角度为 * 270--90(开始)=360; * 270-0(开始)=270 * 270-90(开始)=180 * 所以sweepAngle=startAngle-270(endAngle) */ int startAngle= (int) ((getProgress()*1.0f/getMax())*360)-90; canvas.drawArc(new RectF(0+mProgressWidth/2, 0+mProgressWidth/2,mRadius*2-mProgressWidth/2, mRadius*2-mProgressWidth/2), startAngle, endAngle-startAngle, false, mBgPaint); }
附带sp2px,dp2px的方法
/** * dp2px * -90 360 0 270 90 180 * @param value * @return px */ private int dp2px(float value) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, getResources().getDisplayMetrics()); } /** * sp2px * * @param value * @return px */ private int sp2px(float value) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, value, getResources().getDisplayMetrics()); }
第四步:打工告成了,测试一下(我们这就偷偷懒,用属性动画测试了,不过开发中可以换子线程+Handler或者别的方式实现,因为属性动画不兼容低版本)
public CountDownView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); obtainStyleAttr(context, attrs, defStyleAttr); ////////////////测试 setMax(100); startAnim(); } private void startAnim() { ValueAnimator a=ValueAnimator.ofInt(0, getMax() + 1); a.setDuration(5000); a.setInterpolator(new LinearInterpolator()); a.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int value = (int) animation.getAnimatedValue(); setProgress(value); postInvalidate(); } }); a.start(); a.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { Toast.makeText(getContext(), "计时完成,进入主页", Toast.LENGTH_SHORT).show(); } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); }
其实很简单吧,加起来才几十行代码,以前总感觉自定义view离我是那么的远,只要动手敲敲,摸索着前进,一定会有结果的~!!!
最后附上CountDownView代码:
package com.cisetech.customer.customer.Animation; import android.animation.Animator; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.RectF; import android.util.AttributeSet; import android.util.TypedValue; import android.view.animation.LinearInterpolator; import android.widget.ProgressBar; import android.widget.Toast; import com.cisetech.customer.customer.R; /** * Author:Yqy * Date:2016-08-08 * Desc:倒计时View * Company:cisetech */ public class CountDownView extends ProgressBar { /** * 结束的位置 */ private int endAngle=270; private Paint mTextPaint; private Paint mBgPaint; /** * 绘画的半径 */ private int mRadius=dp2px(30); /** * 进度条color默认红色 */ private int mProgressColor = Color.RED; /** * 进度条宽度,默认2dp */ private int mProgressWidth = dp2px(2); /** * 文本 */ private String mText="跳过"; /** * 文本颜色 */ private int mTextColor=Color.WHITE; /** * 文本大小,默认14.5sp */ private int mTextSize=sp2px(14.5f); /** * 默认中心背景色 */ private int centerBg=Color.GRAY; /** * 当wrapcontent的时候 * 控件的宽高为文字的宽度加上offset */ private float offset=15; public CountDownView(Context context) { this(context, null); } public CountDownView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public CountDownView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); obtainStyleAttr(context, attrs, defStyleAttr); ////////////////测试 setMax(100); startAnim(); } private void startAnim() { ValueAnimator a=ValueAnimator.ofInt(0, getMax() + 1); a.setDuration(5000); a.setInterpolator(new LinearInterpolator()); a.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int value = (int) animation.getAnimatedValue(); setProgress(value); postInvalidate(); } }); a.start(); a.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { Toast.makeText(getContext(), "完成", Toast.LENGTH_SHORT).show(); } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); } /** * 获取参数 * @param context * @param attrs * @param defStyleAttr */ private void obtainStyleAttr(Context context, AttributeSet attrs, int defStyleAttr) { TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.count_down, defStyleAttr, 0); int count=a.getIndexCount(); for (int i = 0; i < count; i++) { int attr=a.getIndex(i); switch (attr){ case R.styleable.count_down_center_bg: centerBg=a.getColor(attr,centerBg); break; case R.styleable.count_down_text: mText=a.getString(attr); break; case R.styleable.count_down_progress_color: mProgressColor=a.getColor(attr,mProgressColor); break; case R.styleable.count_down_text_color: mTextColor=a.getColor(attr,mTextColor); break; case R.styleable.count_down_text_size: mTextSize=a.getDimensionPixelSize(attr,mTextSize); break; case R.styleable.count_down_progress_width: mProgressWidth=a.getDimensionPixelSize(attr,mProgressWidth); break; } } a.recycle(); mTextPaint=new Paint(Paint.ANTI_ALIAS_FLAG); mTextPaint.setDither(true); mTextPaint.setTextSize(mTextSize); mTextPaint.setColor(mTextColor); mTextPaint.setStyle(Paint.Style.FILL); mBgPaint=new Paint(Paint.ANTI_ALIAS_FLAG); mBgPaint.setDither(true); mBgPaint.setStyle(Paint.Style.STROKE); mBgPaint.setStrokeCap(Paint.Cap.ROUND); mBgPaint.setStrokeJoin(Paint.Join.ROUND); mBgPaint.setStrokeWidth(mProgressWidth); mBgPaint.setColor(mProgressColor); } @Override protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { /** * 当为wrap_content的时候,返回的大小为 (文本的长度+红色进度条的宽度*2+一个自定义的offset) */ int result= (int) (getPaddingLeft()+getPaddingRight()+mTextPaint.measureText(mText)+mProgressWidth*2)+dp2px(offset); /** * 传入一个result,当widthMode为MeasureSpec.UNSPECIFIED的时候返回result, * 否则为设定的值 */ int width=resolveSize(result, widthMeasureSpec); int height=resolveSize(result,heightMeasureSpec); result=Math.min(width,height); setMeasuredDimension(result, result); mRadius=getMeasuredWidth()/2; } @Override protected synchronized void onDraw(Canvas canvas) { //画中间的圈 mTextPaint.setColor(centerBg); canvas.drawCircle(mRadius, mRadius, mRadius-mProgressWidth/2, mTextPaint); //绘制文本 mTextPaint.setColor(mTextColor); int baseX= (int) (mRadius-mTextPaint.measureText(mText)/2); int baseY= (int) (mRadius+(mTextPaint.getFontMetrics().bottom-mTextPaint.getFontMetrics().top)/2 -mTextPaint.getFontMetrics().bottom); canvas.drawText(mText, baseX, baseY, mTextPaint); /** * 绘制进度条, * 比如从startAngle从-90度的位置开始,endAngle为270位置(y的负轴位置) * 需要绘制的角度为 * 270--90(开始)=360; * 270-0(开始)=270 * 270-90(开始)=180 * 所以sweepAngle=startAngle-270(endAngle) */ int startAngle= (int) ((getProgress()*1.0f/getMax())*360)-90; canvas.drawArc(new RectF(0+mProgressWidth/2, 0+mProgressWidth/2,mRadius*2-mProgressWidth/2, mRadius*2-mProgressWidth/2), startAngle, endAngle-startAngle, false, mBgPaint); } /** * dp2px * -90 360 0 270 90 180 * @param value * @return px */ private int dp2px(float value) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, getResources().getDisplayMetrics()); } /** * sp2px * * @param value * @return px */ private int sp2px(float value) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, value, getResources().getDisplayMetrics()); } }
相关文章推荐
- Android高手进阶教程(三)之----Android 中自定义View的应用
- Android软件开发之盘点自定义View界面大合集
- Android 中自定义View的应用
- Android高手进阶教程(三)之 ----Android 中自定义View的应用
- Android移动应用界面的模板化设计【自定义BaseActivity】
- Android高手进阶教程(三)之----Android 中自定义View的应用.
- 17、Android之SurfaceView实例自定义SurfaceView的应用——小球跟着手指移动
- Android软件开发之盘点自定义View界面大合集(二) .
- Android ViewPager实现应用欢迎界面
- Android基础教程(九)之自定义下拉菜单模式----Spinner与setDropDownViewResource的应用
- 【Android应用实例之三】跟随手指的小球——自定义SurfaceView应用
- Android 中自定义View的应用
- Android 自定义View界面大合集(二)
- android菜鸟测试之Aj_02的Android 中自定义View的应用,在main.xml中嵌入自己写的view
- Android软件开发之盘点自定义View界面大合集(二)
- 【Android应用实例之二】跟随手指的小球——自定义View应用
- Android高手进阶教程(三)之----Android 中自定义View的应用.
- Android 中自定义View的应用.
- Android高手进阶教程(三)之----Android 中自定义View的应用
- Android高手进阶教程(三)之----Android 中自定义View的应用.