安卓自定义View系列
自定义圆形麦克风
前言
对讲应用发射键用图片在不同设备上容易模糊不清,达不到清晰的要求,索性写个自定义View。
外圈是圆形,中间是个麦克风 的自定义View 应运而生了。
先看看效果图
效果图
先来做个自定义属性
一、自定义属性
<declare-styleable name="PttView">
<!-- 外圆 画笔 描边 宽度-->
<attr name="ptt_view_circle_paint_stroke_width" format="reference|float|dimension" />
<!-- 外圆 画笔的颜色-->
<attr name="ptt_view_circle_paint_color" format="reference|color" />
<!-- 外圆 画笔样式 开始 -->
<attr name="ptt_view_shader_line_x0" format="reference|float|dimension" />
<attr name="ptt_view_shader_line_y0" format="reference|float|dimension" />
<attr name="ptt_view_shader_line_x1" format="reference|float|dimension" />
<attr name="ptt_view_shader_line_y1" format="reference|float|dimension" />
<attr name="ptt_view_shader_color_1" format="reference|color" />
<attr name="ptt_view_shader_color_2" format="reference|color" />
<!-- 外圆 画笔样式 结束 -->
<!-- 麦克风 画笔 描边 宽度-->
<attr name="ptt_view_mic_paint_stroke_width" format="reference|float|dimension" />
<!-- 麦克风 画笔的颜色-->
<attr name="ptt_view_mic_paint_color" format="reference|color" />
<attr name="ptt_view_image_resource" format="reference" />
</declare-styleable>
二、完整编码如下
package com.linkpoon.hamlauncher.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Shader;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.linkpoon.hamlauncher.R;
import com.linkpoon.hamlauncher.util.DisplayUtil;
public class PttView extends View {
public PttView(Context context) {
super(context);
init(context, null, 0);
}
public PttView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs, 0);
}
public PttView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
private int dipToPx(float v) {
return DisplayUtil.dip2px(getContext(), v);
}
private Paint paintCircle;// 外围的圆
private Paint paintMic;// 中心的麦克风 画笔
private Paint paintMicArc; //麦克风圆弧部分 画笔
private RectF roundRectF;// 用于绘制 麦克风上面的圆角矩形部分
private RectF micRectF;// 用于绘制 麦克风中间的弧线部分
private boolean isRunning = false;
private final Handler handlerRun = new Handler(Looper.getMainLooper());
private final Runnable runnableRun = new Runnable() {
@Override
public void run() {
changeAlpha();
handlerRun.postDelayed(runnableRun, 500);
}
};
private int currentCircleAlpha = 255;
private void init(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
int circleStrokeWidth;
int circleColor;
int micStrokeWidth;
int micColor;
int color1;
int color2;
int color1Def = context.getResources().getColor(R.color.color_ptt_normal_1);
int color2Def = context.getResources().getColor(R.color.color_ptt_normal_2);
float x0;
float y0;
float x1;
float y1;
try (TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.PttView)) {
float circleStrokeWidthDef = typedArray.getFloat(R.styleable.PttView_ptt_view_circle_paint_stroke_width, 3);
circleStrokeWidth = dipToPx(circleStrokeWidthDef);
circleColor = typedArray.getColor(R.styleable.PttView_ptt_view_circle_paint_color, color2Def);
color1 = typedArray.getColor(R.styleable.PttView_ptt_view_shader_color_1, color1Def);
color2 = typedArray.getColor(R.styleable.PttView_ptt_view_shader_color_2, color2Def);
x0 = typedArray.getFloat(R.styleable.PttView_ptt_view_shader_line_x0, 0);
x1 = typedArray.getFloat(R.styleable.PttView_ptt_view_shader_line_y0, 0);
y0 = typedArray.getFloat(R.styleable.PttView_ptt_view_shader_line_x1, 500);
y1 = typedArray.getFloat(R.styleable.PttView_ptt_view_shader_line_y1, 500);
float micStrokeWidthDef = typedArray.getFloat(R.styleable.PttView_ptt_view_mic_paint_stroke_width, 3);
micStrokeWidth = DisplayUtil.dip2px(context, micStrokeWidthDef);
micColor = typedArray.getColor(R.styleable.PttView_ptt_view_mic_paint_color, color2Def);
//typedArray.recycle();
}
paintCircle = new Paint();
paintCircle.setColor(circleColor);
paintCircle.setStrokeWidth(circleStrokeWidth);
paintCircle.setStyle(Paint.Style.STROKE);
paintCircle.setAntiAlias(true);// 抗锯齿
int[] colorArray = {color1, color2};
LinearGradient linearGradient = new LinearGradient(x0, y0, x1, y1, colorArray, null, Shader.TileMode.MIRROR);
paintCircle.setShader(linearGradient);
paintMic = new Paint();
paintMic.setColor(micColor);
paintMic.setStrokeWidth(micStrokeWidth);
paintMic.setStyle(Paint.Style.FILL);
paintMic.setAntiAlias(true);// 抗锯齿
paintMicArc = new Paint();
paintMicArc.setColor(micColor);
paintMicArc.setStrokeWidth(micStrokeWidth);
paintMicArc.setStyle(Paint.Style.STROKE);
paintMicArc.setAntiAlias(true);// 抗锯齿
roundRectF = new RectF();
micRectF = new RectF();
}
public boolean isRunning() {
return this.isRunning;
}
public void setRunning(boolean running) {
this.isRunning = running;
}
public void setCircleColor(int color) {
if (paintCircle != null) {
paintCircle.setColor(color);
postInvalidate();
}
}
public void setCircleStrokeWidth(int strokeWidth) {
if (paintCircle != null) {
paintCircle.setStrokeWidth(strokeWidth);
postInvalidate();
}
}
public void setCircleShader(Shader shader) {
if (paintCircle != null && shader != null) {
paintCircle.setShader(shader);
postInvalidate();
}
}
public void setMicColor(int color) {
if (paintMic != null) {
paintMic.setColor(color);
postInvalidate();
}
}
public void setMicArcColor(int color) {
if (paintMicArc != null) {
paintMicArc.setColor(color);
postInvalidate();
}
}
@Override
protected void onDraw(@NonNull Canvas canvas) {
super.onDraw(canvas);
int width = getMeasuredWidth();
int height = getMeasuredHeight();
float minValue = (float) Math.min(width, height);
float radius = (float) (minValue * (0.85) / 2); //半径
float cx = (float) (width / 2); //圆心横坐标
float cy = (float) (height / 2); //圆心纵坐标
//Log.i("pttView", "width==" + width + ",height==" + height + ",radius==" + radius + ",cx==" + cx + ",cy==" + cy);
//paintCircle.setAlpha();
canvas.drawCircle(cx, cy, radius, paintCircle);
float paddingTopRect = 0;// 单位 dp 偏移量,负数表示麦克风图形向上移动,正数向下,0不偏移
float roundLeft = cx - cx * 0.023f;
float roundTop = cy * 0.72f + dipToPx(paddingTopRect);
float roundRight = cx + cx * 0.023f;
float roundBottom = cy + dipToPx(paddingTopRect - 6);
// 设置矩形区域,用于确定麦克风上部的位置
roundRectF.set(roundLeft, roundTop, roundRight, roundBottom);
// 画一个圆角矩形 圆角度数18
canvas.drawRoundRect(roundRectF, 16, 16, paintMic);
float vLineStartY = cy + dipToPx(paddingTopRect + 3);
float baseVLineStopY = cy + cy * 0.12f;
float vLineStopY = baseVLineStopY + dipToPx(paddingTopRect + 3);
// 画一段竖线
canvas.drawLine(cx, vLineStartY, cx, vLineStopY, paintMic);
float dxForArc;
if (radius <= 19) {
// 圆形很小的情况下 ,圆弧部分要窄些,不然很难看
dxForArc = cx * 0.04f;
} else if (radius <= 36) {
dxForArc = cx * 0.07f;
} else {
// 圆形较大的情况下 ,圆弧部分要宽些,不然很难看
dxForArc = cx * 0.18f;
}
float micArcLeft = cx - dxForArc;
float micArcTop = cy * 0.68f + dipToPx(paddingTopRect + 3);
float micArcRight = cx + dxForArc;
float micArcBottom = cy + dipToPx(paddingTopRect + 3);
micRectF.set(micArcLeft, micArcTop, micArcRight, micArcBottom);
/***
* path.arcTo 用来绘制一段圆弧,实际上是截取圆或者椭圆的一部分
* 该方法接收三个参数,
* 第一个表示弧形所在的矩形,
* 如果矩形为正方形,则画出的弧形为圆的一部分,
* 如果矩形宽高不等,画出的弧形为椭圆的一部分,
* 第二个参数表示绘制的起点位置,
* 0 度为钟表三点位置, 顺时针方向为正
* 第三个参数表示绘制的度数。
* */
//画一段圆弧
//canvas.drawArc(micRectF, -10, 180 + 10 + 10, false, paintMicArc);
canvas.drawArc(micRectF, 0, 180, false, paintMicArc);
float hLineStartX = cx - cx / 15;
float hLineStartY = baseVLineStopY + dipToPx(paddingTopRect + 5);
float hLineStopX = cx + cx / 15;
float hLineStopY = baseVLineStopY + dipToPx(paddingTopRect + 5);
// 画 麦克风 底部的 横线
canvas.drawLine(hLineStartX, hLineStartY, hLineStopX, hLineStopY, paintMic);
}
private void changeAlpha() {
if (currentCircleAlpha == 255) {
currentCircleAlpha = 30;
} else {
currentCircleAlpha = 255;
}
if (paintCircle != null) {
paintCircle.setAlpha(currentCircleAlpha);
invalidate();
}
}
public void startRunning() {
setRunning(true);
handlerRun.removeCallbacks(runnableRun);
handlerRun.removeCallbacksAndMessages(null);
handlerRun.post(runnableRun);
}
public void stopRunning() {
setRunning(false);
handlerRun.removeCallbacks(runnableRun);
handlerRun.removeCallbacksAndMessages(null);
if (paintCircle != null) {
currentCircleAlpha = 255;
paintCircle.setAlpha(currentCircleAlpha);
invalidate();
}
}
}
二、如何使用?
<?xml version="1.0" encoding="utf-8"?>
<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:orientation="vertical">
<com.linkpoon.hamlauncher.view.PttView
android:id="@+id/vox_group_view_ptt"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center"
app:ptt_view_circle_paint_stroke_width="4"
app:ptt_view_mic_paint_stroke_width="6"
app:ptt_view_shader_color_1="@color/color_ptt_normal_1"
app:ptt_view_shader_color_2="@color/color_ptt_normal_2"
app:ptt_view_shader_line_x0="0"
app:ptt_view_shader_line_x1="500"
app:ptt_view_shader_line_y0="0"
app:ptt_view_shader_line_y1="500" />
</LinearLayout>
``