Android SwipeBackLayout源碼解析

pciz9843 8年前發布 | 15K 次閱讀 安卓開發 Android開發 移動開發

 

Github: SwipeBackLayout 分析版本: e4ddae6

SwipeBackLayout 是一個仿 IOS 通過手勢退出界面的開源庫。

SwipeBackLayout

SwipeBackLayout 可以通過在左、右和下邊緣來拖動整個 Activity 達到退出 Activity 的效果。

使用

添加到 Gradle :

compile 'me.imid.swipebacklayout.lib:library:1.0.0'

繼承 SwipeBackActivity :

public class DemoActivity extends SwipeBackActivity {
}
  • onCreate 中 setContentView() 照常使用
  • 可以通過 getSwipeBackLayout() 定制 SwipeBackLayout

在 styles.xml 中的主題中添加:

<item name="android:windowIsTranslucent">true</item>

注意

需要在項目中添加最新的 supportV4 包

demo

public class DemoActivity extends SwipeBackActivity implements View.OnClickListener {
    private int[] mBgColors;

    private static int mBgIndex = 0;

    private String mKeyTrackingMode;

    private RadioGroup mTrackingModeGroup;

    private SwipeBackLayout mSwipeBackLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_demo);
        changeActionBarColor();
        findViews();
        mKeyTrackingMode = getString(R.string.key_tracking_mode);
        mSwipeBackLayout = getSwipeBackLayout();

        mTrackingModeGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(RadioGroup group, int checkedId) {
                int edgeFlag;
                switch (checkedId) {
                    case R.id.mode_left:
                        edgeFlag = SwipeBackLayout.EDGE_LEFT;
                        break;
                    case R.id.mode_right:
                        edgeFlag = SwipeBackLayout.EDGE_RIGHT;
                        break;
                    case R.id.mode_bottom:
                        edgeFlag = SwipeBackLayout.EDGE_BOTTOM;
                        break;
                    default:
                        edgeFlag = SwipeBackLayout.EDGE_ALL;
                }
                mSwipeBackLayout.setEdgeTrackingEnabled(edgeFlag);
                saveTrackingMode(edgeFlag);
            }
        });
    }
...

源碼

SwipeBackActivity

public class SwipeBackActivity extends AppCompatActivity implements SwipeBackActivityBase {
    private SwipeBackActivityHelper mHelper;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mHelper = new SwipeBackActivityHelper(this);
        mHelper.onActivityCreate();
    }

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

    @Override
    public View findViewById(int id) {
        View v = super.findViewById(id);
        if (v == null && mHelper != null)
            return mHelper.findViewById(id);
        return v;
    }

    @Override
    public SwipeBackLayout getSwipeBackLayout() {//SwipeBackActivityBase接口中的方法
        return mHelper.getSwipeBackLayout();
    }

    @Override
    public void setSwipeBackEnable(boolean enable) {//SwipeBackActivityBase接口中的方法
        getSwipeBackLayout().setEnableGesture(enable);
    }

    @Override
    public void scrollToFinishActivity() {//SwipeBackActivityBase接口中的方法
        Utils.convertActivityToTranslucent(this);
        getSwipeBackLayout().scrollToFinishActivity();
    }
}

在 SwipeBackActivity 中實現了 SwipeBackActivityBase 接口,在 Activity 的生命周期函數 onCreate() 中創建了 SwipeBackActivityHelper 對象, 該類的作用是設置 Activity 的透明和在 DecorView 中替換 SwipeBackLayout 。 onPostCreate() 是在 Activity 完全運行起來之后才會被調用。其中 findViewById() 方法進行了判斷,首先在 Activity 的 contentView 中獲取,獲取不到再到 SwipeBackLayout 中獲取。

SwipeBackActivityHelper

在 SwipeBackActivity 的 onCreate() 中的調用方法:

public class SwipeBackActivityHelper {

    private SwipeBackLayout mSwipeBackLayout;

    public SwipeBackActivityHelper(Activity activity) {
        mActivity = activity;
    }

    @SuppressWarnings("deprecation")
    public void onActivityCreate() {
        //設置Window的background為透明
        mActivity.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
        //設置decorView沒有background
        mActivity.getWindow().getDecorView().setBackgroundDrawable(null);
        //inflate一個SwipeBackLayout出來
        mSwipeBackLayout = (SwipeBackLayout) LayoutInflater.from(mActivity).inflate(
                me.imid.swipebacklayout.lib.R.layout.swipeback_layout, null);
        //設置手勢滑動監聽器
        mSwipeBackLayout.addSwipeListener(new SwipeBackLayout.SwipeListener() {
            @Override
            public void onScrollStateChange(int state, float scrollPercent) {
            }

            @Override
            public void onEdgeTouch(int edgeFlag) {
                //當有邊界觸摸的時候設置成透明的
                Utils.convertActivityToTranslucent(mActivity);
            }

            @Override
            public void onScrollOverThreshold() {

            }
        });
    }
}

在 onActivityCreate 中主要就是將 window 、 decorView 的背景設置為透明的。

在 SwipeBackActivity 的 onPostCreate() 中的調用方法:

public class SwipeBackActivityHelper {
    public void onPostCreate() {
        mSwipeBackLayout.attachToActivity(mActivity);
    }
}

在 attachToActivity 中的操作就是將 decorView 中的 childView 換成 SwipeBackLayout ,然后將 childView 添加到 SwipeBackLayout 中。

其他的方法:

public class SwipeBackActivityHelper {
    public View findViewById(int id) {
        if (mSwipeBackLayout != null) {
            return mSwipeBackLayout.findViewById(id);
        }
        return null;
    }

    public SwipeBackLayout getSwipeBackLayout() {
        return mSwipeBackLayout;
    }
}

SwipeBackLayout

SwipeBackLayout 是一個 View ,可以從構造函數開始看:

public class SwipeBackLayout extends FrameLayout {

    /**
 * Minimum velocity that will be detected as a fling
 */
    private static final int MIN_FLING_VELOCITY = 400; // dips per second

    private static final int[] EDGE_FLAGS = {
            EDGE_LEFT, EDGE_RIGHT, EDGE_BOTTOM, EDGE_ALL
    };
    private int mEdgeFlag;

    private ViewDragHelper mDragHelper;

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

    public SwipeBackLayout(Context context, AttributeSet attrs) {
        this(context, attrs, R.attr.SwipeBackLayoutStyle);
    }

    public SwipeBackLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs);
        mDragHelper = ViewDragHelper.create(this, new ViewDragCallback());

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SwipeBackLayout, defStyle,
                R.style.SwipeBackLayout);

        //與邊緣可拖動的距離
        int edgeSize = a.getDimensionPixelSize(R.styleable.SwipeBackLayout_edge_size, -1);
        if (edgeSize > 0) {
            //設置給ViewDragHelper
            setEdgeSize(edgeSize);
        }
        //邊緣模式,分為EDGE_LEFT, EDGE_RIGHT, EDGE_BOTTOM, EDGE_ALL
        int mode = EDGE_FLAGS[a.getInt(R.styleable.SwipeBackLayout_edge_flag, 0)];
        //設置給ViewDragHelper
        setEdgeTrackingEnabled(mode);
        //邊緣滑動的時候的陰影
        int shadowLeft = a.getResourceId(R.styleable.SwipeBackLayout_shadow_left,
                R.drawable.shadow_left);
        int shadowRight = a.getResourceId(R.styleable.SwipeBackLayout_shadow_right,
                R.drawable.shadow_right);
        int shadowBottom = a.getResourceId(R.styleable.SwipeBackLayout_shadow_bottom,
                R.drawable.shadow_bottom);
        setShadow(shadowLeft, EDGE_LEFT);
        setShadow(shadowRight, EDGE_RIGHT);
        setShadow(shadowBottom, EDGE_BOTTOM);
        a.recycle();
        //得到密度
        final float density = getResources().getDisplayMetrics().density;
        //手勢滑動最小速度
        final float minVel = MIN_FLING_VELOCITY * density;
        //設置給ViewDragHelper
        mDragHelper.setMinVelocity(minVel);
        mDragHelper.setMaxVelocity(minVel * 2f);
    }

    /**
 * Set the size of an edge. This is the range in pixels along the edges of
 * this view that will actively detect edge touches or drags if edge
 * tracking is enabled.
 *
 * @param size The size of an edge in pixels
 */
    public void setEdgeSize(int size) {
        mDragHelper.setEdgeSize(size);
    }

    /**
 * Enable edge tracking for the selected edges of the parent view. The
 * callback's
 * {@link me.imid.swipebacklayout.lib.ViewDragHelper.Callback#onEdgeTouched(int, int)}
 * and
 * {@link me.imid.swipebacklayout.lib.ViewDragHelper.Callback#onEdgeDragStarted(int, int)}
 * methods will only be invoked for edges for which edge tracking has been
 * enabled.
 *
 * @param edgeFlags Combination of edge flags describing the edges to watch
 * @see #EDGE_LEFT
 * @see #EDGE_RIGHT
 * @see #EDGE_BOTTOM
 */
    public void setEdgeTrackingEnabled(int edgeFlags) {
        mEdgeFlag = edgeFlags;
        mDragHelper.setEdgeTrackingEnabled(mEdgeFlag);
    }

    public void setShadow(int resId, int edgeFlag) {
        setShadow(getResources().getDrawable(resId), edgeFlag);
    }

    /**
 * Set a drawable used for edge shadow.
 *
 * @param shadow Drawable to use
 * @param edgeFlag Combination of edge flags describing the edge to set
 * @see #EDGE_LEFT
 * @see #EDGE_RIGHT
 * @see #EDGE_BOTTOM
 */
    public void setShadow(Drawable shadow, int edgeFlag) {
        if ((edgeFlag & EDGE_LEFT) != 0) {
            mShadowLeft = shadow;
        } else if ((edgeFlag & EDGE_RIGHT) != 0) {
            mShadowRight = shadow;
        } else if ((edgeFlag & EDGE_BOTTOM) != 0) {
            mShadowBottom = shadow;
        }
        invalidate();
    }

    //處理ViewDragHelper
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        if (!mEnable) {
            return false;
        }
        try {
            return mDragHelper.shouldInterceptTouchEvent(event);
        } catch (ArrayIndexOutOfBoundsException e) {
            // FIXME: handle exception
            // issues #9
            return false;
        }
    }

    //處理ViewDragHelper
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (!mEnable) {
            return false;
        }
        mDragHelper.processTouchEvent(event);
        return true;
    }
}

SwipeBackLayout 繼承自 FrameLayout ,其中手勢的操作是通過 ViewDragHelper 來實現的。在構造函數中一些必要的參數設置給 ViewDragHelper 。

public class SwipeBackLayout extends FrameLayout {
   /**
 * Edge flag indicating that the left edge should be affected.
 */
    public static final int EDGE_LEFT = ViewDragHelper.EDGE_LEFT;

    /**
 * Edge flag indicating that the right edge should be affected.
 */
    public static final int EDGE_RIGHT = ViewDragHelper.EDGE_RIGHT;

    /**
 * Edge flag indicating that the bottom edge should be affected.
 */
    public static final int EDGE_BOTTOM = ViewDragHelper.EDGE_BOTTOM;

    /**
 * Edge flag set indicating all edges should be affected.
 */
    public static final int EDGE_ALL = EDGE_LEFT | EDGE_RIGHT | EDGE_BOTTOM;
    /**
 * Default threshold of scroll
 * 超過0.3f的屏幕比例的距離之后可以滑動出去了,臨界值是0.3f
 */
    private static final float DEFAULT_SCROLL_THRESHOLD = 0.3f;

    private static final int OVERSCROLL_DISTANCE = 10;

    private float mScrimOpacity;

    /**
 * Edge being dragged
 */
    private int mTrackingEdge;

    //滑動了距離和整個屏幕的的百分比
    private float mScrollPercent;

    private int mContentLeft;

    private int mContentTop;

    /**
 * Threshold of scroll, we will close the activity, when scrollPercent over
 * this value;
 */
    private float mScrollThreshold = DEFAULT_SCROLL_THRESHOLD;

    private class ViewDragCallback extends ViewDragHelper.Callback {
        private boolean mIsScrollOverValid;

        //如果可拖動則返回true 否則為false
        @Override
        public boolean tryCaptureView(View view, int i) {//i是pointerId
            //是否touch到了邊緣
            boolean ret = mDragHelper.isEdgeTouched(mEdgeFlag, i);
            //哪個邊緣被touch了
            if (ret) {
                if (mDragHelper.isEdgeTouched(EDGE_LEFT, i)) {
                    mTrackingEdge = EDGE_LEFT;
                } else if (mDragHelper.isEdgeTouched(EDGE_RIGHT, i)) {
                    mTrackingEdge = EDGE_RIGHT;
                } else if (mDragHelper.isEdgeTouched(EDGE_BOTTOM, i)) {
                    mTrackingEdge = EDGE_BOTTOM;
                }
                //回調出去
                if (mListeners != null && !mListeners.isEmpty()) {
                    for (SwipeListener listener : mListeners) {
                        listener.onEdgeTouch(mTrackingEdge);
                    }
                }
                mIsScrollOverValid = true;
            }
            boolean directionCheck = false;
            //是否達到了滑動的門檻
            if (mEdgeFlag == EDGE_LEFT || mEdgeFlag == EDGE_RIGHT) {
                directionCheck = !mDragHelper.checkTouchSlop(ViewDragHelper.DIRECTION_VERTICAL, i);
            } else if (mEdgeFlag == EDGE_BOTTOM) {
                directionCheck = !mDragHelper
                        .checkTouchSlop(ViewDragHelper.DIRECTION_HORIZONTAL, i);
            } else if (mEdgeFlag == EDGE_ALL) {
                directionCheck = true;
            }
            return ret & directionCheck;
        }

        //返回指定View在橫向上能滑動的最大距離
        @Override
        public int getViewHorizontalDragRange(View child) {
            return mEdgeFlag & (EDGE_LEFT | EDGE_RIGHT);
        }

        //返回指定View在縱向上能滑動的最大距離
        @Override
        public int getViewVerticalDragRange(View child) {
            return mEdgeFlag & EDGE_BOTTOM;
        }

        //當子視圖位置變化時,會回調這個函數
        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            super.onViewPositionChanged(changedView, left, top, dx, dy);
            //計算當前滑動比例
            if ((mTrackingEdge & EDGE_LEFT) != 0) {
                mScrollPercent = Math.abs((float) left
                        / (mContentView.getWidth() + mShadowLeft.getIntrinsicWidth()));
            } else if ((mTrackingEdge & EDGE_RIGHT) != 0) {
                mScrollPercent = Math.abs((float) left
                        / (mContentView.getWidth() + mShadowRight.getIntrinsicWidth()));
            } else if ((mTrackingEdge & EDGE_BOTTOM) != 0) {
                mScrollPercent = Math.abs((float) top
                        / (mContentView.getHeight() + mShadowBottom.getIntrinsicHeight()));
            }
            mContentLeft = left;
            mContentTop = top;
            invalidate();
            //當滑動比例小于可滑動出去的時候,且mIsScrollOverValid已經為false的時候
            if (mScrollPercent < mScrollThreshold && !mIsScrollOverValid) {
                mIsScrollOverValid = true;
            }
            if (mListeners != null && !mListeners.isEmpty()
                    && mDragHelper.getViewDragState() == STATE_DRAGGING
                    && mScrollPercent >= mScrollThreshold && mIsScrollOverValid) {
                mIsScrollOverValid = false;
                //回調出去,已經達到可以滑出結束Activity的標準了
                for (SwipeListener listener : mListeners) {
                    listener.onScrollOverThreshold();
                }
            }
            //當比例大于等于1的時候,就可以關閉掉Activity了
            if (mScrollPercent >= 1) {
                if (!mActivity.isFinishing()) {
                    mActivity.finish();
                    mActivity.overridePendingTransition(0, 0);
                }
            }
        }

        //當手指從子視圖松開時,會調用這個函數,同時返回在x軸和y軸上當前的速度
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            final int childWidth = releasedChild.getWidth();
            final int childHeight = releasedChild.getHeight();

            int left = 0, top = 0;
            if ((mTrackingEdge & EDGE_LEFT) != 0) {//左邊邊緣
                //速度滿足>=0且已經滑過了臨界點0.3f,滑到最右邊,不然滑到0的位置
                left = xvel > 0 || xvel == 0 && mScrollPercent > mScrollThreshold ? childWidth + mShadowLeft.getIntrinsicWidth() + OVERSCROLL_DISTANCE : 0;
            } else if ((mTrackingEdge & EDGE_RIGHT) != 0) {//右邊邊緣
                //速度滿足>=0且已經滑過了臨界點0.3f,滑到最左邊,不然滑到0的位置
                left = xvel < 0 || xvel == 0 && mScrollPercent > mScrollThreshold ? -(childWidth + mShadowLeft.getIntrinsicWidth() + OVERSCROLL_DISTANCE) : 0;
            } else if ((mTrackingEdge & EDGE_BOTTOM) != 0) {//上邊邊緣
                //速度滿足>=0且已經滑過了臨界點0.3f,滑到最下邊,不然滑到0的位置
                top = yvel < 0 || yvel == 0 && mScrollPercent > mScrollThreshold ? -(childHeight + mShadowBottom.getIntrinsicHeight() + OVERSCROLL_DISTANCE) : 0;
            }
            //移動View
            mDragHelper.settleCapturedViewAt(left, top);
            //刷新View
            invalidate();
        }

        //返回一個值,告訴Helper,這個view能滑動的最大(或者負向最大)的橫向坐標
        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            int ret = 0;
            if ((mTrackingEdge & EDGE_LEFT) != 0) {
                ret = Math.min(child.getWidth(), Math.max(left, 0));
            } else if ((mTrackingEdge & EDGE_RIGHT) != 0) {
                ret = Math.min(0, Math.max(left, -child.getWidth()));
            }
            return ret;
        }

        //返回一個值,告訴Helper,這個view能滑動的最大(或者負向最大)的縱向坐標
        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            int ret = 0;
            if ((mTrackingEdge & EDGE_BOTTOM) != 0) {
                ret = Math.min(0, Math.max(top, -child.getHeight()));
            }
            return ret;
        }

        //當邊緣開始拖動的時候,會調用這個回調
        @Override
        public void onViewDragStateChanged(int state) {
            super.onViewDragStateChanged(state);
            if (mListeners != null && !mListeners.isEmpty()) {
                for (SwipeListener listener : mListeners) {
                    listener.onScrollStateChange(state, mScrollPercent);
                }
            }
        }
    }

    @Override
    public void computeScroll() {
        //調用mDragHelper.settleCapturedViewAt(left, top)之后會進到這里
        mScrimOpacity = 1 - mScrollPercent;
        if (mDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        mInLayout = true;
        if (mContentView != null) {
            mContentView.layout(mContentLeft, mContentTop,
                    mContentLeft + mContentView.getMeasuredWidth(),
                    mContentTop + mContentView.getMeasuredHeight());
        }
        mInLayout = false;
    }

    @Override
    public void requestLayout() {
        if (!mInLayout) {
            super.requestLayout();
        }
    }
}

在 ViewDragHelper.Callback 的手勢判斷中,處理的主要邏輯主要在 tryCaptureView 、 onViewPositionChanged 、 onViewReleased 三個方法中,分別是在準備滑動、滑動時、和放手的時候的邏輯。

在 tryCaptureView 中主要進行了邊緣的判斷,以及是否滿足滑動條件;在 onViewPositionChanged 中計算了當前滑動距離與整個 ContentView 的距離的比例,是否超越臨界值等;在 onViewReleased 中處理了手抬起之后的操作,比如將 View 滑歸位或者滑出去等。

現在基本上了解了滑動的機制了,那么回過頭來看看 attachToActivity :

public class SwipeBackLayout extends FrameLayout {

    private View mContentView;

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

        ViewGroup decor = (ViewGroup) activity.getWindow().getDecorView();
        // 拿到decorView的第一個子view
        ViewGroup decorChild = (ViewGroup) decor.getChildAt(0);
        decorChild.setBackgroundResource(background);
        //把這個decorChild從decorView刪除掉
        decor.removeView(decorChild);
        //將decorView添加到SwipeBackLayout中
        addView(decorChild);
        //將decorChild賦值給成員變量mContentView
        setContentView(decorChild);
        // 在DecorView下增加SwipeBackLayout
        decor.addView(this);
    }

    /**
 * Set up contentView which will be moved by user gesture
 *
 * @param view
 */
    private void setContentView(View view) {
        mContentView = view;
    }
}

通過 attachToActivity 將 decorView 中的 contentView 換成了 SwipeBackLayout ,而 contentView 則被添加到了 SwipeBackLayout 中。與正常的相比,之間多了一個 SwipeBackLayout 。

在滑動的時候哪些陰影是怎么出現的呢:

public class SwipeBackLayout extends FrameLayout {

    private static final int DEFAULT_SCRIM_COLOR = 0x99000000;

    private float mScrimOpacity;

    private int mScrimColor = DEFAULT_SCRIM_COLOR;

    private float mScrollPercent;

    private Drawable mShadowLeft;
    private Drawable mShadowRight;
    private Drawable mShadowBottom;

    private Rect mTmpRect = new Rect();

    @Override
    public void computeScroll() {
        mScrimOpacity = 1 - mScrollPercent;
        if (mDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

    @Override
    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        final boolean drawContent = child == mContentView;

        boolean ret = super.drawChild(canvas, child, drawingTime);
        if (mScrimOpacity > 0 && drawContent
                && mDragHelper.getViewDragState() != ViewDragHelper.STATE_IDLE) {
            drawShadow(canvas, child);
            drawScrim(canvas, child);
        }
        return ret;
    }

    private void drawScrim(Canvas canvas, View child) {
        //得到alpha值
        final int baseAlpha = (mScrimColor & 0xff000000) >>> 24;
        //得到新的alpha值
        final int alpha = (int) (baseAlpha * mScrimOpacity);
        //得到新的color
        final int color = alpha << 24 | (mScrimColor & 0xffffff);
        //繪制
        if ((mTrackingEdge & EDGE_LEFT) != 0) {
            canvas.clipRect(0, 0, child.getLeft(), getHeight());
        } else if ((mTrackingEdge & EDGE_RIGHT) != 0) {
            canvas.clipRect(child.getRight(), 0, getRight(), getHeight());
        } else if ((mTrackingEdge & EDGE_BOTTOM) != 0) {
            canvas.clipRect(child.getLeft(), child.getBottom(), getRight(), getHeight());
        }
        canvas.drawColor(color);
    }


    private void drawShadow(Canvas canvas, View child) {
        final Rect childRect = mTmpRect;
        //得到當前View的位置
        child.getHitRect(childRect);

        if ((mEdgeFlag & EDGE_LEFT) != 0) {
            //給drawable設置位置
            mShadowLeft.setBounds(childRect.left - mShadowLeft.getIntrinsicWidth(), childRect.top, childRect.left, childRect.bottom);
            //設置透明度
            mShadowLeft.setAlpha((int) (mScrimOpacity * FULL_ALPHA));
            //畫到canvas上
            mShadowLeft.draw(canvas);
        }
        //給drawable設置位置、設置透明度、畫到canvas上
        if ((mEdgeFlag & EDGE_RIGHT) != 0) {
            mShadowRight.setBounds(childRect.right, childRect.top, childRect.right + mShadowRight.getIntrinsicWidth(), childRect.bottom);
            mShadowRight.setAlpha((int) (mScrimOpacity * FULL_ALPHA));
            mShadowRight.draw(canvas);
        }
        //給drawable設置位置、設置透明度、畫到canvas上
        if ((mEdgeFlag & EDGE_BOTTOM) != 0) {
            mShadowBottom.setBounds(childRect.left, childRect.bottom, childRect.right, childRect.bottom + mShadowBottom.getIntrinsicHeight());
            mShadowBottom.setAlpha((int) (mScrimOpacity * FULL_ALPHA));
            mShadowBottom.draw(canvas);
        }
    }
}

就這樣,陰影就繪制出來了。

再看看 scrollToFinishActivity :

public class SwipeBackLayout extends FrameLayout {
    /**
 * Scroll out contentView and finish the activity
 */
    public void scrollToFinishActivity() {
        //得到contentView的寬高
        final int childWidth = mContentView.getWidth();
        final int childHeight = mContentView.getHeight();
        //要移動到的位置
        int left = 0, top = 0;
        if ((mEdgeFlag & EDGE_LEFT) != 0) {
            left = childWidth + mShadowLeft.getIntrinsicWidth() + OVERSCROLL_DISTANCE;
            mTrackingEdge = EDGE_LEFT;
        } else if ((mEdgeFlag & EDGE_RIGHT) != 0) {
            left = -childWidth - mShadowRight.getIntrinsicWidth() - OVERSCROLL_DISTANCE;
            mTrackingEdge = EDGE_RIGHT;
        } else if ((mEdgeFlag & EDGE_BOTTOM) != 0) {
            top = -childHeight - mShadowBottom.getIntrinsicHeight() - OVERSCROLL_DISTANCE;
            mTrackingEdge = EDGE_BOTTOM;
        }

        mDragHelper.smoothSlideViewTo(mContentView, left, top);
        invalidate();
    }

    @Override
    public void computeScroll() {
        //調用mDragHelper.smoothSlideViewTo(mContentView, left, top);之后進到這里
        mScrimOpacity = 1 - mScrollPercent;
        if (mDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }
}

Utils

public class Utils {
    private Utils() {
    }

    /**
 * Convert a translucent themed Activity
 * {@link android.R.attr#windowIsTranslucent} back from opaque to
 * translucent following a call to
 * {@link #convertActivityFromTranslucent(android.app.Activity)} .
 * <p>
 * Calling this allows the Activity behind this one to be seen again. Once
 * all such Activities have been redrawn
 * <p>
 * This call has no effect on non-translucent activities or on activities
 * with the {@link android.R.attr#windowIsFloating} attribute.
 */
    public static void convertActivityToTranslucent(Activity activity) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            convertActivityToTranslucentAfterL(activity);
        } else {
            convertActivityToTranslucentBeforeL(activity);
        }
    }

    /**
 * Calling the convertToTranslucent method on platforms before Android 5.0
 */
    public static void convertActivityToTranslucentBeforeL(Activity activity) {
        try {
            Class<?>[] classes = Activity.class.getDeclaredClasses();
            Class<?> translucentConversionListenerClazz = null;
            for (Class clazz : classes) {
                if (clazz.getSimpleName().contains("TranslucentConversionListener")) {
                    translucentConversionListenerClazz = clazz;
                }
            }
            Method method = Activity.class.getDeclaredMethod("convertToTranslucent",
                    translucentConversionListenerClazz);
            method.setAccessible(true);
            method.invoke(activity, new Object[] {
                null
            });
        } catch (Throwable t) {
        }
    }

    /**
 * Calling the convertToTranslucent method on platforms after Android 5.0
 */
    private static void convertActivityToTranslucentAfterL(Activity activity) {
        try {
            Method getActivityOptions = Activity.class.getDeclaredMethod("getActivityOptions");
            getActivityOptions.setAccessible(true);
            Object options = getActivityOptions.invoke(activity);

            Class<?>[] classes = Activity.class.getDeclaredClasses();
            Class<?> translucentConversionListenerClazz = null;
            for (Class clazz : classes) {
                if (clazz.getSimpleName().contains("TranslucentConversionListener")) {
                    translucentConversionListenerClazz = clazz;
                }
            }
            Method convertToTranslucent = Activity.class.getDeclaredMethod("convertToTranslucent",
                    translucentConversionListenerClazz, ActivityOptions.class);
            convertToTranslucent.setAccessible(true);
            convertToTranslucent.invoke(activity, null, options);
        } catch (Throwable t) {
        }
    }
}

通過反射改變 Activity 的屬性值。

 

來自: http://yydcdut.com/2016/05/08/swipebacklayout-analyse/

 本文由用戶 pciz9843 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!