Android 向右滑動銷毀(finish)Activity, 隨著手勢的滑動而滑動的效果
今天給大家帶來一個向右滑動銷毀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布局文件
- <LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
- xmlns:tools="https://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:gravity="center"
- android:orientation="vertical" >
- </LinearLayout>
了解了實現的原理之後,我們就來編寫代碼吧,首先新建一個android工程,取名SildingFinish
由於我們的需求可能不是在一個界麵提供這個滑動切換的效果,所以我們應該將這部分滑動的邏輯抽取出來,我這裏就他寫成了一個擴展RelativeLayout的自定義布局SildingFinishLayout,首先我們看其代碼
- package com.example.view;
- import android.content.Context;
- import android.util.AttributeSet;
- import android.view.MotionEvent;
- import android.view.View;
- import android.view.View.OnTouchListener;
- import android.view.ViewConfiguration;
- import android.view.ViewGroup;
- import android.widget.AbsListView;
- import android.widget.RelativeLayout;
- import android.widget.ScrollView;
- import android.widget.Scroller;
- /**
- * 自定義可以滑動的RelativeLayout, 類似於IOS的滑動刪除頁麵效果,當我們要使用
- * 此功能的時候,需要將該Activity的頂層布局設置為SildingFinishLayout,
- * 然後需要調用setTouchView()方法來設置需要滑動的View
- *
- * @author xiaanming
- *
- * @blog https://blog.csdn.net/xiaanming
- *
- */
- public class SildingFinishLayout extends RelativeLayout implements
- OnTouchListener {
- /**
- * SildingFinishLayout布局的父布局
- */
- private ViewGroup mParentView;
- /**
- * 處理滑動邏輯的View
- */
- private View touchView;
- /**
- * 滑動的最小距離
- */
- private int mTouchSlop;
- /**
- * 按下點的X坐標
- */
- private int downX;
- /**
- * 按下點的Y坐標
- */
- private int downY;
- /**
- * 臨時存儲X坐標
- */
- private int tempX;
- /**
- * 滑動類
- */
- private Scroller mScroller;
- /**
- * SildingFinishLayout的寬度
- */
- private int viewWidth;
- /**
- * 記錄是否正在滑動
- */
- private boolean isSilding;
- private OnSildingFinishListener onSildingFinishListener;
- private boolean isFinish;
- public SildingFinishLayout(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
- public SildingFinishLayout(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
- mScroller = new Scroller(context);
- }
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- super.onLayout(changed, l, t, r, b);
- if (changed) {
- // 獲取SildingFinishLayout所在布局的父布局
- mParentView = (ViewGroup) this.getParent();
- viewWidth = this.getWidth();
- }
- }
- /**
- * 設置OnSildingFinishListener, 在onSildingFinish()方法中finish Activity
- *
- * @param onSildingFinishListener
- */
- public void setOnSildingFinishListener(
- OnSildingFinishListener onSildingFinishListener) {
- this.onSildingFinishListener = onSildingFinishListener;
- }
- /**
- * 設置Touch的View
- *
- * @param touchView
- */
- public void setTouchView(View touchView) {
- this.touchView = touchView;
- touchView.setOnTouchListener(this);
- }
- public View getTouchView() {
- return touchView;
- }
- /**
- * 滾動出界麵
- */
- private void scrollRight() {
- final int delta = (viewWidth + mParentView.getScrollX());
- // 調用startScroll方法來設置一些滾動的參數,我們在computeScroll()方法中調用scrollTo來滾動item
- mScroller.startScroll(mParentView.getScrollX(), 0, -delta + 1, 0,
- Math.abs(delta));
- postInvalidate();
- }
- /**
- * 滾動到起始位置
- */
- private void scrollOrigin() {
- int delta = mParentView.getScrollX();
- mScroller.startScroll(mParentView.getScrollX(), 0, -delta, 0,
- Math.abs(delta));
- postInvalidate();
- }
- /**
- * touch的View是否是AbsListView, 例如ListView, GridView等其子類
- *
- * @return
- */
- private boolean isTouchOnAbsListView() {
- return touchView instanceof AbsListView ? true : false;
- }
- /**
- * touch的view是否是ScrollView或者其子類
- *
- * @return
- */
- private boolean isTouchOnScrollView() {
- return touchView instanceof ScrollView ? true : false;
- }
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- downX = tempX = (int) event.getRawX();
- downY = (int) event.getRawY();
- break;
- case MotionEvent.ACTION_MOVE:
- int moveX = (int) event.getRawX();
- int deltaX = tempX - moveX;
- tempX = moveX;
- if (Math.abs(moveX - downX) > mTouchSlop
- && Math.abs((int) event.getRawY() - downY) < mTouchSlop) {
- isSilding = true;
- // 若touchView是AbsListView,
- // 則當手指滑動,取消item的點擊事件,不然我們滑動也伴隨著item點擊事件的發生
- if (isTouchOnAbsListView()) {
- MotionEvent cancelEvent = MotionEvent.obtain(event);
- cancelEvent
- .setAction(MotionEvent.ACTION_CANCEL
- | (event.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
- v.onTouchEvent(cancelEvent);
- }
- }
- if (moveX - downX >= 0 && isSilding) {
- mParentView.scrollBy(deltaX, 0);
- // 屏蔽在滑動過程中ListView ScrollView等自己的滑動事件
- if (isTouchOnScrollView() || isTouchOnAbsListView()) {
- return true;
- }
- }
- break;
- case MotionEvent.ACTION_UP:
- isSilding = false;
- if (mParentView.getScrollX() <= -viewWidth / 2) {
- isFinish = true;
- scrollRight();
- } else {
- scrollOrigin();
- isFinish = false;
- }
- break;
- }
- // 假如touch的view是AbsListView或者ScrollView 我們處理完上麵自己的邏輯之後
- // 再交給AbsListView, ScrollView自己處理其自己的邏輯
- if (isTouchOnScrollView() || isTouchOnAbsListView()) {
- return v.onTouchEvent(event);
- }
- // 其他的情況直接返回true
- return true;
- }
- @Override
- public void computeScroll() {
- // 調用startScroll的時候scroller.computeScrollOffset()返回true,
- if (mScroller.computeScrollOffset()) {
- mParentView.scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
- postInvalidate();
- if (mScroller.isFinished()) {
- if (onSildingFinishListener != null && isFinish) {
- onSildingFinishListener.onSildingFinish();
- }
- }
- }
- }
- public interface OnSildingFinishListener {
- public void onSildingFinish();
- }
- }
我們在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中
- <LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
- xmlns:tools="https://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:gravity="center"
- android:orientation="vertical" >
- <Button
- android:id="@+id/normal_activity"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="普通的Activity" />
- <Button
- android:id="@+id/absListview_activity"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="有AbsListView的Activity" />
- <Button
- android:id="@+id/scrollview_activity"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="有ScrollView的Activity" />
- </LinearLayout>
然後就是MainActivity的代碼,根據ID實例化Button,然後為Button設置OnClickListener事件,不同的按鈕跳轉到不同的Activity,然後設置從右向左滑動的動畫,重寫onBackPressed()方法,當我們按下手機物理鍵盤的返回鍵,添加從左向右滑出的動畫
- package com.example.slidingfinish;
- import android.app.Activity;
- import android.content.Intent;
- import android.os.Bundle;
- import android.view.View;
- import android.view.View.OnClickListener;
- import android.view.Window;
- import android.widget.Button;
- import com.example.slidingfinish.R;
- public class MainActivity extends Activity implements OnClickListener {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- requestWindowFeature(Window.FEATURE_NO_TITLE);
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- Button mButtonNormal = (Button) findViewById(R.id.normal_activity);
- mButtonNormal.setOnClickListener(this);
- Button mButtonAbs = (Button) findViewById(R.id.absListview_activity);
- mButtonAbs.setOnClickListener(this);
- Button mButtonScroll = (Button) findViewById(R.id.scrollview_activity);
- mButtonScroll.setOnClickListener(this);
- }
- @Override
- public void onClick(View v) {
- Intent mIntent = null;
- switch (v.getId()) {
- case R.id.normal_activity:
- mIntent = new Intent(MainActivity.this, NormalActivity.class);
- break;
- case R.id.absListview_activity:
- mIntent = new Intent(MainActivity.this, AbsActivity.class);
- break;
- case R.id.scrollview_activity:
- mIntent = new Intent(MainActivity.this, ScrollActivity.class);
- break;
- }
- startActivity(mIntent);
- overridePendingTransition(R.anim.base_slide_right_in, R.anim.base_slide_remain);
- }
- //Press the back button in mobile phone
- @Override
- public void onBackPressed() {
- super.onBackPressed();
- overridePendingTransition(0, R.anim.base_slide_right_out);
- }
- }
- <?xml version="1.0" encoding="UTF-8"?>
- <com.example.view.SildingFinishLayout xmlns:android="https://schemas.android.com/apk/res/android"
- xmlns:tools="https://schemas.android.com/tools"
- android:id="@+id/sildingFinishLayout"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="#556677" >
- <ListView
- android:id="@+id/listView"
- android:cacheColorHint="@android:color/transparent"
- android:layout_width="match_parent"
- android:layout_height="match_parent" >
- </ListView>
- </com.example.view.SildingFinishLayout>
- package com.example.slidingfinish;
- import java.util.ArrayList;
- import java.util.List;
- import android.app.Activity;
- import android.content.Intent;
- import android.os.Bundle;
- import android.view.View;
- import android.view.Window;
- import android.widget.AdapterView;
- import android.widget.AdapterView.OnItemClickListener;
- import android.widget.ArrayAdapter;
- import android.widget.ListView;
- import com.example.slidingfinish.R;
- import com.example.view.SildingFinishLayout;
- import com.example.view.SildingFinishLayout.OnSildingFinishListener;
- public class AbsActivity extends Activity {
- private List<String> list = new ArrayList<String>();
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- requestWindowFeature(Window.FEATURE_NO_TITLE);
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_abslistview);
- for (int i = 0; i <= 30; i++) {
- list.add("測試數據" + i);
- }
- ListView mListView = (ListView) findViewById(R.id.listView);
- ArrayAdapter<String> adapter = new ArrayAdapter<String>(
- AbsActivity.this, android.R.layout.simple_list_item_1, list);
- mListView.setAdapter(adapter);
- SildingFinishLayout mSildingFinishLayout = (SildingFinishLayout) findViewById(R.id.sildingFinishLayout);
- mSildingFinishLayout
- .setOnSildingFinishListener(new OnSildingFinishListener() {
- @Override
- public void onSildingFinish() {
- AbsActivity.this.finish();
- }
- });
- // touchView要設置到ListView上麵
- mSildingFinishLayout.setTouchView(mListView);
- mListView.setOnItemClickListener(new OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view,
- int position, long id) {
- startActivity(new Intent(AbsActivity.this, NormalActivity.class));
- overridePendingTransition(R.anim.base_slide_right_in,
- R.anim.base_slide_remain);
- }
- });
- }
- // Press the back button in mobile phone
- @Override
- public void onBackPressed() {
- super.onBackPressed();
- overridePendingTransition(0, R.anim.base_slide_right_out);
- }
- }
在運行項目之前還有一個很重要的操作,也是之前我被卡到的問題,就是我們需要對Activity設置為透明,即設置主題android:theme="@android:style/Theme.Translucent"
- <activity
- android:name=".AbsActivity"
- android:theme="@android:style/Theme.Translucent" >
- </activity>
- <activity
- android:name=".NormalActivity"
- android:theme="@android:style/Theme.Translucent" >
- </activity>
- <activity
- android:name=".ScrollActivity"
- android:theme="@android:style/Theme.Translucent" >
- </activity>
tail/xiaanming/7026873
正是我們想要的效果,如果想要加入滑動切換界麵的效果隻需要三步就行了,首先將Activity布局的最外層修改為SildingFinishLayout,然後在Activity裏麵調用setTouchView()方法設置touchView,設置OnSildingFinishListener監聽在onSildingFinish()方法中finish界麵,最後設置Activity的背景為透明(不是設置Activity布局文件的最頂層布局背景顏色透明,這點要區分一下)是不是很方便呢?好了,今天的講解到這裏就結束了,有疑問的朋友請在下麵留言,有興趣的朋友可以下載源碼看看!
最近看到有網頁留言,同時也有網友對代碼進行了修改,參見:https://blog.csdn.net/liuyifirsttime/article/details/50610991
修改地方:
(1) 不在通過setTouchView來處理事件,采用事件攔截機製
(2)加入了右側滑動機製
(3)不在對整個activity的觸摸都有效,采用了觸摸點判斷,使其隻是左右邊界時才有效,從而是activity的觸摸事件和listview等控件的滾動相分開
(4)加入左右滑動切換機製的禁用與開啟功能,使其更方便
下載地址: Activity滑動跳轉 https://download.csdn.net/detail/liuyifirsttime/9423006最後更新:2017-04-03 12:55:36