【android開發】手勢滑動關閉Activity(隨手指消失)的輔助類的實現
【CSDN抽風,把我寫一個多小時的東西覆蓋了。真的是嗶了狗了,自己又沒有備份。。。重寫吧。。。】
這個類主要是實現向右滑動關閉Activity,效果如下:
老套路,先寫思路:
1)將Activity的背景設定為透明模式。(從而可以看到下一層Activity)
2)建立一個FrameLayout,將contentView從DecorView中移除,並將contentView新增到我們的FrameLayout中,最後再將我們的FrameLayout添加回原來contentView的地方。
【熟悉android ViewTree的人應該能輕鬆理解contentView和DecorView。針對不熟悉的朋友,我做簡單介紹:我們將Activity介面看做由ActionBar部分 + ContentView部分(onCreate中的setContentView方法設定的部分) 組成,如果我們設定為NoActionBar型別的主題時,ActionBar部分將不存在,整個Activity都由ContentView部分組成。而Activity會有個rootView來容納他們,這個rootView就是DecorView。】示意圖如下:
3)實現FrameLayout的攔截邏輯,當手勢向右滑動時,攔截事件,將contentView進行水平方向移動。
分析完開動程式碼。
1、建立自定義ViewGroup類並申明必須要的引數,程式碼如下:
class MySwipeView extends FrameLayout {
private Activity activity;// 繫結的Activity
private ViewGroup decorView;
private View contentView;// activity的ContentView
private float intercept_X = 0 ;// onInterceptTouchEvent剛觸控時的X座標
private float intercept_Y = 0;// onInterceptTouchEvent手指剛觸控時的y座標
private int touchSlop = 0;// 產生滑動的最小值
public MySwipeView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
touchSlop = ViewConfiguration.get (getContext())
.getScaledTouchSlop();
setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT));
}
public MySwipeView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MySwipeView(Context context) {
this(context, null);
}
}
2、實現Activity的繫結,並將contentView從decorView中移除,然後新增到我們的FrameLayout中,最後將我們的FrameLayout添加回DecorView。程式碼如下:
/**
* 繫結Activity
* @param activity
*/
public void setActivity(Activity activity) {
this.activity = activity;
initCoverView();
}
/**
* 將contentView從DecorView中移除,並新增到CoverView中,最後再將CoverView新增到DecorView中
*/
private void initCoverView() {
decorView = (ViewGroup) activity.getWindow().getDecorView();
// decorView.setBackgroundColor(Color.parseColor("#33000000"));
contentView = (ViewGroup) decorView
.findViewById(android.R.id.content);
ViewGroup contentParent = (ViewGroup) contentView.getParent();
contentParent.removeView(contentView);
addView(contentView);
contentView.setBackgroundColor(Color.WHITE);
contentParent.addView(this);
}
3、實現事件攔截程式碼。說到事件攔截,熟悉android事件分發機制的朋友,應該知道。ViewGroup有個public boolean onInterceptTouchEvent(MotionEvent ev)方法,當此方法返回true時,ViewGroup將不會傳遞事件給他的childView,同時會呼叫該ViewGroup的onTouchEvent來處理該事件;反之,返回false時,事件將會傳遞給他的childView處理。我們通過實現onInterceptTouchEvent的邏輯來過濾我們需要的事件。程式碼如下,看一兩遍理解沒問題的:
@Override
public boolean onInterceptTouchEvent(android.view.MotionEvent ev) {
return shouldInterceptEvent(ev);
};
/**
* 判斷是否應該攔截事件。
* 如果水平方向的偏移量(不取絕對值) > 垂直方向的偏移量(取絕對值),並且水平方向的偏移量大於最小滑動距離,我們將攔截事件。
* 【實際過程中,我們發現touchSlope還是偏小,所以取了其3倍的數值作為最小滑動距離】
* @param event
* 事件物件
* @return true表示攔截,false反之
*/
private boolean shouldInterceptEvent(MotionEvent event) {
boolean shouldInterceptEvent = false;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
intercept_X = event.getX();
intercept_Y = event.getY();
break;
case MotionEvent.ACTION_MOVE:
float offsetY = Math.abs(event.getY() - intercept_Y);
float offsetX = Math.abs(event.getX() - intercept_X);
if (offsetY >= touchSlop * 3 || offsetY > offsetX) {
shouldInterceptEvent = false;
} else if (event.getX() - intercept_X >= touchSlop * 3) {
shouldInterceptEvent = true;
} else {
shouldInterceptEvent = false;
}
break;
case MotionEvent.ACTION_UP:
shouldInterceptEvent = false;
break;
default:
break;
}
return shouldInterceptEvent;
}
4、實現onTouchEvent程式碼:
@Override
public boolean onTouchEvent(android.view.MotionEvent event) {
processTouchEvent(event);
return true;
};
/**
* 對onTouchEvent事件進行處理
* @param event
* 事件物件
*/
private void processTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
//如果是向右滑動,我們動態改變contentView的便宜值
float offsetX = event.getX() - intercept_X;
if (offsetX > 0) {
contentView.setTranslationX(offsetX);
}
break;
case MotionEvent.ACTION_UP:
//如果手釋放時是在螢幕的1/3之內,我們視為使用者不想關閉Activity,則彈回。反之,關閉
if (contentView.getTranslationX() >= contentView
.getMeasuredWidth() / 3) {
collapse();
} else {
open();
}
break;
default:
break;
}
}
/**
* 展開Activity
*/
private void open() {
contentView.clearAnimation();
ObjectAnimator anim = ObjectAnimator.ofFloat(contentView,
View.TRANSLATION_X, 0);
anim.start();
}
/**
* 摺疊Activity(finish掉)
*/
private void collapse() {
contentView.clearAnimation();
ObjectAnimator anim = ObjectAnimator.ofFloat(contentView,
View.TRANSLATION_X, contentView.getMeasuredWidth());
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
activity.finish();
}
});
anim.start();
}
5、到此,我們基本完成了FrameLayout的開發。我們最後可以將此FrameLayout放到一個類中包裹起來,做成一個工具類。(可選)。程式碼如下:
public class SwipeToFinishView {
private MySwipeView mySwipeView;
public SwipeToFinishView(Activity activity) {
mySwipeView = new MySwipeView(activity);
mySwipeView.setActivity(activity);
}
//核心View
private class MySwipeView extends FrameLayout {
.....
}
}
6、將Activity設定為背景透明模式。
1)新建透明Activity的style。程式碼形如:
<style name="SwipTheme" parent="BaseTheme">
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowAnimationStyle">
@android:style/Animation.Translucent
</item>
</style>
2)應用此Style到Activity,在AndroidManifest檔案中指定的Activity程式碼新增程式碼如下:
android:theme="@style/SwipTheme"
7、如何使用:
在Activity的setContentView之後,new此類即可,程式碼如下。
SwipeToFinishView swipeToFinishView= new SwipeToFinishView(this);
【 使 用 前 提 】
1、該Activity的主題應該是NoActionBar(沒有ActionBar)
2、該Activity使用的主題應該包含下面三個item(用於將Activity的背景透明)
<item name="android:windowBackground">@android:color/transparent </item>
<item name="android:windowIsTranslucent">true </item>
<item name="android:windowAnimationStyle">@android:style/Animation.Translucent </item>
3、並在style.xml中新增加一個Style應用到該Activity的主題上。新增的style程式碼如下:
<style name="SwipTheme" parent="BaseTheme">
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowAnimationStyle" >@android:style/Animation.Translucent</item> </style>
最後在Activity的申明中新增下面的程式碼:
android:theme="@style/SwipTheme"
【 總 結 】
這個類寫的很簡單,基本就是一個ViewGroup的攔截程式碼和處理程式碼,但是中間也需要了一些小問題。
剛開始,我是使用的ViewDragHelper來做的,但是,後來當Activity有可獲取焦點型別的View時(例如使用了ListView),ViewDragHelper無法正常工作。無奈之下自己手動實現攔截程式碼。
老樣子,原始碼如下。
/**
* 跟隨手勢向右滑動消失的View幫助類。<br/>
* 使用方法:在Activity的setContentView之後,new此類即可,程式碼如下。<br/>
* SwipeToFinishView swipeToFinishView= new SwipeToFinishView(this);
* <hr/>
* 使用前提:<br/>
* <ol>
* <li>該Activity的主題應該是NoActionBar(沒有ActionBar)</li>
* <li>該Activity使用的主題應該包含下面三個item(用於將Activity的背景透明)
* <ul>
* <li>
*
* <item name="android:windowBackground">@android:color/transparent
* </item>
*
* </li>
* <li>
*
* <item name="android:windowIsTranslucent">true </item>
*
* </li>
* <li>
*
* <item
* name="android:windowAnimationStyle">@android:style/Animation.Translucent
* </item>
*
* </li>
*
* </ul>
* </li>
* </ol>
* 例如:我想給SecondActivity新增滑動消失功能,我就會在onCreate中新增程式碼:<br/>
* SwipeToFinishView swipeToFinishView= new SwipeToFinishView(this);<br/>
* 並在style.xml中新增加一個Style應用到該Activity的主題上。新增的style程式碼如下:<br/>
* <!-- SecondActivity theme. --> <br/>
* <style name="SwipTheme" parent="BaseTheme"><br/>
* <item
* name="android:windowBackground">@android:color/transparent</item> <br/>
* <item
* name="android:windowIsTranslucent">true</item> <br/>
* <item name="android:windowAnimationStyle"
* >@android:style/Animation.Translucent</item> </style> <br/>
* 最後在Activity的申明中新增下面的程式碼:<br/>
* android:theme="@style/SwipTheme"
*
* @author 藍亭書序 2016.11.29
*
*/
public class SwipeToFinishView {
private MySwipeView mySwipeView;
public SwipeToFinishView(Activity activity) {
mySwipeView = new MySwipeView(activity);
mySwipeView.setActivity(activity);
}
/**
* 核心View
*
* @author 藍亭書序
*
*/
private class MySwipeView extends FrameLayout {
private Activity activity;// 繫結的Activity
private ViewGroup decorView;
private View contentView;// activity的ContentView
private float intercept_X = 0;// onInterceptTouchEvent剛觸控時的X座標
private float intercept_Y = 0;// onInterceptTouchEvent手指剛觸控時的y座標
private int touchSlop = 0;// 產生滑動的最小值
public MySwipeView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
touchSlop = ViewConfiguration.get(getContext())
.getScaledTouchSlop();
setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT));
}
public MySwipeView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MySwipeView(Context context) {
this(context, null);
}
@Override
public boolean onInterceptTouchEvent(android.view.MotionEvent ev) {
return shouldInterceptEvent(ev);
};
@SuppressLint("ClickableViewAccessibility")
public boolean onTouchEvent(android.view.MotionEvent event) {
processTouchEvent(event);
return true;
};
/**
* 繫結Activity
*
* @param activity
*/
public void setActivity(Activity activity) {
this.activity = activity;
initCoverView();
}
/**
* 將contentView從DecorView中移除,並新增到CoverView中,最後再將CoverView新增到DecorView中
*/
private void initCoverView() {
decorView = (ViewGroup) activity.getWindow().getDecorView();
// decorView.setBackgroundColor(Color.parseColor("#33000000"));
contentView = (ViewGroup) decorView
.findViewById(android.R.id.content);
ViewGroup contentParent = (ViewGroup) contentView.getParent();
contentParent.removeView(contentView);
addView(contentView);
contentView.setBackgroundColor(Color.WHITE);
contentParent.addView(this);
}
/**
* 判斷是否應該攔截事件
*
* @param event
* 事件物件
* @return true表示攔截,false反之
*/
private boolean shouldInterceptEvent(MotionEvent event) {
boolean shouldInterceptEvent = false;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
intercept_X = event.getX();
intercept_Y = event.getY();
break;
case MotionEvent.ACTION_MOVE:
float offsetY = Math.abs(event.getY() - intercept_Y);
float offsetX = Math.abs(event.getX() - intercept_X);
if (offsetY >= touchSlop * 3 || offsetY > offsetX) {
shouldInterceptEvent = false;
} else if (event.getX() - intercept_X >= touchSlop * 3) {
shouldInterceptEvent = true;
} else {
shouldInterceptEvent = false;
}
break;
case MotionEvent.ACTION_UP:
shouldInterceptEvent = false;
break;
default:
break;
}
return shouldInterceptEvent;
}
/**
* 對onTouchEvent事件進行處理
*
* @param event
* 事件物件
*/
private void processTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
float offsetX = event.getX() - intercept_X;
if (offsetX > 0) {
contentView.setTranslationX(offsetX);
}
break;
case MotionEvent.ACTION_UP:
if (contentView.getTranslationX() >= contentView
.getMeasuredWidth() / 3) {
collapse();
} else {
open();
}
break;
default:
break;
}
}
/**
* 展開Activity
*/
private void open() {
contentView.clearAnimation();
ObjectAnimator anim = ObjectAnimator.ofFloat(contentView,
View.TRANSLATION_X, 0);
anim.start();
}
/**
* 摺疊Activity(finish掉)
*/
private void collapse() {
contentView.clearAnimation();
ObjectAnimator anim = ObjectAnimator.ofFloat(contentView,
View.TRANSLATION_X, contentView.getMeasuredWidth());
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
activity.finish();
}
});
anim.start();
}
}
}