溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Android仿考拉全局滑動返回及聯(lián)動效果的實現(xiàn)方法

發(fā)布時間:2020-08-22 11:57:21 來源:腳本之家 閱讀:250 作者:網(wǎng)易考拉移動端團隊 欄目:移動開發(fā)

前言

首次通過右滑來返回到上一個頁面的操作是在 IOS7上出現(xiàn)。到目前android應用上支持這種操作的依然不多。分析其主要原因應該是android已有實體的返回按鍵,這樣的功能變得不重要,但我覺得有這樣的功能便于單手操作,能提升app的用戶體驗,特別是從ios轉(zhuǎn)到android的用戶。寫這篇博文希望可以對大家有所幫助,希望自己的app上有滑動返回功能的可以參考下。

原理的簡單描述

Android系統(tǒng)里有很多滑動相關(guān)的API和類,比如ViewDragHelper就是一個很好的滑動助手類。首先設(shè)置Window的背景為透明,再通過ViewDragHelper對Activity上DecorView的子view進行滑動,當滑動到一定距離,手指離開后就自動滑到最右側(cè),然后finish當前的activity,這樣即可實現(xiàn)滑動返回效果。為了能夠 “全局的”、“聯(lián)動的” 實現(xiàn)滑動返回效果,在每個activity的DecorView下插入了SwipeBackLayout,當前activity滑動和下層activity的聯(lián)動都在該類中完成。

效果圖

Android仿考拉全局滑動返回及聯(lián)動效果的實現(xiàn)方法

布局圖

Android仿考拉全局滑動返回及聯(lián)動效果的實現(xiàn)方法

實現(xiàn)主要類:

SwipeBackActivity //滑動返回基類

SwipeBackLayout //滑動返回布局類

SwipeBackLayoutDragHelper  //修改ViewDragHelper后助手類

TranslucentHelper //代碼中修改透明或者不透明的助手類

##代碼層面的講解

一. 設(shè)置activity為透明、activity跳轉(zhuǎn)動畫(TranslucentHelper 講解)

這個看起來很簡單,但如果要兼容到API16及以下,會遇到過一個比較麻煩的頁面切換動畫問題:

1.1、通過activity的主題style進行設(shè)置

<item name="android:windowBackground">@color/transparent</item>
<item name="android:windowIsTranslucent">true</item>```

**遇到問題:**如果在某個activity的主題style中設(shè)置了android:windowIsTranslucent屬性為true,那么該activity切換動畫與沒設(shè)置之前是不同的,有些手機切換動畫會變得非常跳。所以需要自定義activity的切換動畫。
接下來我們會想到通過主題style里的windowAnimationStyle來設(shè)置切換動畫

@anim/activity_open_enter

@anim/activity_open_exit

@anim/activity_close_enter

@anim/activity_close_exit```

**實踐證明:**當android:windowIsTranslucent為true時,以上幾個屬性是無效的,而下面兩個屬性還是可以用。但是這兩個屬性一個是窗口進來動畫,一個是窗口退出動畫,明顯是不夠。

<item name="android:windowEnterAnimation">@anim/***</item>
<item name="android:windowExitAnimation">@anim/***</item>``

結(jié)合overridePendingTransition(int enterAnim, int exitAnim)可以復寫窗口進來動畫和窗口退出動畫,這種我覺得最終可能是可以實現(xiàn)的,不過控制起來比較復雜:

比如有A、B、C三個頁面:

A跳到B,進場頁面B動畫從右進來,出場頁面A動畫從左出去,可以直接在style中寫死

@anim/***

@anim/***```

如果B返回到A,進場頁面A動畫從左進來,出場頁面B動畫從右出去,此時需要通過復寫onBackPressed() 方法,
在其中添加overridePendingTransition(int enterAnim, int exitAnim)方法來改變動畫。

如果B是finish()后到A頁面,在finish()后面加上overridePendingTransition ……

由于onBackPressed() 方法最終會調(diào)finish(),所以實際上只需要復寫finish(),在其中添加overridePendingTransition……

但是假如B finish()后跳到C,則又不應該執(zhí)行overridePendingTransition……,那么就需要判斷finish執(zhí)行后是否要加 overridePendingTransition……

對于一個較為龐大的項目,采取這種方法需要對每個頁面進行排查,因此是不可行的,而對于剛剛起步的應用來說則是一個選擇。

1.2、通過透明助手類(TranslucentHelper)進行設(shè)置

透明助手類(TranslucentHelper)里主要又有兩個方法,一個是讓activity變不透明,一個是讓activity變透明,這兩個都是通過反射來調(diào)用隱藏的系統(tǒng)api來實現(xiàn)的。因為較低的版本不支持代碼中修改背景透明不透明,所以在類中有個靜態(tài)變量mTranslucentState 來記錄是否可以切換背景,這樣低版本就不需要每次都反射通過捕獲到的異常來做兼容方案。

另外:發(fā)現(xiàn)有些手機支持背景變黑,但不支持背景變透明(中興z9 mini 5.0.2系統(tǒng))

public class TranslucentHelper {
 private static final String TRANSLUCENT_STATE = "translucentState";
 private static final int INIT = 0;//表示初始
 private static final int CHANGE_STATE_FAIL = INIT + 1;//表示確認不可以切換透明狀態(tài)
 private static final int CHANGE_STATE_SUCCEED = CHANGE_STATE_FAIL + 1;//表示確認可以切換透明狀態(tài)
 private static int mTranslucentState = INIT;

 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;
  }
 }

 static boolean convertActivityFromTranslucent(Activity activity) {
  if (mTranslucentState == INIT) {
   mTranslucentState = PreferencesUtils.getInt(TRANSLUCENT_STATE, INIT);
  }
  if (mTranslucentState == INIT) {
   convertActivityToTranslucent(activity, null);
  } else if (mTranslucentState == CHANGE_STATE_FAIL) {
   return false;
  }

  try {
   Method method = Activity.class.getDeclaredMethod("convertFromTranslucent");
   method.setAccessible(true);
   method.invoke(activity);
   mTranslucentState = CHANGE_STATE_SUCCEED;
   return true;
  } catch (Throwable t) {
   mTranslucentState = CHANGE_STATE_FAIL;
   PreferencesUtils.saveInt(TRANSLUCENT_STATE, CHANGE_STATE_FAIL);
   return false;
  }
 }

 static void convertActivityToTranslucent(Activity activity, final TranslucentListener listener) {
  if (mTranslucentState == CHANGE_STATE_FAIL) {
   if (listener != null) {
    listener.onTranslucent();
   }
   return;
  }

  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);
   }
   mTranslucentState = CHANGE_STATE_SUCCEED;
  } catch (Throwable t) {
   mTranslucentState = CHANGE_STATE_FAIL;
   PreferencesUtils.saveInt(TRANSLUCENT_STATE, CHANGE_STATE_FAIL);
   new Handler().postDelayed(new Runnable() {
    @Override
    public void run() {
     if (listener != null) {
      listener.onTranslucent();
     }
    }
   }, 100);
  }
 }
}

讓activity變不透明的方法比較簡單;讓activity變透明的方法參數(shù)里傳入了一個listener接口 ,主要是當antivity變透明后會回調(diào),因為這個接口也在activity里,而且是私有的,所以我們只能通過動態(tài)代理去獲取這個回調(diào)。最后如果版本大于等于5.0,還需要再傳入一個ActivityOptions參數(shù)。

在實際開發(fā)中,這兩個方法在android 5.0以上是有效的,在5.0以下需要當android:windowIsTranslucent為true時才有效,這樣又回到了之前的問題activity切換動畫異常。

**最終決解方法:**setContentView之前就調(diào)用 convertActivityFromTranslucent方法,讓activity背景變黑,這樣activity切換效果就正常。

**總結(jié):**在style中設(shè)置android:windowIsTranslucent為true ,setContentView之前就調(diào)用 convertActivityFromTranslucent方法,當觸發(fā)右滑時調(diào)用convertActivityToTranslucent,通過動態(tài)代理獲取activity變透明后的回調(diào),在回調(diào)后允許開始滑動。

二. 讓BaseActivity繼承SwipeBackActivity(SwipeBackActivity講解)

先直接看代碼,比較少

public abstract class SwipeBackActivity extends CoreBaseActivity {
 /**
  * 滑動返回View
  */
 private SwipeBackLayout mSwipeBackLayout;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  if (!isSwipeBackDisableForever()) {
   TranslucentHelper.convertActivityFromTranslucent(this);
   mSwipeBackLayout = new SwipeBackLayout(this);
  }
 }

 @Override
 protected void onPostCreate(Bundle savedInstanceState) {
  super.onPostCreate(savedInstanceState);
  if (!isSwipeBackDisableForever()) {
   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 (!isSwipeBackDisableForever() && hasFocus) {
   getSwipeBackLayout().recovery();
  }
 }

 /**
  * 滑動返回開始時的回調(diào)
  */
 protected void onSwipeBackStart() {}

 /**
  * 滑動返回結(jié)束時的回調(diào)
  */
 protected void onSwipeBackEnd() {}

 /**
  * 設(shè)置是否可以邊緣滑動返回,需要在onCreate方法調(diào)用
  */
 public void setSwipeBackEnable(boolean enable) {
  if (mSwipeBackLayout != null) {
   mSwipeBackLayout.setSwipeBackEnable(enable);
  }
 }

 public boolean isSwipeBackDisableForever() {
  return false;
 }

 public SwipeBackLayout getSwipeBackLayout() {
  return mSwipeBackLayout;
 }
}

SwipeBackActivity中包含了一個SwipeBackLayout對象

在onCreate方法中:

1.activity轉(zhuǎn)化為不透明。

2.new了一個SwipeBackLayout。

在onPostCreate方法中:

1.attachToActivity主要是插入SwipeBackLayout、窗口背景設(shè)置……

2.設(shè)置了滑動返回開始和結(jié)束的監(jiān)聽接口,建議在滑動返回開始時,把PopupWindow給dismiss掉。

onWindowFocusChanged 方法中

如果是hasFocus == true,就recovery()這個SwipeBackLayout,這個也是因為下層activity有聯(lián)動效果而移動了SwipeBackLayout,所以需要recovery()下,防止異常情況。

isSwipeBackDisableForever 方法是一個大開關(guān),默認返回false,在代碼中復寫后返回 true,則相當于直接繼承了SwipeBackActivity的父類。

setSwipeBackEnable 方法是一個小開關(guān),設(shè)置了false之后就暫時不能滑動返回了,可以在特定的時機設(shè)置為true,就恢復滑動返回的功能。

**總結(jié)說明:**下層activity設(shè)置了setSwipeBackEnable 為 false,上層activity滑動時還是可以聯(lián)動的,比如MainActivity。而isSwipeBackDisableForever 返回true就不會聯(lián)動了,而且一些仿PopupWindow的activity需要復寫這個方法,因為activity需要透明。

三、滑動助手類的使用和滑動返回布局類的實現(xiàn)(SwipeBackLayout講解)

直接貼SwipeBackLayout源碼:

/**
 * 滑動返回容器類
 */
public class SwipeBackLayout extends FrameLayout {
 /**
  * 滑動銷毀距離比例界限,滑動部分的比例超過這個就銷毀
  */
 private static final float DEFAULT_SCROLL_THRESHOLD = 0.5f;
 /**
  * 滑動銷毀速度界限,超過這個速度就銷毀
  */
 private static final float DEFAULT_VELOCITY_THRESHOLD = ScreenUtils.dpToPx(250);
 /**
  * 最小滑動速度
  */
 private static final int MIN_FLING_VELOCITY = ScreenUtils.dpToPx(200);
 /**
  * 左邊移動的像素值
  */
 private int mContentLeft;
 /**
  * 左邊移動的像素值 / (ContentView的寬+陰影)
  */
 private float mScrollPercent;
 /**
  * (ContentView可見部分+陰影)的比例 (即1 - mScrollPercent)
  */
 private float mContentPercent;
 /**
  * 陰影圖
  */
 private Drawable mShadowDrawable;
 /**
  * 陰影圖的寬
  */
 private int mShadowWidth;
 /**
  * 內(nèi)容view,DecorView的原第一個子view
  */
 private View mContentView;
 /**
  * 用于記錄ContentView所在的矩形
  */
 private Rect mContentViewRect = new Rect();
 /**
  * 設(shè)置是否可滑動
  */
 private boolean mIsSwipeBackEnable = true;
 /**
  * 是否正在放置
  */
 private boolean mIsLayout = true;
 /**
  * 判斷背景Activity是否啟動進入動畫
  */
 private boolean mIsEnterAnimRunning = false;
 /**
  * 是否是透明的
  */
 private boolean mIsActivityTranslucent = false;
 /**
  * 進入動畫(只在釋放手指時使用)
  */
 private ObjectAnimator mEnterAnim;
 /**
  * 退拽助手類
  */
 private SwipeBackLayoutDragHelper mViewDragHelper;
 /**
  * 執(zhí)行滑動時的最頂層Activity
  */
 private Activity mTopActivity;
 /**
  * 后面的Activity的弱引用
  */
 private WeakReference<Activity> mBackActivityWeakRf;
 /**
  * 監(jiān)聽滑動開始和結(jié)束
  */
 private onSwipeBackListener mListener;

 public SwipeBackLayout(Context context) {
  super(context);
  init(context);
 }

 public SwipeBackLayout(Context context, AttributeSet attrs) {
  super(context, attrs);
  init(context);
 }

 public SwipeBackLayout(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  init(context);
 }

 private void init(Context context) {
  mViewDragHelper = SwipeBackLayoutDragHelper.create(SwipeBackLayout.this, new ViewDragCallback());
  mViewDragHelper.setEdgeTrackingEnabled(SwipeBackLayoutDragHelper.EDGE_LEFT);
  mViewDragHelper.setMinVelocity(MIN_FLING_VELOCITY);
  mViewDragHelper.setMaxVelocity(MIN_FLING_VELOCITY * 2);
  try {
   mShadowDrawable = context.getResources().getDrawable(R.drawable.swipeback_shadow_left);
  } catch (Exception ignored) {
  }
 }

 @Override
 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
  try {
   if (!mIsSwipeBackEnable) {
    super.onLayout(changed, left, top, right, bottom);
    return;
   }
   mIsLayout = true;
   if (mContentView != null) {
    mContentView.layout(mContentLeft, top, mContentLeft + mContentView.getMeasuredWidth(),
      mContentView.getMeasuredHeight());
   }
   mIsLayout = false;
  } catch (Exception e) {
   super.onLayout(changed, left, top, right, bottom);
  }
 }

 @Override
 public void requestLayout() {
  if (!mIsLayout || !mIsSwipeBackEnable) {
   super.requestLayout();
  }
 }

 @Override
 protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
  try {
   //繪制陰影
   if (mContentPercent > 0
     && mShadowDrawable != null
     && child == mContentView
     && mViewDragHelper.getViewDragState() != SwipeBackLayoutDragHelper.STATE_IDLE) {
    child.getHitRect(mContentViewRect);
    mShadowWidth = mShadowDrawable.getIntrinsicWidth();
    mShadowDrawable.setBounds(mContentViewRect.left - mShadowWidth, mContentViewRect.top,
      mContentViewRect.left, mContentViewRect.bottom);
    mShadowDrawable.draw(canvas);
   }
   return super.drawChild(canvas, child, drawingTime);
  } catch (Exception e) {
   return super.drawChild(canvas, child, drawingTime);
  }
 }

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

 @Override
 public boolean onInterceptTouchEvent(MotionEvent event) {
  if (!mIsSwipeBackEnable) {
   return false;
  }
  try {
   return mViewDragHelper.shouldInterceptTouchEvent(event);
  } catch (ArrayIndexOutOfBoundsException e) {
   return super.onInterceptTouchEvent(event);
  }
 }

 @Override
 public boolean onTouchEvent(MotionEvent event) {
  if (!mIsSwipeBackEnable) {
   return false;
  }
  try {
   mViewDragHelper.processTouchEvent(event);
   return true;
  } catch (Exception e) {
   return super.onTouchEvent(event);
  }
 }

 /**
  * 將View添加到Activity
  */
 public void attachToActivity(Activity activity) {
  //插入SwipeBackLayout
  ViewGroup decor = (ViewGroup) activity.getWindow().getDecorView();
  ViewGroup decorChild = (ViewGroup) decor.getChildAt(0);
  decor.removeView(decorChild);
  if (getParent() != null) {
   decor.removeView(this);
  }
  decor.addView(this);
  this.removeAllViews();
  this.addView(decorChild);

  //mContentView為SwipeBackLayout的直接子view,獲取window背景色進行賦值
  activity.getWindow().setBackgroundDrawableResource(R.color.transparent_white);
  TypedArray a = activity.getTheme().obtainStyledAttributes(new int[] {
    android.R.attr.windowBackground
  });
  mContentView = decorChild;
  mContentView.setBackgroundResource(a.getResourceId(0, 0));
  a.recycle();

  //拿到頂層activity和下層activity,做聯(lián)動操作
  mTopActivity = activity;
  Activity backActivity = ActivityUtils.getSecondTopActivity();
  if (backActivity != null && backActivity instanceof SwipeBackActivity) {
   if (!((SwipeBackActivity) backActivity).isSwipeBackDisableForever()) {
    mBackActivityWeakRf = new WeakReference<>(backActivity);
   }
  }
 }

 /**
  * 設(shè)置是否可以滑動返回
  */
 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 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;
    //未執(zhí)行動畫就平移
    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() + mShadowWidth, 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);
  }
 }

 interface onSwipeBackListener {
  void onStart();

  void onEnd();
 }

 public void setOnSwipeBackListener(onSwipeBackListener listener) {
  mListener = listener;
 }
}

attachToActivity

上面講到SwipeBackLayout是在activity的onCreate時被創(chuàng)建,在onPostCreate是插入到DecorView里,主要是因為DecorView是在setContentView時與Window關(guān)聯(lián)起來插入SwipeBackLayout方法如代碼所示,不難,然后是設(shè)置了window的背景色為透明色,mContentView為SwipeBackLayout的直接子view,獲取window背景色進行賦值。由于考拉項目已經(jīng)有很多activity,而這些activity中android:windowBackground設(shè)置的顏色大部分是白色,少部分是灰色和透明的,所以需要在代碼中設(shè)置統(tǒng)一設(shè)置一遍透明的,原來的背景色則賦值給SwipeBackLayout的子View就可以達到最少修改代碼的目的。最后拿到頂層activity和下層activity,做聯(lián)動操作

SwipeBackLayoutDragHelper 和 ViewDragCallback

SwipeBackLayout中包含了一個滑動助手類(SwipeBackLayoutDragHelper)的對象,該類是在ViewDragHelper的基礎(chǔ)上進行修改得來的。

修改點:

1.右側(cè)觸發(fā)區(qū)域 EDGE_SIZE由 20dp 改到 25dp

2.提供滑動最大速度 的設(shè)置方法

3.在ViewDragHelper 的內(nèi)部類Callback方法中提供是否activity為透明的回調(diào)接口

4.在最終調(diào)用滑動的方法dragTo中添加判斷邏輯,activity為透明時才支持滑動

SwipeBackLayoutDragHelper 在init 方法中初始化,通過onInterceptTouchEvent和onTouchEvent拿到滑動事件,通過ViewDragCallback的一些方法返回相應的滑動回調(diào),ViewDragCallback實現(xiàn)了SwipeBackLayoutDragHelper.Callback里的以下幾個接口,其中其中isTranslucent()是自己添加進去的。

Android仿考拉全局滑動返回及聯(lián)動效果的實現(xiàn)方法 

tryCaptureView方法當觸摸到SwipeBackLayout里的子View時觸發(fā)的,當返回true,表示捕捉成功,否則失敗。判斷條件是如果支持滑動返回并且是左側(cè)邊距被觸摸時才可以,我們知道這個時候的的背景色是不透明的,如果直接開始滑動則是黑色的,所以需要在這里背景色改成透明的,如果直接調(diào)用 TranslucentHelper.convertActivityToTranslucent(mTopActivity, null)后直接返回true,會出現(xiàn)一個異常情況,就是滑動過快時會導致背景還來不及變成黑色就滑動出來了,之后才變成透明的,從而導致了會從黑色到透明的一個閃爍現(xiàn)象,解決的辦法是在代碼中用了一個回調(diào)和標記,當變成透明后設(shè)置了mIsActivityTranslucent = true;通過mIsActivityTranslucent 這個變量來判斷是否進行移動的操作。由于修改activity變透明的方法是通過反射的,不能簡單的設(shè)置一個接口后進行回調(diào),而是通過動態(tài)代理的方式來實現(xiàn)的(InvocationHandler),在convertToTranslucent方法的第一個參數(shù)剛好是一個判斷activity是否已經(jīng)變成透明的回調(diào),看下面代碼中 if 語句里的注釋和回調(diào),如果窗口已經(jīng)變成透明的話,就傳了一個drawComplete (true)。通過動態(tài)代理,將translucentConversionListenerClazz 執(zhí)行其方法onTranslucentConversionComplete的替換成myInvocationHandler中執(zhí)行invoke方法。其中賦值給success的args[0]正是 drawComplete。

isTranslucent是自己添加了一個方法,主要是返回activity是否是透明的默認為true,在SwipeBackLayout重寫后將mIsActivityTranslucent返回。仔細看SwipeBackLayoutDragHelper方法的話,會發(fā)現(xiàn)最后通過dragTo方法對view進行移動,因此在進行水平移動前判斷下是否是透明的,只有透明了才能移動

onViewPositionChanged view移動過程中會持續(xù)調(diào)用,這里面的邏輯主要有這幾個:

1.實時計算滑動了多少距離,用于繪制左側(cè)陰影等

2.使下面的activity進行移動moveBackActivity();

3.當view完全移出屏幕后,銷毀當前的activity

onViewReleased是手指釋放后觸發(fā)的一個方法。如果滑動速度大于最大速度或者滑動的距離大于設(shè)定的閾值距離,則直接移到屏幕外,同時觸發(fā)下層activity的復位動畫,否則移會到原來位置 。

onViewDragStateChanged當滑動的狀態(tài)發(fā)生改變時的回調(diào),主要是停止滑動后,將背景改成不透明,這樣跳到別的頁面是動畫就是正常的。

clampViewPositionHorizontal 返回水平移動距離,防止滑出父 view。

getViewHorizontalDragRange對于clickable=true的子view,需要返回大于0的數(shù)字才能正常捕獲。

其他方法都較為簡單,注釋也寫了,就不多說了,最后毫不吝嗇的貼上SwipeBackLayoutDragHelper的dragTo代碼,就多了if (mCallback.isTranslucent())

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

總結(jié)

以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對億速云的支持。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI