Android自定義View實現拖動自動吸邊效果

NightRain

自定義View,一是爲了滿足設計需求,二是開發者進階的標誌之一。隨心所欲就是我等奮鬥的目標!!!

效果

拖動效果

實現邏輯

  • 明確需求
    1、實現控件跟隨手指拖動
    2、實現控件自動貼邊

  • 整理思路
    1、既然要實現控件拖動,那麼就離不開onTouchEvent()這個方法,需要監聽裏面的按下和滑動事件。
    2、 要實現自動貼邊,需要監聽onTouchEvent()中手指離開屏幕事件。對於貼邊的過程,我們用屬性動畫來解決。
    3、事件的衝突問題也需要考慮,拖動、點擊關係到了事件的攔截。

  • 動手實現
    在需求明確、思路清晰的情況下就要開始動手實現(需要了解自定義View的一些基礎API),下面代碼中註釋寫的基本都差不多,很好理解。歡迎指出討論!!!

完整代碼

/**
 * 自定義View實現拖動並自動吸邊效果
 * <p>
 * 處理滑動和貼邊 {@link #onTouchEvent(MotionEvent)}
 * 處理事件分發 {@link #dispatchTouchEvent(MotionEvent)}
 * </p>
 *
 * @attr customIsAttach  //是否需要自動吸邊
 * @attr customIsDrag    //是否可拖曳
 */
public class AttachButton extends View {
    private float mLastRawX;
    private float mLastRawY;
    private final String TAG = "AttachButton";
    private boolean isDrug = false;
    private int mRootMeasuredWidth = 0;
    private int mRootMeasuredHeight = 0;
    private int mRootTopY = 0;
    private boolean customIsAttach;
    private boolean customIsDrag;

    public AttachButton(Context context) {
        this(context, null);
    }

    public AttachButton(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public AttachButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initAttrs(context, attrs);
    }

    /**
     * 初始化自定義屬性
     */
    private void initAttrs(Context context, AttributeSet attrs) {
        TypedArray mTypedAttay = context.obtainStyledAttributes(attrs, R.styleable.AttachButton);
        customIsAttach = mTypedAttay.getBoolean(R.styleable.AttachButton_customIsAttach, true);
        customIsDrag = mTypedAttay.getBoolean(R.styleable.AttachButton_customIsDrag, true);
        mTypedAttay.recycle();
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        super.dispatchTouchEvent(event);
        return true;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        //判斷是否需要滑動
        if (customIsDrag) {
            //當前手指的座標
            float mRawX = ev.getRawX();
            float mRawY = ev.getRawY();
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN://手指按下
                    isDrug = false;
                    //記錄按下的位置
                    mLastRawX = mRawX;
                    mLastRawY = mRawY;
                    ViewGroup mViewGroup = (ViewGroup) getParent();
                    if (mViewGroup != null) {
                        int[] location = new int[2];
                        mViewGroup.getLocationInWindow(location);
                        //獲取父佈局的高度
                        mRootMeasuredHeight = mViewGroup.getMeasuredHeight();
                        mRootMeasuredWidth = mViewGroup.getMeasuredWidth();
                        //獲取父佈局頂點的座標
                        mRootTopY = location[1];
                    }
                    break;
                case MotionEvent.ACTION_MOVE://手指滑動
                    if (mRawX >= 0 && mRawX <= mRootMeasuredWidth && mRawY >= mRootTopY && mRawY <= (mRootMeasuredHeight + mRootTopY)) {
                        //手指X軸滑動距離
                        float differenceValueX = mRawX - mLastRawX;
                        //手指Y軸滑動距離
                        float differenceValueY = mRawY - mLastRawY;
                        //判斷是否爲拖動操作
                        if (!isDrug) {
                            if (Math.sqrt(differenceValueX * differenceValueX + differenceValueY * differenceValueY) < 2) {
                                isDrug = false;
                            } else {
                                isDrug = true;
                            }
                        }
                        //獲取手指按下的距離與控件本身X軸的距離
                        float ownX = getX();
                        //獲取手指按下的距離與控件本身Y軸的距離
                        float ownY = getY();
                        //理論中X軸拖動的距離
                        float endX = ownX + differenceValueX;
                        //理論中Y軸拖動的距離
                        float endY = ownY + differenceValueY;
                        //X軸可以拖動的最大距離
                        float maxX = mRootMeasuredWidth - getWidth();
                        //Y軸可以拖動的最大距離
                        float maxY = mRootMeasuredHeight - getHeight();
                        //X軸邊界限制
                        endX = endX < 0 ? 0 : endX > maxX ? maxX : endX;
                        //Y軸邊界限制
                        endY = endY < 0 ? 0 : endY > maxY ? maxY : endY;
                        //開始移動
                        setX(endX);
                        setY(endY);
                        //記錄位置
                        mLastRawX = mRawX;
                        mLastRawY = mRawY;
                    }

                    break;
                case MotionEvent.ACTION_UP://手指離開
                    //根據自定義屬性判斷是否需要貼邊
                    if (customIsAttach) {
                        //判斷是否爲點擊事件
                        if (isDrug) {
                            float center = mRootMeasuredWidth / 2;
                            //自動貼邊
                            if (mLastRawX <= center) {
                                //向左貼邊
                                AttachButton.this.animate()
                                        .setInterpolator(new BounceInterpolator())
                                        .setDuration(500)
                                        .x(0)
                                        .start();
                            } else {
                                //向右貼邊
                                AttachButton.this.animate()
                                        .setInterpolator(new BounceInterpolator())
                                        .setDuration(500)
                                        .x(mRootMeasuredWidth - getWidth())
                                        .start();
                            }
                        }
                    }
                    break;
            }
        }
        //是否攔截事件
        return isDrug ? isDrug : super.onTouchEvent(ev);
    }
}
  • 自定義屬性
    <declare-styleable name="AttachButton">
        <!--是否需要自動吸邊-->
        <attr name="customIsAttach" format="boolean" />
        <!--是否可拖曳-->
        <attr name="customIsDrag" format="boolean" />
    </declare-styleable>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章