Android 向右滑動銷燬(finish)Activity, 隨着手勢的滑動而滑動的效果

轉載請註明本文出自xiaanming的博客(http://blog.csdn.net/xiaanming/article/details/20934541),請尊重他人的辛勤勞動成果,謝謝!

今天給大家帶來一個向右滑動銷燬Activity的效果,Activtiy隨着手指的移動而移動,該效果在Android應用中還是比較少見的,在iOS中就比較常見了,例如“網易新聞” ,"美食傑" , "淘寶"等應用採用此效果,而Android應用中“知乎”採用的也是這種滑動切換Activity的效果, 不過我發現“淘寶”並沒有隨着手勢的移動而移動,只是捕捉到滑動手勢,然後產生平滑切換界面的動畫效果,這個在Android中還是很好實現的,  網上很多滑動切換Activity的Demo貌似都是這種效果的吧,如果要實現類似“網易新聞”的隨手勢的滑動而滑動,似乎就要複雜一些了,我之前在IOS中看到"網易新聞"的這種效果就很感興趣,然後羣裏也有朋友問我怎麼實現類似“知乎”這個應用的滑動切換的效果,我也特意去下了一個“知乎”,在之前的實現中我遇到了一些瓶頸,沒有實現出來就擱置了在那裏,今天無意中看到給Activity設置透明的背景,於是乎我恍然大悟,真是靈感來源於瞬間,不能強求啊,然後自己就將此效果實現了出來,給大家分享一下,希望給有此需求的你一點點幫助。

不知道大家對Scroller這個類以及View的scrollBy() 和scrollTo()的使用熟悉不?我之前介紹了Scroller類的滑動實現原理Android 帶你從源碼的角度解析Scroller的滾動實現原理,在那裏面也介紹了scrollBy() 和scrollTo()方法,不明白的同學可以去看看,這對實現此效果有很大的幫助,瞭解scrollBy() 和scrollTo()的朋友應該知道,如果想對某個View(例如Button)就行滾動,我們直接調用該View(Button)的scrollBy()方法,並不是該View(Button)進行滾動,而是該View裏面的內容(Button上面的文字)進行滾動,所以我們假如要讓View整體滾動就需要對其View的父佈局調用scrollBy()方法,回到這篇文章來,假如我們想要對一個Activity進行滾動,我們就需求對這個Activity佈局文件的頂層佈局的父佈局進行滾動

例如下面的XML佈局文件

[html] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:gravity="center"  
  6.     android:orientation="vertical" >  
  7.   
  8.   
  9. </LinearLayout>  
如果我們對LinearLayout進行滾動,並不能實現我們想要的效果,而只能對LinearLayout裏面的內容或者說是子View進行滾動,所以我們需要獲取利用LinearLayout的getParent()方法獲取父佈局,其實Android系統會對我們的佈局文件的最外層套一個FrameLayout,所以我們其實就是對FrameLayout進行滾動就行了

瞭解了實現的原理之後,我們就來編寫代碼吧,首先新建一個android工程,取名SildingFinish

由於我們的需求可能不是在一個界面提供這個滑動切換的效果,所以我們應該將這部分滑動的邏輯抽取出來,我這裏就他寫成了一個擴展RelativeLayout的自定義佈局SildingFinishLayout,首先我們看其代碼

[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. package com.example.view;  
  2.   
  3. import android.content.Context;  
  4. import android.util.AttributeSet;  
  5. import android.view.MotionEvent;  
  6. import android.view.View;  
  7. import android.view.View.OnTouchListener;  
  8. import android.view.ViewConfiguration;  
  9. import android.view.ViewGroup;  
  10. import android.widget.AbsListView;  
  11. import android.widget.RelativeLayout;  
  12. import android.widget.ScrollView;  
  13. import android.widget.Scroller;  
  14.   
  15. /** 
  16.  * 自定義可以滑動的RelativeLayout, 類似於IOS的滑動刪除頁面效果,當我們要使用 
  17.  * 此功能的時候,需要將該Activity的頂層佈局設置爲SildingFinishLayout, 
  18.  * 然後需要調用setTouchView()方法來設置需要滑動的View 
  19.  *  
  20.  * @author xiaanming 
  21.  *  
  22.  * @blog http://blog.csdn.net/xiaanming 
  23.  *  
  24.  */  
  25. public class SildingFinishLayout extends RelativeLayout implements  
  26.         OnTouchListener {  
  27.     /** 
  28.      * SildingFinishLayout佈局的父佈局 
  29.      */  
  30.     private ViewGroup mParentView;  
  31.     /** 
  32.      * 處理滑動邏輯的View 
  33.      */  
  34.     private View touchView;  
  35.     /** 
  36.      * 滑動的最小距離 
  37.      */  
  38.     private int mTouchSlop;  
  39.     /** 
  40.      * 按下點的X座標 
  41.      */  
  42.     private int downX;  
  43.     /** 
  44.      * 按下點的Y座標 
  45.      */  
  46.     private int downY;  
  47.     /** 
  48.      * 臨時存儲X座標 
  49.      */  
  50.     private int tempX;  
  51.     /** 
  52.      * 滑動類 
  53.      */  
  54.     private Scroller mScroller;  
  55.     /** 
  56.      * SildingFinishLayout的寬度 
  57.      */  
  58.     private int viewWidth;  
  59.     /** 
  60.      * 記錄是否正在滑動 
  61.      */  
  62.     private boolean isSilding;  
  63.       
  64.     private OnSildingFinishListener onSildingFinishListener;  
  65.     private boolean isFinish;  
  66.       
  67.   
  68.     public SildingFinishLayout(Context context, AttributeSet attrs) {  
  69.         this(context, attrs, 0);  
  70.     }  
  71.   
  72.     public SildingFinishLayout(Context context, AttributeSet attrs, int defStyle) {  
  73.         super(context, attrs, defStyle);  
  74.   
  75.         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();  
  76.         mScroller = new Scroller(context);  
  77.     }  
  78.   
  79.     @Override  
  80.     protected void onLayout(boolean changed, int l, int t, int r, int b) {  
  81.         super.onLayout(changed, l, t, r, b);  
  82.         if (changed) {  
  83.             // 獲取SildingFinishLayout所在佈局的父佈局  
  84.             mParentView = (ViewGroup) this.getParent();  
  85.             viewWidth = this.getWidth();  
  86.         }  
  87.     }  
  88.   
  89.     /** 
  90.      * 設置OnSildingFinishListener, 在onSildingFinish()方法中finish Activity 
  91.      *  
  92.      * @param onSildingFinishListener 
  93.      */  
  94.     public void setOnSildingFinishListener(  
  95.             OnSildingFinishListener onSildingFinishListener) {  
  96.         this.onSildingFinishListener = onSildingFinishListener;  
  97.     }  
  98.   
  99.     /** 
  100.      * 設置Touch的View 
  101.      *  
  102.      * @param touchView 
  103.      */  
  104.     public void setTouchView(View touchView) {  
  105.         this.touchView = touchView;  
  106.         touchView.setOnTouchListener(this);  
  107.     }  
  108.   
  109.     public View getTouchView() {  
  110.         return touchView;  
  111.     }  
  112.   
  113.     /** 
  114.      * 滾動出界面 
  115.      */  
  116.     private void scrollRight() {  
  117.         final int delta = (viewWidth + mParentView.getScrollX());  
  118.         // 調用startScroll方法來設置一些滾動的參數,我們在computeScroll()方法中調用scrollTo來滾動item  
  119.         mScroller.startScroll(mParentView.getScrollX(), 0, -delta + 10,  
  120.                 Math.abs(delta));  
  121.         postInvalidate();  
  122.     }  
  123.   
  124.     /** 
  125.      * 滾動到起始位置 
  126.      */  
  127.     private void scrollOrigin() {  
  128.         int delta = mParentView.getScrollX();  
  129.         mScroller.startScroll(mParentView.getScrollX(), 0, -delta, 0,  
  130.                 Math.abs(delta));  
  131.         postInvalidate();  
  132.     }  
  133.   
  134.     /** 
  135.      * touch的View是否是AbsListView, 例如ListView, GridView等其子類 
  136.      *  
  137.      * @return 
  138.      */  
  139.     private boolean isTouchOnAbsListView() {  
  140.         return touchView instanceof AbsListView ? true : false;  
  141.     }  
  142.   
  143.     /** 
  144.      * touch的view是否是ScrollView或者其子類 
  145.      *  
  146.      * @return 
  147.      */  
  148.     private boolean isTouchOnScrollView() {  
  149.         return touchView instanceof ScrollView ? true : false;  
  150.     }  
  151.   
  152.     @Override  
  153.     public boolean onTouch(View v, MotionEvent event) {  
  154.         switch (event.getAction()) {  
  155.         case MotionEvent.ACTION_DOWN:  
  156.             downX = tempX = (int) event.getRawX();  
  157.             downY = (int) event.getRawY();  
  158.             break;  
  159.         case MotionEvent.ACTION_MOVE:  
  160.             int moveX = (int) event.getRawX();  
  161.             int deltaX = tempX - moveX;  
  162.             tempX = moveX;  
  163.             if (Math.abs(moveX - downX) > mTouchSlop  
  164.                     && Math.abs((int) event.getRawY() - downY) < mTouchSlop) {  
  165.                 isSilding = true;  
  166.   
  167.                 // 若touchView是AbsListView,  
  168.                 // 則當手指滑動,取消item的點擊事件,不然我們滑動也伴隨着item點擊事件的發生  
  169.                 if (isTouchOnAbsListView()) {  
  170.                     MotionEvent cancelEvent = MotionEvent.obtain(event);  
  171.                     cancelEvent  
  172.                             .setAction(MotionEvent.ACTION_CANCEL  
  173.                                     | (event.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));  
  174.                     v.onTouchEvent(cancelEvent);  
  175.                 }  
  176.   
  177.             }  
  178.   
  179.             if (moveX - downX >= 0 && isSilding) {  
  180.                 mParentView.scrollBy(deltaX, 0);  
  181.   
  182.                 // 屏蔽在滑動過程中ListView ScrollView等自己的滑動事件  
  183.                 if (isTouchOnScrollView() || isTouchOnAbsListView()) {  
  184.                     return true;  
  185.                 }  
  186.             }  
  187.             break;  
  188.         case MotionEvent.ACTION_UP:  
  189.             isSilding = false;  
  190.             if (mParentView.getScrollX() <= -viewWidth / 2) {  
  191.                 isFinish = true;  
  192.                 scrollRight();  
  193.             } else {  
  194.                 scrollOrigin();  
  195.                 isFinish = false;  
  196.             }  
  197.             break;  
  198.         }  
  199.   
  200.         // 假如touch的view是AbsListView或者ScrollView 我們處理完上面自己的邏輯之後  
  201.         // 再交給AbsListView, ScrollView自己處理其自己的邏輯  
  202.         if (isTouchOnScrollView() || isTouchOnAbsListView()) {  
  203.             return v.onTouchEvent(event);  
  204.         }  
  205.   
  206.         // 其他的情況直接返回true  
  207.         return true;  
  208.     }  
  209.   
  210.     @Override  
  211.     public void computeScroll() {  
  212.         // 調用startScroll的時候scroller.computeScrollOffset()返回true,  
  213.         if (mScroller.computeScrollOffset()) {  
  214.             mParentView.scrollTo(mScroller.getCurrX(), mScroller.getCurrY());  
  215.             postInvalidate();  
  216.   
  217.             if (mScroller.isFinished()) {  
  218.   
  219.                 if (onSildingFinishListener != null && isFinish) {  
  220.                     onSildingFinishListener.onSildingFinish();  
  221.                 }  
  222.             }  
  223.         }  
  224.     }  
  225.       
  226.   
  227.     public interface OnSildingFinishListener {  
  228.         public void onSildingFinish();  
  229.     }  
  230.   
  231. }  

我們在onLayout()方法中利用getParent()方法獲取該佈局的父佈局和獲取其控件的寬度,主要是爲之後的實現做準備工作。

我們的滑動邏輯主要是利用View的scrollBy() 方法, scrollTo()方法和Scroller類來實現的,當手指拖動視圖的時候,我們監聽手指在屏幕上滑動的距離利用View的scrollBy() 方法使得View隨着手指的滑動而滑動,而當手指離開屏幕,我們在根據邏輯使用Scroller類startScroll()方法設置滑動的參數,然後再根據View的scrollTo進行滾動。

對於View的滑動,存在一些Touch事件消費的處理等問題,因此我們需要對View的整個Touch事件很熟悉 ,最主要的就是Activity裏面有一些ListView、 GridView、ScrollView等控件了, 假如我們Activity裏面存在ListView、GridView等控件的話,我們對Activity的最外層佈局進行滾動根本就無效果,因爲Touch事件被ListView、GridView等控件消費了,所以Activity的最外層佈局根本得不到Touch事件,也就實現不了Touch邏輯了,所以爲了解決此Touch事件問題我提供了setTouchView(View touchView) 方法,這個方法是將Touch事件動態的設置到到View上面,所以針對上面的問題,我們將OnTouchListener直接設置到ListView、GridView上面,這樣子就避免了Activity的最外層接受不到Touch事件的問題了


接下來看onTouch()方法

首先我們在ACTION_DOWN記錄按下點的X,Y座標

然後在ACTION_MOVE中判斷,如果我們在水平方向滑動的距離大於mTouchSlop並且在豎直方向滑動的距離小於mTouchSlop,表示Activity處於滑動狀態,我們判斷如果touchView是ListView、GridView或者其子類的時候,因爲我們手指在ListView、GridView上面,伴隨着item的點擊事件的發生,所以我們對touchView設置ACTION_CANCEL來取消item的點擊事件,然後對該佈局的父佈局調用scrollBy()進行滾動,並且如果TouchView是AbsListView或者ScrollView直接返回true,來取消AbsListView或者ScrollView本身的ACTION_MOVE事件,最直觀的感受就是我們在滑動Activity的時候,禁止AbsListView或者ScrollView的上下滑動

最後在ACTION_UP中判斷如果手指滑動的距離大於控件長度的二分之一,表示將Activity滑出界面,否則滑動到起始位置,我們利用Scroller類的startScroll()方法設置好開始位置,滑動距離和時間,然後調用postInvalidate()刷新界面,之後就到computeScroll()方法中,我們利用scrollTo()方法對該佈局的父佈局進行滾動,滾動結束之後,我們判斷界面是否滑出界面,如果是就調用OnSildingFinishListener接口的onSildingFinish()方法,所以只要在onSildingFinish()方法中finish界面就行了

整個滑動佈局的代碼就是這個樣子,接下來我們就來使用了,主界面Activity只有三個按鈕,分別跳轉到普通佈局的Activity,有ListView的Activity和有ScrollView的Activity中

[html] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:gravity="center"  
  6.     android:orientation="vertical" >  
  7.   
  8.     <Button  
  9.         android:id="@+id/normal_activity"  
  10.         android:layout_width="match_parent"  
  11.         android:layout_height="wrap_content"  
  12.         android:text="普通的Activity" />  
  13.   
  14.     <Button  
  15.         android:id="@+id/absListview_activity"  
  16.         android:layout_width="match_parent"  
  17.         android:layout_height="wrap_content"  
  18.         android:text="有AbsListView的Activity" />  
  19.   
  20.     <Button  
  21.         android:id="@+id/scrollview_activity"  
  22.         android:layout_width="match_parent"  
  23.         android:layout_height="wrap_content"  
  24.         android:text="有ScrollView的Activity" />  
  25.   
  26. </LinearLayout>  

然後就是MainActivity的代碼,根據ID實例化Button,然後爲Button設置OnClickListener事件,不同的按鈕跳轉到不同的Activity,然後設置從右向左滑動的動畫,重寫onBackPressed()方法,當我們按下手機物理鍵盤的返回鍵,添加從左向右滑出的動畫

[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. package com.example.slidingfinish;  
  2.   
  3. import android.app.Activity;  
  4. import android.content.Intent;  
  5. import android.os.Bundle;  
  6. import android.view.View;  
  7. import android.view.View.OnClickListener;  
  8. import android.view.Window;  
  9. import android.widget.Button;  
  10.   
  11. import com.example.slidingfinish.R;  
  12.   
  13. public class MainActivity extends Activity implements OnClickListener {  
  14.   
  15.     @Override  
  16.     protected void onCreate(Bundle savedInstanceState) {  
  17.         requestWindowFeature(Window.FEATURE_NO_TITLE);  
  18.         super.onCreate(savedInstanceState);  
  19.         setContentView(R.layout.activity_main);  
  20.   
  21.         Button mButtonNormal = (Button) findViewById(R.id.normal_activity);  
  22.         mButtonNormal.setOnClickListener(this);  
  23.   
  24.         Button mButtonAbs = (Button) findViewById(R.id.absListview_activity);  
  25.         mButtonAbs.setOnClickListener(this);  
  26.   
  27.         Button mButtonScroll = (Button) findViewById(R.id.scrollview_activity);  
  28.         mButtonScroll.setOnClickListener(this);  
  29.   
  30.     }  
  31.   
  32.     @Override  
  33.     public void onClick(View v) {  
  34.         Intent mIntent = null;  
  35.         switch (v.getId()) {  
  36.         case R.id.normal_activity:  
  37.             mIntent = new Intent(MainActivity.this, NormalActivity.class);  
  38.             break;  
  39.         case R.id.absListview_activity:  
  40.             mIntent = new Intent(MainActivity.this, AbsActivity.class);  
  41.             break;  
  42.         case R.id.scrollview_activity:  
  43.             mIntent = new Intent(MainActivity.this, ScrollActivity.class);  
  44.             break;  
  45.         }  
  46.   
  47.         startActivity(mIntent);  
  48.         overridePendingTransition(R.anim.base_slide_right_in, R.anim.base_slide_remain);  
  49.     }  
  50.       
  51.     //Press the back button in mobile phone  
  52.     @Override  
  53.     public void onBackPressed() {  
  54.         super.onBackPressed();  
  55.         overridePendingTransition(0, R.anim.base_slide_right_out);  
  56.     }  
  57.   
  58. }  
在這裏我之貼出含有ListView的Activity的代碼,先看佈局,我們自定義滑動佈局SildingFinishLayout應該放在XML的最頂層

[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <com.example.view.SildingFinishLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     xmlns:tools="http://schemas.android.com/tools"  
  4.     android:id="@+id/sildingFinishLayout"  
  5.     android:layout_width="match_parent"  
  6.     android:layout_height="match_parent"  
  7.     android:background="#556677" >  
  8.   
  9.     <ListView  
  10.         android:id="@+id/listView"  
  11.         android:cacheColorHint="@android:color/transparent"  
  12.         android:layout_width="match_parent"  
  13.         android:layout_height="match_parent" >  
  14.     </ListView>  
  15.       
  16.       
  17. </com.example.view.SildingFinishLayout>  


[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. package com.example.slidingfinish;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.List;  
  5.   
  6. import android.app.Activity;  
  7. import android.content.Intent;  
  8. import android.os.Bundle;  
  9. import android.view.View;  
  10. import android.view.Window;  
  11. import android.widget.AdapterView;  
  12. import android.widget.AdapterView.OnItemClickListener;  
  13. import android.widget.ArrayAdapter;  
  14. import android.widget.ListView;  
  15.   
  16. import com.example.slidingfinish.R;  
  17. import com.example.view.SildingFinishLayout;  
  18. import com.example.view.SildingFinishLayout.OnSildingFinishListener;  
  19.   
  20. public class AbsActivity extends Activity {  
  21.     private List<String> list = new ArrayList<String>();  
  22.   
  23.     @Override  
  24.     protected void onCreate(Bundle savedInstanceState) {  
  25.         requestWindowFeature(Window.FEATURE_NO_TITLE);  
  26.         super.onCreate(savedInstanceState);  
  27.         setContentView(R.layout.activity_abslistview);  
  28.   
  29.         for (int i = 0; i <= 30; i++) {  
  30.             list.add("測試數據" + i);  
  31.         }  
  32.   
  33.         ListView mListView = (ListView) findViewById(R.id.listView);  
  34.         ArrayAdapter<String> adapter = new ArrayAdapter<String>(  
  35.                 AbsActivity.this, android.R.layout.simple_list_item_1, list);  
  36.         mListView.setAdapter(adapter);  
  37.   
  38.         SildingFinishLayout mSildingFinishLayout = (SildingFinishLayout) findViewById(R.id.sildingFinishLayout);  
  39.         mSildingFinishLayout  
  40.                 .setOnSildingFinishListener(new OnSildingFinishListener() {  
  41.   
  42.                     @Override  
  43.                     public void onSildingFinish() {  
  44.                         AbsActivity.this.finish();  
  45.                     }  
  46.                 });  
  47.   
  48.         // touchView要設置到ListView上面  
  49.         mSildingFinishLayout.setTouchView(mListView);  
  50.   
  51.         mListView.setOnItemClickListener(new OnItemClickListener() {  
  52.   
  53.             @Override  
  54.             public void onItemClick(AdapterView<?> parent, View view,  
  55.                     int position, long id) {  
  56.   
  57.                 startActivity(new Intent(AbsActivity.this, NormalActivity.class));  
  58.                 overridePendingTransition(R.anim.base_slide_right_in,  
  59.                         R.anim.base_slide_remain);  
  60.             }  
  61.         });  
  62.     }  
  63.   
  64.     // Press the back button in mobile phone  
  65.     @Override  
  66.     public void onBackPressed() {  
  67.         super.onBackPressed();  
  68.         overridePendingTransition(0, R.anim.base_slide_right_out);  
  69.     }  
  70.   
  71. }  
利用ID找到SildingFinishLayout實例,利用setTouchView()方法設置touchView到ListView上面,然後調用setOnSildingFinishListener()設置OnSildingFinishListener,在onSildingFinish()中finish界面就可以啦。

在運行項目之前還有一個很重要的操作,也是之前我被卡到的問題,就是我們需要對Activity設置爲透明,即設置主題android:theme="@android:style/Theme.Translucent" 

[html] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. <activity  
  2.            android:name=".AbsActivity"  
  3.            android:theme="@android:style/Theme.Translucent" >  
  4.        </activity>  
  5.        <activity  
  6.            android:name=".NormalActivity"  
  7.            android:theme="@android:style/Theme.Translucent" >  
  8.        </activity>  
  9.        <activity  
  10.            android:name=".ScrollActivity"  
  11.            android:theme="@android:style/Theme.Translucent" >  
  12.        </activity>  
好了,現在我們可以運行項目看看效果啦

 



正是我們想要的效果,如果想要加入滑動切換界面的效果只需要三步就行了,首先將Activity佈局的最外層修改爲SildingFinishLayout,然後在Activity裏面調用setTouchView()方法設置touchView,設置OnSildingFinishListener監聽在onSildingFinish()方法中finish界面,最後設置Activity的背景爲透明(不是設置Activity佈局文件的最頂層佈局背景顏色透明,這點要區分一下)是不是很方便呢?好了,今天的講解到這裏就結束了,有疑問的朋友請在下面留言,有興趣的朋友可以下載源碼看看!

項目源碼,點擊下載


博主後面又改了下代碼,寫了一個簡潔加強版的demo,代碼更加簡潔了,也對裏面是ViewPager做了處理,並且加了邊界的陰影效果,也不必要調用setTouchView()來設置那個View響應滾動,對SildingFinishLayout裏面的子View是什麼沒有任何關係,如果大家想使用此效果的話,建議下載如下版本


加強簡潔版,點擊下載


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章