GestureDetector:是Android官方提供的手势识别组件,可以识别点击、双击、长按事件、拖动等等手势操作,这里我们借助GestureDetector支持View的滑动,并通过拓展代码支持长按拖动。
以下代码已经过测试可用,需要的可自行拷贝哈。
拖动
public class MoveView extends AppCompatButton {
private GestureDetector mGesture;
public MoveView(Context context) {
super(context);
}
public MoveView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public MoveView(Context context, AttributeSet attrs) {
super(context, attrs);
mGesture = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
setTranslationX(getTranslationX() + e2.getX() - e1.getX());
setTranslationY(getTranslationY() + e2.getY() - e1.getY());
return true;
}
});
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mGesture.onTouchEvent(event);
if (event.getAction() == MotionEvent.ACTION_UP) { // 自动归位
placeIconToSide();
}
return super.onTouchEvent(event);
}
/**
* 移动位置对齐右边
* 如需要判断对齐左右两边的,可以在onTouchEvent中记录ACTION_UP时的x,然后placeIconToSide中判断 2 * x > getScreenWidth() - width,
* -- true:setX(getScreenWidth() - width)
* -- false:setX(0)
*/
private void placeIconToSide() {
int width = getMeasuredWidth();
int maxX = getScreenWidth() - width; // maxX为控件左边位置,即屏幕宽度 - 控件宽度
setX(maxX); // 移动控件对齐
}
/**
* @return 屏幕宽度
*/
private int getScreenWidth() {
WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
if (wm == null) return -1;
Point point = new Point();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
wm.getDefaultDisplay().getRealSize(point);
} else {
wm.getDefaultDisplay().getSize(point);
}
return point.x;
}
}
可直接拷贝上面代码,放在XML后测试触摸拖动
长按拖动
public class LongTouchMoveView extends AppCompatButton {
public LongTouchMoveView(Context context) {
super(context);
}
public LongTouchMoveView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private float mLastFocusX;
private float mLastFocusY;
private boolean mInLongPress;
private MotionEvent mCurrentDownEvent;
private GestureDetector mGestureDetector;
private GestureDetector.SimpleOnGestureListener mSimpleOnGestureListener;
public LongTouchMoveView(Context context, AttributeSet attrs) {
super(context, attrs);
setOnClickListener(v -> {
//ToastUtils.showShort("onClick");
});
mSimpleOnGestureListener = new GestureDetector.SimpleOnGestureListener() {
@Override
public void onLongPress(MotionEvent e) {
vibrate(50);
mInLongPress = true;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if (!mInLongPress) return false;
setTranslationX(getTranslationX() + e2.getX() - e1.getX());
setTranslationY(getTranslationY() + e2.getY() - e1.getY());
return true;
}
};
mGestureDetector = new GestureDetector(context, mSimpleOnGestureListener);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
boolean consumeEvent = false;
boolean result = mGestureDetector.onTouchEvent(ev);
if (!result) { // GestureDetector不支持长按滑动,这里通过自定义代码进行补充支持(GestureDetector#onTouchEvent()#MotionEvent.ACTION_MOVE事件处理时,只要是长按的直接不消费事件,即不予处理)
/* 下面摘抄自 GestureDetector#onTouchEvent() 源码 */
final int action = ev.getAction();
final boolean pointerUp =
(action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP;
final int skipIndex = pointerUp ? ev.getActionIndex() : -1;
// Determine focal point
float sumX = 0, sumY = 0;
final int count = ev.getPointerCount();
for (int i = 0; i < count; i++) {
if (skipIndex == i) continue;
sumX += ev.getX(i);
sumY += ev.getY(i);
}
final int div = pointerUp ? count - 1 : count;
final float focusX = sumX / div;
final float focusY = sumY / div;
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
if (mCurrentDownEvent != null) {
mCurrentDownEvent.recycle();
}
mCurrentDownEvent = MotionEvent.obtain(ev);
break;
case MotionEvent.ACTION_MOVE:
if (mInLongPress) { // 正在长按中,支持滑动
final float scrollX = mLastFocusX - focusX;
final float scrollY = mLastFocusY - focusY;
if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {
mSimpleOnGestureListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
mLastFocusX = focusX;
mLastFocusY = focusY;
consumeEvent = true; // 消费 长按滑动
}
}
break;
case MotionEvent.ACTION_UP:
if (mInLongPress) {
placeIconToSide();
mInLongPress = false;
consumeEvent = true; // 消费 提起动作
}
break;
}
}
if (consumeEvent) {
return true; // 自己处理长按滑动、提起;(避免与单击事件冲突)
} else {
return super.onTouchEvent(ev); // 继续走默认路径
}
}
/**
* 移动位置对齐右边
* 如需要判断对齐左右两边的,可以在onTouchEvent中记录ACTION_UP时的x,然后placeIconToSide中判断 2 * x > getScreenWidth() - width,
* -- true:setX(getScreenWidth() - width)
* -- false:setX(0)
*/
private void placeIconToSide() {
int width = getMeasuredWidth();
int maxX = getScreenWidth() - width;
setX(maxX);
}
/**
* @return 屏幕宽度
*/
private int getScreenWidth() {
WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
if (wm == null) return -1;
Point point = new Point();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
wm.getDefaultDisplay().getRealSize(point);
} else {
wm.getDefaultDisplay().getSize(point);
}
return point.x;
}
private void vibrate(final long milliseconds) {
Vibrator vibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
if (vibrator == null) return;
vibrator.vibrate(milliseconds);
}
}