上次做一个项目看到一个很简约的进度条,然后就做了一个,结果发现这个进度条网上的做法有好多,不过也不管了,先来看看样子
一、首先讲下要用到的两个函数
在自定义中最重要的就是onMeasure、onDraw以及onLayout了,此次我们用到的是前两个
onMeasur():这个方法是用来测量View的大小的(宽高)
用户在这个函数里先计算好宽和高然后调用setMeasuredDimension(width, height)方法就可以完成绘制
setMeasuredDimension(width, height)方法可以理解为通知view我计算好了并把计算好的宽高传给view
onDraw():这个方法是用来绘画的(例如要显示文字就利用画笔画一个文字出来)
二、编写自定义view代码
通过上面的图片可以看到我们需要进度条的高度、字体的大小、进度条的已完成的颜色、未完成的颜色、字体颜色以及起始进度值着六个自定义属性。
1、首先在values目录下新建attrs.xml文件,然后定义上面所讲的六个属性
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="progress_lineheight" format="dimension"></attr>
<attr name="progress_textsize" format="dimension"></attr>
<attr name="progress_finishlinecolor" format="color"></attr>
<attr name="progress_unfinishlinecolor" format="color"></attr>
<attr name="progress_textcolort" format="color"></attr>
<attr name="progress" format="float"></attr>
<declare-styleable name="MyProgress">
<attr name="progress_lineheight" ></attr>
<attr name="progress_textsize" ></attr>
<attr name="progress_finishlinecolor" ></attr>
<attr name="progress_unfinishlinecolor" ></attr>
<attr name="progress_textcolort"></attr>
<attr name="progress"></attr>
</declare-styleable>
</resources>
2、接下来创建我们自定义控件类MyProgress继承自View(当然也可以继承ProgressBar这样还不用自己考虑进度方面的问题),然后声明6个属性变量,以及6个静态变量(对应6个属性变量的默认值),当然不能忘记将dp、sp等单位转化为px单位了
private static final int LINE_HEIGHT = 2;//进度条高度;dp
private static final int TEXT_SIZE = 13;//显示进度的字体的大小;sp
private static final int FINISHLINE_COLOR = 0xFFFF0000;//完成部分进度的颜色
private static final int UNFINISHLINE_COLOR = 0xFF00FFFF;//未完成部分进度的颜色
private static final int TEXT_COLOR = FINISHLINE_COLOR;//字体的颜色
private static final int PROGRESS = 0;//进度;
private int mLineHeight = dptopx(LINE_HEIGHT);
private int mTextSize = sptopx(TEXT_SIZE);
private int mFinishLineColor = FINISHLINE_COLOR;
private int mUnFinishLineColor = UNFINISHLINE_COLOR;
private int mTextColor = TEXT_COLOR;
private float mProgress = PROGRESS;
/**
* dp转px
* @param dp
* @return
*/
private int dptopx(int dp) {
return (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
}
/**
* sp转px
* @param sp
* @return
*/
private int sptopx(int sp) {
return (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP, sp, getResources().getDisplayMetrics());
}
然后定义画笔变量,以及构造函数,在构造函数中,我们初始化画笔,获取自定义属性,然后设置画笔的字体大小以便以后计算字体打宽高使用
private Paint mPaint ;
public MyProgress(Context context) {
this(context,null);
}
public MyProgress(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyProgress(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
inint(attrs);
}
private void inint(AttributeSet attrs) {
mPaint = new Paint();
obtainStyledAttr(attrs);
mPaint.setTextSize(mTextSize);
}
/**
* 获取自定义属性
* @param attrs
*/
private void obtainStyledAttr(AttributeSet attrs) {
TypedArray t = getContext()
.obtainStyledAttributes(attrs,R.styleable.MyProgress);
mLineHeight= (int) t.getDimension(
R.styleable.MyProgress_progress_lineheight,mLineHeight);
mTextSize= (int) t.getDimension(
R.styleable.MyProgress_progress_textsize,mTextSize);
mFinishLineColor = t.getColor(
R.styleable.MyProgress_progress_finishlinecolor,mFinishLineColor);
mUnFinishLineColor = t.getColor(
R.styleable.MyProgress_progress_unfinishlinecolor,mUnFinishLineColor);
mTextColor = t.getColor(
R.styleable.MyProgress_progress_textcolort,mTextColor);
mProgress = t.getFloat(
R.styleable.MyProgress_progress,mProgress);
t.recycle();
}
接着给进度度添加get和set,get我们*100再输出,set的是后要通知view重绘
public int getprogress() {
return (int) (mProgress*100);
}
public void setprogress(float progress) {
this.mProgress = progress;
//通知view重绘
invalidate();
}
接下来在onMeasure方法里面计算view的宽和高,widthMeasureSpec和heightMeasureSpec传入的不是一个值,而是两个,我们可以通过MeasureSpec.getMode()和MeasureSpec.getSize()获取类型和大小,另外我们的是条形横向进度条所以宽度一定是给定的不能包裹内容的所以我们只需要获取size就可以了,计算完之后调用setMeasuredDimension(width,heitht)方法通知view就可以了
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int heitht = getmeasure(heightMeasureSpec);
//通知view测量完毕,并将数据传给它
setMeasuredDimension(width,heitht);
}
/**
* 计算view的高度
* @param heightMeasureSpec
* @return view的高度
*/
private int getmeasure(int heightMeasureSpec) {
int result = 0;
int Mode = MeasureSpec.getMode(heightMeasureSpec);
int size = MeasureSpec.getSize(heightMeasureSpec);
switch (Mode){
case MeasureSpec.EXACTLY://用户给定精确值
result = size;
break;
case MeasureSpec.UNSPECIFIED://父容器对于子容器没有任何限制,子容器想要多大就多大
//测量文字的高度
int textHeight = (int) (mPaint.descent()+mPaint.ascent());
//获取文字和进度条两者高度的最大值
result = getPaddingBottom()+getPaddingTop()+Math.max(textHeight,mLineHeight);
break;
case MeasureSpec.AT_MOST://子容器可以是声明大小内的任意大小
int textheight = (int) (mPaint.descent()+mPaint.ascent());
int maxHeight = getPaddingBottom()+getPaddingTop()+Math.max(textheight,mLineHeight);
//获取计算出来的高度和实际可用最大高度的最小值
result = Math.min(maxHeight,size);
break;
}
return result;
}
接着是绘画了,绘画分为三个部分,第一部分是画以及完成的进度条部分(首先判断进度是否为0,如果为0就说名没有已经完成的部分,第一部分就不需要画了),首先计算起点,起点X应该是从paddingleft开始,Y嘛就是一半的高度了,然后将画布移过去就好了,然后计算第一部分的长度,第一部分的长度等于(view的宽度-左右padding-字体的长度)*进度/100。
canvas.translate(getPaddingLeft(),getHeight()/2);
//两边添加空格使三个部分有分开的感觉
String str = " "+getprogress()+"% ";
int strWidth = (int) mPaint.measureText(str);
int width = getWidth()-getPaddingLeft()-getPaddingRight()-strWidth;
int drawToX = width*getprogress()/100;
//已经完成的部分
if (getprogress()!=0){
mPaint.setColor(mFinishLineColor);
mPaint.setStrokeWidth(mLineHeight);
canvas.drawLine(0,0,drawToX,0,mPaint);
}
第二部分是文字部分,文字部分起点是第一部分的结束,就是代码中的drawToX,高度要向下移动字体一半的高度
//进度文字
mPaint.setColor(mTextColor);
int y = (int) ((mPaint.descent()+mPaint.ascent())/2);
canvas.drawText(str,drawToX,-y,mPaint);
第三部分是未完成部分,和第一部分类似,判断进度是否为100,如果是一百就不需要画了,第三部分的起始点是第一部分的长度+第二部份的长度,结束点是view的宽度-左右padding
//未完成部分
if (getprogress()!=100){
mPaint.setColor(mUnFinishLineColor);
mPaint.setStrokeWidth(mLineHeight);
canvas.drawLine(drawToX+strWidth,0,width+strWidth,0,mPaint);
}
下面是自定义view的全部代码:
package com.linzhou.myview;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
/*
*项目名: MyView
*包名: com.linzhou.myview
*创建者: linzhou
*创建时间:17/08/05
*描述:
*/
public class MyProgress extends View {
private static final int LINE_HEIGHT = 2;//进度条高度;dp
private static final int TEXT_SIZE = 13;//显示进度的字体的大小;sp
private static final int FINISHLINE_COLOR = 0xFFFF0000;//完成部分进度的颜色
private static final int UNFINISHLINE_COLOR = 0xFF00FFFF;//未完成部分进度的颜色
private static final int TEXT_COLOR = FINISHLINE_COLOR;//字体的颜色
private static final int PROGRESS = 0;//进度;
private int mLineHeight = dptopx(LINE_HEIGHT);
private int mTextSize = sptopx(TEXT_SIZE);
private int mFinishLineColor = FINISHLINE_COLOR;
private int mUnFinishLineColor = UNFINISHLINE_COLOR;
private int mTextColor = TEXT_COLOR;
private float mProgress = PROGRESS;
private Paint mPaint ;
public MyProgress(Context context) {
this(context,null);
}
public MyProgress(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyProgress(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
inint(attrs);
}
private void inint(AttributeSet attrs) {
mPaint = new Paint();
obtainStyledAttr(attrs);
mPaint.setTextSize(mTextSize);
}
/**
* 获取自定义属性
* @param attrs
*/
private void obtainStyledAttr(AttributeSet attrs) {
TypedArray t = getContext()
.obtainStyledAttributes(attrs,R.styleable.MyProgress);
mLineHeight= (int) t.getDimension(
R.styleable.MyProgress_progress_lineheight,mLineHeight);
mTextSize= (int) t.getDimension(
R.styleable.MyProgress_progress_textsize,mTextSize);
mFinishLineColor = t.getColor(
R.styleable.MyProgress_progress_finishlinecolor,mFinishLineColor);
mUnFinishLineColor = t.getColor(
R.styleable.MyProgress_progress_unfinishlinecolor,mUnFinishLineColor);
mTextColor = t.getColor(
R.styleable.MyProgress_progress_textcolort,mTextColor);
mProgress = t.getFloat(
R.styleable.MyProgress_progress,mProgress);
t.recycle();
}
public int getprogress() {
return (int) (mProgress*100);
}
public void setprogress(float progress) {
this.mProgress = progress;
//通知view重绘
invalidate();
}
/**
* dp转px
* @param dp
* @return
*/
private int dptopx(int dp) {
return (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
}
/**
* sp转px
* @param sp
* @return
*/
private int sptopx(int sp) {
return (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP, sp, getResources().getDisplayMetrics());
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int heitht = getmeasure(heightMeasureSpec);
//通知view测量完毕,并将数据传给它
setMeasuredDimension(width,heitht);
}
/**
* 计算view的高度
* @param heightMeasureSpec
* @return view的高度
*/
private int getmeasure(int heightMeasureSpec) {
int result = 0;
int Mode = MeasureSpec.getMode(heightMeasureSpec);
int size = MeasureSpec.getSize(heightMeasureSpec);
switch (Mode){
case MeasureSpec.EXACTLY://用户给定精确值
result = size;
break;
case MeasureSpec.UNSPECIFIED://父容器对于子容器没有任何限制,子容器想要多大就多大
//测量文字的高度
int textHeight = (int) (mPaint.descent()+mPaint.ascent());
//获取文字和进度条两者高度的最大值
result = getPaddingBottom()+getPaddingTop()+Math.max(textHeight,mLineHeight);
break;
case MeasureSpec.AT_MOST://子容器可以是声明大小内的任意大小
int textheight = (int) (mPaint.descent()+mPaint.ascent());
int maxHeight = getPaddingBottom()+getPaddingTop()+Math.max(textheight,mLineHeight);
//获取计算出来的高度和实际可用最大高度的最小值
result = Math.min(maxHeight,size);
break;
}
return result;
}
@Override
protected void onDraw(Canvas canvas) {
canvas.translate(getPaddingLeft(),getHeight()/2);
String str = " "+getprogress()+"% ";
int strWidth = (int) mPaint.measureText(str);
int width = getWidth()-getPaddingLeft()-getPaddingRight()-strWidth;
int drawToX = width*getprogress()/100;
//已经完成的部分
if (getprogress()!=0){
mPaint.setColor(mFinishLineColor);
mPaint.setStrokeWidth(mLineHeight);
canvas.drawLine(0,0,drawToX,0,mPaint);
}
//进度文字
mPaint.setColor(mTextColor);
int y = (int) ((mPaint.descent()+mPaint.ascent())/2);
canvas.drawText(str,drawToX,-y,mPaint);
//未完成部分
if (getprogress()!=100){
mPaint.setColor(mUnFinishLineColor);
mPaint.setStrokeWidth(mLineHeight);
canvas.drawLine(drawToX+strWidth,0,width+strWidth,0,mPaint);
}
}
}
三、测试代码
MainActivity的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:lz="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.linzhou.myview.MainActivity">
<com.linzhou.myview.MyProgress
android:id="@+id/pg1"
android:layout_width="match_parent"
android:layout_marginTop="20dp"
android:layout_height="wrap_content"
android:padding="10dp" />
<com.linzhou.myview.MyProgress
android:id="@+id/pg2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
lz:progress="0.5"
lz:progress_textcolort="@color/coloAccent"
lz:progress_finishlinecolor="@color/colorAccent"
lz:progress_unfinishlinecolor="@color/coloAccent" />
<Button
android:id="@+id/start"
android:text="start"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
MainActivity代码:
package com.linzhou.myview;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
private MyProgress pg1,pg2;
private Button start;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Bundle bundle = msg.getData();
int pg = bundle.getInt("pg");
Log.d("linzhou123",pg+"");
if (pg>=100) start.setEnabled(true);
pg1.setprogress(((float)pg)/100);
pg2.setprogress(((float)pg)/100);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
pg1 = (MyProgress) findViewById(R.id.pg1);
pg2 = (MyProgress) findViewById(R.id.pg2);
start = (Button) findViewById(R.id.start);
start.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
start.setEnabled(false);
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0;i<=100;i++){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
Message message = new Message();
Bundle bundle = new Bundle();
bundle.putInt("pg",i);
message.setData(bundle);
mHandler.sendMessage(message);
}
}
}).start();
}
});
}
}
运行图: