现在我确实花了一些时间来尝试为您制作一个很好的例子来玩画布。以下代码仍然可以通过线性/径向渐变进行改进,如果您愿意,您也可以使其更具可定制性。也许我将来会这样做,但我今天已经完成了。
首先将此添加到您的资源值中。我使用 /values/attrs.xml 如果你没有看到这个文件,创建它。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="Triangle">
<attr name="triangleColor" format="color"/>
<attr name="triangleStrokeColor" format="color" />
<attr name="triangleStrokeWidth" format="dimension" />
</declare-styleable>
</resources>
现在创建一个 Triangle 类,最好在保存自定义视图的文件夹中。修复包名称和R类导入。
package si.kseneman.views;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.View;
import si.kseneman.mobile.R;
public class Triangle extends View {
private int measuredWidth, measuredHeight;
private float density;
private Paint mTrianglePaint, mStrokePaint;
private PointF a, b, c;
private Path mTrianglePath;
private float mStrokeWidth;
// Default values
private int mTriangleColor = 0xAA4CAF50; //ARGB int
private int mStrokeColor = Color.BLACK; //ARGB int
private float defaultPadding = 5; //dp
public Triangle(Context context) {
super(context);
init(context, null, 0);
}
public Triangle(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs, 0);
}
public Triangle(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
private void init(Context context, AttributeSet attrs, int style) {
Resources res = context.getResources();
density = res.getDisplayMetrics().density;
defaultPadding *= density;
// Get the values from XML
TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.Triangle, style, 0);
int tmp;
mTriangleColor = typedArray.getColor(R.styleable.Triangle_triangleColor, mTriangleColor);
mStrokeColor = typedArray.getColor(R.styleable.Triangle_triangleStrokeColor, mStrokeColor);
tmp = typedArray.getDimensionPixelSize(R.styleable.Triangle_triangleStrokeWidth, -1);
mStrokeWidth = tmp != -1 ? tmp : 2 * density; // Use 2dp as a default value
typedArray.recycle();
a = new PointF();
b = new PointF();
c = new PointF();
mTrianglePath = new Path();
mTrianglePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTrianglePaint.setStyle(Paint.Style.FILL);
mTrianglePaint.setColor(mTriangleColor);
mStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mStrokePaint.setStyle(Paint.Style.STROKE);
mStrokePaint.setStrokeWidth(mStrokeWidth);
mStrokePaint.setColor(mStrokeColor);
}
public void setTriangleColorResId(int resId) {
// Example: setTriangleColorResId(R.color.blue);
setTriangleColor(getContext().getResources().getColor(resId));
}
public void setTriangleColor(String color) {
setTriangleColor(Color.parseColor(color));
}
public void setTriangleColor(int color) {
mTriangleColor = color;
mTrianglePaint.setColor(mTriangleColor);
invalidate();
}
public void setStrokeColorResId(int resId) {
// Example: setTriangleColorResId(R.color.blue);
setStrokeColor(getContext().getResources().getColor(resId));
}
public void setStrokeColor(String color) {
setTriangleColor(Color.parseColor(color));
}
public void setStrokeColor(int color) {
mStrokeColor = color;
mStrokePaint.setColor(mStrokeColor);
invalidate();
}
public void setStrokeWidth(float strokeWidth) {
if (strokeWidth < 0)
throw new IllegalArgumentException("Stroke width cannot be < 0");
//NOTE: input parameter is meant to be in pixels, you need to convert dp, sp or anything else
// when calling this method
mStrokeWidth = strokeWidth;
mStrokePaint.setStrokeWidth(mStrokeWidth);
invalidate();
}
public int getStrokeColor() {
return mStrokeColor;
}
public float getStrokeWidth() {
return mStrokeWidth;
}
public int getTriangleColor() {
return mTriangleColor;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
measuredHeight = getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec);
measuredWidth = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);
setMeasuredDimension(measuredWidth, measuredHeight);
//Log.d(TAG, "Height: " + measuredHeight + " Width: " + measuredWidth);
}
private float getPaddingOrDefault(int padding, float defaultValue) {
return padding != 0 ? padding : defaultValue;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (measuredHeight <= 0 || measuredWidth <= 0) {
// Not much we can draw... :/
return;
}
// Define the points of the triangle... make this so that it suits your needs
// Top point
a.x = measuredWidth / 2f;
a.y = getPaddingOrDefault(getPaddingTop(), defaultPadding);
// Bottom left point
b.x = getPaddingOrDefault(getPaddingLeft(), defaultPadding);
b.y = measuredHeight - getPaddingOrDefault(getPaddingBottom(), defaultPadding);
// Bottom right point
c.x = measuredWidth - getPaddingOrDefault(getPaddingRight(), defaultPadding);
c.y = b.y;
// Clear the path from previous onDraw
mTrianglePath.reset();
// Make a path of triangle
mTrianglePath.moveTo(a.x, a.y);
mTrianglePath.lineTo(b.x, b.y);
mTrianglePath.lineTo(c.x, c.y);
mTrianglePath.lineTo(a.x, a.y);
mTrianglePath.close();
// Draw the triangle and the stroke
canvas.drawPath(mTrianglePath, mTrianglePaint);
canvas.drawPath(mTrianglePath, mStrokePaint);
}
}
现在创建一个新类(这不是必需的,但我这样做是为了让您了解如何使用画布)
package si.kseneman.views;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.view.View;
public class TriangleAndArc extends View {
private int measuredWidth, measuredHeight;
private float density;
private float a1Degrees;
private float textHeight;
private double a1Radians;
private Paint mLinePaint, mPointPaint, mTextPaint, mCirclePaint;
private DashPathEffect dashedEffect;
private PointF p1, p2, m, c;
public TriangleAndArc(Context context) {
super(context);
init(context);
}
public TriangleAndArc(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public TriangleAndArc(Context context, AttributeSet attrs, int style) {
super(context, attrs, style);
init(context);
}
private void init(Context ctx) {
p1 = new PointF();
p2 = new PointF();
c = new PointF();
m = new PointF();
Resources res = ctx.getResources();
density = res.getDisplayMetrics().density;
a1Degrees = 36.0f;
a1Radians = Math.toRadians(a1Degrees);
dashedEffect = new DashPathEffect(new float[]{5, 5}, 1.0f);
mLinePaint = new Paint();
mLinePaint.setAntiAlias(true);
mLinePaint.setStyle(Paint.Style.STROKE);
mLinePaint.setStrokeJoin(Paint.Join.ROUND);
mLinePaint.setStrokeWidth(2 * density);
mLinePaint.setColor(Color.BLACK);
//mPaint.setPathEffect(dashedEffect);
mPointPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPointPaint.setStyle(Paint.Style.FILL);
mPointPaint.setColor(Color.RED);
mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTextPaint.setStyle(Paint.Style.FILL);
mTextPaint.setTextSize(10 * density);
mTextPaint.setTypeface(Typeface.create("Roboto-Thin", Typeface.BOLD));
mTextPaint.setColor(Color.RED);
if(!isInEditMode()) {
// Shadow layer is not supported in preview mode and android studio makes an ugly warning about it!
mTextPaint.setShadowLayer(0.1f, 0, 1, Color.GRAY);
}
textHeight = Math.abs(mTextPaint.descent() + mTextPaint.ascent());
mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mCirclePaint.setColor(Color.BLUE);
mCirclePaint.setStyle(Paint.Style.STROKE);
mCirclePaint.setStrokeWidth(density);
}
// Square a number
private double sq(double a) {
return a * a;
}
private void drawIncenterAndExcenter(PointF A, PointF B, PointF C, Canvas canvas) {
double a = Math.sqrt((B.x - C.x) * (B.x - C.x) + (B.y - C.y) * (B.y - C.y));
double b = Math.sqrt((A.x - C.x) * (A.x - C.x) + (A.y - C.y) * (A.y - C.y));
double c = Math.sqrt((A.x - B.x) * (A.x - B.x) + (A.y - B.y) * (A.y - B.y));
double perimeter = a + b + c;
double s = 0.5 * perimeter;
double area = Math.sqrt(s * (s - a) * (s - b) * (s - c));
// Inscribed Circle
double iRadius = area / s;
PointF iCenter = new PointF();
iCenter.x = (float) ((a * A.x + b * B.x + c * C.x) / (perimeter));
iCenter.y = (float) ((a * A.y + b * B.y + c * C.y) / (perimeter));
// Circumscribed Circle
PointF cCenter = new PointF();
double cRadius = (a * b * c) / (4.0 * area);
double D = 2 * (A.x * (B.y - C.y) + B.x * (C.y - A.y) + C.x * (A.y - B.y));
double sqA = sq(A.x) + sq(A.y);
double sqB = sq(B.x) + sq(B.y);
double sqC = sq(C.x) + sq(C.y);
cCenter.x = (float) ((sqA * (B.y - C.y) + sqB * (C.y - A.y) + sqC * (A.y - B.y)) / D);
cCenter.y = (float) ((sqA * (C.x - B.x) + sqB * (A.x - C.x) + sqC * (B.x - A.x)) / D);
// Draw
canvas.drawCircle(iCenter.x, iCenter.y, density * 5, mPointPaint);
canvas.drawCircle(iCenter.x, iCenter.y, (float) iRadius, mCirclePaint);
canvas.drawText("I", iCenter.x - mTextPaint.measureText("I") * 0.5f, iCenter.y - textHeight - 2 * density, mTextPaint);
canvas.drawCircle(cCenter.x, cCenter.y, density * 5, mPointPaint);
canvas.drawCircle(cCenter.x, cCenter.y, (float) cRadius, mCirclePaint);
canvas.drawText("E", cCenter.x - mTextPaint.measureText("E") * 0.5f, cCenter.y - textHeight - 2 * density, mTextPaint);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
measuredHeight = getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec);
measuredWidth = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);
setMeasuredDimension(measuredWidth, measuredHeight);
//Log.d(TAG, "Height: " + measuredHeight + " Width: " + measuredWidth);
}
@Override
protected void onDraw(Canvas canvas) {
if (measuredHeight <= 0 || measuredWidth <= 0) {
// Not much we can draw... :/
return;
}
// Orientation independent drawing
int width = (measuredWidth > measuredHeight) ? measuredWidth : measuredHeight;
int height = (width == measuredWidth) ? measuredHeight : measuredWidth;
// Define points
p1.x = width * 0.7f;
p1.y = height * 0.2f - 20;
p2.x = width * 0.85f;
p2.y = 2 * height / 3.0f - 20;
float dx = p2.x - p1.x;
float dy = p2.y - p1.y;
// l1 is half the length of the line from p1 to p2
double l = Math.sqrt(dx * dx + dy * dy);
double l1 = l / 2.0;
// Center of the circle
double h = l1 / (Math.tan(a1Radians / 2.0));
// Radius of the circle
double r = l1 / (Math.sin(a1Radians / 2.0));
// a2 is the angle at which L intersects the x axis
double a2 = Math.atan2(dy, dx);
// a3 is the angle at which H intersects the x axis
double a3 = (Math.PI / 2.0) - a2;
// m is the midpoint of the line from e1 to e2
m.x = (p1.x + p2.x) / 2.0f;
m.y = (p1.y + p2.y) / 2.0f;
// c is the the center of the circle
c.x = (float) (m.x - (h * Math.cos(a3)));
c.y = (float) (m.y + (h * Math.sin(a3)));
// rect is the square RectF that bounds the "oval"
RectF oval = new RectF((float) (c.x - r), (float) (c.y - r), (float) (c.x + r), (float) (c.y + r));
// a4 is the starting sweep angle
double rawA4 = Math.atan2(p1.y - c.y, p1.x - c.x);
float a4 = (float) Math.toDegrees(rawA4);
// Draw lines
canvas.drawLine(p1.x, p1.y, p2.x, p2.y, mLinePaint);
canvas.drawLine(c.x, c.y, p1.x, p1.y, mLinePaint);
canvas.drawLine(c.x, c.y, p2.x, p2.y, mLinePaint);
canvas.drawLine(c.x, c.y, m.x, m.y, mLinePaint);
// Draw arc
mLinePaint.setPathEffect(dashedEffect);
canvas.drawArc(oval, a4, a1Degrees, false, mLinePaint);
// Draw dots
canvas.drawCircle(p1.x, p1.y, density * 5, mPointPaint);
canvas.drawCircle(p2.x, p2.y, density * 5, mPointPaint);
canvas.drawCircle(m.x, m.y, density * 5, mPointPaint);
canvas.drawCircle(c.x, c.y, density * 5, mPointPaint);
// Draw text
float halfOfTextHeight = textHeight * 0.5f; // We need an offset of a half
canvas.drawText("p1", p1.x + density * 7, p1.y + halfOfTextHeight, mTextPaint);
canvas.drawText("p2", p2.x + density * 7, p2.y + halfOfTextHeight, mTextPaint);
canvas.drawText("m", m.x + density * 7, m.y + halfOfTextHeight, mTextPaint);
canvas.drawText("c", c.x - mTextPaint.measureText("c") - density * 7, c.y + halfOfTextHeight, mTextPaint);
drawIncenterAndExcenter(p1, p2, c, canvas);
}
}
现在最后是名为 canvas_demo.xml 的布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:triangle="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:weightSum="2">
<si.kseneman.views.Triangle
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:padding="10dp"
android:rotation="0"
triangle:triangleStrokeColor="@android:color/black"
triangle:triangleColor="#FF33B5E5"
triangle:triangleStrokeWidth="3dp"/>
<si.kseneman.views.TriangleAndArc
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"/>
</LinearLayout>
最终结果将如下所示:
您可以使用android:rotation属性来旋转它,例如使用 180 它看起来像这样
三角形当然可以更小,无论如何它是可扩展的。如果此设置不适合您,您应该设置它的点 a、b、c。
注意:我没有对参数进行任何检查,因此如果您设置的 strokeWidth 值大于视图本身,则不会出现任何错误。小心是你的责任:)
快乐编码!