Android 自定义悬浮拖动吸附按钮

news2025/5/19 6:01:14

 一个悬浮的拨打电话按钮,使用CardView+ImageView可能会出现适配问题,也就是图片显示不全,出现这种问题,就直接替换控件了,因为上述的组合控件没有FloatingActionButton使用方便,还可以有拖动和吸附效果不是更好吗。

1.一般自定义就可以实现,看看第一种方式:直接上代码了:


import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.widget.ImageView;

import com.baijie.crm.activity.utils.Util;
import com.google.android.material.floatingactionbutton.FloatingActionButton;

/**
 * 悬浮吸附可拖动按钮
 */
@SuppressLint("AppCompatCustomView")
public class DragFloatActionButton extends FloatingActionButton {

    private static final String TAG = "DragButton";
    private int parentHeight;
    private int parentWidth;

    private int lastX;
    private int lastY;

    private boolean isDrag;
    private ViewGroup parent;

    public DragFloatActionButton(Context context) {
        super(context);
    }

    public DragFloatActionButton(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public DragFloatActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int rawX = (int) event.getRawX();
        int rawY = (int) event.getRawY();
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                isDrag = false;
                this.setAlpha(0.9f);
                setPressed(true);
                getParent().requestDisallowInterceptTouchEvent(true);
                lastX = rawX;
                lastY = rawY;
                if (getParent() != null) {
                    parent = (ViewGroup) getParent();
                    parentHeight = parent.getHeight();
                    parentWidth = parent.getWidth();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                this.setAlpha(0.9f);
                int dx = rawX - lastX;
                int dy = rawY - lastY;
                int distance = (int) Math.sqrt(dx * dx + dy * dy);
                if (distance > 2 && !isDrag) {
                    isDrag = true;
                }

                float x = getX() + dx;
                float y = getY() + dy;
                //检测是否到达边缘 左上右下
                x = x < 0 ? 0 : x > parentWidth - getWidth() ? parentWidth - getWidth() : x;
                y = getY() < 0 ? 0 : getY() + getHeight() > parentHeight ? parentHeight - getHeight() : y;
                setX(x);
                setY(y);
                lastX = rawX;
                lastY = rawY;
                break;
            case MotionEvent.ACTION_UP:
                if (isDrag) {
                    //恢复按压效果
                    setPressed(false);
                    moveHide(rawX);
                }
                break;
        }
        //如果是拖拽则消耗事件,否则正常传递即可。
        return isDrag || super.onTouchEvent(event);
    }


    private void moveHide(int rawX) {
        if (rawX >= parentWidth / 2) {
            //靠右吸附
            animate().setInterpolator(new DecelerateInterpolator())
                    .setDuration(500)
                    //.xBy(parentWidth - getWidth() - getX())
//                    .xBy(parentWidth - getWidth() - getX() - DensityUtils.dp2px(getContext(), 20))
                    .xBy(parentWidth - getWidth() - getX() - Util.dp2px(getContext(), 20))
                    .start();
        } else {
            //靠左吸附
            //ObjectAnimator oa = ObjectAnimator.ofFloat(this, "x", getX(), 0);
            // 设置左侧间距 20
            ObjectAnimator oa = ObjectAnimator.ofFloat(this, "x", getX(),
                    Util.dp2px(getContext(), 20));
            oa.setInterpolator(new DecelerateInterpolator());
            oa.setDuration(500);
            oa.start();

        }
    }
}
public static int dp2px(Context context, float dp)
    {
        return (int ) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics());
    }
<com.666.widget.DragFloatActionButton
        android:id="@+id/dragFloatActionButton"
        android:visibility="visible"
        android:layout_width="58dp"
        android:layout_height="58dp"
        android:layout_alignParentRight="true"
        android:layout_alignParentBottom="true"
        android:layout_gravity="bottom|center"
        android:layout_marginRight="15dp"
        android:layout_marginBottom="350dp"
        android:src="@mipmap/sixsixsix"
        app:backgroundTint="?attr/colorPrimary"
        app:borderWidth="0.0dip"
        app:elevation="15.0dip"
        app:fabCustomSize="58dp"
        app:rippleColor="#BFEFFF"/>

 

2.下面是之前借鉴的写法,仅供参考


import android.animation.ObjectAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;

import com.alipay.pushsdk.util.log.LogUtil;
import com.google.android.material.floatingactionbutton.FloatingActionButton;

/**
 * 可拖拽 吸附的悬浮按钮
 */
public class AiDragFloatActionButton extends FloatingActionButton {

    private int parentHeight;
    private int parentWidth;

    public AiDragFloatActionButton(Context context) {
        super(context);
    }

    public AiDragFloatActionButton(Context context, AttributeSet attrs) {
        super(context, attrs);

    }

    public AiDragFloatActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

    }


    private int lastX;
    private int lastY;

    private boolean isDrag;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int rawX = (int) event.getRawX();
        int rawY = (int) event.getRawY();
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                LogUtil.d("SGF","MotionEvent.ACTION_DOWN");
                setPressed(true);
                isDrag = false;
                getParent().requestDisallowInterceptTouchEvent(true);
                lastX = rawX;
                lastY = rawY;
                ViewGroup parent;
                if (getParent() != null) {
                    parent = (ViewGroup) getParent();
                    parentHeight = parent.getHeight();
                    parentWidth = parent.getWidth();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                LogUtil.d("SGF","MotionEvent.ACTION_MOVE");
                if (parentHeight <= 0 || parentWidth == 0) {
                    isDrag = false;
                    break;
                } else {
                    isDrag = true;
                }
                int dx = rawX - lastX;
                int dy = rawY - lastY;
                //这里修复一些华为手机无法触发点击事件
                int distance = (int) Math.sqrt(dx * dx + dy * dy);
                if (distance == 0) {
                    isDrag = false;
                    break;
                }
                float x = getX() + dx;
                float y = getY() + dy;
                //检测是否到达边缘 左上右下
                x = x < 0 ? 0 : x > parentWidth - getWidth() ? parentWidth - getWidth() : x;
                y = getY() < 0 ? 0 : getY() + getHeight() > parentHeight ? parentHeight - getHeight() : y;
                setX(x);
                setY(y);
                lastX = rawX;
                lastY = rawY;
                Log.i("aa", "isDrag=" + isDrag + "getX=" + getX() + ";getY=" + getY() + ";parentWidth=" + parentWidth);
                break;
            case MotionEvent.ACTION_UP:
                LogUtil.d("SGF","MotionEvent.ACTION_UP");
                if (!isNotDrag()) {
                    //恢复按压效果
                    setPressed(false);
                    //Log.i("getX="+getX()+";screenWidthHalf="+screenWidthHalf);
                    if (rawX >= parentWidth / 2) {
                        //靠右吸附
                        animate().setInterpolator(new DecelerateInterpolator())
                                .setDuration(500)
                                // 将松开后悬停的位置修改一下(右侧保留35的间距)
                                .xBy((parentWidth - getWidth() - getX()) - 35)
                                .start();
                    } else {
                        //靠左吸附
                        // 将松开后悬停的位置修改一下(左侧保留35的间距)
                        ObjectAnimator oa = ObjectAnimator.ofFloat(this, "x", getX(), 35);
                        oa.setInterpolator(new DecelerateInterpolator());
                        oa.setDuration(500);
                        oa.start();

//                        animate().setInterpolator(new DecelerateInterpolator())
//                                .setDuration(500)
//                                // 将松开后悬停的位置修改一下(右侧保留35的间距)
//                                .xBy(50)
//                                .start();
                    }
                }
                break;
//            case MotionEvent.ACTION_CANCEL:
//                LogUtil.d("SGF","MotionEvent.ACTION_CANCEL");
//                getParent().requestDisallowInterceptTouchEvent(false);
//                break;
        }
        //如果是拖拽则消s耗事件,否则正常传递即可。
        return !isNotDrag() || super.onTouchEvent(event);
    }

    private boolean isNotDrag() {
        return !isDrag && (getX() == 0
                || (getX() == parentWidth - getWidth()));
    }
}

这种方式测试功能基本是一样的,但是就是无法直接设置控件的监听事件,可以看看。

 


import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;

import com.google.android.material.floatingactionbutton.FloatingActionButton;

public class DraggableFloatingActionButton extends FloatingActionButton {

    private int lastX, lastY;
    private WindowManager.LayoutParams params;

    public DraggableFloatingActionButton(Context context) {
        super(context);
        init();
    }

    public DraggableFloatingActionButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public DraggableFloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        lastX = (int) event.getRawX();
                        lastY = (int) event.getRawY();
                        break;
                    case MotionEvent.ACTION_MOVE:
                        int dx = (int) event.getRawX() - lastX;
                        int dy = (int) event.getRawY() - lastY;

                        RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
                        layoutParams.leftMargin += dx;
                        layoutParams.topMargin += dy;
                        setLayoutParams(layoutParams);

                        lastX = (int) event.getRawX();
                        lastY = (int) event.getRawY();

                        break;
                }
                return true;
            }
        });
    }
}

上面这种就是最原始的自定义方式了,没有什么好描述的了。

其它相关案例:

GitHub - liwenzhi/FloatingActionButton: 悬浮的按钮设计

 

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

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

相关文章

一种开源的高斯泼溅实现库——gsplat: An Open-Source Library for Gaussian Splatting

一种开源的高斯泼溅实现库——gsplat: An Open-Source Library for Gaussian Splatting 文章目录 一种开源的高斯泼溅实现库——gsplat: An Open-Source Library for Gaussian Splatting摘要Abstract1. 基本思想1.1 设计1.2 特点 2. Nerfstudio&Splatfacto2.1 Nerfstudio2.…

ARM A64 STR指令

ARM A64 STR指令 1 STR (immediate)1.1 Post-index1.1.1 32-bit variant1.1.2 64-bit variant 1.2 Pre-index1.2.1 32-bit variant1.2.2 64-bit variant 1.3 Unsigned offset1.3.1 32-bit variant1.3.2 64-bit variant 1.4 Assembler symbols 2 STR (register)2.1 32-bit varia…

Linux wlan 单频段 dual wifi创建

环境基础 TP LINK WN722N V1网卡linux 主机 查看设备是否支持双ap managed&#xff1a;客户端模式&#xff08;连接路由器/AP&#xff09;AP&#xff1a;接入点模式&#xff08;创建热点&#xff09;AP/VLAN&#xff1a;支持带VLAN标签的虚拟AP{ AP, mesh point, P2P-GO } &l…

【CSS】使用 CSS 绘制三角形

一、Border 边框法&#xff08;最常用&#xff09; 原理&#xff1a;通过设置元素的宽高为 0&#xff0c;利用透明边框相交形成三角形。 .triangle {width: 0;height: 0;border-left: 50px solid transparent; /* 左侧边框透明 */border-right: 50px solid transparent; /* …

信奥赛-刷题笔记-栈篇-T2-P3056括号调整问题0518

总题单 ​ 本部分总题单如下 【腾讯文档】副本-CSP-JSNOI 题单 (未完待续) https://docs.qq.com/sheet/DSmJuVXR4RUNVWWhW?tabBB08J2 栈篇题单 P3056 [USACO12NOV] Clumsy Cows S https://www.luogu.com.cn/problem/P3056 题目描述 Bessie the cow is trying to type …

inverse-design-of-grating-coupler-3d

一、设计和优化3D光栅耦合器 1.1 代码讲解 通过预定义的环形间距参数(distances数组),在FDTD中生成椭圆光栅结构,并通过用户交互确认几何正确性后,可进一步执行参数扫描优化。 # os:用于操作系统相关功能(如文件路径操作) import os import sys# lumapi:Lumerical 的…

Science Robotics 封面论文:基于形态学开放式参数化的仿人灵巧手设计用于具身操作

人形机械手具有无与伦比的多功能性和精细运动技能&#xff0c;使其能够精确、有力和稳健地执行各种任务。在古生物学记录和动物王国中&#xff0c;我们看到了各种各样的替代手和驱动设计。了解形态学设计空间和由此产生的涌现行为不仅可以帮助我们理解灵巧的作用及其演变&#…

DSU-Net

目录 Abstract 摘要 DSU-Net 模型框架 编码器 轻量级适配器模块 特征融合与协作 解码器 模型优势 实验 代码 总结 Abstract DSU-Net is an improved U-Net model based on DINOv2 and SAM2. It addresses the limitations of existing image segmentation models …

2025年- H30-Lc138- 141.环形链表(快慢指针,快2慢1)---java版

1.题目描述 2.思路 弗洛伊德算法&#xff08;快慢指针 3.代码实现 public boolean hasCycle(ListNode head) {//1.如果空节点或者只有一个节点&#xff0c;都说明没有环&#xff0c;返回falseif(headnull||head.nextnull){return false;}//2.定义快慢指针&#xff0c;都从头…

LoadBarWorks:一款赛博风加载动画生成器的构建旅程

我正在参加CodeBuddy「首席试玩官」内容创作大赛&#xff0c;本文所使用的 CodeBuddy 免费下载链接&#xff1a;腾讯云代码助手 CodeBuddy - AI 时代的智能编程伙伴 项目缘起&#xff1a;赛博与实用的结合 在日常开发中&#xff0c;我经常需要为不同的项目添加加载动画&#x…

SAP集团内部公司间交易自动开票

SAP集团内部公司间交易自动开票(非STO/EDI模式) 集团内部公司间采购与销售业务&#xff0c;在确认相应单据无误后&#xff0c;为减少人工开票业务&#xff0c; 可以用系统标准功能来实现自动开票。 1.采购发票自动开票(ERS) T-CODE:BP,勾选“基于收货的发票校验”、“自动G…

【YOLO(txt)格式转VOC(xml)格式数据集】以及【制作VOC格式数据集 】

1.txt—>xml转化代码 如果我们手里只有YOLO标签的数据集&#xff0c;我们要进行VOC格式数据集的制作首先要进行标签的转化&#xff0c;以下是标签转化的脚本。 其中picPath为图片所在文件夹路径&#xff1b; txtPath为你的YOLO标签对应的txt文件所在路径&#xff1b; xmlPa…

Linux 的 UDP 网络编程 -- 回显服务器,翻译服务器

目录 1. 回显服务器 -- echo server 1.1 相关函数介绍 1.1.1 socket() 1.1.2 bind() 1.1.3 recvfrom() 1.1.4 sendto() 1.1.5 inet_ntoa() 1.1.6 inet_addr() 1.2 Udp 服务端的封装 -- UdpServer.hpp 1.3 服务端代码 -- UdpServer.cc 1.4 客户端代码 -- UdpClient.…

C++笔试题(金山科技新未来训练营):

题目分布&#xff1a; 17道单选&#xff08;每题3分&#xff09;3道多选题&#xff08;全对3分&#xff0c;部分对1分&#xff09;2道编程题&#xff08;每一道20分&#xff09;。 不过题目太多&#xff0c;就记得一部分了&#xff1a; 单选题&#xff1a; static变量的初始…

【RabbitMQ】 RabbitMQ高级特性(二)

文章目录 一、重试机制1.1、重试配置1.2、配置交换机&队列1.3、发送消息1.4、消费消息1.5、运行程序1.6、 手动确认 二、TTL2.1、设置消息的TTL2.2、设置队列的TTL2.3、两者区别 三 、死信队列6.1 死信的概念3.2 代码示例3.2.1、声明队列和交换机3.2.2、正常队列绑定死信交…

电子电路:什么是电流离散性特征?

关于电荷的量子化,即电荷的最小单位是电子的电荷量e。在宏观电路中,由于电子数量极大,电流看起来是连续的。但在微观层面,比如纳米器件或单电子晶体管中,单个电子的移动就会引起可观测的离散电流。 还要提到散粒噪声,这是电流离散性的表现之一。当电流非常小时,例如在二…

深入理解位图(Bit - set):概念、实现与应用

目录 引言 一、位图概念 &#xff08;一&#xff09;基本原理 &#xff08;二&#xff09;适用场景 二、位图的实现&#xff08;C 代码示例&#xff09; 三、位图应用 1. 快速查找某个数据是否在一个集合中 2. 排序 去重 3. 求两个集合的交集、并集等 4. 操作系…

猫番阅读APP:丰富资源,优质体验,满足你的阅读需求

猫番阅读APP是一款专为书籍爱好者设计的移动阅读应用&#xff0c;致力于提供丰富的阅读体验和多样化的书籍资源。它不仅涵盖了小说、非虚构、杂志等多个领域的电子书&#xff0c;还提供了个性化推荐、书架管理、离线下载等功能&#xff0c;满足不同读者的阅读需求。无论是通勤路…

MetaMask安装及使用-使用水龙头获取测试币的坑?

常见的异常有&#xff1a; 1.unable to request drip, please try again later. 2.You must hold at least 1 LINK on Ethereum Mainnet to request native tokens. 3.The address provided does not have sufficient historical activity or balance on the Ethereum Mainne…

AI:OpenAI论坛分享—《AI重塑未来:技术、经济与战略》

AI&#xff1a;OpenAI论坛分享—《AI重塑未来&#xff1a;技术、经济与战略》 导读&#xff1a;2025年4月24日&#xff0c;OpenAI论坛全面探讨了 AI 的发展趋势、技术范式、地缘政治影响以及对经济和社会的广泛影响。强调了 AI 的通用性、可扩展性和高级推理能力&#xff0c;以…