android 全域性頁面滑動返回聯動效果的實現
轉載請標明出處:http://www.jianshu.com/p/705c2397a7f9
主要參考專案:https://github.com/ikew0ng/SwipeBackLayout
背景
首次通過向右滑動來返回的操作是在 IOS7系統上出現,android系統特性上並不支援兩個activity間的滑動返回,但是android上有很多關於滑動的api,通過這些api也是可以實現視覺上的滑動返回效果。
效果圖
原理的簡單描述
首先設定activity的背景是透明的,然後讓每個頁面的DecorView下新增一個自定義的ViewGroup(SwipeBackLayout),讓原先的DecorView裡的子view新增到SwipeBackLayout裡,通過滑動的api對SwipeBackLayout裡的view進行滑動,當滑動結束後就finish當前的activity,為了實現聯動,在滑動的過程中拿到下層的activity的SwipeBackLayout進行滑動操作即可。
佈局圖
實現
主要有以下四個類:
SwipeBackActivity //滑動返回基類
SwipeBackLayout //滑動返回viewGroup
SwipeBackLayoutDragHelper //修改ViewDragHelper後助手類
TranslucentHelper //程式碼中修改透明或者不透明的助手類
1. 設定activity為透明
這個看起來很簡單,其實在實際開發中遇到過一個比較麻煩的頁面切換動畫的問題。在程式碼中,和activity透明背景相關的地方有兩個:
- 第一個是在activity的主題style裡設定
<item name="android:windowBackground">@color/transparent</item>
<item name="android:windowIsTranslucent">true</item>
但是問題來了,如果在某個activity的主題style中設定了android:windowIsTranslucent該屬性為true,那麼該activity切換動畫變成了手機預設效果,不同手機有不同的效果,有些手機上完全不能看。於是需要自定義activity的切換動畫(windowAnimationStyle),但是又發現以下幾個屬性是無效的
<!-- activity 新建立時進來的動畫-->
<item name="android:activityOpenEnterAnimation">@anim/activity_open_enter</item>
<!-- 上層activity返回後,下層activity重新出現的動畫-->
<item name="android:activityOpenExitAnimation">@anim/activity_open_exit</item>
<!-- 跳到新的activity後,該activity被隱藏時的動畫-->
<item name="android:activityCloseEnterAnimation">@anim/activity_close_enter</item>
<!-- activity 銷燬時的動畫-->
<item name="android:activityCloseExitAnimation">@anim/activity_close_exit</item>
在網上搜了下,發現下面兩個屬性還是可以用的
<item name="windowEnterAnimation">@anim/***</item>
<item name="windowExitAnimation">@anim/***</item>
但是這個在一個真正的專案中明顯是不夠的,一個是視窗進來動畫,一個是視窗退出動畫,因此還需要在程式碼中動態設定,也就有了TranslucentHelper 助手類。
- 第二個透明助手類(TranslucentHelper)裡主要又有兩個方法,一個是讓activity變透明,一個是讓activity變不透明,這兩個都是通過反射來呼叫隱藏的系統api來實現的。
public class TranslucentHelper {
public interface TranslucentListener {
void onTranslucent();
}
private static class MyInvocationHandler implements InvocationHandler {
private TranslucentListener listener;
MyInvocationHandler(TranslucentListener listener) {
this.listener = listener;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
boolean success = (boolean) args[0];
if (success && listener != null) {
listener.onTranslucent();
}
} catch (Exception ignored) {
}
return null;
}
}
public static boolean convertActivityFromTranslucent(Activity activity) {
try {
Method method = Activity.class.getDeclaredMethod("convertFromTranslucent");
method.setAccessible(true);
method.invoke(activity);
return true;
} catch (Throwable t) {
return false;
}
}
public static void convertActivityToTranslucent(Activity activity, final TranslucentListener listener) {
try {
Class<?>[] classes = Activity.class.getDeclaredClasses();
Class<?> translucentConversionListenerClazz = null;
for (Class clazz : classes) {
if (clazz.getSimpleName().contains("TranslucentConversionListener")) {
translucentConversionListenerClazz = clazz;
}
}
MyInvocationHandler myInvocationHandler = new MyInvocationHandler(listener);
Object obj = Proxy.newProxyInstance(Activity.class.getClassLoader(),
new Class[] { translucentConversionListenerClazz }, myInvocationHandler);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Method getActivityOptions = Activity.class.getDeclaredMethod("getActivityOptions");
getActivityOptions.setAccessible(true);
Object options = getActivityOptions.invoke(activity);
Method method = Activity.class.getDeclaredMethod("convertToTranslucent",
translucentConversionListenerClazz, ActivityOptions.class);
method.setAccessible(true);
method.invoke(activity, obj, options);
} else {
Method method =
Activity.class.getDeclaredMethod("convertToTranslucent", translucentConversionListenerClazz);
method.setAccessible(true);
method.invoke(activity, obj);
}
} catch (Throwable t) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if (listener != null) {
listener.onTranslucent();
}
}
}, 100);
}
}
}
讓activity變不透明的方法比較簡單就不多說了;讓activity變透明的方法引數裡出入了一個listener ,這個後面再講,主要看呼叫invoke方法時,判斷了版本是否是大於等於5.0,如果是,需要再傳入一個ActivityOptions引數。
2. 讓BaseActivity繼承SwipeBackActivity
先直接看程式碼,比較少
public class SwipeBackActivity extends FragmentActivity {
/**
* 滑動返回ViewGroup
*/
private SwipeBackLayout mSwipeBackLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mSwipeBackLayout = new SwipeBackLayout(this);
getWindow().setBackgroundDrawableResource(R.color.transparent);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
mSwipeBackLayout.attachToActivity(this);
mSwipeBackLayout.setOnSwipeBackListener(new SwipeBackLayout.onSwipeBackListener() {
@Override
public void onStart() {
onSwipeBackStart();
}
@Override
public void onEnd() {
onSwipeBackEnd();
}
});
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
getSwipeBackLayout().recovery();
}
}
/**
* 滑動返回開始時的回撥
*/
protected void onSwipeBackStart() {
}
/**
* 滑動返回結束時的回撥
*/
protected void onSwipeBackEnd() {
}
@Override
public View findViewById(int id) {
View v = super.findViewById(id);
if (v == null && mSwipeBackLayout != null) {
return mSwipeBackLayout.findViewById(id);
}
return v;
}
/**
* 設定是否可以邊緣滑動返回,需要在onCreate方法呼叫
*/
public void setSwipeBackEnable(boolean enable) {
mSwipeBackLayout.setSwipeBackEnable(enable);
}
public SwipeBackLayout getSwipeBackLayout() {
return mSwipeBackLayout;
}
}
SwipeBackActivity中包含了一個SwipeBackLayout ,在onCreate方法中,new了一個SwipeBackLayout 、設定了window的背景色為透明色、主題設定為不透明。不要被這個透明搞暈了,window的背景色相當於在style中設定android:windowBackground為透明,這個也是activity透明的必要條件,由於我所開發的這個專案已經迭代了很多個版本,activity很多,而這些activity中android:windowBackground設定的顏色大部分是白色,少部分是灰色和透明的,所以需要在程式碼中設定統一設定一遍透明的,以達到最少修改程式碼的目的,如果是一個新的專案,可以直接在style中寫死windowBackground為透明,這樣就不要再程式碼中設定了。那麼問題是在程式碼中設定了window是透明的,原來如果是灰色的視覺效果被影響到了怎麼辦?解決辦法是獲取原來的背景色賦值給原來DecorView的子view(也就是現在SwipeBackLayout的子view)就可以了。背景色賦值就是在onPostCreate方法的mSwipeBackLayout.attachToActivity(this);裡做的。onPostCreate的執行時機是在onStart和onResume之間的。attachToActivity就是將SwipeBackLayout插入到DecorView和其子view之間,可以先看下程式碼:
/**
* 將View新增到Activity
*/
public void attachToActivity(Activity activity) {
mTopActivity = 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();
ViewGroup decorChild = (ViewGroup) decor.getChildAt(0);
decorChild.setBackgroundResource(background);
decor.removeView(decorChild);
addView(decorChild);
setContentView(decorChild);
decor.addView(this);
Activity backActivity = ActivityUtils.getSecondTopActivity();
if (backActivity != null && backActivity instanceof SwipeBackActivity) {
mBackActivityWeakRf = new WeakReference<>(backActivity);
}
}
程式碼整體上都還是比較簡單的,應該都能看懂。前面是獲取window背景色,插入過程到decor.addView(this);為止,下面拿到backActivity 的引用是為了做到下層activity聯動時用到的。
繼續來看SwipeBackActivity,onPostCreate還設定了開始滑動和滑動結束的回撥,在某些場合下還是需要的,比如一些PopupWindow在滑動返回時不會被消除,這個時候可以在onSwipeBackStart()呼叫其dismiss()方法。在onWindowFocusChanged中如果是hasFocus == true,就recovery()這個SwipeBackLayout,這個也是因為下層activity有聯動效果而移動了SwipeBackLayout,所以需要recovery()下,防止異常情況。最後再提下setSwipeBackEnable(…),某些不可以滑動返回的頁面比如MainActivity需要在其onCreate方法中呼叫下設定為false就可以了。
3. SwipeBackLayout和SwipeBackLayoutDragHelper
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
//繪製陰影
if (mContentPercent > 0
&& child == mContentView
&& mViewDragHelper.getViewDragState() != SwipeBackLayoutDragHelper.STATE_IDLE) {
child.getHitRect(mContentViewRect);
mShadowLeft.setBounds(mContentViewRect.left - mShadowLeft.getIntrinsicWidth(), mContentViewRect.top,
mContentViewRect.left, mContentViewRect.bottom);
//mShadowLeft.setAlpha((int) (mContentPercent * FULL_ALPHA));
mShadowLeft.draw(canvas);
}
return super.drawChild(canvas, child, drawingTime);
}
@Override
public void computeScroll() {
mContentPercent = 1 - mScrollPercent;
if (mViewDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
/**
* 設定是否可以滑動返回
*/
public void setSwipeBackEnable(boolean enable) {
mIsSwipeBackEnable = enable;
}
public boolean isActivityTranslucent() {
return mIsActivityTranslucent;
}
/**
* 啟動進入動畫
*/
private void startEnterAnim() {
if (mContentView != null) {
ObjectAnimator anim = ObjectAnimator
.ofFloat(mContentView, "TranslationX", mContentView.getTranslationX(), 0f);
anim.setDuration((long) (125 * mContentPercent));
mEnterAnim = anim;
mEnterAnim.start();
}
}
protected View getContentView() {
return mContentView;
}
private void setContentView(ViewGroup decorChild) {
mContentView = decorChild;
}
private class ViewDragCallback extends SwipeBackLayoutDragHelper.Callback {
@Override
public boolean tryCaptureView(View child, int pointerId) {
if (mIsSwipeBackEnable && mViewDragHelper.isEdgeTouched(SwipeBackLayoutDragHelper.EDGE_LEFT, pointerId)) {
TranslucentHelper.convertActivityToTranslucent(mTopActivity,
new TranslucentHelper.TranslucentListener() {
@Override
public void onTranslucent() {
if (mListener != null) {
mListener.onStart();
}
mIsActivityTranslucent = true;
}
});
return true;
}
return false;
}
@Override
public int getViewHorizontalDragRange(View child) {
return mIsSwipeBackEnable ? SwipeBackLayoutDragHelper.EDGE_LEFT : 0;
}
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
super.onViewPositionChanged(changedView, left, top, dx, dy);
if (changedView == mContentView) {
mScrollPercent = Math.abs((float) left / mContentView.getWidth());
mContentLeft = left;
//未執行動畫就平移
if (!mIsEnterAnimRunning) {
moveBackActivity();
}
invalidate();
if (mScrollPercent >= 1 && !mTopActivity.isFinishing()) {
if (mBackActivityWeakRf != null && ActivityUtils.activityIsAlive(mBackActivityWeakRf.get())) {
((SwipeBackActivity) mBackActivityWeakRf.get()).getSwipeBackLayout().invalidate();
}
mTopActivity.finish();
mTopActivity.overridePendingTransition(0, 0);
}
}
}
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
if (xvel > DEFAULT_VELOCITY_THRESHOLD || mScrollPercent > DEFAULT_SCROLL_THRESHOLD){
if (mIsActivityTranslucent) {
mViewDragHelper.settleCapturedViewAt(releasedChild.getWidth() + mShadowLeft.getIntrinsicWidth(), 0);
if (mContentPercent < 0.85f) {
startAnimOfBackActivity();
}
}
} else {
mViewDragHelper.settleCapturedViewAt(0, 0);
}
if (mListener != null) {
mListener.onEnd();
}
invalidate();
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return Math.min(child.getWidth(), Math.max(left, 0));
}
@Override
public void onViewDragStateChanged(int state) {
super.onViewDragStateChanged(state);
if (state == SwipeBackLayoutDragHelper.STATE_IDLE && mScrollPercent < 1f) {
TranslucentHelper.convertActivityFromTranslucent(mTopActivity);
mIsActivityTranslucent = false;
}
}
@Override
public boolean isTranslucent() {
return SwipeBackLayout.this.isActivityTranslucent();
}
}
/**
* 背景Activity開始進入動畫
*/
private void startAnimOfBackActivity() {
if (mBackActivityWeakRf != null && ActivityUtils.activityIsAlive(mBackActivityWeakRf.get())) {
mIsEnterAnimRunning = true;
SwipeBackLayout swipeBackLayout = ((SwipeBackActivity) mBackActivityWeakRf.get()).getSwipeBackLayout();
swipeBackLayout.startEnterAnim();
}
}
/**
* 移動背景Activity
*/
private void moveBackActivity() {
if (mBackActivityWeakRf != null && ActivityUtils.activityIsAlive(mBackActivityWeakRf.get())) {
View view = ((SwipeBackActivity) mBackActivityWeakRf.get()).getSwipeBackLayout().getContentView();
if (view != null) {
int width = view.getWidth();
view.setTranslationX(-width * 0.3f * Math.max(0f, mContentPercent - 0.15f));
}
}
}
/**
* 回覆介面的平移到初始位置
*/
public void recovery() {
if (mEnterAnim != null && mEnterAnim.isRunning()) {
mEnterAnim.end();
} else {
mContentView.setTranslationX(0);
}
}
}
drawChild是來繪製了左側陰影的,獲取到原 子view 所在螢幕的矩形,之後確定陰影所在的矩形,然後就直接繪製了。
child.getHitRect(mContentViewRect);
mShadowLeft.setBounds(mContentViewRect.left - mShadowLeft.getIntrinsicWidth(), mContentViewRect.top,
mContentViewRect.left, mContentViewRect.bottom);
mShadowLeft.draw(canvas);
程式碼中滑動是用了ViewDragHelper,如果不熟悉這個類需要先自行百度。只是這個官方封裝類有幾個地方不能滿足這個滑動返回的需求,於是就有了SwipeBackLayoutDragHelper,SwipeBackLayoutDragHelper主要是copy了ViewDragHelper原始碼後再新增了一些程式碼。
首先原生ViewDragHelper裡面沒有setMaxVelocity方法,如果滑動過快,會導致下層聯動滑動跟不上,上層滑動結束後下層還沒移動好,這導致的結果就是下下層可以被看到,下下層也許就是手機桌面,影響了體驗,所以需要設定了最大速度。
我們知道ViewDragHelper需要通過shouldInterceptTouchEvent(event)和processTouchEvent(event)獲取該view的onInterceptTouchEvent和onTouchEvent事件,之後設定一個回撥ViewDragCallback裡面寫幾個方法就基本上可以實現用手指拖拽了,回撥中有許多的方法,其中isTranslucent()是自己新增進去的,接下來就講講重寫回撥裡的方法都做了什麼。
@Override
public boolean tryCaptureView(View child, int pointerId) {
if (mIsSwipeBackEnable && mViewDragHelper.isEdgeTouched(SwipeBackLayoutDragHelper.EDGE_LEFT, pointerId)) {
TranslucentHelper.convertActivityToTranslucent(mTopActivity,
new TranslucentHelper.TranslucentListener() {
@Override
public void onTranslucent() {
if (mListener != null) {
mListener.onStart();
}
mIsActivityTranslucent = true;
}
});
return true;
}
return false;
}
** tryCaptureView**方法當觸控到SwipeBackLayout裡的子View時觸發的,當返回true,表示捕捉成功,否則失敗。判斷條件是如果支援滑動返回並且是左側邊距被觸控時才可以,我們知道這個時候的的背景色是不透明的,如果直接開始滑動則是黑色的,所以需要在這裡背景色改成透明的,如果直接呼叫 TranslucentHelper.convertActivityToTranslucent(mTopActivity)
後直接返回true,會出現一個異常情況,就是滑動過快時會導致背景還來不及變成黑色就滑動出來了,之後才變成透明的,從而導致了會從黑色到透明的一個閃爍現象,解決的辦法是在程式碼中用了一個回撥和標記,當變成透明後設定了mIsActivityTranslucent = true;
通過mIsActivityTranslucent 這個變數來判斷是否進行移動的操作。由於修改activity變透明的方法是通過反射的,不能簡單的設定一個介面後進行回撥,而是通過動態代理的方式來實現的(InvocationHandler),在convertToTranslucent方法的第一個引數剛好是一個判斷activity是否已經變成透明的回撥,看下面程式碼中 if 語句裡的註釋和回撥,如果視窗已經變成透明的話,就傳了一個drawComplete (true)。
@SystemApi
public boolean convertToTranslucent(TranslucentConversionListener callback,
ActivityOptions options) {
boolean drawComplete;
try {
mTranslucentCallback = callback;
mChangeCanvasToTranslucent =
ActivityManagerNative.getDefault().convertToTranslucent(mToken, options);
WindowManagerGlobal.getInstance().changeCanvasOpacity(mToken, false);
drawComplete = true;
} catch (RemoteException e) {
// Make callback return as though it timed out.
mChangeCanvasToTranslucent = false;
drawComplete = false;
}
if (!mChangeCanvasToTranslucent && mTranslucentCallback != null) {
// Window is already translucent.
mTranslucentCallback.onTranslucentConversionComplete(drawComplete);
}
return mChangeCanvasToTranslucent;
}
在TranslucentHelper的convertActivityToTranslucent(…)方法中
MyInvocationHandler myInvocationHandler =
new MyInvocationHandler(new WeakReference<>(listener));
Object obj = Proxy.newProxyInstance(Activity.class.getClassLoader(),
new Class[] { translucentConversionListenerClazz }, myInvocationHandler);
通過動態代理,將translucentConversionListenerClazz 執行其方法onTranslucentConversionComplete的替換成myInvocationHandler中執行invoke方法。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
boolean success = (boolean) args[0];
if (success && listener.get() != null) {
listener.get().onTranslucent();
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
其中賦值給success的args[0]正是 drawCompletemTranslucentCallback.onTranslucentConversionComplete(drawComplete);
isTranslucent是上面提到,在SwipeBackLayoutDragHelper中的Callback回撥裡,自己新增了一個方法,主要是返回activity是否是透明的
public boolean isTranslucent() {
return true;
}
預設為true,在SwipeBackLayout重寫後將mIsActivityTranslucent返回SwipeBackLayoutDragHelper
@Override
public boolean isTranslucent() {
return SwipeBackLayout.this.isActivityTranslucent();
}
仔細看SwipeBackLayoutDragHelper方法的話,會發現最後通過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);
if (mCallback.isTranslucent()) {
ViewCompat.offsetLeftAndRight(mCapturedView, clampedX - oldLeft);
}
}
if (dy != 0) {
clampedY = mCallback.clampViewPositionVertical(mCapturedView, top, dy);
ViewCompat.offsetTopAndBottom(mCapturedView, clampedY - oldTop);
}
if (dx != 0 || dy != 0) {
final int clampedDx = clampedX - oldLeft;
final int clampedDy = clampedY - oldTop;
if (mCallback.isTranslucent()) {
mCallback.onViewPositionChanged(mCapturedView, clampedX, clampedY,
clampedDx, clampedDy);
}
}
}
onViewPositionChanged view移動過程中會持續呼叫,這裡面的邏輯主要有這幾個:
實時計算滑動了多少距離,用於繪製左側陰影等
使下面的activity進行移動moveBackActivity();
當view完全移出螢幕後,銷燬當前的activity
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
super.onViewPositionChanged(changedView, left, top, dx, dy);
if (changedView == mContentView) {
mScrollPercent = Math.abs((float) left / mContentView.getWidth());
mContentLeft = left;
//未執行動畫就平移
if (!mIsEnterAnimRunning) {
moveBackActivity();
}
invalidate();
if (mScrollPercent >= 1 && !mTopActivity.isFinishing()) {
if (mBackActivityWeakRf != null && ActivityUtils.activityIsAlive(mBackActivityWeakRf.get())) {
((SwipeBackActivity) mBackActivityWeakRf.get()).getSwipeBackLayout().invalidate();
}
mTopActivity.finish();
mTopActivity.overridePendingTransition(0, 0);
}
}
}
/**
* 移動背景Activity
*/
private void moveBackActivity() {
if (mBackActivityWeakRf != null && ActivityUtils.activityIsAlive(mBackActivityWeakRf.get())) {
View view = ((SwipeBackActivity) mBackActivityWeakRf.get()).getSwipeBackLayout().getContentView();
if (view != null) {
int width = view.getWidth();
view.setTranslationX(-width * 0.3f * Math.max(0f, mContentPercent - 0.15f));
}
}
}
onViewReleased是手指釋放後觸發的一個方法
如果滑動速度大於最大速度或者滑動的距離大於設定的閾值距離,則直接移到螢幕外,同時觸發下層activity的復位動畫mViewDragHelper.settleCapturedViewAt(releasedChild.getWidth() + mShadowLeft.getIntrinsicWidth(), 0);
否則移會到原來位置 mViewDragHelper.settleCapturedViewAt(0, 0);
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
if (xvel > DEFAULT_VELOCITY_THRESHOLD || mScrollPercent > DEFAULT_SCROLL_THRESHOLD){
if (mIsActivityTranslucent) {
mViewDragHelper.settleCapturedViewAt(releasedChild.getWidth() + mShadowLeft.getIntrinsicWidth(), 0);
if (mContentPercent < 0.85f) {
startAnimOfBackActivity();
}
}
} else {
mViewDragHelper.settleCapturedViewAt(0, 0);
}
if (mListener != null) {
mListener.onEnd();
}
invalidate();
}
/**
* 背景Activity開始進入動畫
*/
private void startAnimOfBackActivity() {
if (mBackActivityWeakRf != null && ActivityUtils.activityIsAlive(mBackActivityWeakRf.get())) {
mIsEnterAnimRunning = true;
SwipeBackLayout swipeBackLayout = ((SwipeBackActivity) mBackActivityWeakRf.get()).getSwipeBackLayout();
swipeBackLayout.startEnterAnim();
}
}
/**
* 啟動進入動畫
*/
private void startEnterAnim() {
if (mContentView != null) {
ObjectAnimator anim = ObjectAnimator
.ofFloat(mContentView, "TranslationX", mContentView.getTranslationX(), 0f);
anim.setDuration((long) (125 * mContentPercent));
mEnterAnim = anim;
mEnterAnim.start();
}
}
onViewDragStateChanged當滑動的狀態發生改變時的回撥
主要是停止滑動後,將背景改成不透明,這樣跳到別的頁面是動畫就是正常的。
@Override
public void onViewDragStateChanged(int state) {
super.onViewDragStateChanged(state);
if (state == SwipeBackLayoutDragHelper.STATE_IDLE && mScrollPercent < 1f) {
TranslucentHelper.convertActivityFromTranslucent(mTopActivity);
mIsActivityTranslucent = false;
}
}
clampViewPositionHorizontal 返回水平移動距離,下面這樣寫可以防止滑出父 view
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return Math.min(child.getWidth(), Math.max(left, 0));
}
getViewHorizontalDragRange對於clickable=true的子view,需要返回大於0的數字才能正常捕獲。
@Override
public int getViewHorizontalDragRange(View child) {
return mIsSwipeBackEnable ? SwipeBackLayoutDragHelper.EDGE_LEFT : 0;
}
相關文章
- 考拉Android全域性滑動返回及聯動效果的實現Android
- Android實現Activity的滑動返回效果Android
- CSS實現頁面切換時的滑動效果CSS
- Flutter 實現類似美團外賣店鋪頁面滑動效果Flutter
- Android左右滑動效果的程式碼實現Android
- 仿 “即刻APP” 滑動返回的效果APP
- 滑鼠雙擊頁面實現自動滾動效果
- uni-app 實現滑動列表(slider)頁面效果 完整程式碼示例APPIDE
- vue路由切換滑動效果 vue頁面跳轉互動 vue實現動畫跳轉Vue路由動畫
- 利用JavaScript實現註冊頁面省市聯動效果(附程式碼)JavaScript
- Android滑動返回-swipebacklayout解析Android
- H5頁面滾動阻尼效果實現H5
- iOS6下實現滑動返回iOS
- H5頁面實現滑動控制音訊播放H5音訊
- SVG 動畫實現彈性的頁面元素效果SVG動畫
- Vue實現一個頁面快取、左滑返回的navigatorVue快取
- Android 設定TextView滑動滾動條和滑動效果AndroidTextView
- jquery實現滑動門效果詳解jQuery
- 實現抖音 “影片無限滑動“效果
- 短視訊程式原始碼,PageSlider實現滑動頁面原始碼IDE
- 類似微信首頁彈性滾動和慣性滾動效果的實現——OverScroll
- iOS 實現UINavigation全屏滑動返回(二)iOSUINavigation
- iOS系統右滑返回全域性控制方案iOS
- 微信小程式實現卡片左右滑動效果微信小程式
- (十)如果實現滑動展示選單效果
- android: slide 滑動動畫效果AndroidIDE動畫
- 移動端上滑實現翻頁功能
- js實現手機網頁滑動JS網頁
- 直播app原始碼,標題欄隨頁面滑動之title移動定位效果APP原始碼
- jquery實現的滑動軸效果程式碼例項jQuery
- Android 進出activity的滑動動畫效果Android動畫
- 純CSS3實現滑動開關效果CSSS3
- 記錄---實現抖音 “影片無限滑動“效果
- Swift全屏滑動返回Swift
- jQuery 效果 – 滑動jQuery
- Android 的滑動分析以及各種實現Android
- 使用UICollectionView實現首頁的滾動效果UIView
- 直播商城原始碼,實現左右聯動商品分類頁面原始碼