Android SwipeBackLayout源碼解析
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/