Android App中實現(xiàn)向右滑動銷毀功能的要點解析
今天給大家?guī)硪粋€向右滑動銷毀Activity的效果,Activtiy隨著手指的移動而移動,該效果在Android應(yīng)用中還是比較少見的,在IOS中就比較常見了,例如“網(wǎng)易新聞” ,"美食杰" , "淘寶"等應(yīng)用采用此效果,而Android應(yīng)用中“知乎”采用的也是這種滑動切換Activity的效果, 不過我發(fā)現(xiàn)“淘寶”并沒有隨著手勢的移動而移動,只是捕捉到滑動手勢,然后產(chǎn)生平滑切換界面的動畫效果,這個在Android中還是很好實現(xiàn)的, 網(wǎng)上很多滑動切換Activity的Demo貌似都是這種效果的吧,如果要實現(xiàn)類似“網(wǎng)易新聞”的隨手勢的滑動而滑動,似乎就要復(fù)雜一些了,我之前在IOS中看到"網(wǎng)易新聞"的這種效果就很感興趣,然后群里也有朋友問我怎么實現(xiàn)類似“知乎”這個應(yīng)用的滑動切換的效果,我也特意去下了一個“知乎”,在之前的實現(xiàn)中我遇到了一些瓶頸,沒有實現(xiàn)出來就擱置了在那里,今天無意中看到給Activity設(shè)置透明的背景,于是乎我恍然大悟,真是靈感來源于瞬間,不能強求啊,然后自己就將此效果實現(xiàn)了出來,給大家分享一下,希望給有此需求的你一點點幫助。
不知道大家對Scroller這個類以及View的scrollBy() 和scrollTo()的使用熟悉不?我之前介紹了Scroller類的滑動實現(xiàn)原理Android 帶你從源碼的角度解析Scroller的滾動實現(xiàn)原理,在那里面也介紹了scrollBy() 和scrollTo()方法,不明白的同學(xué)可以去看看,這對實現(xiàn)此效果有很大的幫助,了解scrollBy() 和scrollTo()的朋友應(yīng)該知道,如果想對某個View(例如Button)就行滾動,我們直接調(diào)用該View(Button)的scrollBy()方法,并不是該View(Button)進行滾動,而是該View里面的內(nèi)容(Button上面的文字)進行滾動,所以我們假如要讓View整體滾動就需要對其View的父布局調(diào)用scrollBy()方法,回到這篇文章來,假如我們想要對一個Activity進行滾動,我們就需求對這個Activity布局文件的頂層布局的父布局進行滾動
例如下面的XML布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical" > </LinearLayout>
如果我們對LinearLayout進行滾動,并不能實現(xiàn)我們想要的效果,而只能對LinearLayout里面的內(nèi)容或者說是子View進行滾動,所以我們需要獲取利用LinearLayout的getParent()方法獲取父布局,其實Android系統(tǒng)會對我們的布局文件的最外層套一個FrameLayout,所以我們其實就是對FrameLayout進行滾動就行了
了解了實現(xiàn)的原理之后,我們就來編寫代碼吧,首先新建一個android工程,取名SildingFinish
由于我們的需求可能不是在一個界面提供這個滑動切換的效果,所以我們應(yīng)該將這部分滑動的邏輯抽取出來,我這里就他寫成了一個擴展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的滑動刪除頁面效果,當(dāng)我們要使用
* 此功能的時候,需要將該Activity的頂層布局設(shè)置為SildingFinishLayout,
* 然后需要調(diào)用setTouchView()方法來設(shè)置需要滑動的View
*
* @author xiaanming
*
* @blog http://blog.csdn.net/xiaanming
*
*/
public class SildingFinishLayout extends RelativeLayout implements
OnTouchListener {
/**
* SildingFinishLayout布局的父布局
*/
private ViewGroup mParentView;
/**
* 處理滑動邏輯的View
*/
private View touchView;
/**
* 滑動的最小距離
*/
private int mTouchSlop;
/**
* 按下點的X坐標(biāo)
*/
private int downX;
/**
* 按下點的Y坐標(biāo)
*/
private int downY;
/**
* 臨時存儲X坐標(biāo)
*/
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();
}
}
/**
* 設(shè)置OnSildingFinishListener, 在onSildingFinish()方法中finish Activity
*
* @param onSildingFinishListener
*/
public void setOnSildingFinishListener(
OnSildingFinishListener onSildingFinishListener) {
this.onSildingFinishListener = onSildingFinishListener;
}
/**
* 設(shè)置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());
// 調(diào)用startScroll方法來設(shè)置一些滾動的參數(shù),我們在computeScroll()方法中調(diào)用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,
// 則當(dāng)手指滑動,取消item的點擊事件,不然我們滑動也伴隨著item點擊事件的發(fā)生
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() {
// 調(diào)用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()方法獲取該布局的父布局和獲取其控件的寬度,主要是為之后的實現(xiàn)做準(zhǔn)備工作。
我們的滑動邏輯主要是利用View的scrollBy() 方法, scrollTo()方法和Scroller類來實現(xiàn)的,當(dāng)手指拖動視圖的時候,我們監(jiān)聽手指在屏幕上滑動的距離利用View的scrollBy() 方法使得View隨著手指的滑動而滑動,而當(dāng)手指離開屏幕,我們在根據(jù)邏輯使用Scroller類startScroll()方法設(shè)置滑動的參數(shù),然后再根據(jù)View的scrollTo進行滾動。
對于View的滑動,存在一些Touch事件消費的處理等問題,因此我們需要對View的整個Touch事件很熟悉 ,最主要的就是Activity里面有一些ListView、 GridView、ScrollView等控件了, 假如我們Activity里面存在ListView、GridView等控件的話,我們對Activity的最外層布局進行滾動根本就無效果,因為Touch事件被ListView、GridView等控件消費了,所以Activity的最外層布局根本得不到Touch事件,也就實現(xiàn)不了Touch邏輯了,所以為了解決此Touch事件問題我提供了setTouchView(View touchView) 方法,這個方法是將Touch事件動態(tài)的設(shè)置到到View上面,所以針對上面的問題,我們將OnTouchListener直接設(shè)置到ListView、GridView上面,這樣子就避免了Activity的最外層接受不到Touch事件的問題了
接下來看onTouch()方法
首先我們在ACTION_DOWN記錄按下點的X,Y坐標(biāo)
然后在ACTION_MOVE中判斷,如果我們在水平方向滑動的距離大于mTouchSlop并且在豎直方向滑動的距離小于mTouchSlop,表示Activity處于滑動狀態(tài),我們判斷如果touchView是ListView、GridView或者其子類的時候,因為我們手指在ListView、GridView上面,伴隨著item的點擊事件的發(fā)生,所以我們對touchView設(shè)置ACTION_CANCEL來取消item的點擊事件,然后對該布局的父布局調(diào)用scrollBy()進行滾動,并且如果TouchView是AbsListView或者ScrollView直接返回true,來取消AbsListView或者ScrollView本身的ACTION_MOVE事件,最直觀的感受就是我們在滑動Activity的時候,禁止AbsListView或者ScrollView的上下滑動
最后在ACTION_UP中判斷如果手指滑動的距離大于控件長度的二分之一,表示將Activity滑出界面,否則滑動到起始位置,我們利用Scroller類的startScroll()方法設(shè)置好開始位置,滑動距離和時間,然后調(diào)用postInvalidate()刷新界面,之后就到computeScroll()方法中,我們利用scrollTo()方法對該布局的父布局進行滾動,滾動結(jié)束之后,我們判斷界面是否滑出界面,如果是就調(diào)用OnSildingFinishListener接口的onSildingFinish()方法,所以只要在onSildingFinish()方法中finish界面就行了
整個滑動布局的代碼就是這個樣子,接下來我們就來使用了,主界面Activity只有三個按鈕,分別跳轉(zhuǎn)到普通布局的Activity,有ListView的Activity和有ScrollView的Activity中
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://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的代碼,根據(jù)ID實例化Button,然后為Button設(shè)置OnClickListener事件,不同的按鈕跳轉(zhuǎn)到不同的Activity,然后設(shè)置從右向左滑動的動畫,重寫onBackPressed()方法,當(dāng)我們按下手機物理鍵盤的返回鍵,添加從左向右滑出的動畫
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);
}
}
在這里我之貼出含有ListView的Activity的代碼,先看布局,我們自定義滑動布局SildingFinishLayout應(yīng)該放在XML的最頂層
<?xml version="1.0" encoding="UTF-8"?>
<com.example.view.SildingFinishLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://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("測試數(shù)據(jù)" + 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要設(shè)置到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);
}
}
利用ID找到SildingFinishLayout實例,利用setTouchView()方法設(shè)置touchView到ListView上面,然后調(diào)用setOnSildingFinishListener()設(shè)置OnSildingFinishListener,在onSildingFinish()中finish界面就可以啦。
在運行項目之前還有一個很重要的操作,也是之前我被卡到的問題,就是我們需要對Activity設(shè)置為透明,即設(shè)置主題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>
好了,現(xiàn)在我們可以運行項目看看效果啦

正是我們想要的效果,如果想要加入滑動切換界面的效果只需要三步就行了,首先將Activity布局的最外層修改為SildingFinishLayout,然后在Activity里面調(diào)用setTouchView()方法設(shè)置touchView,設(shè)置OnSildingFinishListener監(jiān)聽在onSildingFinish()方法中finish界面,最后設(shè)置Activity的背景為透明(不是設(shè)置Activity布局文件的最頂層布局背景顏色透明,這點要區(qū)分一下)是不是很方便呢?好了,今天的講解到這里就結(jié)束了~
- Android編程實現(xiàn)橫豎屏切換時不銷毀當(dāng)前activity和鎖定屏幕的方法
- Android的Activity跳轉(zhuǎn)動畫各種效果整理
- android的activity跳轉(zhuǎn)到另一個activity
- android PopupWindow 和 Activity彈出窗口實現(xiàn)方式
- Android基礎(chǔ)之Fragment與Activity交互詳解
- Android Activity之間傳遞圖片(Bitmap)的方法
- Android筆記之:App應(yīng)用之啟動界面SplashActivity的使用
- Activity透明/半透明效果的設(shè)置transparent(兩種實現(xiàn)方法)
- android獲取當(dāng)前運行Activity名字的方法
- Android Activity切換(跳轉(zhuǎn))時出現(xiàn)黑屏的解決方法 分享
- Android實現(xiàn)Activity界面切換添加動畫特效的方法
- 詳解Android.activity銷毀流程的工作原理
相關(guān)文章
Android開發(fā)之Button事件實現(xiàn)與監(jiān)聽方法總結(jié)
這篇文章主要介紹了Android開發(fā)之Button事件實現(xiàn)與監(jiān)聽方法,結(jié)合實例形式總結(jié)分析了Android開發(fā)中Button事件的兩種實現(xiàn)方法以及針對Button控件的幾種常用監(jiān)聽方法,需要的朋友可以參考下2016-01-01
ListView實現(xiàn)下拉刷新加載更多的實例代碼(直接拿來用)
這篇文章主要介紹了ListView實現(xiàn)下拉刷新加載更多的實例代碼(直接拿來用)的相關(guān)資料,需要的朋友可以參考下2016-07-07
Android加載loading對話框的功能及實例代碼(不退出沉浸式效果)
這篇文章主要介紹了Android加載loading對話框的功能及實例代碼,不退出沉浸式效果,本文通過實例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下2018-12-12
Android 列表倒計時的實現(xiàn)的示例代碼(CountDownTimer)
本篇文章主要介紹了Android 列表倒計時的實現(xiàn)的示例代碼(CountDownTimer),具有一定的參考價值,有興趣的可以了解一下2017-09-09
Android編程監(jiān)聽APK安裝與刪除等過程的方法
這篇文章主要介紹了Android編程監(jiān)聽APK安裝與刪除等過程的方法,涉及Android事件監(jiān)聽、權(quán)限控制、廣播操作等相關(guān)實現(xiàn)技巧,需要的朋友可以參考下2017-10-10
Android 將本地資源圖片轉(zhuǎn)換成Drawable,進行設(shè)置大小操作
這篇文章主要介紹了Android 將本地資源圖片轉(zhuǎn)換成Drawable,進行設(shè)置大小操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-08-08

