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

news2024/11/18 17:42:59

按钮图片素材

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: Bitmap
    private var button: Bitmap
    private var buttonWidth: Int = 0
    private var paint: Paint
    private val antiShake : AntiShake

    // X轴最大移动距离
    private var maxMoveV: Int = 0

    // 时时的移动距离
    private var translateX: Float = 0f

    // 方向:向左false 向右true
    private var direction = false

    // 手指按下时的X轴位置
    var startX: Float = 0f

    // 用来记录上一个move坐标,用来判断,左滑还是右滑
    private var tempV: Float = 0f

    // 按钮的left
    private var btnLeft: Float = 0f

    // 按钮的right
    private var btnRight: Float = button.width.toFloat()

    // 当前手指按下的位置
    private var currentLocation: Float = 0f

    // 按下的区域,是否处于按钮区域
    private var isBtn = false

    // offOrNo:开true 关false
    var offOrNo = false

    // 是否可以触发点击事件
    var isOpenClick = false

    // 平移动画是否结束
    var animatorComplete = true

    override 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没有覆盖背景一半区域,手动增加按钮width
        buttonWidth = (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 = 0f

        super.onTouchEvent(event)
        when (event?.action) {
            MotionEvent.ACTION_DOWN -> {
                startX = event.x
                tempV = event.x

                isBtnContent(event)
                if (isBtn && animatorComplete) {
                    currentLocation = startX - btnLeft
                }
            }
            MotionEvent.ACTION_UP -> {
                if (antiShake.isFastClick()) {
                    endX = event.x
                    tempV = event.x

                    isOpenClick = startX == endX

                    if (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 = 0f
        var animatorEndX = 0f

        // 超过50%
        if (round > 50) {
            // 停留在右边
            animatorStartX = btnLeft
            animatorEndX = maxMoveV.toFloat()
            offOrNo = true
        } else {
            // 停留在左边
            animatorStartX = btnLeft
            animatorEndX = 0f
            offOrNo = false
        }

        translateAnimator(animatorStartX, animatorEndX)
        getBtnLeftRight(true)
    }

    // 平移动画
    private fun translateAnimator(
        animatorStartX: Float,
        animatorEndX: Float,
        duration: Long = 300
    ) {

        val animator = ValueAnimator.ofFloat(animatorStartX, animatorEndX)
        animator.duration = duration

        animator.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 关false
    private fun getBtnLeftRight(complete: Boolean = false) {
        if (complete) {
            if (offOrNo) {
                translateX = maxMoveV.toFloat()
            } else {
                translateX = 0f
            }
        }
        btnRight = translateX + buttonWidth
        btnLeft = btnRight - buttonWidth
    }

    // 防止快速点击
    class AntiShake(minTime: Int = 1000) {

        // 两次点击间隔不能少于1000ms
        private var MIN_DELAY_TIME : Int

        private var lastClickTime: Long = 0

        init {
            MIN_DELAY_TIME = minTime
        }

        fun isFastClick(): Boolean {
            var flag = true
            val currentClickTime = System.currentTimeMillis()
            if ((currentClickTime - lastClickTime) < MIN_DELAY_TIME) {
                flag = false
            }
            lastClickTime = currentClickTime
            return 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.MyButton
        android:layout_width="150dp"
        android:layout_height="55dp"/>

</LinearLayout>

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

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

相关文章

C 语言多线程(上)

一&#xff0c;线程创建 1.1 每一个线程都有一个唯一的线程ID&#xff0c;ID类型为pthread_t&#xff0c;这个ID是一个无符号长整形数&#xff0c;如果想要得到当前线程的线程ID&#xff0c;可以调用如下函数&#xff1a; pthread_t pthread_self(void); // 返回当前线程的线…

如何设计一个自动化测试框架?

一个成熟的测试框架主要由 4 部分组成&#xff1a;基础模块、管理模块、运行模块和统计模块 基础模块 底层核心库 一般指用于操作被测试应用程序的第三方库&#xff0c;例如在 Web 端的 Selenium/WebDriver。如API端的Requests 对象库 PO模式中的页面对象 可重用组件 如一些…

JavaScript 手撕大厂面试题数组扁平化以及增加版本 plus

前言 现在的前端面试手撕题是一个必要环节&#xff0c;有点时候八股回答的不错但是手撕题没写出来就会让面试官印象分大减&#xff0c;很可能就挂了… 概念 数组的扁平化其实就是将一个多层嵌套的数组转换为只有一层的数组 比如&#xff1a; [1, [2, [3, [4, 5]]]] > [1…

张量Tensor 深度学习

1 张量的定义 张量tensor理论是数学的一个分支学科&#xff0c;在力学中有重要的应用。张量这一术语源于力学&#xff0c;最初是用来表示弹性介质中各点应力状态的&#xff0c;后来张量理论发展成为力学和物理学的一个有力数学工具。 张量&#xff08;Tensor&#xff09;是一个…

WAF绕过-工具特征-菜刀+冰蝎+哥斯拉

WAF绕过主要集中在信息收集&#xff0c;漏洞发现&#xff0c;漏洞利用&#xff0c;权限控制四个阶段。 1、什么是WAF&#xff1f; Web Application Firewall&#xff08;web应用防火墙&#xff09;&#xff0c;一种公认的说法是“web应用防火墙通过执行一系列针对HTTP/HTTPS的安…

当服务器域名出现解析错误的问题该怎么办?

​  域名解析是互联网用户接收他们正在寻找的域的地址的过程。更准确地说&#xff0c;域名解析是人们在浏览器中输入时使用的域名与网站IP地址之间的转换过程。您需要站点的 IP 地址才能知道它所在的位置并加载它。但&#xff0c;在这个过程中&#xff0c;可能会出现多种因素…

leetcode(力扣)剑指 Offer 16. 数值的整数次方 (快速幂)

文章目录 题目描述思路分析完整代码 题目描述 实现 pow(x, n) &#xff0c;即计算 x 的 n 次幂函数&#xff08;即&#xff0c;xn&#xff09;。不得使用库函数&#xff0c;同时不需要考虑大数问题。 示例 1&#xff1a; 输入&#xff1a;x 2.00000, n 10 输出&#xff1a;10…

Scratch 教程 -- 如何绘制像素画

1.像素画的定义 像素画就是以1像素的正方形为最小单位画的画&#xff0c;且物体有明显的分界线 这是像素画 这不是像素画 来看这两个法棍 这是像素画 这不是像素画 为什么第二个不是像素画&#xff1f;因为不能区分边缘和物体&#xff0c;它们之间有很多过渡色。 中间的过渡色属…

JUC并发编程之volatile详解

目录 1. volatile 1.1 volatile关键字的作用 1.1.1 变量可见性 1.1.2 禁止指令重排序 1.2 volatile可见性案例 1.3 volatile非原子性案例 1.4 volatile 禁止重排序 1.5 volatile 日常使用场景 送书活动 1. volatile 在并发编程中&#xff0c;多线程操作共享的变量时&a…

SAP MIRO 报错 Allowed posting periods:xx xxx

背景&#xff1a;在八月初&#xff0c;七月份财务账期没关&#xff0c;七月份物料帐期已关 用户在做MIRO的时候&#xff0c;无法开票成功&#xff0c;报错&#xff1a; Allowed posting periods:xx xxx 但是集团要求&#xff0c;这些帐应该记在七月份 查询相关资料得知。MI…

PLUS模型 | 历史土地利用数据,进行多情景模式下的未来土地利用预测

工业革命以来&#xff0c;社会生产力迅速提高&#xff0c;人类活动频繁&#xff0c;此外人口与日俱增对土地的需求与改造更加强烈&#xff0c;人-地关系日益紧张。此外&#xff0c;土地资源的不合理开发利用更是造成了水土流失、植被退化、水资源短缺、区域气候变化、生物多样性…

【嵌入式学习笔记】嵌入式入门3——串口

1.数据通信的基础概念 1.1.串行/并行通信 数据通信按数据通信方式分类&#xff1a;串行通信、并行通信 1.2.单工/半双工/全双工通信 数据通信按数据传输方向分类&#xff1a;单工通信、半双工通信、全双工通信 单工通信&#xff1a;数据只能沿一个方向传输半双工通信&…

支持中文创成式填充 AI版PS 2023 v25.0安装教程

抖音保姆级视频教程: https://v.douyin.com/iJdUjg2o/ PS 2023 v25.0安装包地址&#xff1a; 链接: https://pan.baidu.com/s/1PXgVHDHdMIRcDzV4IfGAQw?pwd2023 提取码: 2023 如有疑问请加交流请加QQ群&#xff1a;814894746 安装教程总结&#xff1a; 卸载之前的PS beta版…

C++11 通用工具

通用工具 目录 pair和tuple智能指针数值极值type trait 和type utility辅助函数clock和timerbitset随机数 1 pair和Tuple 1.1 pair 头文件 #include<utility>pair定义 pair<string,string> author{James","joyce"};)] --> pair操作 1.2 tup…

阿里云 MSE + ZadigX ,无门槛实现云原生全链路灰度发布

作者&#xff1a;ZadigX 企业发布现状痛点 目前企业在选择和实施发布策略时面临以下困境&#xff1a; 1. 缺乏云原生能力&#xff1a; 由于从传统部署转变为云原生模式后&#xff0c;技术架构改造需要具备相关能力的人才。这使得企业在发布策略方面难以入手。 2. 缺乏自动化…

一张图看懂应用程序访问驱动的内部原理

咱就浅谈一下字符设备驱动的内部实现~ 1、当我们在应用程序中使用open打开文件的时候&#xff0c;会自动在/dev/mycdev下生成一个inode号。 2、只要文件存在于系统中&#xff0c;在系统内核就会存在一个inode结构体&#xff0c;里面存储文件的一些相关信息&#xff0c;其中信息…

如何使用STAR原则优化项目管理?

介绍STAR原则 1.1 STAR原则的定义 STAR原则是一个行为面试技术&#xff0c;即Situation&#xff08;情境&#xff09;、Task&#xff08;任务&#xff09;、Action&#xff08;行动&#xff09;和Result&#xff08;结果&#xff09;。这种原则被广泛应用在职业面试中&#x…

【严重】Metabase 基于H2引擎的远程代码执行漏洞

漏洞描述 Metabase 是一个开源的数据分析和可视化工具。 由于 CVE-2023-38646 的补丁(从H2 JDBC连接字符串中删除INIT脚本以防止命令注入)修复不完全&#xff0c;Metabase 仍受到命令注入的影响。攻击者可使用 H2 作为数据库引擎&#xff0c;通过 /api/setup/validate 端点发…

idea设置项目使用本地仓库

方法一&#xff1a;最简单有效的方法 在pom加入以下内容 <repositories><repository><id>local-repo</id><url>file://本地仓库目录</url></repository></repositories>方法二&#xff1a;idea设置maven为workoffline&#x…

局域网内电脑ping不通(防火墙惹的祸)

明明是同一网段&#xff08;同一局域网&#xff09;的电脑&#xff0c;却ping不通&#xff0c;这种情况大概率就是防火墙惹得祸了。除了把防火墙关掉&#xff0c;我们还可以采取如下解决方案。 解决方案&#xff1a; 1. 打开&#xff1a;控制面板\系统和安全\Windows Defende…