Android自定义View实现随手势滑动控件

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 );

 

如代码即可解决点击事件无效,滑动事件冲突,滑出屏幕之外的问题。

备注:如上代码关于隐藏与显示动画是根据项目需求而设定的,并不适用所有,如果有这方面的需求可自行更动画。

 

效果图如下:

你可能感兴趣的:(andriod)