现在自定义控件越来越多,我们现在来写一个选择百分比的自定义控件,效果图如下:
从图片来看,这个view由中间数据展示部分、可以滑动的箭头、另个实心圆、固定百分比对应的点和默认百分比组成,接下来我们就一步步实现这个效果。 首先需要在attrs.xml中定义需要的参数(内圆颜色、外圆颜色、箭头颜色…….):
[AppleScript] 纯文本查看 复制代码 <declare-styleable name="ProgressDashboardView">
<attr name="inner_cir_color" format="color"/>
<attr name="inner_cir_radius" format="dimension"/>
<attr name="outer_cir_color" format="color"/>
<attr name="outer_cir_radius" format="dimension"/>
<attr name="arrow_color" format="color"/>
<attr name="outer_circle_line_color" format="color"/>
<attr name="outer_circle_line_radius" format="dimension"/>
<attr name="outer_circle_line_text_size" format="dimension"/>
<attr name="inner_circle_value_text_size" format="dimension"/>
<attr name="outer_circle_line_text_color" format="color"/>
<attr name="inner_circle_value_text_color" format="color"/>
<attr name="outer_circle_persent_point_color" format="color"/>
<attr name="outer_circle_persent_point_select_color" format="color"/>
</declare-styleable>
然后在构造函数中获取并初始化各部分用到的画笔,这样我们在布局文件中就可以直接使用这些参数了,代码如下: [AppleScript] 纯文本查看 复制代码 public ProgressDashboardView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ProgressDashboardView, defStyleAttr, 0);
inner_cir_color = typedArray.getColor(R.styleable.ProgressDashboardView_inner_cir_color, Color.parseColor(default_inner_cir_color));
inner_cir_radius = typedArray.getDimensionPixelSize(R.styleable.ProgressDashboardView_inner_cir_radius, 30);
outer_cir_color = typedArray.getColor(R.styleable.ProgressDashboardView_outer_cir_color, Color.parseColor(default_out_cir_color));
outer_cir_radius = typedArray.getDimensionPixelSize(R.styleable.ProgressDashboardView_outer_cir_radius, 35);
arrow_color = typedArray.getColor(R.styleable.ProgressDashboardView_arrow_color, Color.parseColor(default_arrow_color));
outer_circle_line_color = typedArray.getColor(R.styleable.ProgressDashboardView_outer_circle_line_color, Color.parseColor(default_out_cir_line_color));
outer_circle_line_radius = typedArray.getDimensionPixelSize(R.styleable.ProgressDashboardView_outer_circle_line_radius, 50);
outer_circle_line_text_size = typedArray.getDimensionPixelSize(R.styleable.ProgressDashboardView_outer_circle_line_text_size, 22);
inner_circle_value_text_size = typedArray.getDimensionPixelSize(R.styleable.ProgressDashboardView_inner_circle_value_text_size, 22);
outer_circle_line_text_color = typedArray.getColor(R.styleable.ProgressDashboardView_outer_circle_line_text_color, Color.parseColor(default_outer_circle_line_text_color));
inner_circle_value_text_color = typedArray.getColor(R.styleable.ProgressDashboardView_inner_circle_value_text_color, Color.parseColor(default_inner_circle_value_text_color));
outer_circle_persent_point_color = typedArray.getColor(R.styleable.ProgressDashboardView_outer_circle_persent_point_color, Color.parseColor(default_outer_circle_persent_point_color));
outer_circle_persent_point_select_color = typedArray.getColor(R.styleable.ProgressDashboardView_outer_circle_persent_point_select_color, Color.parseColor(default_outer_circle_persent_point_select_color));
//外半圆线画笔
out_cir_line_paint = new Paint();
out_cir_line_paint.setStyle(Paint.Style.STROKE);
out_cir_line_paint.setStrokeWidth(dip2px(getContext(),2));
out_cir_line_paint.setAntiAlias(true);
out_cir_line_paint.setColor(outer_circle_line_color);
//内圆画笔
inner_cir_paint = new Paint();
inner_cir_paint.setStyle(Paint.Style.FILL);
inner_cir_paint.setAntiAlias(true);
inner_cir_paint.setColor(inner_cir_color);
//外圆画笔
outer_cir_paint = new Paint();
outer_cir_paint.setStyle(Paint.Style.FILL);
outer_cir_paint.setAntiAlias(true);
outer_cir_paint.setColor(outer_cir_color);
//百分比点画笔
persent_point_paint = new Paint();
persent_point_paint.setStrokeWidth(dip2px(getContext(), 4));
persent_point_paint.setStyle(Paint.Style.FILL);
persent_point_paint.setAntiAlias(true);
persent_point_paint.setColor(outer_circle_line_color);
//外圆环线对应的百分比画笔
persent_default_paint = new TextPaint();
persent_point_paint.setStyle(Paint.Style.FILL);
persent_point_paint.setAntiAlias(true);
persent_default_paint.setColor(outer_circle_line_color);
persent_default_paint.setTextSize(outer_circle_line_text_size);
//内圆百分比值对应的画笔
persent_result_paint = new TextPaint();
persent_result_paint.setStyle(Paint.Style.FILL);
persent_result_paint.setAntiAlias(true);
persent_result_paint.setColor(inner_circle_value_text_color);
persent_result_paint.setTextSize(inner_circle_value_text_size);
//箭头画笔
arrow_paint = new Paint();
arrow_paint.setColor(arrow_color);
arrow_paint.setStyle(Paint.Style.FILL);
arrow_paint.setAntiAlias(true);
}
好了,参数定义好了,我们开始画这个效果图,先画固定不变的部分,首先是中间两个实心圆,既然是画圆,就需要圆心和半径,半径已经定义了,圆心哪来呢,简单起见直接取控件中心点,如下: [AppleScript] 纯文本查看 复制代码 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int desiredWidth = dip2px(getContext(), 250);
int desiredHeight = dip2px(getContext(), 250);
/** 处理wrapcontent*****************************************************************/
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
//Measure Width
if (widthMode == MeasureSpec.EXACTLY) {
//Must be this size
width = widthSize;
} else if (widthMode == MeasureSpec.AT_MOST) {
//Can't be bigger than...
width = Math.min(desiredWidth, widthSize);
} else {
//Be whatever you want
width = desiredWidth;
}
//Measure Height
if (heightMode == MeasureSpec.EXACTLY) {
//Must be this size
height = heightSize;
} else if (heightMode == MeasureSpec.AT_MOST) {
//Can't be bigger than...
height = Math.min(desiredHeight, heightSize);
} else {
//Be whatever you want
height = desiredHeight;
}
circleX = width / 2;
circleY = height / 2;
//MUST CALL THIS
setMeasuredDimension(width, height);
}
现在圆心和半径都有了,实现两个圆的代码如下: [AppleScript] 纯文本查看 复制代码 private void drawInnerCircle(Canvas canvas) {
canvas.drawCircle(circleX, circleY, outer_cir_radius, outer_cir_paint);
canvas.drawCircle(circleX, circleY, inner_cir_radius, inner_cir_paint);
}
两个实心圆画好了,现在来画50%、60%、70%、80%、90%、100%对应的点: [AppleScript] 纯文本查看 复制代码 private void drawPersentPoint(Canvas canvas, int currentjiaodu) {
float pointRadius = (outer_circle_line_radius - outer_cir_radius) / 2 + outer_cir_radius;
for (int i = 0; i < 6; i++) {
int jiaodu = 36 * i;
if (Math.abs(currentjiaodu - jiaodu) < 3) {
persent_point_paint.setColor(outer_circle_persent_point_select_color);
}
int radius = dip2px(getContext(), 2);
if (jiaodu < 90) {
canvas.drawCircle(circleX - (float) (pointRadius * Math.cos(Math.toRadians(jiaodu))), circleY - (float) (pointRadius * Math.sin(Math.toRadians(jiaodu))), radius, persent_point_paint);
} else if (jiaodu == 90) {
canvas.drawCircle(circleX, circleY - pointRadius, radius, persent_point_paint);
} else if (jiaodu > 90) {
canvas.drawCircle(circleX + (float) (pointRadius * Math.cos(Math.toRadians(180 - jiaodu))), circleY - (float) (pointRadius * Math.sin(Math.toRadians(180 - jiaodu))), radius, persent_point_paint);
}
persent_point_paint.setColor(outer_circle_persent_point_color);
}
}
然后是外层圆环,代码比较简单: [AppleScript] 纯文本查看 复制代码 private void drawOuterCirLine(Canvas canvas) {
float left = circleX - outer_circle_line_radius;
float top = circleY - outer_circle_line_radius;
float right = circleX + outer_circle_line_radius;
float bottom = circleY + outer_circle_line_radius;
RectF rectF = new RectF(left, top, right, bottom);
canvas.drawArc(rectF, -180, 180, false, out_cir_line_paint);
}
然后是固定的50%、60%、70%、80%、90%、100%这几个值: [AppleScript] 纯文本查看 复制代码 private void drawPersentDefaultValue(Canvas canvas) {
for (int i = 0; i < 6; i++) {
int jiaodu = 36 * i;
float X = 0;
float Y = 0;
if (jiaodu < 90) {
X = circleX - (float) (outer_circle_line_radius * Math.cos(Math.toRadians(jiaodu))) - persent_default_paint.measureText("50%");
Y = circleY - (float) (outer_circle_line_radius * Math.sin(Math.toRadians(jiaodu)));
} else if (jiaodu == 90) {
X = circleX;
Y = circleY - outer_circle_line_radius;
} else if (jiaodu > 90) {
X = circleX + (float) (outer_circle_line_radius * Math.cos(Math.toRadians(180 - jiaodu)));
Y = circleY - (float) (outer_circle_line_radius * Math.sin(Math.toRadians(180 - jiaodu)));
}
canvas.drawText(50 + i * 10 + "%", X, Y, persent_default_paint);
}
}
静态部分已经画完,接下来就要画动态部分(箭头和中间数值部分),箭头的位置和中间数值部分都是根据角度变化而变化的,假设50%的点对应的角度为0度,100%的点对应的角度为180,则每个百分点对应的角度为3.6度,假设当前的角度为jiaodu,现在开始画箭头,箭头其实就是三个点连成的线,我们知道canvas有一个方法canvas. drawPath(Path path, Paint paint)画一个闭合的回路,方法知道了,具体代码如下: [AppleScript] 纯文本查看 复制代码 private void drawArrow(Canvas canvas, int jiaodu) {
int triangleJiaodu = 5;
Path path = new Path();
if (jiaodu < 90) {
path.moveTo(circleX - (float) (outer_cir_radius * Math.cos(Math.toRadians(jiaodu))), circleY - (float) (outer_cir_radius * Math.sin(Math.toRadians(jiaodu))));
//左点
path.lineTo(circleX - (float) (inner_cir_radius * Math.cos(Math.toRadians(jiaodu - triangleJiaodu))), circleY - (float) (inner_cir_radius * Math.sin(Math.toRadians(jiaodu - triangleJiaodu))));
//判断画右点
if ((jiaodu + triangleJiaodu) < 90) {
path.lineTo(circleX - (float) (inner_cir_radius * Math.cos(Math.toRadians(jiaodu + triangleJiaodu))), circleY - (float) (inner_cir_radius * Math.sin(Math.toRadians(jiaodu + triangleJiaodu))));
} else if ((jiaodu + triangleJiaodu) == 90) {
path.lineTo(circleX, circleY - inner_cir_radius);
} else if ((jiaodu + triangleJiaodu) > 90) {
path.lineTo(circleX + (float) (inner_cir_radius * Math.cos(Math.toRadians(180 - (jiaodu + triangleJiaodu)))), circleY - (float) (inner_cir_radius * Math.sin(Math.toRadians(180 - jiaodu - triangleJiaodu))));
}
} else if (jiaodu == 90) {
path.moveTo(circleX, circleY - outer_cir_radius);
//左点
path.lineTo(circleX - (float) (inner_cir_radius * Math.cos(Math.toRadians(jiaodu + triangleJiaodu))), circleY - (float) (inner_cir_radius * Math.sin(Math.toRadians(jiaodu + triangleJiaodu))));
//右点
path.lineTo(circleX + (float) (inner_cir_radius * Math.cos(Math.toRadians(180 - (jiaodu + triangleJiaodu)))), circleY - (float) (inner_cir_radius * Math.sin(Math.toRadians(180 - jiaodu - triangleJiaodu))));
} else if (jiaodu > 90) {
path.moveTo(circleX + (float) (outer_cir_radius * Math.cos(Math.toRadians(180 - jiaodu))), circleY - (float) (outer_cir_radius * Math.sin(Math.toRadians(180 - jiaodu))));
//判断画左点
if ((jiaodu - triangleJiaodu) < 90) {
path.lineTo(circleX - (float) (inner_cir_radius * Math.cos(Math.toRadians(jiaodu - triangleJiaodu))), circleY - (float) (inner_cir_radius * Math.sin(Math.toRadians(jiaodu - triangleJiaodu))));
} else if ((jiaodu - triangleJiaodu) == 90) {
path.lineTo(circleX, circleY - inner_cir_radius);
} else if ((jiaodu - triangleJiaodu) > 90) {
path.lineTo(circleX + (float) (inner_cir_radius * Math.cos(Math.toRadians(180 - jiaodu + triangleJiaodu))), circleY - (float) (inner_cir_radius * Math.sin(Math.toRadians(180 - jiaodu + triangleJiaodu))));
}
path.lineTo(circleX + (float) (inner_cir_radius * Math.cos(Math.toRadians(180 - jiaodu - triangleJiaodu))), circleY - (float) (inner_cir_radius * Math.sin(Math.toRadians(180 - jiaodu - triangleJiaodu))));
}
path.close();
canvas.drawPath(path, arrow_paint);
} 箭头的重点在于三个点的计算,因为三个点对应的角度都可能为90度的情况,所以要判断计算。箭头这样就画完了,还剩中间结果百分比,这个其实就是画一个textview,根据角度算出值即可,代码如下: [AppleScript] 纯文本查看 复制代码 private void drawPersentValue(Canvas canvas, int jiaodu) {
canvas.drawText((50 + (int) (jiaodu / 3.6)) + "%", circleX - persent_result_paint.measureText("70%") / 2, circleY + persent_result_paint.measureText("00") / 2, persent_result_paint);
}
箭头和中间的百分比都是根据角度来画的,那这个角度是怎么来的呢,我们实现的功能为手指可以波动箭头,手指移动到哪,箭头就旋转到对应的位置,所以我们需要确定手机移动到的位置,重写onTouchEvent方法,MotionEvent.ACTION_MOVE事件中不断计算角度的值,然后不断重绘,代码如下: [AppleScript] 纯文本查看 复制代码 @Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
startY = event.getY();
if (startY >= circleY) {
startY = circleY;
}
case MotionEvent.ACTION_MOVE:
float endX = event.getX();
float endY = event.getY();
if (endY >= circleY) {
endY = circleY;
}
jiaodu = (int) calAngle(circleX - outer_cir_radius, circleY, event.getX(), endY);
if (changeValueListener != null) {
changeValueListener.onValueChange((int) (50 + jiaodu / 3.6));
}
invalidate();
startY = endY;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
return true;
}
因为在程序用运用的时候我们是需要获取到当前百分比的值得,写一个回调方法,当手指滑动时不停的回调该方法。[AppleScript] 纯文本查看 复制代码 public void setOnChangeValueListener(ChangeValueListener changeValueListener) {
this.changeValueListener = changeValueListener;
}
public interface ChangeValueListener {
void onValueChange(int value);
}
控件这就写完了,然后就是使用过程了,首先布局文件:[AppleScript] 纯文本查看 复制代码 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
android:orientation="vertical">
<com.hobby.progressdashboardviewdemo.ProgressDashboardView
android:id="@+id/progressDashboardView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
app:arrow_color="@color/txt_color"
app:inner_cir_radius="40dp"
app:inner_circle_value_text_size="18sp"
app:outer_cir_radius="50dp"
app:outer_circle_line_radius="80dp"
app:outer_circle_line_text_size="16sp" />
<TextView
android:id="@+id/tv_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/txt_color"
android:layout_gravity="center"
android:textSize="18sp" />
</LinearLayout>
然后在Activity中获取百分(设置value变化监听):[AppleScript] 纯文本查看 复制代码 progressDashboardView.setOnChangeValueListener(changeValueListener);
ProgressDashboardView.ChangeValueListener changeValueListener = new ProgressDashboardView.ChangeValueListener() {
@Override
public void onValueChange(int value) {
tvValue.setText(value + "%");
}
};
|