安卓自定义View

安卓自定义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>
``
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值