本篇实战内容主要围绕draw、paint、path等常用API展开,为达到熟练使用目的,对一些基础常见的view效果进行实现。
实战view一:圆盘刻度
相关知识点:
使用PathDashPathEffect画圆弧上的刻度,PathMeasure计算圆弧长度;
可参考:https://hencoder.com/ui-1-1/ https://hencoder.com/ui-1-2/
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathDashPathEffect;
import android.graphics.PathEffect;
import android.graphics.PathMeasure;
import android.util.AttributeSet;
import android.view.View;
import com.test.drawing.Utils;
import androidx.annotation.Nullable;
public class Dashboard extends View {
private static final int ANGLE = 120;
private static final float RADIUS = Utils.dp2px(150);
private static final float LENGTH = Utils.dp2px(100);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
PathEffect pathEffect;
public Dashboard(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(Utils.dp2px(2));
/**
* 使用PathDashPathEffect画圆弧上的刻度
*/
Path dashPath = new Path();
dashPath.addRect(0, 0, Utils.dp2px(2), Utils.dp2px(10), Path.Direction.CCW);
//计算圆弧的长度,为了计算出每个刻度之间的间隔
Path arc = new Path();
arc.addArc(getWidth() / 2 - RADIUS, getHeight() / 2 - RADIUS, getWidth() / 2 + RADIUS, getHeight() / 2 + RADIUS, 90 + ANGLE / 2, 360 - ANGLE);
PathMeasure pathMeasure = new PathMeasure(arc, false);
pathEffect = new PathDashPathEffect(dashPath, (pathMeasure.getLength() - Utils.dp2px(2)) / 20, 0, PathDashPathEffect.Style.ROTATE);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//画圆弧
canvas.drawArc(getWidth() / 2 - RADIUS, getHeight() / 2 - RADIUS, getWidth() / 2 + RADIUS, getHeight() / 2 + RADIUS,
ANGLE / 2 + 90, 360 - ANGLE, false, paint);
//画一个圆弧,然后将 PathDashPathEffect 设置到 paint 上,画出圆弧上面的刻度
paint.setPathEffect(pathEffect);
canvas.drawArc(getWidth() / 2 - RADIUS, getHeight() / 2 - RADIUS, getWidth() / 2 + RADIUS, getHeight() / 2 + RADIUS, 90 + ANGLE / 2, 360 - ANGLE, false, paint);
// 画指针
canvas.drawLine(getWidth() / 2, getHeight() / 2,
(float) Math.cos(Math.toRadians(getAngleFromMark(5))) * LENGTH + getWidth() / 2,
(float) Math.sin(Math.toRadians(getAngleFromMark(5))) * LENGTH + getHeight() / 2,
paint);
}
int getAngleFromMark(int mark) {
return (int) (90 + (float) ANGLE / 2 + (360 - (float) ANGLE) / 20 * mark);
}
}
实战view二:饼图
步骤:
先画出四个扇形,起始角度、扫过的角度的定义。
注意点:向外偏移的扇形的角度应该是:该扇形之前扇形角度和+该扇形角度的一半,就是向外的偏移角度
package com.test.drawing.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import com.test.drawing.Utils;
import androidx.annotation.Nullable;
public class PieChart extends View {
private static final float RADIUS = Utils.dp2px(150);
private static final int OUTLENGTH = (int) Utils.dp2px(20);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
int[] angles = {60, 100, 120, 80};
int[] colors = {Color.parseColor("#2979FF"), Color.parseColor("#C2185B"),
Color.parseColor("#009688"), Color.parseColor("#FF8F00")};
public PieChart(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
paint.setStyle(Paint.Style.FILL);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
/**
* 画出四种颜色的圆形,圆形是由四个扇形组成的
*/
int currentAngle = 0;
for (int i = 0; i < colors.length; i++) {
paint.setColor(colors[i]);
canvas.save();
/**
* 根据sin cos算出向外偏移的距离,
* 注意:这里的角度应该是该偏移扇形之前的扇形角度和再加上该扇形角度的一半
*/
if (i == 2) {
canvas.translate((float) Math.cos(Math.toRadians(currentAngle + angles[i] / 2)) * OUTLENGTH,
(float) Math.sin(Math.toRadians(currentAngle + angles[i] / 2)) * OUTLENGTH);
}
canvas.drawArc(getWidth() / 2 - RADIUS, getHeight() / 2 - RADIUS, getWidth() / 2 + RADIUS, getHeight() / 2 + RADIUS,
currentAngle, angles[i], true, paint);
canvas.restore();
currentAngle += angles[i];
}
}
}
实战view三:两个不同形状的图形叠加效果
这个效果是两个不同形状的图形叠加的效果,后面黑色是一个圆形,上面机器猫的图片是一个长方形的图,使用离屏缓冲后,将两个图形进行形状叠加后,形成上面的这种效果。离屏缓冲知识原理参考:https://hencoder.com/ui-1-2/
package com.test.drawing.view;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.graphics.Xfermode;
import android.util.AttributeSet;
import android.view.View;
import com.test.drawing.R;
import com.test.drawing.Utils;
import androidx.annotation.Nullable;
public class AvatarView extends View {
private static final float WIDTH = Utils.dp2px(250);
private static final float PADDING = Utils.dp2px(50);
private static final float EDGE_WIDTH = Utils.dp2px(10);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
Xfermode xfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
Bitmap bitmap;
RectF savedArea = new RectF();
public AvatarView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
bitmap = getAvatar((int) WIDTH);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
savedArea.set(PADDING, PADDING, PADDING + WIDTH, PADDING + WIDTH);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int saved = canvas.saveLayer(savedArea, paint);
canvas.drawOval(PADDING + EDGE_WIDTH, PADDING + EDGE_WIDTH, PADDING + WIDTH - EDGE_WIDTH, PADDING + WIDTH - EDGE_WIDTH, paint);
paint.setXfermode(xfermode);
canvas.drawBitmap(bitmap, PADDING, PADDING + Utils.dp2px(26), paint);
paint.setXfermode(null);
canvas.restoreToCount(saved);
}
Bitmap getAvatar(int width) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.drawable.jiqimao, options);
options.inJustDecodeBounds = false;
options.inDensity = options.outWidth;
options.inTargetDensity = width;
return BitmapFactory.decodeResource(getResources(), R.drawable.jiqimao, options);
}
}
核心点:将两个图形进行叠加效果实现
int saved = canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG);
canvas.drawBitmap(rectBitmap, 0, 0, paint); // 画方
paint.setXfermode(xfermode); // 设置 Xfermode
canvas.drawBitmap(circleBitmap, 0, 0, paint); // 画圆
paint.setXfermode(null); // 用完及时清除 Xfermode
canvas.restoreToCount(saved);