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: 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>

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Excel技巧 - 管理规则设置一行变色

如何设置某一列单元格的值大于一个值时&#xff0c;该单元格所在的一整行都变色呢&#xff1f; 1、先框选内容区域&#xff0c;点击开始&#xff0c;条件格式&#xff0c;新建规则 2、如果销量大于20&#xff0c;则该行都变为绿色 编辑格式选择&#xff1a;使用公式确定要设置…

springboot第33集:nacos图

./startup.sh -m standalone Nacos是一个内部微服务组件&#xff0c;需要在可信的内部网络中运行&#xff0c;不可暴露在公网环境&#xff0c;防止带来安全风险。Nacos提供简单的鉴权实现&#xff0c;为防止业务错用的弱鉴权体系&#xff0c;不是防止恶意攻击的强鉴权体系。 鉴…

ChatGPT及其工作原理;OpenAI申请注册商标GPT-5,引发关注

&#x1f989; AI新闻 &#x1f680; OpenAI申请注册商标GPT-5&#xff0c;引发关注 摘要&#xff1a;OpenAI已在上月18日申请注册商标GPT-5&#xff0c;显示该模型将提供文本生成、自然语言理解、语音转录、翻译、分析等功能。此前OpenAI曾表示尚未开始训练GPT-4的后继者GPT…

在 React 中渲染大型数据集的 3 种方法

随着 Web 应用程序变得越来越复杂&#xff0c;我们需要找到有效的方法来优化性能和渲染大型数据集。在 React 应用程序中处理大型数据集时&#xff0c;一次呈现所有数据可能会导致性能不佳和加载时间变慢。 虚拟化是一种通过一次仅呈现数据集的一部分来解决此问题的技术&#…

I.MX6ULL_Linux_驱动篇(41)platform设备驱动框架

我们在前面几章编写的设备驱动都非常的简单&#xff0c;都是对IO进行最简单的读写操作。像I2C、SPI、 LCD 等这些复杂外设的驱动就不能这么去写了&#xff0c; Linux 系统要考虑到驱动的可重用性&#xff0c;因此提出了驱动的分离与分层这样的软件思路&#xff0c;在这个思路下…

原型链污染,nodejs逃逸例子

文章目录 原型链污染原型链污染原理原型链污染小例子 原型链污染题目解析第一题第二题 Nodejs沙箱逃逸方法一方法二 原型链污染 原型链污染原理 原型链 function test(){this.a test; } b new test;可以看到b在实例化为test对象以后&#xff0c;就可以输出test类中的属性a…

用python做一个小项目,python做简单小项目

大家好&#xff0c;本文将围绕用python做一个小项目展开说明&#xff0c;python做简单小项目是一个很多人都想弄明白的事情&#xff0c;想搞清楚python入门小项目需要先了解以下几个事情。 来源丨网络 经常听到有朋友说&#xff0c;学习编程是一件非常枯燥无味的事情。其实&…

gitlab配置webhook

一.前言 当需要做jenkins的自动化触发构建时&#xff0c;就需要配置gitlab的webhook功能&#xff0c;以下来展示以下如何配置gitlab的webhook&#xff0c;jenkins的配置就不在这里展示了&#xff0c;可以去看我devops文章的完整配置 二.配置 在新版本的gitlab中&#xff0c…

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

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

高速公路巡检无人机,为何成为公路巡检的主流工具

随着无人机技术的不断发展&#xff0c;无人机越来越多地应用于各个领域。其中&#xff0c;在高速公路领域&#xff0c;高速公路巡检无人机已成为公路巡检的得力助手。高速公路巡检无人机之所以能够成为公路巡检中的主流工具&#xff0c;主要是因为其具备以下三大特性。 一、高速…

Android 之 MediaPlayer 播放音频与视频

本节引言&#xff1a; 本节带来的是Android多媒体中的——MediaPlayer&#xff0c;我们可以通过这个API来播放音频和视频 该类是Androd多媒体框架中的一个重要组件&#xff0c;通过该类&#xff0c;我们可以以最小的步骤来获取&#xff0c;解码 和播放音视频。它支持三种不同的…

PHP8的运算符-PHP8知识详解

运算符是可以通过给出的一或多个值&#xff08;用编程行话来说&#xff0c;表达式&#xff09;来产生另一个值&#xff08;因而整个结构成为一个表达式&#xff09;的东西。 PHP8的运算符有很多&#xff0c;按类型分有一元运算符、二元运算符、三元运算符。 一元运算符只对一…

第四讲:利用ADO方式连接Access数据库

【分享成果&#xff0c;随喜正能量】最值得信赖的&#xff0c;其实是自己从孤独中得来的东西&#xff0c;而不是别人给予自己的东西。每个人都是一座孤岛&#xff0c;有些人一生都在想要逃离这座岛&#xff0c;有些人一生都在创造并丰富自己这座岛。。 《VBA数据库解决方案》教…

解释器模式——自定义语言的实现

1、简介 1.1、文法规则和抽象语法树 解释器模式描述了如何为简单的语言定义一个文法&#xff0c;如何在该语言中表示一个句子&#xff0c;以及如何解释这些句子。在正式分析解释器模式结构之前&#xff0c;先来学习如何表示一个语言的文法规则以及如何构造一棵抽象语法树。 …

安全文件传输:如何避免数据泄露和黑客攻击

网络安全问题日益严重&#xff0c;导致许多数据被泄露和黑客袭击的事件频发。为了保证文件传输的安全&#xff0c;需要实施一系列安全文件传输策略来防止数据被泄露和黑客袭击。 第一、选择适合的加密方法是非常关键的 加密是一种将明文转换成密文的过程&#xff0c;这样只有授…

跨部门协作,企业图文档管理的协同管理的重要性

随着企业规模的扩大和业务流程的复杂化&#xff0c;图文档管理涉及的部门和人员越来越多&#xff0c;因此跨部门协作成为了必不可少的管理方式。在线图文档管理作为现代企业的数字化解决方案之一&#xff0c;为跨部门协作提供了强大的支持和便利。在线图文档管理在企业图文档管…

计算机视觉实验:图像增强应用实践

本次实验主要从基于统计、函数映射的图像增强方法和基于滤波的图像增强方法两种方法中对一些图像增强的算法进行实现。主要的编程语言为python&#xff0c;调用了python自带的PIL图像库用于读取图像&#xff0c;利用numpy进行图像运算&#xff0c;最后使用opencv第三方库进行对…

最新版Android13使用Notification,Notification的基本使用和进阶使用

一、使用Notification 1、创建一个通知 1.1 注册一个渠道 在Android13&#xff0c;版本通知的使用发生了新的变化。 首先我们需要创建一个NotificationManager用于管理通知。 //创建notificationManager对通知进行管理 NotificationManager notificationManager getSyste…

输入筛选框搜索

文章目录 输入筛选框实现效果图需求前端工具版本添加依赖main.js导入依赖 代码 后端代码对应 sql对应 mapper.xml 文件的动态 sql 输入筛选框实现 效果图 需求 通过筛选框&#xff0c;选择公司&#xff0c;传入后端&#xff0c;后端根据公司名称去文章的内容中进行模糊查询 …