菜单中的类似iOS中开关的样式

news2024/11/18 19:45:42

背景是我们有需求,做类似ios中开关的按钮。github上有一些开源项目,比如 SwitchButton, 但是这个项目中提供了很多选项,并且实际使用中会出现一些奇怪的问题。

我调整了下代码,把无关的功能都给删了,保留核心的功能,大概这样。

在这里插入图片描述
在这里插入图片描述

package org.yeshen.widget;

// 修改自:https://github.com/zcweng/SwitchButton
// 菜单中的类似iOS中开关的样式

import static org.yeshen.widget.YsSwitchButton.ANIMATE.*;

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.Build;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Checkable;


public final class YsSwitchButton extends View implements Checkable {
    private static final int DEFAULT_WIDTH = dp2pxInt(44);
    private static final int DEFAULT_HEIGHT = dp2pxInt(25);

    private static final int DEFAULT_BUTTON_PADDING = dp2pxInt(8);

    private final int uncheckColor = 0xFFFF0000;

    private final int checkedColor = 0xFF0000FF;

    private final int uncheckButtonColor = Color.WHITE;

    private float viewRadius;

    private float left;
    private float top;
    private float right;
    private float bottom;
    private float centerY;

    private float buttonMinX;

    private float buttonMaxX;

    private final Paint buttonPaint;

    private final Paint paint;

    private final ViewState viewState;
    private final ViewState beforeState;
    private final ViewState afterState;

    private final RectF rect = new RectF();

    private ANIMATE animateState = ANIMATE_STATE_NONE;

    private final ValueAnimator valueAnimator;

    private final android.animation.ArgbEvaluator argbEvaluator = new android.animation.ArgbEvaluator();

    private boolean isChecked = false;

    private boolean isTouchingDown = false;
    private boolean isUiInit = false;
    private boolean isEventBroadcast = false;

    private OnCheckedChangeListener onCheckedChangeListener;

    private long touchDownTime;

    private boolean switchByUser;

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

    public YsSwitchButton(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public YsSwitchButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        buttonPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        buttonPaint.setColor(uncheckButtonColor);
        viewState = new ViewState();
        beforeState = new ViewState();
        afterState = new ViewState();

        valueAnimator = ValueAnimator.ofFloat(0f, 1f);
        valueAnimator.setDuration(300);
        valueAnimator.setRepeatCount(0);
        ValueAnimator.AnimatorUpdateListener animatorUpdateListener = animation -> {
            float value = (Float) animation.getAnimatedValue();
            switch (animateState) {
                case ANIMATE_STATE_PENDING_SETTLE: {
                }
                case ANIMATE_STATE_PENDING_RESET: {
                }
                case ANIMATE_STATE_PENDING_DRAG: {
                    if (animateState != ANIMATE_STATE_PENDING_DRAG) {
                        viewState.buttonX = beforeState.buttonX + (afterState.buttonX - beforeState.buttonX) * value;
                    }
                    viewState.checkStateColor = (int) argbEvaluator.evaluate(value, beforeState.checkStateColor, afterState.checkStateColor);
                    break;
                }
                case ANIMATE_STATE_SWITCH: {
                    viewState.buttonX = beforeState.buttonX + (afterState.buttonX - beforeState.buttonX) * value;
                    float fraction = (viewState.buttonX - buttonMinX) / (buttonMaxX - buttonMinX);
                    viewState.checkStateColor = (int) argbEvaluator.evaluate(fraction, uncheckColor, checkedColor);
                    break;
                }
                default:
                case ANIMATE_STATE_DRAGING: {
                }
                case ANIMATE_STATE_NONE: {
                    break;
                }
            }
            postInvalidate();
        };
        valueAnimator.addUpdateListener(animatorUpdateListener);
        Animator.AnimatorListener animatorListener = new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                switch (animateState) {
                    case ANIMATE_STATE_PENDING_DRAG: {
                        animateState = ANIMATE_STATE_DRAGING;
                        postInvalidate();
                        break;
                    }
                    case ANIMATE_STATE_PENDING_RESET: {
                        animateState = ANIMATE_STATE_NONE;
                        postInvalidate();
                        break;
                    }
                    case ANIMATE_STATE_PENDING_SETTLE: {
                        animateState = ANIMATE_STATE_NONE;
                        postInvalidate();
                        broadcastEvent(true);
                        break;
                    }
                    case ANIMATE_STATE_SWITCH: {
                        isChecked = !isChecked;
                        animateState = ANIMATE_STATE_NONE;
                        postInvalidate();
                        broadcastEvent(switchByUser);
                        break;
                    }
                    case ANIMATE_STATE_DRAGING:
                    case ANIMATE_STATE_NONE:
                    default: {
                        break;
                    }
                }
            }

            @Override
            public void onAnimationCancel(Animator animation) {
            }

            @Override
            public void onAnimationRepeat(Animator animation) {
            }
        };
        valueAnimator.addListener(animatorListener);
        super.setClickable(true);
        this.setPadding(0, 0, 0, 0);
        setLayerType(LAYER_TYPE_SOFTWARE, null);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        if (widthMode == MeasureSpec.UNSPECIFIED || widthMode == MeasureSpec.AT_MOST) {
            widthMeasureSpec = MeasureSpec.makeMeasureSpec(DEFAULT_WIDTH, MeasureSpec.EXACTLY);
        }
        if (heightMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.AT_MOST) {
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(DEFAULT_HEIGHT, MeasureSpec.EXACTLY);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

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

        float viewPadding = 0;
        float height = h - viewPadding - viewPadding;

        viewRadius = height * .5f;
        left = viewPadding;
        top = viewPadding;
        right = w - viewPadding;
        bottom = h - viewPadding;

        centerY = (top + bottom) * .5f;
        buttonMinX = left + viewRadius;
        buttonMaxX = right - viewRadius;

        if (isChecked()) {
            setCheckedViewState(viewState);
        } else {
            setUncheckViewState(viewState);
        }

        isUiInit = true;

        postInvalidate();
    }

    private void setUncheckViewState(ViewState viewState) {
        viewState.checkStateColor = uncheckColor;
        viewState.buttonX = buttonMinX;
        buttonPaint.setColor(uncheckButtonColor);
    }

    private void setCheckedViewState(ViewState viewState) {
        viewState.checkStateColor = checkedColor;
        viewState.buttonX = buttonMaxX;
        int checkedButtonColor = Color.WHITE;
        buttonPaint.setColor(checkedButtonColor);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // background color
        paint.setColor(uncheckColor);
        drawRoundRect(canvas, left, top, right, bottom, viewRadius, paint);

        // select color
        paint.setColor(viewState.checkStateColor);
        drawRoundRect(canvas, left, top, right, bottom, viewRadius, paint);

        // button
        canvas.drawCircle(viewState.buttonX, centerY,
                viewRadius - DEFAULT_BUTTON_PADDING / 2F, buttonPaint);
    }

    @SuppressLint("ObsoleteSdkInt")
    private void drawRoundRect(Canvas canvas, float left, float top, float right,
                               float bottom, float backgroundRadius, Paint paint) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            canvas.drawRoundRect(left, top, right, bottom, backgroundRadius, backgroundRadius, paint);
        } else {
            rect.set(left, top, right, bottom);
            canvas.drawRoundRect(rect, backgroundRadius, backgroundRadius, paint);
        }
    }

    @Override
    public void setChecked(boolean checked) {
        if (checked == isChecked()) {
            postInvalidate();
            return;
        }
        toggle(true, false);
    }

    @Override
    public boolean isChecked() {
        return isChecked;
    }

    @Override
    public void toggle() {
        toggle(true);
    }

    public void toggle(boolean animate) {
        toggle(animate, true);
    }

    private void toggle(boolean animate, boolean broadcast) {
        toggle(animate, broadcast, false);
    }

    private void toggle(boolean animate, boolean broadcast, boolean byUser) {
        if (!isEnabled()) {
            return;
        }
        if (isEventBroadcast) {
            throw new RuntimeException("should NOT switch the state in method: [onCheckedChanged]!");
        }
        if (!isUiInit) {
            isChecked = !isChecked;
            if (broadcast) {
                broadcastEvent(byUser);
            }
            return;
        }

        if (valueAnimator.isRunning()) {
            valueAnimator.cancel();
        }

        if (!animate) {
            isChecked = !isChecked;
            if (isChecked()) {
                setCheckedViewState(viewState);
            } else {
                setUncheckViewState(viewState);
            }
            postInvalidate();
            if (broadcast) {
                broadcastEvent(byUser);
            }
            return;
        }

        animateState = ANIMATE_STATE_SWITCH;
        switchByUser = byUser;
        beforeState.copy(viewState);

        if (isChecked()) {
            setUncheckViewState(afterState);
        } else {
            setCheckedViewState(afterState);
        }
        valueAnimator.start();
    }

    private void broadcastEvent(boolean byUser) {
        if (onCheckedChangeListener != null) {
            isEventBroadcast = true;
            onCheckedChangeListener.onCheckedChanged(this, isChecked(), byUser);
        }
        isEventBroadcast = false;
    }


    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (!isEnabled()) {
            return false;
        }
        int actionMasked = event.getActionMasked();

        switch (actionMasked) {
            case MotionEvent.ACTION_DOWN: {
                isTouchingDown = true;
                touchDownTime = System.currentTimeMillis();
                removeCallbacks(postPendingDrag);
                postDelayed(postPendingDrag, 100);
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                float eventX = event.getX();
                if (isPendingDragState()) {
                    float fraction = eventX / getWidth();
                    fraction = Math.max(0f, Math.min(1f, fraction));
                    viewState.buttonX = buttonMinX + (buttonMaxX - buttonMinX) * fraction;
                } else if (isDragState()) {
                    float fraction = eventX / getWidth();
                    fraction = Math.max(0f, Math.min(1f, fraction));
                    viewState.buttonX = buttonMinX + (buttonMaxX - buttonMinX) * fraction;
                    viewState.checkStateColor = (int) argbEvaluator.evaluate(fraction, uncheckColor, checkedColor);
                    postInvalidate();
                }
                break;
            }
            case MotionEvent.ACTION_UP: {
                isTouchingDown = false;
                removeCallbacks(postPendingDrag);
                if (System.currentTimeMillis() - touchDownTime <= 300) {
                    toggle(true, true, true);
                } else if (isDragState()) {
                    float eventX = event.getX();
                    float fraction = eventX / getWidth();
                    fraction = Math.max(0f, Math.min(1f, fraction));
                    boolean newCheck = fraction > .5f;
                    if (newCheck == isChecked()) {
                        pendingCancelDragState();
                    } else {
                        isChecked = newCheck;
                        pendingSettleState();
                    }
                } else if (isPendingDragState()) {
                    pendingCancelDragState();
                }
                break;
            }
            case MotionEvent.ACTION_CANCEL: {
                isTouchingDown = false;
                removeCallbacks(postPendingDrag);
                if (isPendingDragState() || isDragState()) {
                    pendingCancelDragState();
                }
                break;
            }
        }
        return true;
    }

    private final Runnable postPendingDrag = () -> {
        if (!isInAnimating()) {
            pendingDragState();
        }
    };

    private boolean isInAnimating() {
        return animateState != ANIMATE_STATE_NONE;
    }

    private boolean isPendingDragState() {
        return animateState == ANIMATE_STATE_PENDING_DRAG || animateState == ANIMATE_STATE_PENDING_RESET;
    }

    private boolean isDragState() {
        return animateState == ANIMATE_STATE_DRAGING;
    }

    private void pendingDragState() {
        if (isInAnimating()) {
            return;
        }
        if (!isTouchingDown) {
            return;
        }

        if (valueAnimator.isRunning()) {
            valueAnimator.cancel();
        }

        animateState = ANIMATE_STATE_PENDING_DRAG;

        beforeState.copy(viewState);
        afterState.copy(viewState);

        if (isChecked()) {
            afterState.checkStateColor = checkedColor;
            afterState.buttonX = buttonMaxX;
        } else {
            afterState.checkStateColor = uncheckColor;
            afterState.buttonX = buttonMinX;
        }

        valueAnimator.start();
    }

    private void pendingCancelDragState() {
        if (isDragState() || isPendingDragState()) {
            if (valueAnimator.isRunning()) {
                valueAnimator.cancel();
            }

            animateState = ANIMATE_STATE_PENDING_RESET;
            beforeState.copy(viewState);

            if (isChecked()) {
                setCheckedViewState(afterState);
            } else {
                setUncheckViewState(afterState);
            }
            valueAnimator.start();
        }
    }

    private void pendingSettleState() {
        if (valueAnimator.isRunning()) {
            valueAnimator.cancel();
        }

        animateState = ANIMATE_STATE_PENDING_SETTLE;
        beforeState.copy(viewState);

        if (isChecked()) {
            setCheckedViewState(afterState);
        } else {
            setUncheckViewState(afterState);
        }
        valueAnimator.start();
    }


    @SuppressWarnings("unused")
    public void setOnCheckedChangeListener(OnCheckedChangeListener l) {
        onCheckedChangeListener = l;
    }

    public interface OnCheckedChangeListener {
        void onCheckedChanged(YsSwitchButton view, boolean isChecked, boolean byUser);
    }

    private static float dp2px(float dp) {
        Resources r = Resources.getSystem();
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, r.getDisplayMetrics());
    }

    private static int dp2pxInt(float dp) {
        return (int) dp2px(dp);
    }

    private static class ViewState {
        float buttonX;
        int checkStateColor;

        private void copy(ViewState source) {
            this.buttonX = source.buttonX;
            this.checkStateColor = source.checkStateColor;
        }
    }

    enum ANIMATE {
        ANIMATE_STATE_NONE, ANIMATE_STATE_PENDING_DRAG, ANIMATE_STATE_DRAGING, ANIMATE_STATE_PENDING_RESET, ANIMATE_STATE_PENDING_SETTLE, ANIMATE_STATE_SWITCH;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/898757.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Unsafe Filedownload

文件下载功能在很多web系统上都会出现&#xff0c;一般我们当点击下载链接&#xff0c;便会向后台发送一个下载请求&#xff0c;一般这个请求会包含一个需要下载的文件名称&#xff0c;后台在收到请求后会开始执行下载代码&#xff0c;将该文件名对应的文件response给浏览器&am…

XDR解决方案正式发布

面对日益严峻的网络安全形势&#xff0c;为了增强安全防护能力&#xff0c;不同单位经常不定期举行以真实网络目标为对象的攻防实战演练&#xff0c;旨在发现、暴露和解决安全问题&#xff0c;检验各个企业单位的网络安全防护水平和应急处置能力。 作为攻防实战防守方的蓝队&am…

WebStrom 前端项目Debug

1. 正常启动前端项目 2. 配置webStrom的JavaScript Debugger 点击Edit Configurations添加avaScript Debug填写URL 为项目启动路径配置要Debug的浏览器-remote-allow-origins* &#xff08;最重要&#xff0c;否则唤起的是一个about:blank空白页面&#xff09; 3. 启动Debug模…

[ MySQL ] — 基础增删查改的使用

目录 表的增删查改 Create 单行数据 全列插入 多行数据 全列插入 多行数据 指定列插入 不存在插入存在则更新 替换 Retrieve SELECT 列 全列查询 指定列查询 查询字段为表达式 为查询结果指定别名 结果去重 WHERE 条件 结果排序 筛选结果分页 Update De…

GPT系列总结

1.GPT1 无监督预训练有监督的子任务finetuning https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf 1.1 Unsupervised pre-training &#xff08;1&#xff09;通过一个窗口的输入得到下一个token在目标token上的一个概率分布…

all in one之安装pve(第一章)

第一章 安装PVE PVE安装 pverufusultraISO下载地址下载地址下载地址 因为我使用的是SD卡存储&#xff0c;尝试rufus安装失败&#xff0c;建议使用 ultraISO进行镜像写入。 U盘推荐4G往上。 下载pve 我下载的pve版本是7.4 ultraISO 把镜像写入u盘 下载完成后需要把镜像文件…

小米分享 | 解密面试题:网易面试如何回答“创建线程有哪几种方式?”

大家好&#xff0c;我是你们的小米&#xff01;今天要和大家一起探讨一个在技术面试中常见的问题&#xff1a;创建线程有哪几种方式&#xff1f;这可是个经典面试题哦&#xff01;不过别担心&#xff0c;小米在这里为你详细解析&#xff0c;帮你轻松应对&#xff0c;让你在面试…

【Unity每日一记】关于五种范围检测方法的总结

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;uni…

Hlang--用Python写个编程语言-变量的实现

文章目录 前言语法规则表示次幂实现变量实现优先级实现步骤解析关键字语法解析解释器总结前言 先前的话,我们终于是把我们整个架子搭起来了,这里重复一下我们的流程,那就是,首先,我们通过解析文本,然后呢遍历文本当中的我们定义的合法关键字,然后呢,把他们封装为一个T…

基于Redis的Geo实现附近商铺搜索(含源码)

微信公众号访问地址&#xff1a;基于Redis的Geo实现附近商铺搜索(含源码) 推荐文章&#xff1a; 1、springBoot对接kafka,批量、并发、异步获取消息,并动态、批量插入库表; 2、SpringBoot用线程池ThreadPoolTaskExecutor异步处理百万级数据; 3、基于Redis的Geo实现附近商铺搜索…

34.Netty源码之Netty如何处理网络请求

highlight: arduino-light 通过前面两节源码课程的学习&#xff0c;我们知道 Netty 在服务端启动时会为创建 NioServerSocketChannel&#xff0c;当客户端新连接接入时又会创建 NioSocketChannel&#xff0c;不管是服务端还是客户端 Channel&#xff0c;在创建时都会初始化自己…

Azure文件共享

什么是Azure文件共享 Azure文件共享是一种在云中存储和访问文件的服务。它允许用户在不同的计算机、虚拟机和服务之间共享数据&#xff0c;并在应用程序中进行访问、修改和管理。 Azure文件共享可以用于各种用途&#xff0c;例如&#xff1a; 共享文件资源给多个虚拟机或服务…

P6685 可持久化动态仙人掌的直径问题

思路1&#xff1a;二分快速幂 #include<bits/stdc.h> using namespace std; #define int long long int n,m; bool check(int a,int b){int ans1;while(b){if(a>n)return false;if(b&1)ans*a;if(ans>n)return false;aa*a;b>>1;}return ans<n; } voi…

STM32CubeMx之freeRTOS定时器使用

需要修改定时器时钟 xTimerChangePeriod(tim1Handle,500,200);//发送队列等待时间 第二个参数为修改的ms xTimerStart(tim1Handle,100);//开启定时器 xTimerStop(tim1Handle,100);//关闭定时器 一定注意定时器任务优先级 要大一点 不然会使用不了

【GaussDB】 SQL 篇

建表语句 表的分类 普通的建表语句 复制表内容 只复制表结构 create table 新表名(like 源表名 including all); 如果希望注释被复制的话要指定including comments 复制索引、主键约束和唯一约束&#xff0c;那么需要指定including indexes including constraints &#xf…

AI 媒人:为什么图形神经网络比 MLP 更好?

一、说明 G拉夫神经网络&#xff08;GNN&#xff09;&#xff01;想象他们是人工智能世界的媒人&#xff0c;通过探索他们的联系&#xff0c;不知疲倦地帮助数据点找到朋友和人气。数字派对上的终极僚机。 现在&#xff0c;为什么这些GNN如此重要&#xff0c;你问&#xff1f;好…

铺设道路(c++题解)

题目背景 NOIP2018 提高组 D1T1 题目描述 春春是一名道路工程师&#xff0c;负责铺设一条长度为 n 的道路。 铺设道路的主要工作是填平下陷的地表。整段道路可以看作是 n 块首尾相连的区域&#xff0c;一开始&#xff0c;第 i 块区域下陷的深度为 di​ 。 春春每天可以选择…

没有父母“托底”的人生,到底有多累?

提起著名的心理学家和人际关系学家戴尔卡耐基。 多数人都不会太陌生&#xff0c;他的《人性的弱点》一书更是家喻户晓的程度。 这本书里总是能用一些接近我们生活的实例&#xff0c;去说明一些与人交往的方法和结论&#xff0c;实用性非常强大。 不管是做人还是处事&#xf…

实验一 VMware 17 虚拟机下安装Ubuntu16.04

系列文章目录 文章目录 系列文章目录前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 古人云&#xff1a;“工欲善其事&#xff0c;必先利其器。” 我们在学习分布式数据库原理知识同时少不了实操&#xff0c;在做实验之前&#xff0c;把相关实验…

【图像分类】理论篇(2)经典卷积神经网络 Lenet~Resenet

目录 1、卷积运算 2、经典卷积神经网络 2.1 Lenet 网络构架 代码实现 2.2 Alexnet 网络构架 代码实现 2.3 VGG VGG16网络构架 代码实现 2.4 ResNet ResNet50网络构架 代码实现 1、卷积运算 在二维卷积运算中&#xff0c;卷积窗口从输入张量的左上角开始&#xff…