Android自定义view-多点触控与拖拽

多点触控

Action

通过MotionEvent的getActionMasked()获取支持多点触控的action

动作 说明
MotionEvent.ACTION_DOWN 手指按下
MotionEvent.ACTION_MOVE 手指移动
MotionEvent.ACTION_UP 手指抬起
MotionEvent.ACTION_CANCEL 事件被拦截
MotionEvent.ACTION_OUTSIDE 超出区域
MotionEvent.ACTION_POINTER_DOWN 有非主要的手指按下(即按下之前已经有手指在屏幕上)
MotionEvent.ACTION_POINTER_UP 有非主要的手指抬起(即抬起之前已经有手指在屏幕上)

触控坐标定位

方法 说明
getActionIndex() 获取当前手指的Index 在按下到抬起过程有可能会变化
getPointerId(int pointerIndex) 传入手指的Index获取手指的唯一id 在按下到抬起过程不变
findPointerIndex(int pointerId) 传入手指的唯一id 获取手指的index
getX(int pointerIndex) 传入手指的Index获取当前触控的x坐标
getY(int pointerIndex) 传入手指的Index获取当前触控的y坐标
getPointerCount() 获取在屏幕上手指的个数

多指滑动的处理

第一种 把旧值给活动手指

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
trackingPointerId = event.getPointerId(0);
downX = event.getX();
downY = event.getY();
originalOffsetX = offsetX;
originalOffsetY = offsetY;
break;
case MotionEvent.ACTION_MOVE:
int index = event.findPointerIndex(trackingPointerId);//根据唯一id找index
//实际移动的偏移量为 前偏移量加现偏移量
offsetX = originalOffsetX + (event.getX(index) - downX);
offsetY = originalOffsetY + (event.getY(index) - downY);
invalidate();
break;
case MotionEvent.ACTION_POINTER_DOWN://第二只手指放下
int actionIndex = event.getActionIndex();
trackingPointerId = event.getPointerId(actionIndex);//活动id变为新按下的手指
downX = event.getX(actionIndex);
downY = event.getY(actionIndex);
originalOffsetX = offsetX;//初始位置为上只手指的offset
originalOffsetY = offsetY;//初始位置为上只手指的offset
break;
case MotionEvent.ACTION_POINTER_UP://多手抬起一只
actionIndex = event.getActionIndex();
int pointerId = event.getPointerId(actionIndex);
if (pointerId == trackingPointerId) {//如果抬起的是当前活动的手指
int newIndex;
if (actionIndex == event.getPointerCount() - 1) {//如果当前抬起的index是最后按下的index
newIndex = event.getPointerCount() - 2;//把活动Index改为它前一只按下的手指
} else {
newIndex = event.getPointerCount() - 1;//把活动Index改为最后一只按下的手指
}
trackingPointerId = event.getPointerId(newIndex);//获取新的Index的唯一id
downX = event.getX(actionIndex);
downY = event.getY(actionIndex);
originalOffsetX = offsetX;
originalOffsetY = offsetY;
}
break;
}
return true;
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(bitmap, offsetX, offsetY, paint);
}

第二种 直接叠加多手指平均偏移量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@Override
public boolean onTouchEvent(MotionEvent event) {
float sumX = 0;
float sumY = 0;
int pointerCount = event.getPointerCount();
boolean isPointerUp = event.getActionMasked() == MotionEvent.ACTION_POINTER_UP;
for (int i = 0; i < pointerCount; i++) {
if (!(isPointerUp && i == event.getActionIndex())) {//抬起手指的坐标不累加
sumX += event.getX(i);
sumY += event.getY(i);
}
}
if (isPointerUp) {
pointerCount -= 1;
}
float focusX = sumX / pointerCount;//移动过程中的平均坐标
float focusY = sumY / pointerCount;//移动过程中的平均坐标
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
case MotionEvent.ACTION_POINTER_UP:
downX = focusX; //记录手指放下或抬起的瞬间平均坐标
downY = focusY;//记录手指放下或抬起的瞬间平均坐标
originalOffsetX = offsetX;//记录上次移动坐标
originalOffsetY = offsetY;//记录上次移动坐标
break;
case MotionEvent.ACTION_MOVE:
offsetX = originalOffsetX + (focusX - downX);//最后移动的坐标加上偏移量
offsetY = originalOffsetY + (focusY - downY);//最后移动的坐标加上偏移量
invalidate();
break;
}
return true;
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(bitmap, offsetX, offsetY, paint);
}

第三种互不干扰

多指画图例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
SparseArray<Path> paths = new SparseArray<>();
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getActionMasked()) {
case ACTION_DOWN:
case ACTION_POINTER_DOWN:
int actionIndex = event.getActionIndex();
int pointerId = event.getPointerId(actionIndex);
Path path = new Path();
path.moveTo(event.getX(actionIndex), event.getY(actionIndex));
paths.append(pointerId, path);
invalidate();
break;
case ACTION_MOVE:
for (int i = 0; i < event.getPointerCount(); i++) {
pointerId = event.getPointerId(i);
path = paths.get(pointerId);
path.lineTo(event.getX(i), event.getY(i));
}
invalidate();
break;
case ACTION_UP:
case ACTION_POINTER_UP:
pointerId = event.getPointerId(event.getActionIndex());
paths.remove(pointerId);
invalidate();
break;
}
return true;
}
@Override
protected void onDraw(Canvas canvas) {
for (int i = 0; i <paths.size(); i++) {
Path path = paths.valueAt(i);
canvas.drawPath(path, paint);
}
}

拖拽

OnDragListener 拖拽view影子

ClipData 剪切板

系统服务剪切板

1
ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);

在系统剪贴板里只存在一个,当另一个clip对象进来时,前一个clip对象会消失。
简单使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clipData = ClipData.newPlainText("标签", "内容");
//添加ClipData对象到剪切板中
clipboardManager.setPrimaryClip(clipData);
clipboardManager.addPrimaryClipChangedListener(new ClipboardManager.OnPrimaryClipChangedListener() {
@Override
public void onPrimaryClipChanged() {
// 获取剪贴板的剪贴数据集
ClipData clipData = clipboardManager.getPrimaryClip();
if (clipData != null && clipData.getItemCount() > 0) {
// 从数据集中获取(粘贴)第一条文本数据
for (int i=0;i<clipData.getItemCount();i++){
CharSequence text = clipData.getItemAt(i).getText();
if(text!=null)
Log.e("复制",text.toString());
}
}
}
});

例子


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
img = (ImageView) findViewById(R.id.img);
tv = (TextView) findViewById(R.id.tv);
img.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
ClipData imageData = ClipData.newPlainText("name", "google");
return ViewCompat.startDragAndDrop(v, imageData, new View.DragShadowBuilder(v), null, View.DRAG_FLAG_GLOBAL);
}
});
img.setOnDragListener(new View.OnDragListener() {
@Override
public boolean onDrag(View v, DragEvent event) {
switch (event.getAction()) {
case DragEvent.ACTION_DRAG_STARTED://开始拖拽
tv.setText("开始拖拽");
break;
case DragEvent.ACTION_DRAG_ENDED:
tv.setText("结束拖拽");
break;
}
return false;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
tv = (TextView) findViewById(R.id.tv);
tv.setOnDragListener(new View.OnDragListener() {
@Override
public boolean onDrag(View v, DragEvent event) {
switch (event.getAction()) {
case DragEvent.ACTION_DROP://释放拖拽
tv.setTextSize(16);
tv.setText("释放拖拽:收到信息" + event.getClipData().getItemAt(0).getText());
break;
case DragEvent.ACTION_DRAG_STARTED://开始拖拽
tv.setText("开始拖拽");
break;
case DragEvent.ACTION_DRAG_ENDED:
tv.setText("结束拖拽");
break;
case DragEvent.ACTION_DRAG_ENTERED:
tv.setText("拖拽的view进入监听的view时");
break;
case DragEvent.ACTION_DRAG_EXITED:
tv.setText("拖拽的view离开监听的view时");
break;
case DragEvent.ACTION_DRAG_LOCATION:
float x = event.getX();
float y = event.getY();
tv.setText("拖拽的view在监听view中的位置:x =" + x + ",y=" + y);
break;
}
return true;
}
});

ViewDragHelper 拖拽view本身

方法 说明
settleCapturedViewAt(int finalLeft, int finalTop) onViewReleased上调用 以松手前的滑动速度为初值,让捕获到的子View自动滚动到指定位置
flingCapturedView(int minLeft, int minTop, int maxLeft, int maxTop) onViewReleased上调用 以松手前的滑动速度为初值,让捕获到的子View在指定范围内fling惯性运动
smoothSlideViewTo(View child, int finalLeft, int finalTop) View自动滚动到指定的位置
abort() 中断动画
continueSettling(boolean) 判断动画是否介素

使用前需要重写onInterceptTouchEvent onTouchEvent computeScroll()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return dragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
dragHelper.processTouchEvent(event);
return true;
}
@Override
public void computeScroll() {
if (dragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
public class ViewDragHelperSimple extends FrameLayout {
View view;
ViewDragHelper dragHelper;
ViewDragHelper.Callback dragListener = new DragListener();
ViewConfiguration viewConfiguration;
Button btn;
public ViewDragHelperSimple(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
dragHelper = ViewDragHelper.create(this, dragListener);
viewConfiguration = ViewConfiguration.get(context);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
view = findViewById(R.id.view);
btn = findViewById(R.id.btn);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return dragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
dragHelper.processTouchEvent(event);
return true;
}
@Override
public void computeScroll() {
if (dragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
class DragListener extends ViewDragHelper.Callback {
//判断当前操作的view是否可以进行捕获
@Override
public boolean tryCaptureView(@NonNull View child, int pointerId) {
/* return child ==view;*/
return true;
}
//child:当前操作的view left: 将要到达的垂直方向的距离 dx: 相对于当前位置的偏移量
@Override
public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
return top;
}
//child:当前操作的view left: 将要到达的水平方向的距离 dx: 相对于当前位置的偏移量
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return left;
}
//返回可拖动子视图的水平运动范围的大小(以像素为单位)对于无法水平移动的视图,此方法应返回0 子view有点击事件时要重写
@Override
public int getViewHorizontalDragRange(View child) {
return getMeasuredWidth() - child.getMeasuredWidth();
}
//返回可拖动子视图的垂直运动范围的大小(以像素为单位)对于无法水平移动的视图,此方法应返回0 子view有点击事件时要重写
@Override
public int getViewVerticalDragRange(View child) {
return getMeasuredHeight() - child.getMeasuredHeight();
}
//松手
@Override
public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {
if (Math.abs(yvel) > viewConfiguration.getScaledMinimumFlingVelocity()) {//超过最小滑动速度 移到最下
if (yvel > 0) {
dragHelper.settleCapturedViewAt(0, getHeight() - releasedChild.getHeight());
} else {
dragHelper.settleCapturedViewAt(0, 0);
}
} else {
if (releasedChild.getTop() < getHeight() - releasedChild.getBottom()) {
dragHelper.settleCapturedViewAt(0, 0);
} else {
dragHelper.settleCapturedViewAt(0, getHeight() - releasedChild.getHeight());
}
}
postInvalidateOnAnimation();
}
}
}