Android實(shí)現(xiàn)微信側(cè)滑關(guān)閉頁面效果
最近手機(jī)升級(jí)了5.0系統(tǒng)后,突然間發(fā)現(xiàn)微信竟然有IOS一樣的側(cè)滑關(guān)閉當(dāng)前頁面的效果,就想把這種效果也加進(jìn)自己的項(xiàng)目里面。本著不重復(fù)造輪子的原則,就在網(wǎng)上百度了很久,發(fā)現(xiàn)大多數(shù)人都是采用自定義View來實(shí)現(xiàn),但是對(duì)于我這種已經(jīng)基本完成的項(xiàng)目來說,如果全部的Activity再重新使用自定義的View無疑是一種可怕的噩夢(mèng)。
因此,我這里實(shí)現(xiàn)了另外一種不需要自定義View也能實(shí)現(xiàn)的方法,其子類只要繼承于它,便能擁有其側(cè)滑滑動(dòng)的功能。
隨便說一句,此方法僅對(duì)5.0以上的手機(jī)有效(反正微信也是5.0上才能用),5.0以下的請(qǐng)無視?。?!
原理
在每個(gè)Activity里面都有一個(gè)底層的View,也就是所謂的rootView,當(dāng)我們加載一個(gè)xml布局時(shí),系統(tǒng)就會(huì)自動(dòng)給你生成這個(gè)rootView,由于它是一個(gè)View,那么也就意味著你可以通過一定的代碼隨意移動(dòng)這個(gè)根布局。
如下代碼所示,只要簡(jiǎn)單的幾行代碼便能實(shí)現(xiàn)布局的移動(dòng)。
public class SlideActivity extends AppCompatActivity { View mRootView; private GestureDetector mDetector; private int mWindowWidth; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_demo); mRootView = getWindow().getDecorView(); mRootView.setBackgroundColor(Color.BLUE); mDetector = new GestureDetector(this, new GestureListener()); mWindowWidth = getWindow().getWindowManager().getDefaultDisplay().getWidth(); } @Override public boolean onTouchEvent(MotionEvent event) { return mDetector.onTouchEvent(event); } /** * 手勢(shì)監(jiān)聽 */ private class GestureListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { if (e1 != null) { handlerCurrentActivityScroll(e2); } return super.onScroll(e1, e2, distanceX, distanceY); } /** * 處理當(dāng)前頁面滑動(dòng) */ private void handlerCurrentActivityScroll(MotionEvent e2) { mRootView.setTranslationX(e2.getX()); if (e2.getX() > mWindowWidth - 20) { finish(); } } } }
這是我們的效果
Activity聯(lián)動(dòng)
這差距還是很大的,最明顯的地方是,我們移動(dòng) 的時(shí)候,上一層Activity竟然沒有跟著聯(lián)動(dòng)。
解決這個(gè)問題的方法也簡(jiǎn)單,如圖所示,每當(dāng)啟動(dòng)一個(gè)Activity時(shí),系統(tǒng)都會(huì)把Activity放到一個(gè)棧里面,由于棧的工作原理可知,APP里面的Activity是一層覆蓋一層的,就如上圖所示。為此,每當(dāng)啟動(dòng)一個(gè)Activity時(shí),就可以把當(dāng)前的Actiivty存儲(chǔ)到一個(gè)List里面,這樣,我們就可以在當(dāng)前的Activity里面取出上一個(gè)Activity進(jìn)行操作。
因此,在進(jìn)入一個(gè)新的Activity的時(shí)候,在其onCreate方法里面把當(dāng)前的Activity加載到列表里,當(dāng)退出時(shí),在finish的重載方法里面,將當(dāng)前Activity從列表里面移除。
注意?。。≡诨瑒?dòng)的時(shí)候必須需要考慮到Activity里面有可能會(huì)有類似于ListView一類的滑動(dòng)控件,因此,我們必須對(duì)事件進(jìn)行分發(fā)控制。
代碼如下
/** * Created by yuyu on 2015/10/29. */ public class TestActivity extends AppCompatActivity { View mRootView; private GestureDetector mGestureDetector; private static List<TestActivity> mActivitys = new ArrayList<>(); /** * 移動(dòng)距離 */ private float mWindowWidth; private TestActivity mBeforeActivity; /** * 上一個(gè)Activity偏移量 */ private float mOffsetX; /** * 上一個(gè)頁面移出的位置 */ private float mOutsideWidth; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_demo); /** * 把當(dāng)前Activity加到列表里面 */ mActivitys.add(this); initScrollBack(); } /** * 初始化左滑退出功能 */ private void initScrollBack() { mWindowWidth = getWindowManager().getDefaultDisplay().getWidth(); mOutsideWidth = -mWindowWidth / 4; mOffsetX = mOutsideWidth; mGestureDetector = new GestureDetector(this, new GestureListener()); mRootView = getWindow().getDecorView(); mRootView.setBackgroundColor(Color.BLUE); } /** * 控制分發(fā)事件,在這里控制能能觸發(fā)拖動(dòng)的范圍 */ @Override public boolean dispatchTouchEvent(@NonNull MotionEvent ev) { if (ev.getX() < mWindowWidth / 10) { if (mActivitys.size() > 1) { mBeforeActivity = mActivitys.get(mActivitys.size() - 2); beforeActivityTranslationX(mOutsideWidth); } return onTouchEvent(ev); } return super.dispatchTouchEvent(ev); } @Override public void finish() { mActivitys.remove(this); if (mOffsetX < 0.0001 || mOffsetX > 0.0001) { beforeActivityTranslationX(0); } super.finish(); } public void onClick(View view) { Intent intent = new Intent(this, Activity5.class); startActivity(intent); } public View getRootView() { return mRootView; } /** * 控制上一個(gè)Activity移動(dòng) */ private void beforeActivityTranslationX(float translationX) { if (mBeforeActivity != null) { mBeforeActivity.getRootView().setTranslationX(translationX); } } @Override public boolean onTouchEvent(MotionEvent event) { return mGestureDetector.onTouchEvent(event); } /** * 手勢(shì)監(jiān)聽 */ private class GestureListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { if (e1 != null) { handlerCurrentActivityScroll(e2); handleBeforeActivityScroll(e2, distanceX); } return super.onScroll(e1, e2, distanceX, distanceY); } /** * 處理當(dāng)前頁面滑動(dòng) */ private void handlerCurrentActivityScroll(MotionEvent e2) { mRootView.setTranslationX(e2.getX()); if (e2.getX() > mWindowWidth - 20) { finish(); } } /** * 處理上一個(gè)頁面滑動(dòng) */ private void handleBeforeActivityScroll(MotionEvent e2, float distanceX) { if (mBeforeActivity != null) { mOffsetX = distanceX < 0 ? mOffsetX + Math.abs(distanceX) / 4 : mOffsetX - Math.abs(distanceX) / 4; if (mOffsetX > 0.0001) { mOffsetX = 0f; } mBeforeActivity.getRootView().setTranslationX(mOffsetX); } } } }
這是聯(lián)動(dòng)后的效果圖
現(xiàn)在算是有點(diǎn)效果了,但是和微信的差距還是很大,接下來我們便需要開始處理自動(dòng)滑動(dòng)了
自動(dòng)滑動(dòng)
這個(gè)就不需要多說了,這個(gè)主要就是利用屬性動(dòng)畫進(jìn)行移動(dòng)
以下是完整的代碼
public class SlideActivity extends AppCompatActivity { private static final String TAG = "SlideActivity"; private static List<SlideActivity> mActivitys = new ArrayList<>(); /** * 手勢(shì)監(jiān)聽 */ private GestureDetector mGestureDetector; private View mRootView; private boolean isScroll = false; /** * 移動(dòng)距離 */ private float mWindowWidth; private SlideActivity mBeforeActivity; /** * 上一個(gè)Activity偏移量 */ private float mOffsetX; /** * 上一個(gè)頁面移出的位置 */ private float mOutsideWidth; private boolean canScrollBack = true; private boolean canScroll = false; @Override protected void onCreate(Bundle savedInstanceState) { getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS); getWindow().setEnterTransition(new Slide(Gravity.RIGHT)); super.onCreate(savedInstanceState); /** * 把當(dāng)前Activity加到列表里面 */ mActivitys.add(this); initScrollBack(); } @Override public void startActivity(Intent intent) { startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this).toBundle()); } /** * 初始化左滑退出功能 */ private void initScrollBack() { mWindowWidth = getWindowManager().getDefaultDisplay().getWidth(); mOutsideWidth = -mWindowWidth / 4; mOffsetX = mOutsideWidth; mGestureDetector = new GestureDetector(this, new GestureListener()); mRootView = getWindow().getDecorView(); } /** * 控制上一個(gè)Activity移動(dòng) */ private void beforeActivityTranslationX(float translationX) { if (mBeforeActivity != null) { mBeforeActivity.getRootView().setTranslationX(translationX); } } /** * 設(shè)置是否能滑動(dòng) * * @param canScrollBack true 可以滑動(dòng) */ protected void setCanScrollBack(boolean canScrollBack) { this.canScrollBack = canScrollBack; } public View getRootView() { return mRootView; } @Override public void finish() { mActivitys.remove(this); if (mOffsetX < 0.0001 || mOffsetX > 0.0001) { beforeActivityTranslationX(0); } super.finish(); } /** * 控制分發(fā)事件 */ @Override public boolean dispatchTouchEvent(@NonNull MotionEvent ev) { if (canScrollBack && ev.getX() < mWindowWidth / 10) { if (mActivitys.size() > 1) { mBeforeActivity = mActivitys.get(mActivitys.size() - 2); beforeActivityTranslationX(mOutsideWidth); } canScroll = true; return onTouchEvent(ev); } return super.dispatchTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { if (canScrollBack && canScroll) { if (event.getAction() == MotionEvent.ACTION_UP && isScroll) { isScroll = false; canScroll = false; //退出當(dāng)前Activity if (event.getX() > mWindowWidth / 2) { if (mBeforeActivity != null) { ObjectAnimator.ofFloat(mBeforeActivity.getRootView(), "translationX", mOffsetX, 0).setDuration(500).start(); } ObjectAnimator moveIn = ObjectAnimator.ofFloat(mRootView, "translationX", event.getX(), mWindowWidth); moveIn.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); finish(); } }); moveIn.setDuration(500).start(); //反彈回來 } else if (event.getX() < mWindowWidth / 2) { ObjectAnimator.ofFloat(mRootView, "translationX", event.getX(), 0).setDuration(500).start(); if (mBeforeActivity != null) { ObjectAnimator.ofFloat(mBeforeActivity.getRootView(), "translationX", mOffsetX, mOutsideWidth).setDuration(500).start(); } mOffsetX = mOutsideWidth; } } else { mGestureDetector.onTouchEvent(event); } } return true; } /** * 手勢(shì)監(jiān)聽 */ private class GestureListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { if (e1 != null) { handlerCurrentActivityScroll(e2); handleBeforeActivityScroll(e2, distanceX); } return super.onScroll(e1, e2, distanceX, distanceY); } /** * 處理當(dāng)前頁面滑動(dòng) */ private void handlerCurrentActivityScroll(MotionEvent e2) { isScroll = true; mRootView.setTranslationX(e2.getX()); if (e2.getX() > mWindowWidth - 20) { finish(); } } /** * 處理上一個(gè)頁面滑動(dòng) */ private void handleBeforeActivityScroll(MotionEvent e2, float distanceX) { if (mBeforeActivity != null) { mOffsetX = distanceX < 0 ? mOffsetX + Math.abs(distanceX) / 4 : mOffsetX - Math.abs(distanceX) / 4; if (mOffsetX > 0.0001) { mOffsetX = 0f; } mBeforeActivity.getRootView().setTranslationX(mOffsetX); } } } }
這是我們最終的效果圖
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
android中使用SharedPreferences進(jìn)行數(shù)據(jù)存儲(chǔ)的操作方法
本篇文章介紹了,在android中使用SharedPreferences進(jìn)行數(shù)據(jù)存儲(chǔ)的操作方法。需要的朋友參考下2013-04-04Android時(shí)間日期拾取器學(xué)習(xí)使用(DatePicker、TimePicker)
這篇文章主要為大家詳細(xì)介紹了Android提供的DatePicker日期拾取器和TimePicker時(shí)間拾取器的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-02-02Android仿天貓橫向滑動(dòng)指示器功能的實(shí)現(xiàn)
這篇文章主要介紹了Android仿天貓橫向滑動(dòng)指示器,Android開發(fā)中會(huì)有很多很新奇的交互,比如天貓商城的首頁頭部的分類,使用的是GridLayoutManager+橫向指示器實(shí)現(xiàn)的,需要的朋友可以參考下2022-08-08Android自定義控件eBook實(shí)現(xiàn)翻書效果實(shí)例詳解
這篇文章主要介紹了Android自定義控件eBook實(shí)現(xiàn)翻書效果的方法,結(jié)合實(shí)例形式分析了Android自定義控件實(shí)現(xiàn)翻書效果的具體步驟與相關(guān)操作技巧,需要的朋友可以參考下2016-10-10Android P實(shí)現(xiàn)靜默安裝的方法示例(官方Demo)
這篇文章主要介紹了Android P實(shí)現(xiàn)靜默安裝,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02