本文來(lái)源于這個(gè)開(kāi)源項(xiàng)目,由于作者只給出了用法和基本原理,因此才有了本文的產(chǎn)生。本文去除了原項(xiàng)目中較瑣碎和不必要的一些內(nèi)容,只實(shí)現(xiàn)了基本的Activity滑動(dòng)返回功能。
先看效果圖:

效果還是挺不錯(cuò)的。
基本原理:利用Application類的registerActivityLifecycleCallbacks(ActivityLifecycleCallbacks)方法,可以記錄全局所有Activity的生命周期,因此我們可以利用這點(diǎn)來(lái)存儲(chǔ)我們所有的Activity于一個(gè)棧中,每次滑動(dòng)返回時(shí)從棧中取出前一個(gè)Activtity,然后分離出其中id為Window.ID_ANDROID_CONTENT的FrameLayout,這個(gè)FrameLayout就是我們setContentView中的那個(gè)view的父view,利用這個(gè)FrameLayout就可以獲取Activity界面顯示的View。然后我們監(jiān)聽(tīng)手勢(shì)事件,在滑動(dòng)的時(shí)候?qū)⑶耙粋€(gè)Activity的View加載進(jìn)來(lái)并不斷更改其偏移量即可。
首先是實(shí)現(xiàn)ActivityLifecycleCallbacks接口,并在其中用一個(gè)棧存儲(chǔ)我們所有的Activity:
public class ActivityLifeCycleHelper implements Application.ActivityLifecycleCallbacks {
private Stack<Activity> mActivities;
public ActivityLifeCycleHelper(){
mActivities=new Stack<>();
}
@Override
public void onActivityCreated(Activity activity, Bundle bundle) {
mActivities.add(activity);
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
}
@Override
public void onActivityDestroyed(Activity activity) {
mActivities.remove(activity);
}
public Activity getPreActivity(){
int size=mActivities.size();
if(size<2) return null;
else return mActivities.get(size-2);
}
public void removeActivity(Activity activity){
mActivities.remove(activity);
}
}
看得出這個(gè)類很簡(jiǎn)單,只是在Activity創(chuàng)建的時(shí)候加入棧中,銷毀的時(shí)候移除。
然后就是在Application中調(diào)用registerActivityLifecycleCallbacks()方法了:
public class MyApplication extends Application {
public ActivityLifeCycleHelper getHelper() {
return mHelper;
}
private ActivityLifeCycleHelper mHelper;
@Override
public void onCreate() {
super.onCreate();
mHelper=new ActivityLifeCycleHelper();
//store all the activities
registerActivityLifecycleCallbacks(mHelper);
}
}
然后定義一個(gè)最基本的SwipeBackActivity,當(dāng)然要繼承自AppCompactActivity,這個(gè)類我們要做的就是重寫它的dispatchTouchEvent()方法,這是因?yàn)槲覀円O(jiān)聽(tīng)邊界滑動(dòng)返回事件,肯定要攔截其中的一些觸摸事件。
public class SwipeBackActivity extends AppCompatActivity {
private TouchHelepr mTouchHelepr;
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if(mTouchHelepr==null)
mTouchHelepr=new TouchHelepr(getWindow());
boolean consume=mTouchHelepr.processTouchEvent(ev);
if(!consume) return super.dispatchTouchEvent(ev);
return false;
//return super.dispatchTouchEvent(ev)||mTouchHelepr.processTouchEvent(ev);
}
}
這里有一個(gè)我們自己寫的類TouchHelper,具體的邏輯操作就在這里面實(shí)現(xiàn)了。接下來(lái)就是重點(diǎn)類TouchHelper了。
首先我們定義三個(gè)狀態(tài):
private boolean isIdle=true;
private boolean isSlinding=false;
private boolean isAnimating=false;
- isIdle,表示當(dāng)前為靜止?fàn)顟B(tài)。
- isSliding,表示當(dāng)前用戶手指移動(dòng),我們的View隨之滑動(dòng)。
- isAnimating,表示用戶手指松開(kāi),View要么恢復(fù)原狀,要么移動(dòng)至最右并消失,這是一個(gè)Animation過(guò)程,isAnimating=true表示當(dāng)前處于這種動(dòng)畫過(guò)程中。
然后是幾個(gè)成員變量:
private Window mWindow;
private ViewGroup preContentView;
private ViewGroup curContentView;
private ViewGroup curView;
private ViewGroup preView;
private Activity preActivity;
//左邊觸發(fā)的寬度
private int triggerWidth=50;
//陰影寬度
private int SHADOW_WIDTH=30;
mWindow用于初始化TouchHelper,并且這個(gè)window就包含了context,activity等信息。
curContentView、preContentView分別表示當(dāng)前、前一個(gè)Activity中外層的FrameLayout。
curView、preView分別表示當(dāng)前、前一個(gè)Activity的界面View。
然后就是處理手勢(shì)的代碼了:
private Context getContext(){
return mWindow.getContext();
}
//決定是否攔截事件
public boolean processTouchEvent(MotionEvent event){
if(isAnimating) return true;
float x=event.getRawX();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
if(x<=triggerWidth){
isIdle=false;
isSlinding=true;
startSlide();
return true;
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
if(isSlinding) return true;
break;
case MotionEvent.ACTION_MOVE:
if(isSlinding){
if(event.getActionIndex()!=0) return true;
sliding(x);
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
if(!isSlinding) return false;
int width=getContext().getResources().getDisplayMetrics().widthPixels;
isAnimating=true;
isSlinding=false;
startAnimating(width/x<=3,x);
return true;
default:
break;
}
return false;
}
在函數(shù)processTouchEvent()中所有要攔截的地方我們都return true,這樣子View就不會(huì)受到觸摸事件了,其余的則應(yīng)返回false,表示將觸摸事件分發(fā)給子View去處理。
其中狀態(tài)更改的代碼比較簡(jiǎn)單,就不解釋了。主要說(shuō)說(shuō)其中隨著狀態(tài)更改而進(jìn)行的幾個(gè)操作函數(shù):
- startSlide()
- sliding(x)
- startAnimating(width/x<=3,x)
startSlide(),顧名思義,開(kāi)始滑動(dòng),先看看代碼:
private void startSlide() {
preActivity=((MyApplication)getContext().getApplicationContext()).getHelper().getPreActivity();
if(preActivity==null) return;
preContentView=(ViewGroup) preActivity.getWindow().findViewById(Window.ID_ANDROID_CONTENT);
preView= (ViewGroup) preContentView.getChildAt(0);
preContentView.removeView(preView);
curContentView=(ViewGroup) mWindow.findViewById(Window.ID_ANDROID_CONTENT);
curView= (ViewGroup) curContentView.getChildAt(0);
preView.setX(-preView.getWidth()/3);
curContentView.addView(preView,0);
// if(mShadowView==null){
// mShadowView=new ShadowView(getContext());
// }
// FrameLayout.LayoutParams params=new FrameLayout.LayoutParams(SHADOW_WIDTH, FrameLayout.LayoutParams.MATCH_PARENT);
// curContentView.addView(mShadowView,1,params);
// mShadowView.setX(-SHADOW_WIDTH);
}
在startSlide()中,我們給幾個(gè)成員變量賦值,并且將preView添加到curContentView中,并賦予其一個(gè)初始偏移量。這里要特別注意addView(view,index)中的index參數(shù),index參數(shù)越大,代表越靠后繪制。這里添加preView時(shí)的index為0,表示最先繪制preView,否則preView會(huì)顯示在curView的上面,這樣就不正確了。注釋部分稍后再講。
然后看看sliding()方法:
private void sliding(float rawX) {
if(preActivity==null) return;
curView.setX(rawX);
preView.setX(-preView.getWidth()/3+rawX/3);
//mShadowView.setX(-SHADOW_WIDTH+rawX);
}
這個(gè)函數(shù)就簡(jiǎn)單多了,這是隨著用戶手指的位置動(dòng)態(tài)地更改curView、preView而已。注釋稍后講。
然后是startAnimating()方法:
private void startAnimating(final boolean isFinishing, float x) {
int width=getContext().getResources().getDisplayMetrics().widthPixels;
ValueAnimator animator=ValueAnimator.ofFloat(x,isFinishing?width:0);
animator.setInterpolator(new DecelerateInterpolator());
animator.setDuration(200);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
sliding((Float) valueAnimator.getAnimatedValue());
}
});
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
doEndWorks(isFinishing);
}
@Override
public void onAnimationCancel(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
animator.start();
}
當(dāng)用戶松開(kāi)手指時(shí)的位置的x坐標(biāo)小于屏幕寬度的1/3時(shí),恢復(fù)原狀,否則將preView完全顯示,這里利用ValueAnimator來(lái)實(shí)現(xiàn)動(dòng)畫。注意在動(dòng)畫完成后我們還要做一些收尾工作,就是方法doEndWorks():
private void doEndWorks(boolean isFinishing) {
if(preActivity==null) return;
if(isFinishing){
//更改當(dāng)前activity的底view為preView,防止當(dāng)前activity finish時(shí)的白屏閃爍
BackView view=new BackView(getContext());
view.cacheView(preView);
curContentView.addView(view,0);
}
//curContentView.removeView(mShadowView);
if(curContentView==null||preContentView==null) return;
curContentView.removeView(preView);
preContentView.addView(preView);
if(isFinishing){
((Activity)getContext()).finish();
((Activity)getContext()).overridePendingTransition(0,0);
}
isAnimating=false;
isSlinding=false;
isIdle=true;
preView=null;
curView=null;
}
收尾工作中我們將狀態(tài)修正,該移除的View移除,該添加的View添加。若preView完全顯示,就finish當(dāng)前activity,注意還要利用((Activity)getContext()).overridePendingTransition(0,0)取消默認(rèn)的activity更換動(dòng)畫,這樣才能實(shí)現(xiàn)暗度陳倉(cāng)的目的。你應(yīng)該已經(jīng)看到了這里還有一個(gè)BackView,這個(gè)BackView其實(shí)就是preView的一個(gè)副本,我們將BackView添加到curContentView的最底部,覆蓋那個(gè)白色底部,否則動(dòng)畫完成后會(huì)有一個(gè)白屏閃爍現(xiàn)象。
//用于防止白屏閃爍
class BackView extends View{
private View mView;
public BackView(Context context) {
super(context);
}
public void cacheView(View view){
mView=view;
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if(mView!=null){
mView.draw(canvas);
mView=null;
}
}
}
這樣實(shí)現(xiàn)的效果就是如下:

你應(yīng)該注意要這個(gè)和第一個(gè)demo顯示的不太一樣,因?yàn)檫@里沒(méi)有陰影效果,體現(xiàn)不出層次感,不夠美觀,那么接下來(lái)我們只需要在添加一點(diǎn)點(diǎn)代碼就可以添加這樣的一個(gè)陰影效果.
class ShadowView extends View{
private Drawable mDrawable;
public ShadowView(Context context) {
super(context);
int[] colors=new int[]{0x00000000, 0x17000000, 0x43000000};
mDrawable=new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT,colors);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mDrawable.setBounds(0,0,getMeasuredWidth(),getMeasuredHeight());
mDrawable.draw(canvas);
}
}
這個(gè)就是要繪制上去的陰影效果,很簡(jiǎn)單,將前面幾個(gè)方法中注釋的代碼部分還原即可。
這樣,就完成了我們所有的代碼~~~