菜单中的类似iOS中开关的样式
背景是我们有需求,做类似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() {@Overridepublic void onAnimationStart(Animator animation) {}@Overridepublic 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;}}}@Overridepublic void onAnimationCancel(Animator animation) {}@Overridepublic void onAnimationRepeat(Animator animation) {}};valueAnimator.addListener(animatorListener);super.setClickable(true);this.setPadding(0, 0, 0, 0);setLayerType(LAYER_TYPE_SOFTWARE, null);}@Overrideprotected 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);}@Overrideprotected 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);}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);// background colorpaint.setColor(uncheckColor);drawRoundRect(canvas, left, top, right, bottom, viewRadius, paint);// select colorpaint.setColor(viewState.checkStateColor);drawRoundRect(canvas, left, top, right, bottom, viewRadius, paint);// buttoncanvas.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);}}@Overridepublic void setChecked(boolean checked) {if (checked == isChecked()) {postInvalidate();return;}toggle(true, false);}@Overridepublic boolean isChecked() {return isChecked;}@Overridepublic 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")@Overridepublic 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;}
}
相关文章:

菜单中的类似iOS中开关的样式
背景是我们有需求,做类似ios中开关的按钮。github上有一些开源项目,比如 SwitchButton, 但是这个项目中提供了很多选项,并且实际使用中会出现一些奇怪的问题。 我调整了下代码,把无关的功能都给删了,保留核…...

Vue 2 动态组件和异步组件
先阅读 【Vue 2 组件基础】中的初步了解动态组件。 动态组件与keep-alive 我们知道动态组件使用is属性和component标签结合来切换不同组件。 下面给出一个示例: <!DOCTYPE html> <html><head><title>Vue 动态组件</title><scri…...

MongoDB升级经历(4.0.23至5.0.19)
MongoDB从4.0.23至5.0.19升级经历 引子:为了解决MongoDB的两个漏洞决定把MongoDB升级至最新版本,期间也踩了不少坑,在这里分享出来供大家学习与避坑~ 1、MongoDB的两个漏洞 漏洞1:MongoDB Server 安全漏洞(CVE-2021-20330) 漏洞2…...

iPhone上的个人热点丢失了怎么办?如何修复iPhone上不见的个人热点?
个人热点功能可将我们的iPhone手机转变为 Wi-Fi 热点,有了Wi-Fi 热点后就可以与附近的其他设备共享其互联网连接。 一般情况下,个人热点打开就可以使用,但也有部分用户在升级系统或越狱后发现 iPhone 的个人热点消失了。 iPhone上的个人热点…...

AI 媒人:为什么图形神经网络比 MLP 更好?
一、说明 G拉夫神经网络(GNN)!想象他们是人工智能世界的媒人,通过探索他们的联系,不知疲倦地帮助数据点找到朋友和人气。数字派对上的终极僚机。 现在,为什么这些GNN如此重要,你问?好…...
信息学奥赛一本通 1984:【19CSPJ普及组】纪念品 | 洛谷 P5662 [CSP-J2019] 纪念品
【题目链接】 ybt 1984:【19CSPJ普及组】纪念品 洛谷 P5662 [CSP-J2019] 纪念品 【题目考点】 1. 动态规划:完全背包 【解题思路】 由于小伟每天都可以买卖物品无限次,我们可以假想每天开始时,他把所有的商品都卖出ÿ…...
JVM——JVM参数指南
文章目录 1.概述2.堆内存相关2.1.显式指定堆内存–Xms和-Xmx2.2.显式新生代内存(Young Ceneration)2.3.显示指定永久代/元空间的大小 3.垃圾收集相关3.1.垃圾回收器3.2.GC记录 1.概述 在本篇文章中,你将掌握最常用的 JVM 参数配置。如果对于下面提到了一些概念比如…...

马上七夕到了,用各种编程语言实现10种浪漫表白方式
目录 1. 直接表白:2. 七夕节表白:3. 猜心游戏:4. 浪漫诗句:5. 爱的方程式:6. 爱心Python:7. 心形图案JavaScript 代码:8. 心形并显示表白信息HTML 页面:9. Java七夕快乐:…...

Spring Clould 注册中心 - Eureka,Nacos
视频地址:微服务(SpringCloudRabbitMQDockerRedis搜索分布式) Eureka 微服务技术栈导学(P1、P2) 微服务涉及的的知识 认识微服务-服务架构演变(P3、P4) 总结: 认识微服务-微服务技…...
使用appuploader工具发布证书和描述性文件教程
使用APPuploader工具发布证书和描述性文件教程 之前用AppCan平台开发了一个应用,平台可以同时生成安卓版和苹果版,想着也把这应用上架到App Store试试,于是找同学借了个苹果开发者账号,但没那么简单,还要用到Mac电脑的…...

【面试八股文】每日一题:谈谈你对IO的理解
谈谈你对IO的理解 每日一题-Java核心-谈谈你对对IO的理解【面试八股文】 1.Java基础知识 Java IO(Input/Output)是Java编程语言中用于处理输入和输出的一组类和接口。它提供了一种在Java程序中读取和写入数据的方法。 Java IO包括两个主要的部分&#x…...
200. 岛屿数量
思路:遍历整个矩阵,对每个格子执行以下操作: 如果格子是陆地(‘1’),则将其标记为已访问(‘0’),并从当前位置开始进行深度优先搜索,将与当前格子相邻的陆地都…...
【LeetCode】581.最短无序连续子数组
题目 给你一个整数数组 nums ,你需要找出一个 连续子数组 ,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。 请你找出符合题意的 最短 子数组,并输出它的长度。 示例 1: 输入:nums [2,6…...

曲面(弧面、柱面)展平(拉直)瓶子标签识别ocr
瓶子或者柱面在做字符识别的时候由于变形,识别效果是很不好的 或者是检测瓶子表面缺陷的时候效果也没有展平的好 下面介绍两个项目,关于曲面(弧面、柱面)展平(拉直) 项目一:通过识别曲面的6个点…...

知识继承概述
文章目录 知识继承第一章 知识继承概述1.背景介绍第一页 背景第二页 大模型训练成本示例第三页 知识继承的动机 2.知识继承的主要方法 第二章 基于知识蒸馏的知识继承预页 方法概览 1.知识蒸馏概述第一页 知识蒸馏概述第二页 知识蒸馏第三页 什么是知识第四页 知识蒸馏的核心目…...

深度剖析数据在内存中的存储
目录 一、数据类型介绍 类型的基本归类 1.整形家族 2.浮点数家族 3.构造类型 (自定义类型) 4.指针类型 5.空类型 二、整形在内存中的存储 1.原码、反码、补码 1.1原码 1.2反码 1.3补码 1.4计算规则 2 .大小端介绍 三、浮点型在内存中的存…...

【ARM Linux 系统稳定性分析入门及渐进10 -- GDB 初始化脚本介绍及使用】
文章目录 gdb 脚本介绍gdb 初始化脚本使用启动 gdb 的时候自动执行脚本gdb运行期间执行命令脚本 gdb 脚本介绍 GDB脚本是一种使用GDB命令语言编写的脚本,可以用来自动化一些常见的调试任务。这些脚本可以直接在GDB中运行,也可以通过GDB的-x参数或source…...

AQS源码解读
文章目录 前言一、AQS是什么?二、解读重点属性statehead、tail 同步变量竞争acquire 同步变量释放 总结 前言 AQS是AbstractQueuedSynchronizer的缩写,也是大神Doug Lea的得意之作。今天我们来进行尽量简化的分析和理解性的代码阅读。 一、AQS是什么&am…...

QT实现天气预报
1. MainWindow类设计的成员变量和方法 public: MainWindow(QWidget* parent nullptr); ~MainWindow(); protected: 形成文本菜单来用来右键关闭窗口 void contextMenuEvent(QContextMenuEvent* event); 鼠标被点击之后此事件被调用 void mousePressEvent(QMouseEv…...

【马蹄集】第二十三周——进位制专题
进位制专题 目录 MT2186 二进制?不同!MT2187 excel的烦恼MT2188 单条件和MT2189 三进制计算机1MT2190 三进制计算机2 MT2186 二进制?不同! 难度:黄金 时间限制:1秒 占用内存:128M 题目…...

51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动
一、前言说明 在2011版本的gb28181协议中,拉取视频流只要求udp方式,从2016开始要求新增支持tcp被动和tcp主动两种方式,udp理论上会丢包的,所以实际使用过程可能会出现画面花屏的情况,而tcp肯定不丢包,起码…...
QMC5883L的驱动
简介 本篇文章的代码已经上传到了github上面,开源代码 作为一个电子罗盘模块,我们可以通过I2C从中获取偏航角yaw,相对于六轴陀螺仪的yaw,qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...
Go 语言接口详解
Go 语言接口详解 核心概念 接口定义 在 Go 语言中,接口是一种抽象类型,它定义了一组方法的集合: // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的: // 矩形结构体…...

【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)
服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...
系统设计 --- MongoDB亿级数据查询优化策略
系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log,共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题,不能使用ELK只能使用…...
c++ 面试题(1)-----深度优先搜索(DFS)实现
操作系统:ubuntu22.04 IDE:Visual Studio Code 编程语言:C11 题目描述 地上有一个 m 行 n 列的方格,从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子,但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...

1.3 VSCode安装与环境配置
进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件,然后打开终端,进入下载文件夹,键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...

第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词
Q1、[中等] 矩阵中的幻方 1、题目描述 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵,其中每行,每列以及两条对角线上的各数之和都相等。 给定一个由整数组成的row x col 的 grid,其中有多少个 3 3 的 “幻方” 子矩阵&am…...

html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...