Android自定义View实现拖动自动吸边效果

实现自定义view(1):可在全屏幕自由拖动的view Android中自带的view种类很多,但是有时候不能满足我们的需求,下面介绍一种自定义view的方法,实现了拖动矩形到屏幕任意位置的需求。 下图为运行效果图: 废话不多说,直接上代码 Activity.java package com.zhuozhu 阅读详情

NightRain

自定义View,一是为了满足设计需求,二是开发者进阶的标志之一。随心所欲就是我等奋斗的目标!!!

效果

拖动效果

实现逻辑

  • 明确需求
    1、实现控件跟随手指拖动
    2、实现控件自动贴边

  • 整理思路
    1、既然要实现控件拖动,那么就离不开onTouchEvent()这个方法,需要监听里面的按下和滑动事件。
    2、 要实现自动贴边,需要监听onTouchEvent()中手指离开屏幕事件。对于贴边的过程,我们用属性动画来解决。
    3、事件的冲突问题也需要考虑,拖动、点击关系到了事件的拦截。

  • 动手实现
    在需求明确、思路清晰的情况下就要开始动手实现(需要了解自定义View的一些基础API),下面代码中注释写的基本都差不多,很好理解。欢迎指出讨论!!!

完整代码

  • Kotlin
class AttachButton: View {
    private var mLastRawX: Float = 0F
    private var mLastRawY: Float = 0F
    private var isDrug = false
    private var mRootMeasuredWidth = 0
    private var mRootMeasuredHeight = 0
    private var mRootTopY = 0
    private var customIsAttach = false
    private var customIsDrag = false

    constructor(context: Context) : this(context, null)
    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    ) {
        isClickable = true
        initAttrs(context, attrs)
    }

    private fun initAttrs(context: Context, attrs: AttributeSet?) {
        attrs?.let {
            val mTypedAttay = context.obtainStyledAttributes(it, R.styleable.AttachButton)
            customIsAttach =
                mTypedAttay.getBoolean(R.styleable.AttachButton_customIsAttach, true)
            customIsDrag =
                mTypedAttay.getBoolean(R.styleable.AttachButton_customIsDrag, true)
            mTypedAttay.recycle()
        }
    }

    override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
        super.dispatchTouchEvent(event)
        return true
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        event?.let {
            //判断是否需要滑动
            if (customIsDrag) {
                //当前手指的坐标
                val mRawX = it.rawX
                val mRawY = it.rawY
                when (it.action) {
                    MotionEvent.ACTION_DOWN -> {//手指按下
                        isDrug = false
                        //记录按下的位置
                        mLastRawX = mRawX
                        mLastRawY = mRawY
                        if (parent is ViewGroup) {
                            val mViewGroup = parent as ViewGroup
                            val location = IntArray(2)
                            mViewGroup.getLocationInWindow(location)
                            //获取父布局的高度
                            mRootMeasuredHeight = mViewGroup.measuredHeight
                            mRootMeasuredWidth = mViewGroup.measuredWidth
                            //获取父布局顶点的坐标
                            mRootTopY = location[1]
                        }
                    }
                    MotionEvent.ACTION_MOVE -> {//手指滑动
                        if (mRawX >= 0 && mRawX <= mRootMeasuredWidth && mRawY >= mRootTopY && mRawY <= (mRootMeasuredHeight + mRootTopY)) {
                            //手指X轴滑动距离
                            val differenceValueX: Float = mRawX - mLastRawX
                            //手指Y轴滑动距离
                            val differenceValueY: Float = mRawY - mLastRawY
                            //判断是否为拖动操作
                            if (!isDrug) {
                                isDrug =
                                    sqrt(((differenceValueX * differenceValueX) + (differenceValueY * differenceValueY)).toDouble()) >= 2
                            }
                            //获取手指按下的距离与控件本身X轴的距离
                            val ownX = x
                            //获取手指按下的距离与控件本身Y轴的距离
                            val ownY = y
                            //理论中X轴拖动的距离
                            var endX: Float = ownX + differenceValueX
                            //理论中Y轴拖动的距离
                            var endY: Float = ownY + differenceValueY
                            //X轴可以拖动的最大距离
                            val maxX: Float = mRootMeasuredWidth - width.toFloat()
                            //Y轴可以拖动的最大距离
                            val maxY: Float = mRootMeasuredHeight - height.toFloat()
                            //X轴边界限制
                            endX = if (endX < 0) 0F else (if (endX > maxX) maxX else endX)
                            //Y轴边界限制
                            endY = if (endY < 0) 0F else (if (endY > maxY) maxY else endY)
                            //开始移动
                            x = endX
                            y = endY
                            //记录位置
                            mLastRawX = mRawX
                            mLastRawY = mRawY
                        }
                    }

                    MotionEvent.ACTION_UP -> {//手指离开
                        if (customIsAttach) {
                            //判断是否为点击事件
                            if (isDrug) {
                                val center = mRootMeasuredWidth / 2
                                //自动贴边
                                if (mLastRawX <= center) {
                                    //向左贴边
                                    animate()
                                        .setInterpolator(BounceInterpolator())
                                        .setDuration(500)
                                        .x(0F)
                                        .start()
                                } else {
                                    //向右贴边
                                    animate()
                                        .setInterpolator(BounceInterpolator())
                                        .setDuration(500)
                                        .x(mRootMeasuredWidth - width.toFloat())
                                        .start()
                                }
                            }
                        }
                    }
                }
            }
        }
        //是否拦截事件
        return if (isDrug) isDrug else super.onTouchEvent(event)
    }
}
  • Java
/**
 * 自定义View实现拖动并自动吸边效果
 * <p>
 * 处理滑动和贴边 {@link #onTouchEvent(MotionEvent)}
 * 处理事件分发 {@link #dispatchTouchEvent(MotionEvent)}
 * </p>
 *
 * @attr customIsAttach  //是否需要自动吸边
 * @attr customIsDrag    //是否可拖曳
 */
public class AttachButton extends View {
    private float mLastRawX;
    private float mLastRawY;
    private final String TAG = "AttachButton";
    private boolean isDrug = false;
    private int mRootMeasuredWidth = 0;
    private int mRootMeasuredHeight = 0;
    private int mRootTopY = 0;
    private boolean customIsAttach;
    private boolean customIsDrag;

    public AttachButton(Context context) {
        this(context, null);
    }

    public AttachButton(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public AttachButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setClickable(true);
        initAttrs(context, attrs);
    }

    /**
     * 初始化自定义属性
     */
    private void initAttrs(Context context, AttributeSet attrs) {
        TypedArray mTypedAttay = context.obtainStyledAttributes(attrs, R.styleable.AttachButton);
        customIsAttach = mTypedAttay.getBoolean(R.styleable.AttachButton_customIsAttach, true);
        customIsDrag = mTypedAttay.getBoolean(R.styleable.AttachButton_customIsDrag, true);
        mTypedAttay.recycle();
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        super.dispatchTouchEvent(event);
        return true;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        //判断是否需要滑动
        if (customIsDrag) {
            //当前手指的坐标
            float mRawX = ev.getRawX();
            float mRawY = ev.getRawY();
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN://手指按下
                    isDrug = false;
                    //记录按下的位置
                    mLastRawX = mRawX;
                    mLastRawY = mRawY;
                    ViewGroup mViewGroup = (ViewGroup) getParent();
                    if (mViewGroup != null) {
                        int[] location = new int[2];
                        mViewGroup.getLocationInWindow(location);
                        //获取父布局的高度
                        mRootMeasuredHeight = mViewGroup.getMeasuredHeight();
                        mRootMeasuredWidth = mViewGroup.getMeasuredWidth();
                        //获取父布局顶点的坐标
                        mRootTopY = location[1];
                    }
                    break;
                case MotionEvent.ACTION_MOVE://手指滑动
                    if (mRawX >= 0 && mRawX <= mRootMeasuredWidth && mRawY >= mRootTopY && mRawY <= (mRootMeasuredHeight + mRootTopY)) {
                        //手指X轴滑动距离
                        float differenceValueX = mRawX - mLastRawX;
                        //手指Y轴滑动距离
                        float differenceValueY = mRawY - mLastRawY;
                        //判断是否为拖动操作
                        if (!isDrug) {
                            if (Math.sqrt(differenceValueX * differenceValueX + differenceValueY * differenceValueY) < 2) {
                                isDrug = false;
                            } else {
                                isDrug = true;
                            }
                        }
                        //获取手指按下的距离与控件本身X轴的距离
                        float ownX = getX();
                        //获取手指按下的距离与控件本身Y轴的距离
                        float ownY = getY();
                        //理论中X轴拖动的距离
                        float endX = ownX + differenceValueX;
                        //理论中Y轴拖动的距离
                        float endY = ownY + differenceValueY;
                        //X轴可以拖动的最大距离
                        float maxX = mRootMeasuredWidth - getWidth();
                        //Y轴可以拖动的最大距离
                        float maxY = mRootMeasuredHeight - getHeight();
                        //X轴边界限制
                        endX = endX < 0 ? 0 : endX > maxX ? maxX : endX;
                        //Y轴边界限制
                        endY = endY < 0 ? 0 : endY > maxY ? maxY : endY;
                        //开始移动
                        setX(endX);
                        setY(endY);
                        //记录位置
                        mLastRawX = mRawX;
                        mLastRawY = mRawY;
                    }

                    break;
                case MotionEvent.ACTION_UP://手指离开
                    //根据自定义属性判断是否需要贴边
                    if (customIsAttach) {
                        //判断是否为点击事件
                        if (isDrug) {
                            float center = mRootMeasuredWidth / 2;
                            //自动贴边
                            if (mLastRawX <= center) {
                                //向左贴边
                                AttachButton.this.animate()
                                        .setInterpolator(new BounceInterpolator())
                                        .setDuration(500)
                                        .x(0)
                                        .start();
                            } else {
                                //向右贴边
                                AttachButton.this.animate()
                                        .setInterpolator(new BounceInterpolator())
                                        .setDuration(500)
                                        .x(mRootMeasuredWidth - getWidth())
                                        .start();
                            }
                        }
                    }
                    break;
            }
        }
        //是否拦截事件
        return isDrug ? isDrug : super.onTouchEvent(ev);
    }
}
  • 自定义属性
    <declare-styleable name="AttachButton">
        <!--是否需要自动吸边-->
        <attr name="customIsAttach" format="boolean" />
        <!--是否可拖曳-->
        <attr name="customIsDrag" format="boolean" />
    </declare-styleable>
Android 自定义View:实现View的滑动效果 讲解了View的触摸事件,已经实现View滑动的7种方式 阅读详情

相关推荐

一个可拖动item的九宫格自定义View

一、写在前面的话 也不知道给这个取个啥名,就随便取了一个,主要功能就如上图显示的那样,待选区view可以拖拽进九宫格中,九宫格可以按照顺时针滚动(这里没显示,后面会说一下的)。左右两个SeekBar可以配置一些参数,比如速度啊什么的。 二、分析需要的元素 1.待选区的item 2.九宫格 3.拖动的View 4.SeekBar ...

qwe749082787的博客 2932

带你造轮子,自定义一个随意拖拽可吸边的View

在开发中,随意拖拽可吸边的悬浮View还是比较常见的,这种功能网上也有各种各样的轮子,其实写起来并不复杂,看完本文,你也可以手写一个,而且不到`400`行代码就能实现一个通用的随意拖拽可吸边的悬浮View组件。......

yechaoa 2588

【我的Android进阶之旅】Android自定义View来实现解析lrc歌词并同步滚动、上下拖动、缩放歌词、卡拉OK高亮的功能

前言 一LRC歌词文件简介 1什么是LRC歌词文件 2LRC歌词文件的格式 LRC歌词文件的标签类型 1标识标签 2时间标签 二解析LRC歌词 1读取出歌词文件 2解析得到的歌词内容 1表示每行歌词内容的实体类LrcRow 2解析歌词的构造器 ILrcBuilder接口 DefaultLrcBuilder歌词解析构造器 lrc歌词原始内容 lrc歌词解析后的内容 三显示LRC歌词内容 1定义一个IL

字节卷动 1万+

Android使用ViewDragHelper实现简单的view拖拽和吸边功能

工作了几年,最开始做的是安卓开发,后面的做了一段时间逆向和sdk开发,一直没有系统的整理自己的知识,打算从本篇博客开始,陆续复习并记录一下自己的安卓知识 一直不知道怎么排版,先凑合着弄下 实现效果,gif上传被压扁了 ViewDragHelper的用法 viewDragHelper是一个安卓自带的处理拖拽的工具 先看一下viewDragHelper的创建步骤 public...

LitterLy的博客 3590

Android自定义View:实现可拖动圆形进度条

自定义View是Android开发中一个非常重要的概念,它赋予了开发者强大的能力来创建独特且具有高度定制性的用户界面元素。通过继承已有的View类,开发者可以实现新的功能,并在应用中提供一致的用户体验。在自定义View的开发过程中,功能性扩展和自定义属性的设计是至关重要的。它们不仅提高了View的灵活性和可重用性,还增强了用户的体验。本章将深入探讨自定义属性的意义和实现方法,并展示如何扩展进度条的功能。

weixin_42581846的博客 813

Android弹窗客服Android弹窗客服

Android弹窗客服

Android 实现可拖动、吸附左右边界的悬浮框

实现思路: 创建拖动范围和拖动控件 实现拖动控件的可拖动效果,通过OnTouchListener实现 限制拖动范围为边界 实现左右两边停靠,并添加动画返回效果 首先实现可拖动的功能: dragView.setOnTouchListener(new OnTouchListener() { @Override public boolean o...

hsw 4877

深入浅出CenterFusion

结合论文和代码理解CenterFusion

Untitled的专栏 5104

Android 可拖拽的View,限制在父布局中随意拖拽;拖拽结束后可左右吸边;

可随意拖动拖拽的View,限制拖动范围是父布局中;可以左右吸边的效果;ViewDragHelper

Choi晨的博客 1335

退火朗之万动力学采样

退火朗之万动力学采样(Simulated Annealing Langevin Dynamics Sampling)结合了退火技术和朗之万动力学,是一种用于分子模拟和统计物理中的计算方法。这种方法可以帮助系统从局部最小值中逃逸,以找到全局最小值或进行有效的配置空间探索。

weixin_39699362的博客 1948

GAN模型中的常见变体有哪些,如DCGAN、WGAN和CGAN等?

生成对抗网络(GAN)是一种强大的深度学习模型,它由生成器和判别器两个网络组成,通过对抗训练的方式实现生成数据的能力。随着研究的发展,出现了许多GAN的变体,每个变体都有其独特的改进和特点。

huidhsu的博客 2261

android按钮悬停吸附,Android简单实现悬浮吸附的FloatingView

/*** @ProjectName: yqf-android* @Package: com.jao.pointsystem.weight* @ClassName: ScrollFloatinigButton* @Description: java类作用描述* @Author: OlderJiao* @CreateDate: 2020/3/24 10:47* @UpdateUser: 更新者:* @...

weixin_39983223的博客 412

ads设计Doherty功放(1)

Doherty功放的设计

qq_40995480的博客 2万+

android圆圈拖动值变化,android AttachLayout 拖动吸边效果

package com.xin.view;import android.content.Context;import android.content.res.TypedArray;import android.graphics.Rect;import android.util.AttributeSet;import android.view.MotionEvent;import android.v...

weixin_33793620的博客 194

目标检测技术的发展:从R-CNN、YOLO到DETR、DINO

OV-DINO的出现标志着在开放域目标检测领域的重大进步,其在处理未见过的物体类别时的能力得到了显著提升。这项技术对于需要适应未知或变化环境的应用具有重要意义,如自动驾驶、机器人视觉、安防监控等。

DeepAIedu的博客 2629

YOLO11涨点优化:半监督学习 | 结合Unbiased Teacher框架,利用海量无标签数据实现自训练暴力涨点

本文提出了一种基于Unbiased Teacher v2的半监督学习方法,用于解决YOLO11目标检测模型在标注数据不足时的性能瓶颈问题。通过分析纯监督YOLO11在小样本场景下的局限性,揭示了朴素自训练方法的三大陷阱:确认偏差、类别不均衡和噪声累积。文章详细解析了Unbiased Teacher框架的核心设计理念,包括教师-学生模型互监督机制、类别平衡损失和Burn-In预热策略,并重点介绍了v2版本针对无锚框检测器的优化。最后提出将Unbiased Teacher与YOLO11相结合的融合架构设计方案,

2601_95807009的博客 207

Android悬浮图标+自动吸边

Android悬浮服务,自动吸边处理

大大钧的博客 8447

【DRAM存储器四十七】LPDDR5介绍--LPDDR5的低功耗技术之DVFS

讲述LPDDR5的DVFSC和DVFSQ的工作原理和要求

highman110的博客 899

自定义一个随意拖拽可吸边的悬浮View

先定义一个FloatView类,继承自FrameLayout,实现构造方法。创建一个ShapeableImageView,并添加到这个FloatView中。}}}复制代码。

chuyouyinghe的专栏 1474

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

Android自定义View实现拖动吸附效果

1、既然要实现控件拖动,那么就离不开onTouchEvent()这个方法,需要监听里面的按下和滑动事件。 2、 要实现自动贴边,需要监听onTouchEvent()中手指离开屏幕事件。对于贴边的过程,我们用属性动画来解决。 3、事件的冲突问题也需要考虑,拖动、点击关系到了事件的拦截。

yihanss的博客 1198

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

Android 自定义View实现可拖动边框缩放的矩形

先看效果图 我的思路是,首先我给确定下来我点击的是哪一条边,这个可以用onTouchEvent来监听实现。然后,由于用户很难精确的点击到边上,所有点击时需要存在一定的误差范围。最后,我需要判断每条边的边界情况,例如右边框不能移动到左边框的左边,上边框不能移动到下边框的下面等。 下面是自定义View的源码: public class MyRectView extends View { private Paint paint; private Rect rect; priva

zz51233273的博客 2552

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

在android4.0中实现View的拖动效果

在android4.0中实现View的拖动效果

枫轻舞云飘散 3671

自定义View实现拖动并自动吸边效果以及手势

【代码】自定义View实现拖动并自动吸边效果以及手势。

qq_40334300的博客 252
下一篇: 了解RecyclerView的分割线
Android师哥
博客等级 码龄10年 3粉丝 14原创
评论 5
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值