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

 

如代碼即可解決點擊事件無效,滑動事件衝突,滑出屏幕之外的問題。

備註:如上代碼關於隱藏與顯示動畫是根據項目需求而設定的,並不適用所有,如果有這方面的需求可自行更動畫。

 

效果圖如下:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章