本篇實戰內容主要圍繞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);