很久没有用到自定义View了,手有点生疏了,这不同事刚扔给一个活,按照UI的要求,画一个进度条,带动画效果的。需求是这样的:
嗯,实现后效果如下:
嗯,算是基本满足需求吧。
本文包含的知识点
1、自定义view的绘制
2、属性动画
3、图像的合成模式 PorterDuff.Mode
嗯,废话不多说,show me the code
1)WordView.java
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.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.graphics.Xfermode;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
/**
* Author: gongwq on 2017/12/20 002011.
* Email: [email protected]
* Descriptions:
*/
public class WordView extends View {
private Paint linePaint, circlePaint, circlePaintBg;
private Paint textPaint1, textPaint2, textPaint3, textPaint4;
private int screenWidth, screenHeight;
private int mCenter, mRadius;
private RectF mRectF, mRectFBg1, mRectFBg2;
private int defaultValue;
private float rate = 0f;
private String hasStudyText = "";
private String notStudyText = "";
private int startAng = 0;
private int defaultStartAng = 120;
private long animDuration = 2000L;
private int progressColor, wordTitleColor, wordViewBackground;
private Xfermode xfermode;
public WordView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
TypedArray ta = context.obtainStyledAttributes(
attrs, R.styleable.WordView);
progressColor = ta.getColor(R.styleable.WordView_progressColor, 0xFFFFFFFF);
wordViewBackground = ta.getColor(R.styleable.WordView_wordViewBackground, 0xFFFF0000);
wordTitleColor = ta.getColor(R.styleable.WordView_wordTitleColor, 0xFFFF00FF);
ta.recycle();
initPaint(context);
}
private void initPaint(Context context) {
screenWidth = MeasureUtil.getScreenWidth(context);
screenHeight = MeasureUtil.getScreenHeight(context);
linePaint = new Paint();
linePaint.setColor(Color.WHITE);
linePaint.setStyle(Paint.Style.FILL);
linePaint.setAntiAlias(true);
linePaint.setStrokeWidth(6.0f);
circlePaint = new Paint();
circlePaint.setColor(progressColor);
circlePaint.setAntiAlias(true);
circlePaint.setStyle(Paint.Style.STROKE);
circlePaint.setStrokeCap(Paint.Cap.ROUND);
circlePaint.setStrokeWidth(6.0f);
circlePaintBg = new Paint();
circlePaintBg.setColor(wordViewBackground);
circlePaintBg.setAntiAlias(true);
circlePaintBg.setStyle(Paint.Style.FILL);
textPaint1 = new Paint();//已学习生词
textPaint1.setColor(wordTitleColor);
textPaint1.setTextAlign(Paint.Align.CENTER);
textPaint1.setAntiAlias(true);
textPaint1.setTextSize(25);
textPaint2 = new Paint();//xx个
textPaint2.setColor(Color.WHITE);
textPaint2.setTextAlign(Paint.Align.CENTER);
textPaint2.setAntiAlias(true);
textPaint2.setTextSize(70);
textPaint4 = new Paint();//个
textPaint4.setColor(Color.WHITE);
textPaint4.setTextAlign(Paint.Align.CENTER);
textPaint4.setAntiAlias(true);
textPaint4.setTextSize(25);
textPaint3 = new Paint();//未学习生词x个
textPaint3.setColor(wordTitleColor);
textPaint3.setTextAlign(Paint.Align.CENTER);
textPaint3.setAntiAlias(true);
textPaint3.setTextSize(25);
mCenter = screenWidth / 2;
mRadius = screenWidth / 4;
mRectF = new RectF(10, 10, 2 * mRadius - 10, 2 * mRadius - 10);//外切圆弧
mRectFBg1 = new RectF(20, 20, 2 * mRadius - 20, 2 * mRadius - 20);//中间的背景
mRectFBg2 = new RectF(60, 2 * mRadius - 60, 2 * mRadius - 60, 2 * mRadius + 60);//通过这个去调下面“缺”的弧度
xfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_OUT);//DST_OUT在不相交的地方绘制目标图像,相交处根据源图像alpha进行过滤,完全不透明处则完全过滤,完全透明则不过滤
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawArc(mRectF, startAng, (360 - (startAng - 90) * 2) * rate, false, circlePaint);
//将绘制操作保存到新的图层,将图像合成的处理放到离屏缓存中进行
circlePaintBg.setColor(wordViewBackground);
int saveCount = canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG);
canvas.drawArc(mRectFBg1, 0, 360, true, circlePaintBg);//绘制目标图
circlePaintBg.setXfermode(xfermode);//设置混合模式
circlePaintBg.setColor(Color.parseColor("#FF00FF00"));//这里的颜色只需前面的透明值为FF即完全不透明即可
canvas.drawArc(mRectFBg2, 0, 360, true, circlePaintBg);//绘制源图
circlePaintBg.setXfermode(null);//清除混合模式
canvas.restoreToCount(saveCount);
canvas.drawText("已学习生词", mRadius, mRectF.top + 100, textPaint1);
float width = textPaint2.measureText(hasStudyText);
float width2 = textPaint4.measureText("个");
// float total = mRectFBg1.right - mRectFBg1.left;
// Log.e("文字宽度---->", width + " " + width2 + " " + total);
// canvas.drawText(hasStudyText, mRadius + 30, mRadius, textPaint2);
float center1 = mRadius - (width + width2) / 2 + width / 2;
float center2 = mRadius - (width + width2) / 2 + width + width2 / 2;
canvas.drawText(hasStudyText, center1, mRadius + 20, textPaint2);
canvas.drawText(notStudyText, mRadius, mRectF.bottom - 100, textPaint3);
if (startAng != 0) {
canvas.drawText("个", center2, mRadius + 20, textPaint4);
canvas.rotate(180 + (startAng - 90) + (360 - (startAng - 90) * 2) * rate, mRadius, mRadius);
if (Integer.parseInt(hasStudyText) > 0) {
canvas.drawLine(mRadius, mRectF.top, mRadius, mRectF.top + 20, linePaint);
}
}
}
/**
* 设置学习单词数
*
* @param hasStudyNum 已学习单词数
* @param notStudyNum 未学习单词数
*/
public void setWordsNum(final int hasStudyNum, final int notStudyNum) {
rate = (float) hasStudyNum / (float) (hasStudyNum + notStudyNum);
final float rate2 = rate;
startAng = getStartAng();//startAng必须为大于等于90,小于180
ValueAnimator anim = ValueAnimator.ofFloat(1, 100);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
rate = (float) animation.getAnimatedValue() * rate2 / 100f;
hasStudyText = (int) ((float) animation.getAnimatedValue() * hasStudyNum / 100) + "";
notStudyText = "未学习生词" + (int) ((float) animation.getAnimatedValue() * notStudyNum / 100) + "个";
postInvalidate();
}
});
// hasStudyText = hasStudyNum + "";
// notStudyText = "未学习生词" + notStudyNum+"个";
anim.setDuration(getAnimationDuration());
anim.start();
}
/**
* 设置圆弧的起始角度值
注 1.值必须是[90,180] 2.必须在setWordNum()方法之前调用
* @param ang
*/
public void setStartAng(int ang) {
this.startAng = ang;
}
public int getStartAng() {
if (startAng == 0) {
return defaultStartAng;
}
return startAng;
}
/**
* 设置动画时间 ,注意 需要在setWordsNum前调用才会生效
*
* @param time
*/
public void setAnimationDuration(long time) {
this.animDuration = time;
}
public long getAnimationDuration(){
return animDuration;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec),
measureHeight(heightMeasureSpec));
}
private int measureWidth(int widthMeasureSpec) {
int mode = MeasureSpec.getMode(widthMeasureSpec);
int size = MeasureSpec.getSize(widthMeasureSpec);
// 默认宽高;
defaultValue = screenWidth;
switch (mode) {
case MeasureSpec.AT_MOST:
// 最大值模式 当控件的layout_Width或layout_height属性指定为wrap_content时
// Log.e("cmos---->", "size " + size + " screenWidth " + screenWidth);
// size = Math.min(defaultValue, size);
size = screenWidth / 2;
defaultValue = size;
break;
case MeasureSpec.EXACTLY:
// 精确值模式
// 当控件的android:layout_width=”100dp”或android:layout_height=”match_parent”时
break;
default:
size = defaultValue;
break;
}
return size;
}
private int measureHeight(int heightMeasureSpec) {
int mode = MeasureSpec.getMode(heightMeasureSpec);
int size = MeasureSpec.getSize(heightMeasureSpec);
switch (mode) {
case MeasureSpec.AT_MOST:
// 最大值模式 当控件的layout_Width或layout_height属性指定为wrap_content时
Log.e("cmos---->", "size " + size + " screenHeight " + screenHeight);
// size = Math.min(screenHeight / 2, size);
size = defaultValue;
break;
case MeasureSpec.EXACTLY:
// 精确值模式
// 当控件的android:layout_width=”100dp”或android:layout_height=”match_parent”时
break;
default:
size = defaultValue;
break;
}
return size;
}
}
下面是他的一些配置
attrs.xml
<resources>
<declare-styleable name="WordView">
<attr name="progressColor" format="color" />
<attr name="wordTitleColor" format="color" />
<attr name="wordViewBackground" format="color" />
declare-styleable>
resources>
布局文件
activity_main.xml
<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="@mipmap/details_top_bg"
android:gravity="center_horizontal"
android:orientation="vertical">
<EditText
android:id="@+id/et1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="已学习生词数"
android:inputType="number" />
<EditText
android:id="@+id/et2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="未学习生词数"
android:inputType="number" />
<Button
android:id="@+id/bt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="绘制" />
<com.example.myapplication.WordView
android:id="@+id/customView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:progressColor="#ffffff"
app:wordTitleColor="#ffffff"
app:wordViewBackground="#61000000" />
LinearLayout>
屏幕测量工具
MeasureUtil.java
public class MeasureUtil {
public static int getScreenWidth(Context mContext) {
int width = mContext.getResources().getDisplayMetrics().widthPixels;
return width;
}
public static int getScreenHeight(Context mContext) {
int height = mContext.getResources().getDisplayMetrics().heightPixels;
return height;
}
}
使用
MainActivity.java
......
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.bt:
if (!TextUtils.isEmpty(et1.getText().toString()) && !TextUtils.isEmpty(et2.getText().toString())) {
int num1 = Integer.parseInt(et1.getText().toString());
int num2 = Integer.parseInt(et2.getText().toString());
wordView.setAnimationDuration(4000);
wordView.setStartAng(130);
wordView.setWordsNum(num1, num2);
} else {
Toast.makeText(this, "数值不能为空", Toast.LENGTH_SHORT).show();
}
break;
}
}
......
代码里注释已经相对较清晰了,就不做解释了,有不懂的可以留言。
整个view的重点就在onDraw()
方法里,怎么去放置文字,中间的“xxx个”怎么随着数字的长度变化而始终居中,这主要与initPaint()
画笔方法有关,其中textPaint.setTextAlign(Paint.Align.CENTER);
是重点,它表示画的文字,你后面给定他一个绘制的中心点,然后它的文字会自动居中。第二个要注意的地方是,中间的背景,我是画的,开始准备用UI给的背景的,但是发现不好适配,所以就自己画了,这里主要用到的是图像合成模式PorterDuff.Mode
,图像的合成模式的枚举类一共有16种,通过这16种模式,我们可以自己根据给定的2个图片,合成我们想要的结果。这也给我们一个启示:当需求中的图片可以看成是多个图片组合成的结果的画,不妨可以试试 图像的合成模式PorterDuff.Mode
。关于PorterDuff.Mode,可以查看我的下一篇文章。 Android图像合成模式之PorterDuff.Mode