Android SwipeBackLayout源码解析
pciz9843
8年前
<p> </p> <p>Github: <a href="/misc/goto?guid=4958988897536054229" rel="nofollow,noindex">SwipeBackLayout</a> 分析版本: <a href="/misc/goto?guid=4959672703905383763" rel="nofollow,noindex">e4ddae6</a></p> <p>SwipeBackLayout 是一个仿 IOS 通过手势退出界面的开源库。</p> <h2>SwipeBackLayout</h2> <p><img src="https://simg.open-open.com/show/981e05df3d0c85e4e6ca8bf51bbdd980.png"></p> <p>SwipeBackLayout 可以通过在左、右和下边缘来拖动整个 Activity 达到退出 Activity 的效果。</p> <h2>使用</h2> <p>添加到 Gradle :</p> <pre> <code class="language-java">compile 'me.imid.swipebacklayout.lib:library:1.0.0' </code></pre> <p>继承 SwipeBackActivity :</p> <pre> <code class="language-java">public class DemoActivity extends SwipeBackActivity { } </code></pre> <ul> <li>onCreate 中 setContentView() 照常使用</li> <li>可以通过 getSwipeBackLayout() 定制 SwipeBackLayout</li> </ul> <p>在 styles.xml 中的主题中添加:</p> <pre> <code class="language-java"><item name="android:windowIsTranslucent">true</item> </code></pre> <h3>注意</h3> <p>需要在项目中添加最新的 supportV4 包</p> <h3>demo</h3> <pre> <code class="language-java">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); } }); } ... </code></pre> <h2>源码</h2> <h3>SwipeBackActivity</h3> <pre> <code class="language-java">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(); } } </code></pre> <p>在 SwipeBackActivity 中实现了 SwipeBackActivityBase 接口,在 Activity 的生命周期函数 onCreate() 中创建了 SwipeBackActivityHelper 对象, 该类的作用是设置 Activity 的透明和在 DecorView 中替换 SwipeBackLayout 。 onPostCreate() 是在 Activity 完全运行起来之后才会被调用。其中 findViewById() 方法进行了判断,首先在 Activity 的 contentView 中获取,获取不到再到 SwipeBackLayout 中获取。</p> <h3>SwipeBackActivityHelper</h3> <p>在 SwipeBackActivity 的 onCreate() 中的调用方法:</p> <pre> <code class="language-java">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() { } }); } } </code></pre> <p>在 onActivityCreate 中主要就是将 window 、 decorView 的背景设置为透明的。</p> <p>在 SwipeBackActivity 的 onPostCreate() 中的调用方法:</p> <pre> <code class="language-java">public class SwipeBackActivityHelper { public void onPostCreate() { mSwipeBackLayout.attachToActivity(mActivity); } } </code></pre> <p>在 attachToActivity 中的操作就是将 decorView 中的 childView 换成 SwipeBackLayout ,然后将 childView 添加到 SwipeBackLayout 中。</p> <p>其他的方法:</p> <pre> <code class="language-java">public class SwipeBackActivityHelper { public View findViewById(int id) { if (mSwipeBackLayout != null) { return mSwipeBackLayout.findViewById(id); } return null; } public SwipeBackLayout getSwipeBackLayout() { return mSwipeBackLayout; } } </code></pre> <h3>SwipeBackLayout</h3> <p>SwipeBackLayout 是一个 View ,可以从构造函数开始看:</p> <pre> <code class="language-java">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; } } </code></pre> <p>SwipeBackLayout 继承自 FrameLayout ,其中手势的操作是通过 ViewDragHelper 来实现的。在构造函数中一些必要的参数设置给 ViewDragHelper 。</p> <pre> <code class="language-java">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(); } } } </code></pre> <p>在 ViewDragHelper.Callback 的手势判断中,处理的主要逻辑主要在 tryCaptureView 、 onViewPositionChanged 、 onViewReleased 三个方法中,分别是在准备滑动、滑动时、和放手的时候的逻辑。</p> <p>在 tryCaptureView 中主要进行了边缘的判断,以及是否满足滑动条件;在 onViewPositionChanged 中计算了当前滑动距离与整个 ContentView 的距离的比例,是否超越临界值等;在 onViewReleased 中处理了手抬起之后的操作,比如将 View 滑归位或者滑出去等。</p> <p>现在基本上了解了滑动的机制了,那么回过头来看看 attachToActivity :</p> <pre> <code class="language-java">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; } } </code></pre> <p>通过 attachToActivity 将 decorView 中的 contentView 换成了 SwipeBackLayout ,而 contentView 则被添加到了 SwipeBackLayout 中。与正常的相比,之间多了一个 SwipeBackLayout 。</p> <p>在滑动的时候哪些阴影是怎么出现的呢:</p> <pre> <code class="language-java">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); } } } </code></pre> <p>就这样,阴影就绘制出来了。</p> <p>再看看 scrollToFinishActivity :</p> <pre> <code class="language-java">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); } } } </code></pre> <h3>Utils</h3> <pre> <code class="language-java">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) { } } } </code></pre> <p>通过反射改变 Activity 的属性值。</p> <p> </p> <p>来自: http://yydcdut.com/2016/05/08/swipebacklayout-analyse/</p>