大屏幕手機在返回前頁操做時,點擊左上角的 APP 內返回鍵或者手機自帶的返回按鍵都不是很方便,這時候能經過屏幕側滑退出當前頁面體驗就會好不少了。可是 Android 系統並無想 IOS 同樣自帶側滑返回,好在 Android 輪子比較多,本文記錄一下我的開源項目 PandaEye 中使用的側滑返回庫 SwipBackLayout 。該庫參考 github 上的開源庫 SwipeBackLayout 作了一些簡化;php
側滑返回的實現是基於 Activity 的,能夠直接繼承 Activity 或者繼承本身應用實現的 BaseActivity 而後實現 SwipeBackLayout.SwipeListener 接口便可.java
public class SwipeBackActivity extends BaseActivity implements SwipeBackLayout.SwipeListener {
protected SwipeBackLayout layout;
private ArgbEvaluator argbEvaluator;
@SuppressLint("InflateParams")
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
layout = (SwipeBackLayout) LayoutInflater.from(this).inflate(
R.layout.swipeback_base, null);
layout.attachToActivity(this);
argbEvaluator = new ArgbEvaluator();
layout.addSwipeListener(this);
if (Build.VERSION.SDK_INT >= 23) {
currentStatusColor = getResources().getColor(R.color.colorPrimaryDark, null);
} else {
currentStatusColor = getResources().getColor(R.color.colorPrimaryDark);
}
}
// 提供給子類設置 ViewPager 的接口,用於 SwipeLayout 中處理滑動衝突
public void addViewPager(ViewPager pager) {
layout.addViewPager(pager);
}
}複製代碼
須要側滑返回的 Activity 繼承 SwipeBackActivity 便可實現側滑返回的功能了,可是側滑過程當中返回界面會被默認的窗口背景顏色覆蓋,所以咱們須要把實現側滑返回的界面的 theme 作一些小小的優化,將背景設置爲透明狀態,並設置進入和退出的動畫。
style 中的屬性設置android
<!--全屏加透明-->
<style name="TranslucentFullScreenTheme" parent="AppTheme"> <item name="android:windowBackground">@android:color/transparent</item> <item name="android:colorBackgroundCacheHint">@null</item> <item name="android:windowIsTranslucent">true</item> <!--<item name="android:windowAnimationStyle">@android:style/Animation</item>--> <item name="android:windowAnimationStyle">@style/AnimationActivity</item> </style>
<!--動畫設置-->
<style name="AnimationActivity" mce_bogus="1" parent="@android:style/Animation.Activity"> <item name="android:activityOpenEnterAnimation">@anim/base_slide_right_in</item> <item name="android:activityOpenExitAnimation">@anim/base_slide_right_out</item> <item name="android:activityCloseEnterAnimation">@anim/base_slide_right_in</item> <item name="android:activityCloseExitAnimation">@anim/base_slide_right_out</item> </style>複製代碼
界面進入動畫git
<!--base_slide_right_in-->
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<translate android:duration="300" android:fromXDelta="100.0%" android:interpolator="@android:anim/decelerate_interpolator" android:toXDelta="0.0%" />
</set>複製代碼
界面退出動畫github
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<translate android:duration="300" android:fromXDelta="100.0%" android:interpolator="@android:anim/decelerate_interpolator" android:toXDelta="0.0%" />
</set>複製代碼
而後在 manifest 文件中將繼承 SwipeBackActivity 的 Activity 的 theme 設置爲 TranslucentFullScreenTheme 便可解決滑動過程當中背景覆蓋問題。ide
要明白側滑返回的原理咱們得先明白 Android Activity 界面的視圖層級關係:工具
經過 SDK 自帶的視圖分析工具 Hierarchy View 咱們能夠看到視圖的以下分佈:佈局
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();
//獲取到 DecorView 對象
ViewGroup decor = (ViewGroup) activity.getWindow().getDecorView();
Log.i("decorChildCount", decor.getChildCount() + "");
ViewGroup decorChild = (ViewGroup) decor.getChildAt(0);
Log.i("decorChild", decorChild.toString());
//重置背景色資源
decorChild.setBackgroundResource(background);
//decorView 中將子佈局移除
decor.removeView(decorChild);
//SwipeBackLayout 添加從decorView中移除佈局
addView(decorChild);
//將ContentView設置爲decorChild的父佈局即添加進來的SwipeBackLayout
setContentView(decorChild);
//將SwipeBackLayout添加進DecorView
decor.addView(this);
}複製代碼
從中我添加的註釋不難看出,實現替換的流程:post
在 SwipeBackLayout 中經過重寫 onInterceptTouchEvent(MotionEvent ev) 方法和 onTouchEvent(MotionEvent ev) 方法來實現側滑返回事件的處理及對 ViewPager 滑動的兼容的。優化
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//處理ViewPager衝突問題
ViewPager mViewPager = getTouchViewPager(mViewPagers, ev);
//當無觸摸ViewPager或者該ViewPager未滑動到最左則不對滑動時間進行攔截
if (mViewPager != null && mViewPager.getCurrentItem() != 0) {
return super.onInterceptTouchEvent(ev);
}
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = tempX = (int) ev.getRawX();
downY = (int) ev.getRawY();
canSwipe = downX <= viewWidth / 2;
if (!canSwipe) {
return super.onInterceptTouchEvent(ev);
}
break;
case MotionEvent.ACTION_MOVE:
if (!canSwipe) {
return super.onInterceptTouchEvent(ev);
}
int moveX = (int) ev.getRawX();
// 知足此條件屏蔽SildingFinishLayout裏面子類的touch事件
if (moveX - downX > mTouchSlop
&& Math.abs((int) ev.getRawY() - downY) < mTouchSlop) {
return true;
}
break;
}
return super.onInterceptTouchEvent(ev);
}複製代碼
在手指按下的時候相較於 onTouchEvent() 方法 onInterceptTouchEvent() 方法會先執行,在此方法中先判斷當前觸摸是否爲 ViewPager,是 ViewPager 則判斷是否滑動到了 ViewPager 的最左側。若是觸摸的 ViewPager 且未滑動到最左側則不對事件進行攔截交給 ViewPager 處理觸摸事件,不然觸摸位置進行判斷,在有效區域內則記錄觸摸開始點,不然按系統默認方式處理。在移動事件中會根據按下事件的判斷結果決定是否按默認方式處理,當須要處理側滑時會再次判斷若是 X 方向的滑動大於最小有效滑動距離 Y方向滑動距離小於最小有效滑動距離則這次事件將會被 SwipeBackLayout 所消費,將進入 SwipeBackLayout 的 onTouchEvent() 方法中的處理邏輯。
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
if (!canSwipe) {
return super.onInterceptTouchEvent(event);
}
int moveX = (int) event.getRawX();
int deltaX = tempX - moveX;
tempX = moveX;
if (moveX - downX > mTouchSlop
&& Math.abs((int) event.getRawY() - downY) < mTouchSlop) {
isSilding = true;
}
if (moveX - downX >= 0 && isSilding) {
//deltaX 爲單次移動的距離向右滑爲負數
// TODO: 2017/6/22 實現 y 方向的移動,即向右任意方向滑出界面
mContentView.scrollBy(deltaX, 0);
}
break;
case MotionEvent.ACTION_UP:
if (!canSwipe) {
return super.onInterceptTouchEvent(event);
}
isSilding = false;
if (mContentView.getScrollX() <= -viewWidth / 4) {
isFinish = true;
scrollRight();
} else {
scrollOrigin();
isFinish = false;
}
break;
}
return true;
}複製代碼
一樣此方法中也會根據 onInterceptTouchEvent() 中的 DOWN 事件的斷定結果 canSwipe 來決定是否按默認方式消費事件,MOVE 事件中若是知足側滑條件則會調用 scrollBy() 將 mContentView 按滑動方向進行移動,而此處的 mContentView 便是 SwipeBackLayout 自身,所以整個顯示的界面會被按照滑動方向移動。當手指擡起時若是滑動距離超過 1/4 界面寬度(能夠按本身需求調整),則視爲側滑返回完成,讓 Scroller 自動完成剩餘距離的滑動,不然讓 Scroller 恢復到滑動起始位置
/** * 滾動出界面 */
private void scrollRight() {
final int delta = (viewWidth + mContentView.getScrollX());
// 調用startScroll方法來設置一些滾動的參數,咱們在computeScroll()方法中調用scrollTo來滾動item
mScroller.startScroll(mContentView.getScrollX(), 0, -delta + 1, 0,
Math.abs(delta));
postInvalidate();
}
/** * 滾動到起始位置 */
private void scrollOrigin() {
int delta = mContentView.getScrollX();
// 調用startScroll方法來設置一些滾動的參數,咱們在computeScroll()方法中調用scrollTo來滾動item
mScroller.startScroll(mContentView.getScrollX(), 0, -delta, 0,
Math.abs(delta));
postInvalidate();
}
/** * 具體執行 Scroller 中的滾動及將滑動距離傳遞給外部接口 */
@Override
public void computeScroll() {
Log.i("computeScroll","computeScroll");
if (mSwipeListener != null) {
double scrollx = Math.abs(mContentView.getScrollX());
double offset = scrollx / viewWidth;
if (offset > 0.9) {
offset = 1d;
}
mSwipeListener.swipeValue(offset);
}
if (mScroller.computeScrollOffset()) {
Log.i("computeScroll","mScroller");
mContentView.scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
if (mScroller.isFinished() && isFinish) {
mActivity.finish();
}
}
}複製代碼
以上就是簡化後的側滑返回的基本使用和原理的簡單分析,完整代碼能夠參考 PandaEye歡迎 Star。文章一遍過爲反覆檢查若有不妥之處歡迎你們踊躍交流。