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 );
如代码即可解决点击事件无效,滑动事件冲突,滑出屏幕之外的问题。
备注:如上代码关于隐藏与显示动画是根据项目需求而设定的,并不适用所有,如果有这方面的需求可自行更动画。
效果图如下: