Android自定义View实现随手势滑动控件
1.需要有单击事件
2.可以随手势滑动
3.不会因父控件调用了 requestLayout()方法而回到初始位置
4.可以根据列表(ListView recyclerView)的滑动而隐藏,列表的停止而显示。
实现随手势滑动
思路:重写onTouchEvent(MotionEvent event) 方法,根据移动量,调用 void layout(int l, int t, int r, int b) 方法,重新设置位置即可。
private int lastX;
private int lastY;
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
lastX = (int) event.getRawX();//获取触摸事件触摸位置的原始X坐标
lastY = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
//event.getRawX();获得移动的位置
int dx = (int) event.getRawX() - lastX;
int dy = (int) event.getRawY() - lastY;
int l = this.getLeft() + dx;
int b = this.getBottom() + dy;
int r = getRight() + dx;
int t = getTop() + dy;
this.layout(l, t, r, b);//重新布局
lastX = (int) event.getRawX();
lastY = (int) event.getRawY();
break;
case MotionEvent.ACTION_UP:
break;
}
return true;//由于要处理所有手势,全部返回true;
}
问题:
1). 单击事件不生效
2). 与其它可滑动View(如viewpager recyclerView) 存在滑动事件冲突
3). 该view可以滑出屏幕边界
分析:
与其它View存在滑动事件冲突,可以在down事件中 调用如下代码即可;
getParent().requestDisallowInterceptTouchEvent(true);//通知父控件不要拦截,自己处理手势事件
单击事件不生效是由于onTouchEvent 方法返回的全是true, 导致setOnclickListener 不能正常接收到点击事件,如果onTouchEvent 方法返回的是super.onTouchEvent(event) ,那么每次手势事件,包括移动事件也会触发单击事件,这并不符合需求。为了同时处理滑动事件和单击事件,使用 GestureDetector 来处理复杂手势,在 GestureDetector 的 onSingleTapUp 中处理单击事件, 在onScroll方法中处理滑动事件。
View可滑出屏幕边界的问题,对滑动事件中的位置做一些限制即可。
处理代码如下:
package com.app.haotougu.views.widget;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import com.app.haotougu.common.utils.DensityUtils;
public class HandScollView extends View {
private static final String TAG = "HanderScollView";
private int lastX;
private int lastY;
private int mTranslationLenght;//位移长度
private ObjectAnimator mOutAnim;
private ObjectAnimator mInAnim;
private final int ANIM_DURATION = 300;
private GestureDetector mGestureDetector;
private OnClickListener mClickListener;
private boolean mAlreadyMove;//是否已经手动滑动
private int mScreenWidth;
private int mScreenHeight;
public HandScollView(Context context) {
super(context);
init(context);
}
public HandScollView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
public HandScollView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
/**
* 单击事件
*/
public void setOnSingleTapUp(@Nullable OnClickListener l) {
this.mClickListener = l;
}
private void init(Context context) {
//需要减掉图片的高度
post(new Runnable() {
@Override
public void run() {
mTranslationLenght = getWidth();
mTranslationLenght += DensityUtils.dip2Intpx(context, 25);
}
});
DisplayMetrics dm = getResources().getDisplayMetrics();
mScreenWidth = dm.widthPixels;
mScreenHeight = dm.heightPixels;
mGestureDetector = new GestureDetector(new GestureDetector.SimpleOnGestureListener() {
//当手指按下的时候触发下面的方法
@Override
public boolean onDown(MotionEvent e) {
lastX = (int) e.getRawX();//获取触摸事件触摸位置的原始X坐标
lastY = (int) e.getRawY();
getParent().requestDisallowInterceptTouchEvent(true);
return true;
}
//当手指在屏幕上轻轻点击的时候触发下面的方法
@Override
public boolean onSingleTapUp(MotionEvent e) {
Log.w(TAG, "onSingleTapUp");
if (mClickListener != null) {
mClickListener.onClick(HandScollView.this);
}
return true;
}
//当手指在屏幕上滚动的时候触发这个方法
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
dispathEvent(e2);
return true;
}
});
}
@Override
public void layout(int l, int t, int r, int b) {
if (!mAlreadyMove) {//防止父控件调用requestLayout()方法后,该view回到初始位置
super.layout(l, t, r, b);
}
}
private void moveLayout(int l, int t, int r, int b) {
super.layout(l, t, r, b);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mGestureDetector != null) {
mGestureDetector.onTouchEvent(event);
}
return true;
}
private void dispathEvent(MotionEvent event) {
int ea = event.getAction();
switch (ea) {
case MotionEvent.ACTION_MOVE:
//event.getRawX();获得移动的位置
int dx = (int) event.getRawX() - lastX;
int dy = (int) event.getRawY() - lastY;
int l = this.getLeft() + dx;
int b = this.getBottom() + dy;
int r = getRight() + dx;
int t = getTop() + dy;
//下面判断移动是否超出屏幕
if (l < 0) {
l = 0;
r = l + this.getWidth();
}
if (t < 0) {
t = 0;
b = t + this.getHeight();
}
if (r > mScreenWidth) {
r = mScreenWidth;
l = r - this.getWidth();
}
if (b > mScreenHeight) {
b = mScreenHeight;
t = b - this.getHeight();
}
if (!mAlreadyMove) {//判断是否已经随手势滑动
if (Math.abs(dx) > 30 || Math.abs(dy) > 30) {
mAlreadyMove = true;
}
}
moveLayout(l, t, r, b);
// Log.e(TAG, "onTouch: " + l + "==" + t + "==" + r + "==" + b);
lastX = (int) event.getRawX();
lastY = (int) event.getRawY();
break;
case MotionEvent.ACTION_UP:
break;
}
}
/**
* 向右侧位移,滑出屏幕
*/
public void hide() {
if (mOutAnim == null) {
mOutAnim = ObjectAnimator.ofFloat(this, "translationX", 0, mTranslationLenght);
}
if (mAlreadyMove || mOutAnim.isRunning()) {
return;
}
if ((mInAnim != null && mInAnim.isRunning())) {
mInAnim.end();
}
mOutAnim.setDuration(ANIM_DURATION);
mOutAnim.start();
}
/**
* 向左侧位,由屏幕外向屏幕内移动
*/
public void show() {
if (mInAnim == null) {
mInAnim = ObjectAnimator.ofFloat(this, "translationX", mTranslationLenght, 0);
}
if (mAlreadyMove || mInAnim.isRunning()) {//如果已经手动滑动过,或正在执行动画 则不再执行
return;
}
if (mOutAnim != null && mOutAnim.isRunning()) {
mOutAnim.end();
}
mInAnim.setDuration(ANIM_DURATION);
mInAnim.start();
}
}
单击事件监听 setOnSingleTapUp( OnClickListener l );
如代码即可解决点击事件无效,滑动事件冲突,滑出屏幕之外的问题。
备注:如上代码关于隐藏与显示动画是根据项目需求而设定的,并不适用所有,如果有这方面的需求可自行更动画。
效果图如下: