当前位置: 首页 > news >正文

Android 自定义按钮(可滑动、点击)

按钮图片素材

https://download.csdn.net/download/Lan_Se_Tian_Ma/88151085
 

px 和 dp 转换工具类(Java)

// px 和 dp 转换工具类
public class DensityUtil {/*** 根据手机的分辨率从 dip 的单位 转成为 px(像素)*/public static int dip2px(Context context, float dpValue) {final float scale = context.getResources().getDisplayMetrics().density;return (int) (dpValue * scale + 0.5f);}/*** 根据手机的分辨率从 px(像素) 的单位 转成为 dp*/public static int px2dip(Context context, float pxValue) {final float scale = context.getResources().getDisplayMetrics().density;return (int) (pxValue / scale + 0.5f);}
}

自定义按钮(Kotlin)

import android.animation.Animator
import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Paint
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.View
import java.util.*class MyButton : View, View.OnClickListener {constructor(context: Context) : super(context)constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet)init {// 加载图片资源btnBackground = BitmapFactory.decodeResource(resources, R.drawable.switch_background)button = BitmapFactory.decodeResource(resources, R.drawable.slide_button)paint = Paint()// 抗锯齿paint.isAntiAlias = true// 点击事件setOnClickListener(this)antiShake = AntiShake(500)}private var btnBackground: Bitmapprivate var button: Bitmapprivate var buttonWidth: Int = 0private var paint: Paintprivate val antiShake : AntiShake// X轴最大移动距离private var maxMoveV: Int = 0// 时时的移动距离private var translateX: Float = 0f// 方向:向左false 向右trueprivate var direction = false// 手指按下时的X轴位置var startX: Float = 0f// 用来记录上一个move坐标,用来判断,左滑还是右滑private var tempV: Float = 0f// 按钮的leftprivate var btnLeft: Float = 0f// 按钮的rightprivate var btnRight: Float = button.width.toFloat()// 当前手指按下的位置private var currentLocation: Float = 0f// 按下的区域,是否处于按钮区域private var isBtn = false// offOrNo:开true 关falsevar offOrNo = false// 是否可以触发点击事件var isOpenClick = false// 平移动画是否结束var animatorComplete = trueoverride fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {val widthSize = MeasureSpec.getSize(widthMeasureSpec)val widthModel = MeasureSpec.getMode(widthMeasureSpec)val heightSize = MeasureSpec.getSize(heightMeasureSpec)val heightModel = MeasureSpec.getMode(heightMeasureSpec)if (widthModel == MeasureSpec.AT_MOST && heightModel == MeasureSpec.AT_MOST) {setMeasuredDimension(btnBackground.width, btnBackground.height)} else if (widthModel == MeasureSpec.AT_MOST) {setMeasuredDimension(btnBackground.width, heightSize)} else if (heightModel == MeasureSpec.AT_MOST) {setMeasuredDimension(widthSize, btnBackground.height)} else {setMeasuredDimension(widthSize, heightSize)}}@SuppressLint("DrawAllocation")override fun onDraw(canvas: Canvas?) {// 改变图片尺寸btnBackground = Bitmap.createScaledBitmap(btnBackground, width, height, true)// 按钮width没有覆盖背景一半区域,手动增加按钮widthbuttonWidth = (width / 2) + DensityUtil.dip2px(context, 12f)button = Bitmap.createScaledBitmap(button, buttonWidth, height, true)canvas?.drawBitmap(btnBackground, 0f, 0f, paint)canvas?.drawBitmap(button, translateX, 0f, paint)// 获取按钮,最大X轴移动值maxMoveV = width - buttonWidth}@SuppressLint("ClickableViewAccessibility")override fun onTouchEvent(event: MotionEvent?): Boolean {// 手指离开时的X轴位置var endX: Float = 0fsuper.onTouchEvent(event)when (event?.action) {MotionEvent.ACTION_DOWN -> {startX = event.xtempV = event.xisBtnContent(event)if (isBtn && animatorComplete) {currentLocation = startX - btnLeft}}MotionEvent.ACTION_UP -> {if (antiShake.isFastClick()) {endX = event.xtempV = event.xisOpenClick = startX == endXif (isBtn && !isOpenClick && animatorComplete) {confirmCoordinate()}}}MotionEvent.ACTION_MOVE -> {if (isBtn && animatorComplete) {// getDirection(event)translateCalculate(event)invalidate()}}}return true}override fun onClick(v: View?) {if (isOpenClick && animatorComplete) {if (offOrNo) {translateAnimator((btnLeft + maxMoveV) - translateX, 0f)offOrNo = false} else {translateAnimator(btnLeft, (btnLeft + maxMoveV))offOrNo = true}}}// 判断是否在按钮区域private fun isBtnContent(event: MotionEvent) {getBtnLeftRight()isBtn = event.x > btnLeft && event.x < btnRight}// 确认停留的坐标(手指离开屏幕后)private fun confirmCoordinate() {// 更新按钮的最新位置getBtnLeftRight()// 获取百分比val round = ((translateX / maxMoveV) * 100).toInt()var animatorStartX = 0fvar animatorEndX = 0f// 超过50%// 这里一定要写成 >= 50,只写 > 50,因为有可能刚好是50if (round > 50) {// 停留在右边animatorStartX = btnLeftanimatorEndX = maxMoveV.toFloat()offOrNo = true} else {// 停留在左边animatorStartX = btnLeftanimatorEndX = 0foffOrNo = false}translateAnimator(animatorStartX, animatorEndX)getBtnLeftRight(true)}// 平移动画private fun translateAnimator(animatorStartX: Float,animatorEndX: Float,duration: Long = 300) {val animator = ValueAnimator.ofFloat(animatorStartX, animatorEndX)animator.duration = durationanimator.addUpdateListener {translateX = it.animatedValue as Float// Log.e("TAG", "$translateX")invalidate()}animator.addListener(object : Animator.AnimatorListener {// 动画开始override fun onAnimationStart(animation: Animator?) {// Log.e("TAG","onAnimationStart")animatorComplete = false}// 动画结束override fun onAnimationEnd(animation: Animator?) {animatorComplete = true// Log.e("TAG","onAnimationEnd")}// 动画取消override fun onAnimationCancel(animation: Animator?) {animatorComplete = true// Log.e("TAG","onAnimationCancel")}// 动画重复override fun onAnimationRepeat(animation: Animator?) {animatorComplete = false}})animator.start()}// 获取滑动方向private fun getDirection(event: MotionEvent) {if (event.x > tempV) {if (translateX == maxMoveV.toFloat()) {// Log.e("TAG", "已右滑至最大值")return}// 向右滑动// Log.e("TAG", "向右滑动:$translateX")if (!direction) {direction = true}tempV = event.x} else if (event.x < tempV) {if (translateX == 0f) {// Log.e("TAG", "已左滑至最大值")return}// 向左滑动// Log.e("TAG", "向左滑动:$translateX")if (direction) {direction = false}tempV = event.x}}// 计算平移距离private fun translateCalculate(event: MotionEvent) {// 平移的距离translateX = event.x - currentLocation// 屏蔽非法值if (translateX >= maxMoveV) {translateX = maxMoveV.toFloat()} else if (translateX <= 0) {translateX = 0f}getBtnLeftRight()}// 获取按钮的位置// complete:动画是否结束// offOrNo:开true 关falseprivate fun getBtnLeftRight(complete: Boolean = false) {if (complete) {if (offOrNo) {translateX = maxMoveV.toFloat()} else {translateX = 0f}}btnRight = translateX + buttonWidthbtnLeft = btnRight - buttonWidth}// 防止快速点击class AntiShake(minTime: Int = 1000) {// 两次点击间隔不能少于1000msprivate var MIN_DELAY_TIME : Intprivate var lastClickTime: Long = 0init {MIN_DELAY_TIME = minTime}fun isFastClick(): Boolean {var flag = trueval currentClickTime = System.currentTimeMillis()if ((currentClickTime - lastClickTime) < MIN_DELAY_TIME) {flag = false}lastClickTime = currentClickTimereturn flag}}}

布局中使用(XML)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"tools:context=".MainActivity"><com.test.festec.customizebutton.MyButtonandroid:layout_width="150dp"android:layout_height="55dp"/></LinearLayout>

相关文章:

Android 自定义按钮(可滑动、点击)

按钮图片素材 https://download.csdn.net/download/Lan_Se_Tian_Ma/88151085 px 和 dp 转换工具类&#xff08;Java&#xff09; // px 和 dp 转换工具类 public class DensityUtil {/*** 根据手机的分辨率从 dip 的单位 转成为 px(像素)*/public static int dip2px(Conte…...

mac录屏怎么打开?很简单,让我来教你!

mac电脑作为一款广受欢迎的电脑系统&#xff0c;提供了多种方式来满足用户录屏的需求。无论您是要录制教学视频、制作演示文稿&#xff0c;还是记录游戏精彩瞬间&#xff0c;mac电脑都能帮助您实现这些目标。本文将为您介绍两种mac录屏的方法。通过本文的指导&#xff0c;您将能…...

Stable Diffusion AI绘画学习指南【插件安装设置】

插件安装的方式 可用列表方式安装&#xff0c;点开Extensions 选项卡&#xff0c;找到如下图&#xff0c;找到Available选项卡&#xff0c;点load from加载可用插件&#xff0c;在可用插件列表中找到要装的插件按install 按扭按装&#xff0c;安装完后(Apply and restart UI)应…...

APP开发中的性能优化:提升用户满意度的关键

APP开发中的性能优化是需要持续进行的&#xff0c;它不仅能够让用户体验到 APP的使用感受&#xff0c;还能在一定程度上提升用户的满意度&#xff0c;从而提升 APP的粘性和转化率。不过在实际开发中&#xff0c;很多 APP开发公司会存在性能优化上的问题&#xff0c;这就需要了解…...

Golang 切片 常用方法

文章目录 移除指定位置的元素查找元素的位置查找最大最小的元素去重随机打乱排序二维排序sort.Sort 排序 下面的方法省略一些校验&#xff0c;如数组越界等&#xff0c;且都采用泛型(要求go版本 > 1.18) 移除指定位置的元素 package mainimport ("fmt" )func Del…...

【Linux后端服务器开发】poll/epoll多路转接IO服务器

目录 一、poll原理 二、poll实现多路转接IO服务器 三、epoll函数接口 四、epoll的工作原理 五、epoll实现多路转接IO服务器 一、poll原理 poll函数接口 #include <poll.h> int poll(struct pollfd *fds, nfds_t nfds, int timeout);// pollfd结构 struct pollfd …...

【设计模式——学习笔记】23种设计模式——命令模式Command(原理讲解+应用场景介绍+案例介绍+Java代码实现)

文章目录 案例引入介绍基础介绍登场角色 案例实现案例一实现 案例二介绍实现拓展 命令模式在JdbcTemplate源码中的应用总结文章说明 案例引入 有一套智能家电&#xff0c;其中有照明灯、风扇、冰箱、洗衣机&#xff0c;这些智能家电来自不同的厂家&#xff0c;我们不想针对每一…...

Rust中的高吞吐量流处理

本篇文章主要介绍了Rust中流处理的概念、方法和优化。作者不仅介绍了流处理的基本概念以及Rust中常用的流处理库&#xff0c;还使用这些库实现了一个流处理程序。 最后&#xff0c;作者介绍了如何通过测量空闲和阻塞时间来优化流处理程序的性能&#xff0c;并将这些内容同步至…...

探索编程世界的宝藏:程序员必掌握的20大算法

文章目录 1 引言2 冒泡排序算法&#xff1a;编程世界的排序魔法 &#x1f9d9;‍♀️&#x1f522;3 选择排序算法&#xff1a;排序世界的精确挑选器 &#x1f3af;&#x1f522;4 插入排序算法&#xff1a;排序世界的巧妙插珠者 ✨&#x1f522;5 快速排序算法&#xff1a;排序…...

Android NFC通信示例

前言 近距离无线通信 (NFC) 是一组近距离无线技术&#xff0c;通常只有在距离不超过 4 厘米时才能启动连接。借助 NFC&#xff0c;您可以在 NFC 标签与 Android 设备之间或者两台 Android 设备之间共享小型负载。 支持 NFC 的 Android 设备同时支持以下三种主要操作模式&…...

2023年08月IDE流行度最新排名

点击查看最新IDE流行度最新排名&#xff08;每月更新&#xff09; 2023年08月IDE流行度最新排名 顶级IDE排名是通过分析在谷歌上搜索IDE下载页面的频率而创建的 一个IDE被搜索的次数越多&#xff0c;这个IDE就被认为越受欢迎。原始数据来自谷歌Trends 如果您相信集体智慧&am…...

使用Beego和MySQL实现帖子和评论的应用,并进行接口测试(附源码和代码深度剖析)

文章目录 小项目介绍源码分析main.gorouter.gomodels/user.gomodels/Post.gomodels/comment.gocontrollers/post.gocontrollers/comment.go 接口测试测试增加帖子测试查看帖子测试增加评论测试查看评论 小项目介绍 经过对需求的分析&#xff0c;我增加了一些额外的东西&#x…...

物联网潜在的巨大价值在于大数据分析

物联网潜在的巨大价值在于大数据分析 从数据里去挖掘市场或者用户的精准需求。 往小的说&#xff0c;后台可以统计用户家里各各插座一年甚至更久的用电情况&#xff0c;这些数据也可以通过app或者小程序展现给用户。 用户可以很直观看到自己一年的用电情况&#xff0c;哪个家…...

SSL原理详解

SSL协议结构&#xff1a; SSL协议分为两层&#xff0c;下层为SSL记录协议&#xff0c;上层为SSL握手协议、SSL密码变化协议和SSL警告协议。 1.下层为SSL记录协议&#xff0c;主要作用是为高层协议提供基本的安全服务 建立在可靠的传输之上&#xff0c;负责对上层的数据进行分块…...

linux下的etc目录代表什么意思

在Linux系统中&#xff0c;/etc目录是一个非常重要的目录&#xff0c;它包含了系统的配置文件和相关的配置信息。下面是一些/etc目录中常见的文件和目录&#xff1a; 1. /etc/passwd&#xff1a;此文件包含了所有用户账户的信息&#xff0c;包括用户名、用户ID、用户所属的组I…...

iOS 两种方式设置状态栏

1、ios9.0以前设置状态栏字体颜色 ///白色 [[UIApplication sharedApplication]setStatusBarStyle:UIStatusBarStyleLightContent]; ///黑色 [[UIApplication sharedApplication]setStatusBarStyle:UIStatusBarStyleDefault]; 会看到如下提示&#xff1a; setStatusBarSty…...

html5:webSocket 基础使用

一、理解 HTML5 WebSocket HTML5 WebSocket是一种新型的网络协议&#xff0c;它能够在客户端和服务器之间建立实时的双向通信通道&#xff0c;使得浏览器和服务器之间的数据传输更加高效、快速和可靠。相比传统的HTTP协议&#xff0c;WebSocket协议使用更少的网络开销&#xf…...

html学习10-----总结(完)

<!DOCTYPE html> <html><head><meta charset"utf-8"/><title>html总结</title></head><body><h1>HTML总结</h1><br/><h2>文本格式化</h2><hr/><p><b>粗体文本<…...

Spring使用P命名空间实现注入数值信息-----Spring框架

<?xml version"1.0" encoding"UTF-8"?> <beans xmlns"http://www.springframework.org/schema/beans"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xmlns:p"http://www.springframework.org/schema/p"x…...

windows环境下安装RabbitMQ

一、RabbitMq简介1.1消息队列中间件简介消息队列中间件是分布式系统中重要的组件&#xff0c;主要解决应用耦合&#xff0c;异步消息&#xff0c;流量削锋等问题实现高性能&#xff0c;高可用&#xff0c;可伸缩和最终一致性[架构] 使用较多的消息队列有 ActiveMQ(安全)&#x…...

大数据学习栈记——Neo4j的安装与使用

本文介绍图数据库Neofj的安装与使用&#xff0c;操作系统&#xff1a;Ubuntu24.04&#xff0c;Neofj版本&#xff1a;2025.04.0。 Apt安装 Neofj可以进行官网安装&#xff1a;Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...

模型参数、模型存储精度、参数与显存

模型参数量衡量单位 M&#xff1a;百万&#xff08;Million&#xff09; B&#xff1a;十亿&#xff08;Billion&#xff09; 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的&#xff0c;但是一个参数所表示多少字节不一定&#xff0c;需要看这个参数以什么…...

前端倒计时误差!

提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...

HarmonyOS运动开发:如何用mpchart绘制运动配速图表

##鸿蒙核心技术##运动开发##Sensor Service Kit&#xff08;传感器服务&#xff09;# 前言 在运动类应用中&#xff0c;运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据&#xff0c;如配速、距离、卡路里消耗等&#xff0c;用户可以更清晰…...

力扣热题100 k个一组反转链表题解

题目: 代码: func reverseKGroup(head *ListNode, k int) *ListNode {cur : headfor i : 0; i < k; i {if cur nil {return head}cur cur.Next}newHead : reverse(head, cur)head.Next reverseKGroup(cur, k)return newHead }func reverse(start, end *ListNode) *ListN…...

从面试角度回答Android中ContentProvider启动原理

Android中ContentProvider原理的面试角度解析&#xff0c;分为​​已启动​​和​​未启动​​两种场景&#xff1a; 一、ContentProvider已启动的情况 1. ​​核心流程​​ ​​触发条件​​&#xff1a;当其他组件&#xff08;如Activity、Service&#xff09;通过ContentR…...

【FTP】ftp文件传输会丢包吗?批量几百个文件传输,有一些文件没有传输完整,如何解决?

FTP&#xff08;File Transfer Protocol&#xff09;本身是一个基于 TCP 的协议&#xff0c;理论上不会丢包。但 FTP 文件传输过程中仍可能出现文件不完整、丢失或损坏的情况&#xff0c;主要原因包括&#xff1a; ✅ 一、FTP传输可能“丢包”或文件不完整的原因 原因描述网络…...

用递归算法解锁「子集」问题 —— LeetCode 78题解析

文章目录 一、题目介绍二、递归思路详解&#xff1a;从决策树开始理解三、解法一&#xff1a;二叉决策树 DFS四、解法二&#xff1a;组合式回溯写法&#xff08;推荐&#xff09;五、解法对比 递归算法是编程中一种非常强大且常见的思想&#xff0c;它能够优雅地解决很多复杂的…...

算法—栈系列

一&#xff1a;删除字符串中的所有相邻重复项 class Solution { public:string removeDuplicates(string s) {stack<char> st;for(int i 0; i < s.size(); i){char target s[i];if(!st.empty() && target st.top())st.pop();elsest.push(s[i]);}string ret…...

DAY 45 超大力王爱学Python

来自超大力王的友情提示&#xff1a;在用tensordoard的时候一定一定要用绝对位置&#xff0c;例如&#xff1a;tensorboard --logdir"D:\代码\archive (1)\runs\cifar10_mlp_experiment_2" 不然读取不了数据 知识点回顾&#xff1a; tensorboard的发展历史和原理tens…...