Android 自定义View 遥控器按钮

**在这里插入图片描述**
之前弄过一个,但都是复制来的,经过上一篇的学习,这次自己用kotlin写一个

自定义 View

kotlin
class RemoteControlMenu: View {

    private lateinit var paint: Paint

    private val buttonNum: Int = 4 //按钮数量
    private val offsetAngel: Float = 10f;    //每个按键之间的距离为10°,要求offsetAngel*buttonNum<=360
    private val sweepAngel: Float = (360 - offsetAngel * buttonNum) / buttonNum // 计算每个按键的角度
    private var bigCircleOuterRadius: Float = 300f  // 上下左右按键外半径,300f默认值
    private var bigCircleInnerRadius: Float = 200f  // 上下左右按键内半径,200f默认值
    private var enterCircleRadius: Float = 150f     // ok按键圆半径,150f默认值
    private val unselectedColor: Int = Color.RED    // 未点击时的按键颜色
    private val selectedColor: Int = Color.YELLOW   // 点击时的按键颜色

    private lateinit var buttonRegionList: ArrayList<Region>
    private lateinit var buttonPathList: ArrayList<Path>

    private var previousTouchArea: Int = -1     //上一个触碰的区域
    private var touchArea: Int = -1             //触碰的区域

    private var myOnClickListener: MyOnClickListener? = null //回调方法

    constructor(context: Context): this(context = context, attributeSet = null)
    constructor(context: Context, attributeSet: AttributeSet?): this(context = context,attributeSet = attributeSet,defStyleAttr = 0)
    constructor(context: Context, attributeSet: AttributeSet?, defStyleAttr: Int): super(context,attributeSet,defStyleAttr){
        paint = Paint()
        paint.color = unselectedColor
        paint.style = Paint.Style.FILL
        paint.strokeWidth = 3f;

        buttonPathList = ArrayList<Path>(buttonNum+1)
        buttonRegionList = ArrayList<Region>(buttonNum+1)
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        val canvasWidth: Int = w    // 画布长宽
        val canvasHeight: Int = h

        bigCircleOuterRadius = w.coerceAtMost(h).toFloat()/2    // 计算圆的半径
        bigCircleInnerRadius = bigCircleOuterRadius*2/3
        enterCircleRadius = bigCircleOuterRadius/2


        val circleCenterX:Float = w.toFloat()/2     //计算圆心位置
        val circleCenterY:Float = h.toFloat()/2

        val globalRegion: Region = Region(0, 0, canvasWidth, canvasHeight)

        var startAngel: Float = -90 - sweepAngel/2  // 开始角度,最开始画的为最上面一个,index为0,顺时针一次递增
        for(i in 0 until buttonNum){
            val path: Path = Path()
            val region: Region = Region()
            path.addArc(circleCenterX - bigCircleOuterRadius, circleCenterY - bigCircleOuterRadius, circleCenterX + bigCircleOuterRadius, circleCenterY + bigCircleOuterRadius, startAngel, sweepAngel)
            path.arcTo(circleCenterX - bigCircleInnerRadius, circleCenterY - bigCircleInnerRadius, circleCenterX + bigCircleInnerRadius, circleCenterY + bigCircleInnerRadius, startAngel+sweepAngel, -sweepAngel, false)
            path.close()
            buttonPathList.add(path)

            region.setPath(path, globalRegion)
            buttonRegionList.add(region)

            startAngel += sweepAngel + offsetAngel;
        }
        val centerPath: Path = Path()   // 最后中心 ok 按键
        val centerRegion: Region = Region()
        centerPath.addCircle(circleCenterX, circleCenterY, enterCircleRadius, Path.Direction.CW)
        centerRegion.setPath(centerPath, globalRegion)
        buttonPathList.add(centerPath)
        buttonRegionList.add(centerRegion)
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)

        buttonPathList.forEachIndexed { index, path ->
            if (index == touchArea){
                paint.color = selectedColor
            }else{
                paint.color = unselectedColor
            }
            canvas?.drawPath(path, paint)
        }

    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        buttonRegionList.also {
            it.forEachIndexed regionLoop@ { index, region ->
                if (region.contains(event?.x?.toInt() ?: 0, event?.y?.toInt() ?: 0)) {
                    touchArea = index
                    if (event?.action == MotionEvent.ACTION_UP){
                        Log.d("zwt", "is upup:::"+index)
                        myOnClickListener?.onClick(index)
                        if(touchArea != -1){    //手松开的区域在按钮之中
                            previousTouchArea = -1
                            touchArea = -1
                            invalidate()
                        }
                    }
                    return@also
                }else {
                    touchArea = -1
                }
            }
        }
        if (previousTouchArea != touchArea) { //重新绘制,手指按住按钮移出按钮区域的情况
            previousTouchArea = touchArea
            invalidate()
        }
        return true
    }

    fun setOnClickListener(myOnClickListener: MyOnClickListener) {
        this.myOnClickListener = myOnClickListener
    }

    interface MyOnClickListener{
        fun onClick(touchPosition: Int)
    }

}
Java
public class JRemoteControlMenu extends View {

    private Paint paint;

    private final int buttonNum = 4;
    private final float offsetAngel = 10f;
    private final float sweepAngel = (360 - offsetAngel * buttonNum) / buttonNum; // 计算每个按键的角度

    private float bigCircleOuterRadius = 300f;  // 上下左右按键外半径,300f默认值
    private float bigCircleInnerRadius = 200f;  // 上下左右按键内半径,200f默认值
    private float enterCircleRadius = 150f;     // ok按键圆半径,150f默认值

    private final int unselectedColor = Color.RED;    // 未点击时的按键颜色
    private final int selectedColor = Color.RED;      // 点击时的按键颜色

    private ArrayList<Region> buttonRegionList;
    private ArrayList<Path> buttonPathList;

    private int previousTouchArea  = -1;     //上一个触碰的区域
    private int touchArea = -1;              //触碰的区域

    private MyOnClickListener myOnClickListener = null; //回调方法

    public JRemoteControlMenu(Context context) {
        this(context, null);
    }

    public JRemoteControlMenu(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public JRemoteControlMenu(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        paint = new Paint();
        paint.setColor(unselectedColor);
        paint.setStyle(Paint.Style.FILL);
        paint.setStrokeWidth(3f);

        buttonPathList = new ArrayList<Path>(buttonNum+1);
        buttonRegionList = new ArrayList<Region>(buttonNum+1);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        float canvasWidth = (float) w;
        float canvasHeight = (float) h;

        bigCircleOuterRadius = Math.min(canvasWidth, canvasHeight)/2; // 计算圆的半径
        bigCircleInnerRadius = bigCircleOuterRadius*2/3;
        enterCircleRadius = bigCircleOuterRadius/2;

        float circleCenterX =  canvasWidth/2;     //计算圆心位置
        float circleCenterY = canvasHeight/2;

        Region globalRegion = new Region(0, 0, w, h);

        float startAngel = -90 - sweepAngel/2;
        for (int i=0; i<buttonNum; i++){
            Path path = new Path();
            Region region = new Region();
            path.addArc(circleCenterX - bigCircleOuterRadius, circleCenterY - bigCircleOuterRadius, circleCenterX + bigCircleOuterRadius, circleCenterY + bigCircleOuterRadius, startAngel, sweepAngel);
            path.arcTo(circleCenterX - bigCircleInnerRadius, circleCenterY - bigCircleInnerRadius, circleCenterX + bigCircleInnerRadius, circleCenterY + bigCircleInnerRadius, startAngel+sweepAngel, -sweepAngel, false);
            path.close();
            buttonPathList.add(path);

            region.setPath(path, globalRegion);
            buttonRegionList.add(region);

            startAngel += sweepAngel + offsetAngel;
        }
        Path centerPath = new Path();   // 最后中心 ok 按键
        Region centerRegion = new Region();
        centerPath.addCircle(circleCenterX, circleCenterY, enterCircleRadius, Path.Direction.CW);
        centerRegion.setPath(centerPath, globalRegion);
        buttonPathList.add(centerPath);
        buttonRegionList.add(centerRegion);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (canvas!=null){
            for (int index=0; index<buttonPathList.size(); index++){
                if (index == touchArea){
                    paint.setColor(selectedColor);
                }else{
                    paint.setColor(unselectedColor);
                }
                canvas.drawPath(buttonPathList.get(index), paint);
            }
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        for (int index=0; index<buttonRegionList.size(); index++){
            if (buttonRegionList.get(index).contains((int)event.getX(), (int)event.getY())){
                touchArea = index;
                if (event.getAction() == MotionEvent.ACTION_UP){
                    if (myOnClickListener!=null)
                        myOnClickListener.onClick(index);
                    if (touchArea != -1){
                        previousTouchArea = -1;
                        touchArea = -1;
                        invalidate();
                    }
                    break;
                }else {
                    touchArea = -1;
                }
            }
            if (previousTouchArea != touchArea) { //重新绘制
                previousTouchArea = touchArea;
                invalidate();
            }
        }
        return true;
    }

    public void setOnClickListener(MyOnClickListener myOnClickListener){
        this.myOnClickListener = myOnClickListener;
    }

    interface MyOnClickListener{
        public void onClick(int touchPosition);
    }
}

红外遥控工具类

class ConsumerIrManagerApi {

    private var context: Context? = null
    private var service: ConsumerIrManager? = null

    companion object{ @SuppressLint("StaticFieldLeak")
    val instance = ConsumerIrManagerApiHolder.holder }

    fun getInstance(): ConsumerIrManagerApi{
        return instance;
    }

    fun init(context: Context): Boolean{
        this.context = context
        // android 4.4 版本以上
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
            service = context.applicationContext.getSystemService(Context.CONSUMER_IR_SERVICE) as ConsumerIrManager?
            return hasIrEmitter()
        }
        return false
    }

    // 手机是否有红外功能
    fun hasIrEmitter(): Boolean{
        return service?.hasIrEmitter() == true
    }
    // 发射红外信号
    fun transmit(carrierFrequency: Int, pattern:IntArray){
        Log.d("zwt",""+(service == null))
        service?.transmit(carrierFrequency, pattern)
    }
    fun transmit(carrierFrequency: Int, customerID: String, keyCode: String ){
        val paramString: String = transformationCustomerID(customerID) + transformationKeyCode(keyCode)

        var pattern: IntArray = IntArray(paramString.length * 2 + 4)
        pattern[0] = 9000;
        pattern[1] = 4500;

        var j: Int = 2

        paramString.forEach{ c ->
            if (c == '1'){
                pattern[j] = 560
                pattern[(j + 1)] = 1680
            }else{
                pattern[j] = 560
                pattern[(j + 1)] = 560
            }
            j += 2
        }
        pattern[j] = 560
        pattern[(j + 1)] = 20000

        transmit(carrierFrequency, pattern)
    }
    // 获取可支持的红外信号
    fun getCarrierFrequencies(): Array<out ConsumerIrManager.CarrierFrequencyRange>? {
        return service?.carrierFrequencies
    }
    private object ConsumerIrManagerApiHolder {
        @SuppressLint("StaticFieldLeak")
        val holder = ConsumerIrManagerApi()
    }
    /*****************************************/
    // 转换用户码
    private fun transformationCustomerID(customerID: String): String{
        if (customerID.length == 4){
            val a = customerID.subSequence(0, 2)
            val b = customerID.subSequence(2, 4)

            //手机与遥控器的信号编码有区别,需要逆序编码
            var sb: StringBuilder = StringBuilder(hexToBinary("$b$a", true)).reverse()
            Log.d("zwt", "::::::${sb.toString()}")
            return sb.toString()
        }
        return ""
    }
    private fun transformationKeyCode(keyCode: String): String{
        if (keyCode.length == 2 && isHexNumber(keyCode)){
            val complement: String = ("FF".toInt(16) - keyCode.toInt(16)).toString(16) //计算补码

            //手机与遥控器的信号编码有区别,需要逆序编码
            var sb: StringBuilder = StringBuilder(hexToBinary("$complement$keyCode", true)).reverse()
            Log.d("zwt", "::::::${sb.toString()}")
            return sb.toString()
        }
        return ""
    }
    // 判断字符串是否符合16进制格式
    private fun isHexNumber(paramString: String): Boolean {
        return paramString.matches("^[A-Fa-f0-9]+$".toRegex())
    }

    // 将16进制字符串转化为二进制字符串
    private fun hexToBinary(paramString: String): String{
        if (isHexNumber(paramString)){
//            var dec: Int = paramString.toInt(16)  //16进制字符串转换成10进制Int
//            var binary: String = dec.toString(2)  //10进制Int转换2进制字符串
            return paramString.toInt(16).toString(2)
        }
        return ""
    }
    private fun hexToBinary(paramString: String, isFormat: Boolean): String{
        val binaryString: String = hexToBinary(paramString)
        if (binaryString == "") return ""
        if (isFormat){
            val binaryLength: Int = paramString.length*4
            return binaryString.padStart(binaryLength, '0') // 格式化
        }
        return binaryString
    }
}
Java
public class JConsumerIrManagerApi {

    private Context context;
    private ConsumerIrManager service;

    private JConsumerIrManagerApi  instance;

    private JConsumerIrManagerApi (){
        instance = ConsumerIrManagerApiHolder.holder;
    }

    public JConsumerIrManagerApi  getInstance(){
        return instance;
    }

    public boolean init(Context context){
        this.context = context;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
            service = (ConsumerIrManager) context.getApplicationContext().getSystemService(Context.CONSUMER_IR_SERVICE);
            return hasIrEmitter();
        }
        return false;
    }
    // 手机是否有红外功能
    public Boolean hasIrEmitter(){
        return service.hasIrEmitter() == true;
    }
    // 发射红外信号
    public void transmit(int carrierFrequency, int[] pattern){
        Log.d("zwt",""+(service == null));
        service.transmit(carrierFrequency, pattern);
    }

    // 获取可支持的红外信号
    public ConsumerIrManager.CarrierFrequencyRange[] getCarrierFrequencies() {
        return service.getCarrierFrequencies();
    }

    public void transmit(int carrierFrequency, String customerID, String keyCode){
        String paramString = transformationCustomerID(customerID) + transformationKeyCode(keyCode);

        int[] pattern = new int[paramString.length() * 2 + 4];
        pattern[0] = 9000;
        pattern[1] = 4500;

        int j = 2;

        for (char c : paramString.toCharArray()){
            if (c == '1'){
                pattern[j] = 560;
                pattern[(j + 1)] = 1680;
            }else{
                pattern[j] = 560;
                pattern[(j + 1)] = 560;
            }
            j += 2;
        }

        pattern[j] = 560;
        pattern[(j + 1)] = 20000;

        transmit(carrierFrequency, pattern);
    }

    private static class ConsumerIrManagerApiHolder {
        public static JConsumerIrManagerApi  holder = new JConsumerIrManagerApi ();
    }
    /*****************************************/
    // 转换用户码
    private String transformationCustomerID(String customerID){
        if (customerID.length() == 4){
            String a = customerID.substring(0, 2);
            String b = customerID.substring(2, 4);

            //手机与遥控器的信号编码有区别,需要逆序编码
            StringBuilder sb = new StringBuilder(hexToBinary(b+a, true)).reverse();
            Log.d("zwt", "::::::${sb.toString()}");
            return sb.toString();
        }
        return "";
    }
    private String transformationKeyCode(String keyCode) {
        if (keyCode.length() == 2 && isHexNumber(keyCode)){
            String complement = Integer.toHexString(Integer.parseInt("FF", 16) - Integer.parseInt(keyCode, 16)); //计算补码

            //手机与遥控器的信号编码有区别,需要逆序编码
            StringBuilder sb = new StringBuilder(hexToBinary(complement+keyCode, true)).reverse();
            Log.d("zwt", "::::::${sb.toString()}");
            return sb.toString();
        }
        return "";
    }
    // 判断字符串是否符合16进制格式
    private Boolean isHexNumber(String paramString) {
        return paramString.matches("^[A-Fa-f0-9]+$");
    }
    // 将16进制字符串转化为二进制字符串
    private String hexToBinary(String paramString){
        if (isHexNumber(paramString)){
            return Integer.toBinaryString(Integer.parseInt(paramString, 16));
        }
        return "";
    }
    private String hexToBinary(String paramString , Boolean isFormat) {
        String binaryString = hexToBinary(paramString);
        if ("".equals(binaryString)) return "";
        if (isFormat){
            int binaryLength = paramString.length()*4;
            return String.format("%"+binaryLength+"s", new Object[] {binaryString}).replace(' ', '0');
        }
        return binaryString;
    }
}

调用

class MainActivity : Activity() {
    private val TAG: String = "MainActivity"

    private val customerID: String = "03fc" // 客户头码

    lateinit var remoteControlMenu: RemoteControlMenu


    @SuppressLint("MissingPermission")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initAllDatum()
        initAllViews()
    }

    private fun initAllDatum(){
        if (!ConsumerIrManagerApi.instance.init(this)){  // 初始化红外工具类
            //TODO 手机不支持红外,初始化失败
        }
    }

    private fun initAllViews(){
        remoteControlMenu = findViewById(R.id.view111)
        remoteControlMenu.setOnClickListener(object : RemoteControlMenu.MyOnClickListener{ // 监听
            override fun onClick(touchPosition: Int) {
                when(touchPosition){
                    0 -> {  // 上
                        ConsumerIrManagerApi.instance.transmit(38000, customerID, "0b")
                    }
                    1 -> {  // 右
                        ConsumerIrManagerApi.instance.transmit(38000, customerID, "11")
                    }
                    2 ->{   // 下
                        ConsumerIrManagerApi.instance.transmit(38000, customerID, "0e")
                    }
                    3 ->{   // 左
                        ConsumerIrManagerApi.instance.transmit(38000, customerID, "10")
                    }
                    4 ->{   // ok
                        ConsumerIrManagerApi.instance.transmit(38000, customerID, "0d")
                    }
                }
            }
        })
    }
}
//var patterns: IntArray = intArrayOf(9000,4500,// 开头两个数字表示引导码
//    // 下面两行表示用户码 03fc 00000011  11111100
//    560,1680, 560,1680, 560,560, 560,560, 560,560, 560,560, 560,560, 560,560,
//    560,560, 560,560, 560,1680, 560,1680, 560,1680, 560,1680, 560,1680, 560,1680,
//    // 下面一行表示数据码 0d 00001101
//    560,1680, 560,560, 560,1680, 560,1680, 560,560, 560,560, 560,560, 560,560,
//    // 下面一行表示数据反码 ff-0d=f2 11110010
//    560,560, 560,1680, 560,560, 560,560, 560,1680, 560,1680, 560,1680, 560,1680,
//    560,20000)// 末尾两个数字表示结束码

稍加修饰

在这里插入图片描述
链接:https://pan.baidu.com/s/1mgnanX-H-RanezFwFKXTkA
提取码:6z4b

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值