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

news/2024/11/17 6:43:27/

背景是我们有需求,做类似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;}
}

http://www.ppmy.cn/news/1043709.html

相关文章

HCIP——STP配置案例

STP配置案例 一、简介二、实现说明1、华为实现说明2、其他厂商实现 三、STP原理1、协商原则2、角色和状态3、报文格式4、BPDU报文处理流程4.1 BPDU报文的分类4.2 BPDU报文的处理流程4.3 BPDU报文格式 四、使用注意事项五、配置举例1、组网需求2、配置思路3、操作步骤4、配置文件…

嵌入式设备应用开发(程序构建)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 编译是嵌入式开发很重要的一个环节。记得早年在上海一家通讯公司上班的时候,单位还专门有一个人维护编译脚本。当时用的是makefile,这位同学的主要工作就是替大家维护好各个项目的…

七夕特辑——3D爱心(可监听鼠标移动)

前言 「作者主页」&#xff1a;雪碧有白泡泡 「个人网站」&#xff1a;雪碧的个人网站 「推荐专栏」&#xff1a; ★java一站式服务 ★ ★ React从入门到精通★ ★前端炫酷代码分享 ★ ★ 从0到英雄&#xff0c;vue成神之路★ ★ uniapp-从构建到提升★ ★ 从0到英雄&#xff…

conda 常用命令

conda 常用命令 一、创建环境二、删除环境三、环境重命名四 、查看环境列表五、进入某个虚拟环境六、退出当前环境七、查看当前虚拟环境下的所有安装包八、安装或卸载包(进入虚拟环境之后&#xff09;九、分享虚拟环境十、源服务器管理十一、升级十二、卸载十三、卸载十四、pip…

云服务 Ubuntu 20.04 版本 使用 Nginx 部署静态网页

所需操作&#xff1a; 1.安装Nginx 2.修改配置文件 3.测试、重启 Nginx 4.内部修改防火墙 5.配置解析 6.测试是否部署成功 1.安装Nginx // 未使用 root 账号 apt-get update // 更新apt-get install nginx // 安装 nginx 1.1.测试是否安装没问题 在网页上输入云服务的公网…

企望制造ERP系统 RCE漏洞[2023-HW]

企望制造ERP系统 RCE漏洞 一、 产品简介二、 漏洞概述三、 复现环境四、 漏洞复现小龙POC检测 五、 修复建议 免责声明&#xff1a;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#xff0c;…

微信小程序卡片横向滚动竖图

滚动并不是使用swiper&#xff0c;该方式使用的是scroll-view实现 Swiper局限性太多了&#xff0c;对竖图并不合适 从左往右滚动图片示例 wxml代码&#xff1a; <view class"img-x" style"margin-top: 10px;"><view style"margin: 20rpx;…

简单记录牛客top101算法题(初级题C语言实现)BM24 二叉树的中序遍历 BM28 二叉树的最大深度 BM29 二叉树中和为某一值的路径

1. BM24 二叉树的中序/后续遍历 要求&#xff1a;给定一个二叉树的根节点root&#xff0c;返回它的中序遍历结果。                          输入&#xff1a;{1,2,#,#,3} 返回值&#xff1a;[2,3,1]1.1 自己的整体思路&#xff08;与二叉树的前序遍…