Android自定义View实现拖动自动吸边效果
作者:Android师哥 发布时间:2021-11-18 03:00:38
标签:Android,拖动,吸边
本文实例为大家分享了Android自定义View实现拖动自动吸边的具体代码,供大家参考,具体内容如下
自定义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>
来源:https://blog.csdn.net/Common_it/article/details/89284887
0
投稿
猜你喜欢
- 一、概述在软件开发中,有时需要保存一个对象的状态,以便于允许用户取消相关操作或者从以往的状态中恢复过来。比如一个文档版本管理系统,可以根据需
- 字符串每隔4位加空格今天弄了个银行卡识别功能,回显的时候想要将银行卡号每四位加一个空格,这样核对卡号会方便很多,这里记录一下1.正则表达式实
- Java提供的数据类型主要分为两大类:基本数据类型和引用数据类型。Java中的基本数据类型名称大小取值范围byte型 (字节)8bit-12
- 目录什么是异常?编译时还是运行时?“受检异常”究竟可不可取?我的观点什么是异常?要了解受检异常,首先要了解什么是异常。在Java中,异常是一
- 面向对象语言对事物的体现都是以对象的形式,所以为了方便对多个对象的操作,就对对象进行存储,集合就是存储对象最常用的一种方式。数组虽然也可以存
- 本文实例讲述了Android TextView中文字通过SpannableString设置属性的方法。分享给大家供大家参考,具体如下:在An
- Mybatis-plus官网地址:https://baomidou.com/配置mysql在配置文件连接mysqlspring.dataso
- 一 前言最近网上比较火的代码生成器,知识追寻者抽空试试了一下,感觉不是友好,只能说功能比较呆板吧,还需要自己玩填空题,修修补补,然后再次打开
- HashMap类1、HashMap类概述HashMap是 Map 接口使用频率最高的实现类,允许使用null键和null值,与HashSet
- 首先说明这是我一个不熟悉idea和SSM框架的新手小白遇到的坑,适合用idea搭建SSM框架的小伙伴看一看,老鸟就不用看了。以下为详细步骤(
- 这是一个演示如何使用java执行定时任务的实例,本实例开始运行后不会自动结束,请在运行本实例后手动结束程序。package com.hong
- 小编今天研究了在Unity3D中的数据持久化问题。数据持久化在任何一个开发领域都是一个值得关注的问题,小到一个应用中配置文件的读写,大到数据
- 本文实例为大家分享了java实现选课系统的具体代码,供大家参考,具体内容如下这个程序主要是练习IO(文件读写,序列化),集合框架的使用学生端
- 1.什么是Ribbon目前主流的负载均衡方案分为以下两种:(1)集中式负载均衡:在消费者和服务提供者中间使用独立的代理方式进行负载,有硬件的
- 使用lamda表达式对list进行求和Lambda 表达式是 JDK8 的一个新特性,最近写项目中求和计算使用的较多,写篇文章记录下。1、实
- 本文实例讲述了Java实现文件和base64流的相互转换功能。分享给大家供大家参考,具体如下:import java.io.FileInpu
- 要点有另一种方法来完成语句映射。 它们映射的语句可以不用 XML 来配置,而可以使用 Java 注解来配置。使用注解来映射简单语句会使代码显
- java中synchronized(同步代码块和同步方法)详解及区别问题的由来:看到这样一个面试题://下列两个方法有什么区别p
- 本文实例讲述了C#实现的Win32控制台线程计时器功能。分享给大家供大家参考,具体如下:在C#中提供了三种类型的计时器:1、基于 Windo
- 前言本文学习MP中的更新操作方法,带大家一起查看源码,了解更新操作的方法。学会熟练地去运用更新方法解决自己在项目中的问题。一、通过id更新1