您好,登录后才能下订单哦!
密码登录
登录注册
点击 登录注册 即表示同意《亿速云用户服务条款》
# Android跟随手指移动的控件demo怎么实现
## 一、前言
在Android应用开发中,实现控件跟随手指移动是一个常见的交互需求。这种效果可以用于游戏开发、自定义控件实现、拖拽排序等多种场景。本文将详细介绍如何从零开始实现一个跟随手指移动的View,涵盖触摸事件处理、View位置更新以及性能优化等关键知识点。
## 二、实现原理概述
实现View跟随手指移动的核心原理是:
1. 监听View的触摸事件(`onTouchEvent`)
2. 在`ACTION_DOWN`事件中记录初始位置
3. 在`ACTION_MOVE`事件中计算位移差
4. 根据位移差更新View的位置
5. 在`ACTION_UP`事件中处理抬起逻辑
## 三、基础实现步骤
### 1. 创建自定义View
```java
public class DraggableView extends View {
private float lastX, lastY;
public DraggableView(Context context) {
super(context);
init();
}
public DraggableView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
// 初始化设置
setBackgroundColor(Color.BLUE);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getRawX();
float y = event.getRawY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_MOVE:
float deltaX = x - lastX;
float deltaY = y - lastY;
// 更新View位置
setX(getX() + deltaX);
setY(getY() + deltaY);
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_UP:
// 手指抬起时的处理
break;
}
return true;
}
<com.example.app.DraggableView
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_centerInParent="true"/>
为防止View被拖出屏幕,需要添加边界检测:
// 在ACTION_MOVE中添加边界检查
float newX = getX() + deltaX;
float newY = getY() + deltaY;
// 获取屏幕宽高
DisplayMetrics metrics = getResources().getDisplayMetrics();
int screenWidth = metrics.widthPixels;
int screenHeight = metrics.heightPixels;
// 确保View不会移出屏幕
if (newX < 0) newX = 0;
if (newY < 0) newY = 0;
if (newX > screenWidth - getWidth()) newX = screenWidth - getWidth();
if (newY > screenHeight - getHeight()) newY = screenHeight - getHeight();
setX(newX);
setY(newY);
// 替换直接setX/setY
animate()
.x(newX)
.y(newY)
.setDuration(0)
.start();
// 在ACTION_DOWN时添加阴影
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setElevation(20f);
}
// 在ACTION_UP时移除阴影
setElevation(0f);
public class AdvancedDraggableView extends View {
private float lastX, lastY;
private boolean isDragging = false;
public AdvancedDraggableView(Context context) {
super(context);
init();
}
// 其他构造方法...
private void init() {
setBackgroundColor(Color.parseColor("#6200EE"));
setClickable(true);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getRawX();
float y = event.getRawY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = x;
lastY = y;
isDragging = false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setElevation(20f);
}
break;
case MotionEvent.ACTION_MOVE:
float deltaX = x - lastX;
float deltaY = y - lastY;
if (Math.abs(deltaX) > 5 || Math.abs(deltaY) > 5) {
isDragging = true;
}
updatePosition(deltaX, deltaY);
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_UP:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setElevation(0f);
}
if (!isDragging) {
performClick();
}
break;
}
return true;
}
private void updatePosition(float deltaX, float deltaY) {
float newX = getX() + deltaX;
float newY = getY() + deltaY;
// 边界检查
DisplayMetrics metrics = getResources().getDisplayMetrics();
int screenWidth = metrics.widthPixels;
int screenHeight = metrics.heightPixels;
if (newX < 0) newX = 0;
if (newY < 0) newY = 0;
if (newX > screenWidth - getWidth()) newX = screenWidth - getWidth();
if (newY > screenHeight - getHeight()) newY = screenHeight - getHeight();
// 平滑移动
animate()
.x(newX)
.y(newY)
.setDuration(0)
.start();
}
@Override
public boolean performClick() {
super.performClick();
// 处理点击事件
return true;
}
}
原因:频繁的UI更新可能导致卡顿
解决方案:
- 使用ViewPropertyAnimator
代替直接设置位置
- 减少在onTouchEvent
中的计算量
- 考虑使用硬件加速
// 在ACTION_DOWN时获取pointerId
private int activePointerId = MotionEvent.INVALID_POINTER_ID;
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getActionMasked();
int pointerIndex = event.getActionIndex();
switch (action) {
case MotionEvent.ACTION_DOWN:
activePointerId = event.getPointerId(0);
lastX = event.getX();
lastY = event.getY();
break;
case MotionEvent.ACTION_POINTER_DOWN:
// 处理多指按下
break;
case MotionEvent.ACTION_MOVE:
pointerIndex = event.findPointerIndex(activePointerId);
if (pointerIndex != -1) {
float x = event.getX(pointerIndex);
float y = event.getY(pointerIndex);
// 处理移动...
}
break;
case MotionEvent.ACTION_POINTER_UP:
// 处理多指抬起
break;
}
return true;
}
当可拖动View位于ScrollView等可滚动容器中时,需要处理滚动冲突:
@Override
public boolean onTouchEvent(MotionEvent event) {
// ...原有逻辑
// 在ACTION_MOVE中添加
if (isDragging) {
getParent().requestDisallowInterceptTouchEvent(true);
}
return true;
}
// 在ACTION_UP中添加磁吸逻辑
case MotionEvent.ACTION_UP:
float centerX = getX() + getWidth()/2;
if (centerX < screenWidth/2) {
// 吸附到左边
animate().x(0).setDuration(200).start();
} else {
// 吸附到右边
animate().x(screenWidth - getWidth()).setDuration(200).start();
}
break;
// 添加删除区域检测
Rect deleteArea = new Rect(0, screenHeight-200, screenWidth, screenHeight);
case MotionEvent.ACTION_UP:
if (deleteArea.contains((int)getX(), (int)getY())) {
// 执行删除动画
animate()
.scaleX(0.5f)
.scaleY(0.5f)
.alpha(0f)
.setDuration(300)
.withEndAction(() -> setVisibility(GONE))
.start();
}
break;
onTouchEvent
中创建新对象setLayerType(LAYER_TYPE_HARDWARE, null)
本文详细介绍了Android中实现View跟随手指移动的完整方案,从基础实现到进阶优化,涵盖了边界处理、性能优化、多指触摸等关键知识点。通过这个Demo,开发者可以掌握:
读者可以根据实际需求扩展此Demo,实现更复杂的交互效果,如拖拽排序、游戏角色控制等场景。
”`
注:实际字数约2800字,可根据需要增减内容调整到精确2900字。文章采用Markdown格式,包含代码块、标题层级和结构化内容,适合技术文档发布。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。