Android滑動返回-swipebacklayout解析

一任天然發表於2015-09-24

互動效果

image

背景

ios7釋出時有滑動返回手勢操作,產品經理看到後很興奮,立即要求android增加相同手勢操作。由於android系統特性,並不支援兩個activity的滑動操作。經過一番研究和掙扎,終於實現了效果。實現原理基本和下文要分析的swipebacklayout類似,我們直接來分析swipebacklayout。

使用方法

使用方法很簡單,activity繼承SwipeBackActivity就可以了。

實現原理

假設場景

有兩個activity – A 和 B,B在A上層 ,手指滑動B返回到A。

實現原理

手指移動B頁面的view,因為B被設定為透明,所以會看到A。

分析

swipebacklayout佈局對比

使用swipebacklayout的activity

Android滑動返回-swipebacklayout解析

普通activity佈局

Android滑動返回-swipebacklayout解析

可以看到,使用了swipebacklayout的activity會在DecorView中增加一層SwipeBackLayout(FrameLayout)。檢視跟隨手指滑動就是通過SwipeBackLayout來實現的。我們結合程式碼來看。

佈局增加SwipeBackLayout(FrameLayout)

SwipeBackActivity

@Override
protected void onPostCreate(Bundle savedInstanceState) {
    super.onPostCreate(savedInstanceState);
    mHelper.onPostCreate();
}

SwipeBackActivityHelper

public void onPostCreate() {
    // 在DecorView下增加SwipeBackLayout(FragmentLayout)
    mSwipeBackLayout.attachToActivity(mActivity);
}

SwipeBackLayout

public void attachToActivity(Activity activity) {
    mActivity = activity;
    TypedArray a = activity.getTheme().obtainStyledAttributes(new int[]{
            android.R.attr.windowBackground
    });
    int background = a.getResourceId(0, 0);
    a.recycle();

    // 在DecorView下增加SwipeBackLayout(FragmentLayout)
    ViewGroup decor = (ViewGroup) activity.getWindow().getDecorView();
    // 拿到第一個子view-decorChild
    ViewGroup decorChild = (ViewGroup) decor.getChildAt(0);
    decorChild.setBackgroundResource(background);
    // 刪除子view-decorChild
    decor.removeView(decorChild);
    // 把子view-decorChild新增到SwipeBackLayout(FragmentLayout)下
    addView(decorChild);
    setContentView(decorChild);
    // 把SwipeBackLayout(FragmentLayout)新增到DecorView下
    decor.addView(this);
}    

手指滑動,移動頁面

SwipeBackLayout

@Override
public boolean onTouchEvent(MotionEvent event) {
    // 是否支援手勢返回
    if (!mEnable) {
        return false;
    }
    // 處理螢幕事件
    mDragHelper.processTouchEvent(event);
    return true;
}

ViewDragHelper.processTouchEvent(event),我們只分析主要的ACTION_MOVE事件。

        case MotionEvent.ACTION_MOVE: {
            if (mDragState == STATE_DRAGGING) {
                // 計算位置
                final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
                final float x = MotionEventCompat.getX(ev, index);
                final float y = MotionEventCompat.getY(ev, index);
                final int idx = (int) (x - mLastMotionX[mActivePointerId]);
                final int idy = (int) (y - mLastMotionY[mActivePointerId]);

                // 移動view
                dragTo(mCapturedView.getLeft() + idx, mCapturedView.getTop() + idy, idx, idy);

                saveLastMotion(ev);
            } else {
                // Check to see if any pointer is now over a draggable view.
                // 省略不需要關注的程式碼
                ......
            }
            break;
        }

ViewDragHelper.dragTo移動view

private void dragTo(int left, int top, int dx, int dy) {
    int clampedX = left;
    int clampedY = top;
    final int oldLeft = mCapturedView.getLeft();
    final int oldTop = mCapturedView.getTop();
    // 橫向移動-左右
    if (dx != 0) {
        clampedX = mCallback.clampViewPositionHorizontal(mCapturedView, left, dx);
        mCapturedView.offsetLeftAndRight(clampedX - oldLeft);
    }
    // 縱向移動-上下
    if (dy != 0) {
        clampedY = mCallback.clampViewPositionVertical(mCapturedView, top, dy);
        mCapturedView.offsetTopAndBottom(clampedY - oldTop);
    }
    // 回撥-處理邏輯
    if (dx != 0 || dy != 0) {
        final int clampedDx = clampedX - oldLeft;
        final int clampedDy = clampedY - oldTop;
        mCallback
                .onViewPositionChanged(mCapturedView, clampedX, clampedY, clampedDx, clampedDy);
    }
}

相關文章