Android中系統(tǒng)觸摸相關(guān)輔助類總結(jié)
Android中的觸摸事件,我們可以通過重寫View的OnTouchEvent()
等事件,通過事件類型MotionEvent
來進(jìn)行我們想要實(shí)現(xiàn)的邏輯操作坎弯,有時(shí)候一些簡(jiǎn)單的需求很容易實(shí)現(xiàn),但是有時(shí)候,一些很困難的需求,我們需要編寫大量的代碼來實(shí)現(xiàn)驯绎。好在Android官方就給我們提供了很豐富的觸摸相關(guān)的輔助類,今天谋旦,我就把我知道的分享給大家剩失。
1、GestureDetector(手勢(shì)探測(cè)器)
顧名思義册着,GestureDetector可以很方便的幫我們識(shí)別出我們當(dāng)前在屏幕上的手勢(shì)操作拴孤,比如按下、長(zhǎng)按甲捏、抬起演熟、雙擊、滾動(dòng)司顿、快速滑動(dòng)(慣性滑動(dòng))等芒粹,甚至它還能識(shí)別出是否是鼠標(biāo)右鍵點(diǎn)擊的動(dòng)作,省去了我們大量的手勢(shì)判斷語句免猾,非常的實(shí)用是辕。
1.1 相關(guān)的方法及接口簡(jiǎn)介
GestureDetector的手勢(shì)識(shí)別有三個(gè)接口來處理我們當(dāng)前的手勢(shì)動(dòng)作,還有一個(gè)集成了三個(gè)接口的實(shí)現(xiàn)類方便我們?nèi)ヌ幚矶喾N事件猎提。
1.1.1 GestureDetector.OnGestureListener
作用: 用來監(jiān)聽單擊、長(zhǎng)按旁蔼、滑動(dòng)等操作锨苏,我們可以在這些操作的回調(diào)方法中處理我們自己的邏輯。
具體實(shí)現(xiàn)方法:
見下面代碼棺聊,注釋已經(jīng)寫的很清楚了伞租,示例代碼中僅僅對(duì)滾動(dòng)進(jìn)行了處理。
private static class MyGestureDetectorListener implements GestureDetector.OnGestureListener{
private GestureDetectorView mView;
public MyGestureDetectorListener(GestureDetectorView view){
this.mView = view;
}
/**
* 用戶按下屏幕的時(shí)候回調(diào)
* @param motionEvent
* @return
*/
@Override
public boolean onDown(MotionEvent motionEvent) {
Log.e("ll", "MyGestureDetectorListener : onDown");
return true;
}
/**
* 用戶按下屏幕100ms后限佩,如果還沒有松手或者移動(dòng)就會(huì)回調(diào)
* @param motionEvent
*/
@Override
public void onShowPress(MotionEvent motionEvent) {
Log.e("ll", "MyGestureDetectorListener : onShowPress");
}
/**
* 單純的點(diǎn)擊再抬手時(shí)調(diào)用葵诈。用戶手指松開(UP事件)的時(shí)候如果沒有執(zhí)行onScroll()和onLongPress()這兩個(gè)回調(diào)的話裸弦,就會(huì)回調(diào)
* @param motionEvent
* @return
*/
@Override
public boolean onSingleTapUp(MotionEvent motionEvent) {
Log.e("ll", "MyGestureDetectorListener : onSingleTapUp");
return false;
}
/**
* 屏幕拖動(dòng)事件,如果按下的時(shí)間過長(zhǎng)作喘,調(diào)用了onLongPress理疙,再拖動(dòng)屏幕不會(huì)觸發(fā)onScroll。
* 拖動(dòng)屏幕會(huì)多次觸發(fā)
* @param motionEvent 開始拖動(dòng)的第一次按下down操作,也就是第一個(gè)ACTION_DOWN
* @param motionEvent1 觸發(fā)當(dāng)前onScroll方法的ACTION_MOVE
* @param distanceX 當(dāng)前的x坐標(biāo)與最后一次觸發(fā)scroll方法的x坐標(biāo)的差值泞坦。
* @param distanceY 當(dāng)前的y坐標(biāo)與最后一次觸發(fā)scroll方法的y坐標(biāo)的差值窖贤。
* @return
*/
@Override
public boolean onScroll(MotionEvent motionEvent, MotionEvent motionEvent1, float distanceX, float distanceY) {
Log.e("ll", "MyGestureDetectorListener : onScroll : " + distanceY);
mView.scrollBy(0, (int) distanceY);
return false;
}
/**
* 用戶長(zhǎng)按后(好像不同手機(jī)的時(shí)間不同,源碼里默認(rèn)是100ms+500ms)觸發(fā)贰锁,觸發(fā)之后不會(huì)觸發(fā)其他回調(diào)赃梧,直至松開(UP事件)。
* @param motionEvent
*/
@Override
public void onLongPress(MotionEvent motionEvent) {
Log.e("ll", "MyGestureDetectorListener : onLongPress");
}
/**
* 按下屏幕豌熄,在屏幕上快速滑動(dòng)后松開授嘀,由一個(gè)down,多個(gè)move,一個(gè)up觸發(fā)
* @param motionEvent 開始快速滑動(dòng)的第一次按下down操作,也就是第一個(gè)ACTION_DOWN
* @param motionEvent1 觸發(fā)當(dāng)前onFling方法的move操作,也就是最后一個(gè)ACTION_MOVE
* @param velocityX X軸上的移動(dòng)速度,像素/秒
* @param velocityY Y軸上的移動(dòng)速度锣险,像素/秒
* @return
*/
@Override
public boolean onFling(MotionEvent motionEvent, MotionEvent motionEvent1, float velocityX, float velocityY) {
Log.e("ll", "MyGestureDetectorListener : onFling");
return false;
}
}
注意事項(xiàng):
1蹄皱、onDown()
方法返回true
時(shí),onScroll()
和onFling()
方法才能回調(diào)囱持。
2夯接、onLongPress()
若觸發(fā)了,則不會(huì)再觸發(fā)其他回調(diào)纷妆。
3盔几、事件回調(diào)順序:
- 快速點(diǎn)擊:
onDown()
>onSingleTapUp()
- 慢按屏幕:
onDown()
>onShowPress()
>onSingleTapUp()
- 長(zhǎng)按屏幕:
onDown()
>onShowPress()
>onLongPress()
- 滑動(dòng)屏幕:
onDown()
>onShowPress()
>onScroll()
(多個(gè)) >onFling()
- 快速拖動(dòng)屏幕后松手:
onDown()
>onScroll()
(多個(gè)) >onFling()
1.1.2 GestureDetector.OnDoubleTapListener
作用: 用來監(jiān)聽雙擊事件操作,我們可以在這些操作的回調(diào)方法中處理我們自己的邏輯掩幢。
具體實(shí)現(xiàn)方法:
見下面代碼逊拍,注釋已經(jīng)寫的很清楚了。
private static class MyGestureDetectorDoubleTapListener implements GestureDetector.OnDoubleTapListener{
/**
* 單擊事件际邻。用來判定該次點(diǎn)擊是單純的SingleTap而不是DoubleTap卒茬,如果連續(xù)點(diǎn)擊兩次就是DoubleTap手勢(shì),
* 如果只點(diǎn)擊一次杯活,系統(tǒng)等待一段時(shí)間后沒有收到第二次點(diǎn)擊則判定該次點(diǎn)擊為SingleTap而不是DoubleTap霹抛,然后觸發(fā)SingleTapConfirmed事件。
* @param motionEvent
* @return
*/
@Override
public boolean onSingleTapConfirmed(MotionEvent motionEvent) {
Log.e("ll","MyGestureDetectorDoubleTapListener : onSingleTapConfirmed");
return false;
}
/**
* 雙擊觸發(fā)
* @param motionEvent
* @return
*/
@Override
public boolean onDoubleTap(MotionEvent motionEvent) {
Log.e("ll","MyGestureDetectorDoubleTapListener : onDoubleTap");
return false;
}
/**
* 雙擊間隔中發(fā)生的動(dòng)作轮听。指觸發(fā)onDoubleTap以后骗露,在雙擊之間發(fā)生的其它動(dòng)作,包含down血巍、up和move事件
* @param motionEvent
* @return
*/
@Override
public boolean onDoubleTapEvent(MotionEvent motionEvent) {
Log.e("ll","MyGestureDetectorDoubleTapListener : onDoubleTapEvent");
return false;
}
}
注意事項(xiàng):
1萧锉、onSingleTapConfirmed()
用來判斷是不是單點(diǎn)事件,雙擊不會(huì)執(zhí)行此方法述寡。
2柿隙、事件調(diào)用順序:
- 單擊屏幕時(shí):
OnDown()
>OnsingleTapUp()
>OnsingleTapConfirmed()
- 雙擊屏幕時(shí):
OnDown()
>onSingleTapUp()
>onDoubleTap()
>onDoubleTapEvent()
>onDown()
>onDoubleTapEvent()
1.1.3 GestureDetector.OnContextClickListener
作用: 用來監(jiān)聽鼠標(biāo)右鍵按下的事件叶洞,我們可以在這些操作的回調(diào)方法中處理我們自己的邏輯。
具體實(shí)現(xiàn)方法:
見下面代碼禀崖,注釋已經(jīng)寫的很清楚了衩辟。
private static class MyGestureDetectorContextClickListener implements GestureDetector.OnContextClickListener{
/**
* 當(dāng)鼠標(biāo)/觸摸板,右鍵點(diǎn)擊時(shí)候的回調(diào)帆焕。
* @param motionEvent
* @return
*/
@Override
public boolean onContextClick(MotionEvent motionEvent) {
return false;
}
}
注意事項(xiàng):
1惭婿、OnContextClickListener
接口最低支持的API是23。
1.1.4 GestureDetector.SimpleOnGestureListener
(實(shí)現(xiàn)類)
SimpleOnGestureListener
實(shí)現(xiàn)了上述三個(gè)接口叶雹,我們可以直接使用此類财饥,并重寫我們想要的方法就行。
GestureDetector.SimpleOnGestureListener simpleOnGestureListener = new GestureDetector.SimpleOnGestureListener(){
@Override
public boolean onDown(MotionEvent e) {
return true;
}
};
1.2 GestureDetector使用方法
GestureDetector可以作用于多個(gè)地方折晦,比如Activity的OnTouchEvent()
方法钥星,View的onTouch()
方法和onTouchEvent()
方法上,其中满着,以 后兩者使用較多谦炒。
這里以View.onTouchEvent()
方法舉例:
1.2.1 在View的初始化時(shí)初始化GestureDetector
public GestureDetectorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context){
//傳入View方便對(duì)View進(jìn)行操作
mGestureDetector = new GestureDetector(context, new MyGestureDetectorListener(this));
//設(shè)置雙擊事件監(jiān)聽
mGestureDetector.setOnDoubleTapListener(new MyGestureDetectorDoubleTapListener());
}
1.2.1 在View的onTouchEvent()
方法中,將觸摸事件交給GestureDetector去處理
@Override
public boolean onTouchEvent(MotionEvent event) {
return mGestureDetector.onTouchEvent(event);
}
此處展示一個(gè)使用示例风喇,僅僅監(jiān)聽了OnGestureListener
的onScroll()
方法:
1.3 小結(jié)
GestureDetector可以很方便的為我們檢測(cè)各種觸摸事件宁改,我們只需要再相應(yīng)的回調(diào)方法中處理我們自己的邏輯即可,可以說是觸摸輔助類第一神器魂莫!
2还蹲、ViewDragHelper(子控件拖拽幫手)
顧名思義,ViewDragHelper肯定是用來幫助View進(jìn)行Drag(拖拽)的耙考。它主要是用來給ViewGroup處理它內(nèi)部子View的拖拽動(dòng)作使用的谜喊,有了它,我們可以省去寫ViewGroup中煩人的觸摸攔截方法(onInterceptTouchEvent()
)和子控件的onTouchEvent()
方法了倦始!
2.1 相關(guān)方法及接口介紹
2.1.1 ViewDragHelper.Callback
ViewDragHelper的主要方法都在它內(nèi)部類ViewDragHelper.Callback
中斗遏,這里,我寫了一個(gè)類MyViewDragHelperCallback
實(shí)現(xiàn)了ViewDragHelper.Callback
中的比較重要的方法鞋邑,每個(gè)方法的具體作用诵次,都已在注釋中寫明:
private static class MyViewDragHelperCallback extends ViewDragHelper.Callback{
//傳入父ViewGroup和ViewDragHelper,便于對(duì)子View的操作進(jìn)行判斷
private RelativeLayout mViewGroup;
private ViewDragHelper mDragHelper;
public MyViewDragHelperCallback(ViewDragHelper dragHelper, RelativeLayout view){
this.mDragHelper = dragHelper;
this.mViewGroup = view;
}
/**
* 根據(jù)傳入的child確定是否需要捕獲此child(對(duì)它進(jìn)行操作)
* @param child 被觸摸的子View
* @param pointerId 按下手指的id枚碗,一般多點(diǎn)觸摸時(shí)會(huì)用來判斷是哪根手指觸摸
* @return 返回true表示將要捕獲這個(gè)child
*/
@Override
public boolean tryCaptureView(View child, int pointerId) {
return true;
}
/**
* 返回被橫向移動(dòng)的子控件child的上坐標(biāo)top藻懒,和移動(dòng)距離dy,我們可以根據(jù)這些值來返回child的新的top视译。
* @param child
* @param top 拖拽動(dòng)作后系統(tǒng)建議的距離父布上側(cè)的距離
* @param dy Y軸方向拖拽移動(dòng)的距離
* @return 返回拖拽后child距離父布局上側(cè)的距離
*/
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
return top;
}
/**
* 返回被橫向移動(dòng)的子控件child的左坐標(biāo)left,和移動(dòng)距離dx归敬,我們可以根據(jù)這些值來返回child的新的left酷含。
* @param child
* @param left 拖拽動(dòng)作后系統(tǒng)建議的距離父布局左側(cè)的距離
* @param dx X軸方向拖拽移動(dòng)的距離
* @return 返回拖拽后child距離父布局左側(cè)的距離
*/
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return left;
}
/**
* 這個(gè)用來控制垂直移動(dòng)的邊界范圍鄙早,單位是像素。
* @param child
* @return 返回值大于0時(shí)才能捕獲子View
*/
@Override
public int getViewVerticalDragRange(View child) {
return super.getViewVerticalDragRange(child);
}
/**
* 這個(gè)用來控制橫向移動(dòng)的邊界范圍椅亚,單位是像素限番。
* @param child
* @return 返回值大于0時(shí)才能捕獲子View
*/
@Override
public int getViewHorizontalDragRange(View child) {
return super.getViewHorizontalDragRange(child);
}
/**
* 當(dāng)releasedChild被釋放的時(shí)候回調(diào)
* @param releasedChild
* @param xvel x軸方向的加速度
* @param yvel y軸方向的加速度
*/
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
}
/**
* 若ViewDragHelper設(shè)置了setEdgeTrackingEnabled()此方法,則調(diào)用此方法
* @param edgeFlags 邊緣觸摸方向
* @param pointerId
*/
@Override
public void onEdgeDragStarted(int edgeFlags, int pointerId) {
super.onEdgeDragStarted(edgeFlags, pointerId);
}
}
2.1.2 ViewDragHelper.captureChildView()
直接對(duì)子View進(jìn)行捕獲呀舔,可以繞開Callback.tryCaptureView()
方法弥虐。
2.1.3 ViewDragHelper.setEdgeTrackingEnabled()
設(shè)置ViewGroup的邊緣可以被拖拽,可以設(shè)置左媚赖、上霜瘪、右、下四個(gè)方向或者任意幾個(gè)方向惧磺,一般配合Callback.onEdgeDragStarted()
和ViewDragHelper.captureChildView()
使用(后面有示例)颖对。
2.1.4 ViewDragHelper.settleCapturedViewAt()
對(duì)ViewGroup的某子View進(jìn)行回彈處理,需要配合ViewGroup的computeScroll()
方法和invalidate()
方法磨隘。
2.2 ViewDragHelper使用方式
2.2.1 初始化ViewDragHelper
在ViewGroup的初始化時(shí)缤底,通過ViewDragHelper的靜態(tài)初始化方法create()
進(jìn)行創(chuàng)建:
public ViewDragHelperView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init(){
mViewDragHelper = ViewDragHelper.create(this,1.0f, new MyViewDragHelperCallback(this));
}
創(chuàng)建時(shí)的三個(gè)參數(shù)的意義分別是:
-
forParent
:給哪個(gè)ViewGroup使用的,一般傳入當(dāng)前的ViewGroup番捂; -
sensitivity
:拖拽靈敏度个唧,傳入值越大,靈敏度越小设预,一般傳入1.0f
即可徙歼; -
ViewDragHelperCallback
:ViewDragHelper的實(shí)現(xiàn)類Callback
,基本所有的拖拽后的回調(diào)都在此方法內(nèi)絮缅。
2.2.2 重寫onInterceptTouchEvent()
重寫ViewGroup的onInterceptTouchEvent()
方法鲁沥,把它交給ViewDragHelper的shouldInterceptTouchEvent()
來處理。
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mViewDragHelper.shouldInterceptTouchEvent(ev);
}
注意:ViewDragHelper只對(duì)ViewGroup有效耕魄,并且它也不能直接操作子View画恰,而是借助的ViewGroup!
2.2.3 重寫ViewGroup的onTouchEvent()
重寫ViewGroup的onTouchEvent()
吸奴,把它交給ViewDragHelper的processTouchEvent()
來處理允扇,并返回true
。
@Override
public boolean onTouchEvent(MotionEvent event) {
mViewDragHelper.processTouchEvent(event);
return true;
}
使用方式是非常簡(jiǎn)單的则奥,最重要的是在Callback
中實(shí)現(xiàn)自己的拖拽邏輯考润。
2.3 使用場(chǎng)景簡(jiǎn)介
使用場(chǎng)景都是在ViewDragHelper的實(shí)現(xiàn)類Callback
中進(jìn)行處理的。
2.3.1 對(duì)子View進(jìn)行拖拽到任意位置
效果展示:
通過重寫tryCaptureView()
读处,clampViewPositionVertical()
和clampViewPositionHorizontal()
對(duì)子View進(jìn)行拖拽處理糊治。
其中,tryCaptureView()
用來判斷是否是需要拖拽的子View罚舱,clampViewPositionVertical()
和clampViewPositionHorizontal()
用來返回子View拖拽后的位置井辜。
示例代碼表示绎谦,可以拖拽任意子View到任意位置,但是不能超過父ViewGroup的左側(cè)和右側(cè):
@Override
public boolean tryCaptureView(View child, int pointerId) {
//直接返回true粥脚,表示可以拖拽任意子View
return true;
}
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
//直接返回top窃肠,不對(duì)垂直方向的拖拽進(jìn)行攔截,交給ViewDragHelper去處理
return top;
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
//不能超過父View左側(cè)
if(left < mViewGroup.getPaddingLeft()){
left = mViewGroup.getPaddingLeft();
}
//不能超過父View右側(cè)
if(left + child.getWidth() > mViewGroup.getWidth() - mViewGroup.getPaddingRight()){
left = mViewGroup.getWidth() - mViewGroup.getPaddingRight() - child.getWidth();
}
return left;
}
2.3.2 對(duì)ViewGroup邊緣觸摸事件進(jìn)行捕獲子View的行為
效果展示:
通過ViewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT)
對(duì)左側(cè)邊緣進(jìn)行觸摸刷允,通過Callback.onEdgeDragStarted()
和ViewDragHelper.captureChildView()
對(duì)需要捕獲的子View使用操作冤留。
示例代碼表示,可以在左側(cè)或者上側(cè)邊緣拖拽id為tv2
的子View:
ViewDragHelper:
//對(duì)屏幕左側(cè)或上側(cè)進(jìn)行邊緣觸摸
mViewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT|ViewDragHelper.EDGE_TOP);
Callback:
@Override
public void onEdgeDragStarted(int edgeFlags, int pointerId) {
super.onEdgeDragStarted(edgeFlags, pointerId);
View childView = mViewGroup.findViewById(R.id.tv2);
mDragHelper.captureChildView(childView,pointerId);
}
2.3.3 子View拖拽树灶,在松手后回到原位
效果展示:
這里需要重寫ViewGroup的computeScroll()
方法纤怒,并且調(diào)用ViewDragHelper的continueSettling()
方法對(duì)子View滾動(dòng)位置實(shí)時(shí)刷新。
除此之外破托,還需要在tryCaptureView()
的時(shí)候記錄子View的初始left
和top
肪跋,并且調(diào)用ViewDragHelper
的settleCapturedViewAt()
方法對(duì)子View進(jìn)行復(fù)位。
示例代碼是復(fù)位子View的id為tv1
的View:
ViewGroup:
對(duì)子View的位置進(jìn)行判斷土砂,并不斷刷新州既。
@Override
public void computeScroll() {
if(mViewDragHelper.continueSettling(true)){
invalidate();
}
super.computeScroll();
}
Callback:
//捕獲View時(shí)記錄其left和top
@Override
public boolean tryCaptureView(View child, int pointerId) {
if(child.getId() == R.id.tv1){
originLeft = child.getLeft();
originTop = child.getTop();
}
return true;
}
//松手時(shí)交給ViewDragHelper的settleCapturedViewAt()進(jìn)行復(fù)位。
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
if(releasedChild.getId() == R.id.tv1){
mDragHelper.settleCapturedViewAt(originLeft,originTop);
mViewGroup.invalidate();
}
}
2.4 注意事項(xiàng)
在我們的舉例中萝映,如果你將我們的子控件換成Button或者將子控件的clickable
設(shè)置為true
(子View能消費(fèi)觸摸事件)吴叶,你會(huì)發(fā)現(xiàn)子View無法被拖拽了!原因在于在ViewGroup的onInterceptTouchEvent()
方法中序臂,我們返回的是ViewDragHelper.shouldInterceptTouchEvent(ev)
蚌卤,這樣會(huì)導(dǎo)致ViewGroup的onTouchEvent()
不被調(diào)用,這樣就導(dǎo)致我們的ViewDragHelper.processTouchEvent()
不被調(diào)用奥秆!
如果將onInterceptTouchEvent()
的返回值直接為true
的話逊彭,又會(huì)導(dǎo)致子View的點(diǎn)擊事件被攔截不被觸發(fā)!
查看ViewDragHelper.shouldInterceptTouchEvent(ev)
的源碼:
public boolean shouldInterceptTouchEvent(MotionEvent ev) {
final int action = MotionEventCompat.getActionMasked(ev);
switch (action) {
...
case MotionEvent.ACTION_MOVE: {
final int pointerCount = ev.getPointerCount();
for (int i = 0; i < pointerCount; i++) {
final int horizontalDragRange = mCallback.getViewHorizontalDragRange(
toCapture);
final int verticalDragRange = mCallback.getViewVerticalDragRange(toCapture);
// 如果getViewHorizontalDragRange和getViewVerticalDragRange的返回值都為0构订,則break
if (horizontalDragRange == 0 && verticalDragRange == 0) {
break;
}
// tryCaptureViewForDrag方法中會(huì)設(shè)置mDragState=STATE_DRAGGING
if (pastSlop && tryCaptureViewForDrag(toCapture, pointerId)) {
break;
}
}
break;
}
}
...
return mDragState == STATE_DRAGGING;
}
ViewDragHelper.shouldInterceptTouchEvent(ev)
的返回為true
的條件是ViewDragHelper.mDragState == STATE_DRAGGING
侮叮,然而mDragState
是在tryCaptureViewForDrag()
方法中被設(shè)置為STATE_DRAGGING
的。其中悼瘾,若horizontalDragRange
和verticalDragRange
一直為0囊榜,則mDragState
無法設(shè)置為STATE_DRAGGING
。并且horizontalDragRange
和verticalDragRange
是在ViewDragHelper.getViewVerticalDragRange()
和ViewDragHelper.getViewHorizontalDragRange()
設(shè)置的亥宿,因此卸勺,只要這兩個(gè)方法的返回值大于0就可以正常捕獲了!
@Override
public int getViewVerticalDragRange(View child) {
return 1;
}
/**
* 這個(gè)用來控制橫向移動(dòng)的邊界范圍烫扼,單位是像素曙求。
* @param child
* @return 返回值大于0時(shí)才能捕獲子View
*/
@Override
public int getViewHorizontalDragRange(View child) {
return 1;
}
2.5 ViewDragHelper的一些其它回調(diào)及回調(diào)調(diào)用順序
2.5.1 其它回調(diào)方法
/**
* mDragState改變時(shí)回調(diào)
* STATE_IDLE:所有的View處于靜止空閑狀態(tài)
* STATE_DRAGGING:某個(gè)View正在被用戶拖動(dòng)(用戶正在與設(shè)備交互)
* STATE_SETTLING:某個(gè)View正在安置狀態(tài)中(用戶并沒有交互操作),就是自動(dòng)滾動(dòng)的過程中
* @param state
*/
@Override
public void onViewDragStateChanged(int state) {
super.onViewDragStateChanged(state);
}
/**
* 當(dāng)捕獲的子View的位置發(fā)生改變時(shí)回調(diào)
* @param changedView
* @param left
* @param top
* @param dx
* @param dy
*/
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
super.onViewPositionChanged(changedView, left, top, dx, dy);
}
/**
* 當(dāng)子View被捕獲時(shí)回調(diào)
* @param capturedChild
* @param activePointerId
*/
@Override
public void onViewCaptured(View capturedChild, int activePointerId) {
super.onViewCaptured(capturedChild, activePointerId);
}
/**
* 當(dāng)觸摸到邊界時(shí)回調(diào)。
* @param edgeFlags
* @param pointerId
*/
@Override
public void onEdgeTouched(int edgeFlags, int pointerId) {
super.onEdgeTouched(edgeFlags, pointerId);
}
/**
*
* @param edgeFlags
* @return true的時(shí)候會(huì)鎖住當(dāng)前的邊界圆到,false則unLock怎抛。
*/
@Override
public boolean onEdgeLock(int edgeFlags) {
return super.onEdgeLock(edgeFlags);
}
/**
* 改變同一個(gè)坐標(biāo)(x,y)去尋找captureView位置的方法。(具體作用暫時(shí)未知)
* @param index
* @return
*/
@Override
public int getOrderedChildIndex(int index) {
return super.getOrderedChildIndex(index);
}
2.5.2 回調(diào)調(diào)用順序:
shouldInterceptTouchEvent:
-
ACTION_DOWN
:getOrderedChildIndex(findTopChildUnder)
>onEdgeTouched()
-
ACTION_MOVE
:getOrderedChildIndex(findTopChildUnder)
>getViewHorizontalDragRange() & getViewVerticalDragRange(checkTouchSlop)
(MOVE中可能不止一次) >clampViewPositionHorizontal() & clampViewPositionVertical()
>onEdgeDragStarted()
>tryCaptureView()
>onViewCaptured()
>onViewDragStateChanged()
processTouchEvent:
-
ACTION_DOWN
:getOrderedChildIndex(findTopChildUnder)
>tryCaptureView()
>onViewCaptured()
>onViewDragStateChanged()
>onEdgeTouched()
-
ACTION_MOVE
:STATE == DRAGGING
>STATE!=DRAGGING
>onEdgeDragStarted
>getOrderedChildIndex(findTopChildUnder)
>getViewHorizontalDragRange& getViewVerticalDragRange(checkTouchSlop)
>tryCaptureView()
>onViewCaptured()
>onViewDragStateChanged()
2.6 小結(jié)
ViewDragHelper是一個(gè)強(qiáng)大的拖拽子View的輔助神器芽淡,大家用好這個(gè)類,可以大大提高我們的觸摸手勢(shì)的效率豆赏!
3挣菲、ItemTouchHelper(item觸摸幫手)
ItemTouchHelper是一個(gè)觸摸item時(shí)對(duì)item進(jìn)行處理的類。既然涉及到條目掷邦,那肯定是和一些和item有關(guān)的控件有關(guān)了白胀,沒錯(cuò),這個(gè)類就是一個(gè)針對(duì)RecyclerView
的工具類抚岗,用來處理RecyclerView
內(nèi)部的條目拖拽和滑動(dòng)事件的或杠,它可以讓條目拖拽到一個(gè)新的位置,也可以讓條目滑動(dòng)出來刪除等等場(chǎng)景宣蔚!
3.1 相關(guān)方法和接口介紹
ItemTouchHelper類的具體實(shí)現(xiàn)方法都在它的內(nèi)部類ItemTouchHelper.Callback
中向抢,ItemTouchHelper.Callback
中有三個(gè)必須重寫的方法,也是核心方法胚委。
我寫了一個(gè)類MyItemTouchHelperCallback
繼承ItemTouchHelper.Callback
挟鸠,它們的意義都在注釋中已標(biāo)明:
public static class MyItemTouchHelperCallback extends ItemTouchHelper.Callback{
//RecyclerView的Adapter
private ItemTouchHelperAdapter mAdapter;
public MyItemTouchHelperCallback(ItemTouchHelperAdapter adapter){
this.mAdapter = adapter;
}
/**
* 允許哪個(gè)方向的拖拽和滑動(dòng),一般配合makeMovementFlags(int,int)去使用
* @param recyclerView
* @param viewHolder
* @return
*/
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
//允許上下拖拽亩冬,和從右向左滑動(dòng)
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
int swipeFlags = ItemTouchHelper.LEFT;
//計(jì)算拖拽和滑動(dòng)的方向
return makeMovementFlags(dragFlags,swipeFlags);
}
/**
* 拖拽一個(gè)item到新位置時(shí)會(huì)調(diào)用此方法艘希,一般配合Adapter的notifyItemMoved()方法來交換兩個(gè)ViewHolder的位置
* @param recyclerView
* @param viewHolder
* @param target
* @return true表示已經(jīng)到達(dá)移動(dòng)目的地
*/
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
//交換item位置
Collections.swap(mAdapter.mDatas,viewHolder.getAdapterPosition(),target.getAdapterPosition());
mAdapter.notifyItemMoved(viewHolder.getAdapterPosition(),target.getAdapterPosition());
return true;
}
/**
* 當(dāng)滑動(dòng)到一定程度時(shí),松手會(huì)繼續(xù)滑動(dòng)硅急,然后調(diào)用此方法覆享,反之item會(huì)回到原位,不調(diào)用此方法
* @param viewHolder
* @param direction 滑動(dòng)的方向
*/
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
Log.e("zw", "direction : " + direction);
//刪除此item
mAdapter.mDatas.remove(viewHolder.getAdapterPosition());
mAdapter.notifyItemRemoved(viewHolder.getAdapterPosition());
}
}
ItemTouchHelper.Callback
中其它可以選擇重寫的方法:
/**
* 是否長(zhǎng)按時(shí)才使拖拽生效
* @return 默認(rèn)返回true
*/
@Override
public boolean isLongPressDragEnabled() {
return super.isLongPressDragEnabled();
}
/**
* 是否可以進(jìn)行滑動(dòng)時(shí)的刪除動(dòng)作营袜,也就是是否能調(diào)用onSwiped()方法
* @return 默認(rèn)返回true
*/
@Override
public boolean isItemViewSwipeEnabled() {
return super.isItemViewSwipeEnabled();
}
/**
* 靜止?fàn)顟B(tài)變?yōu)橥献Щ蛘呋瑒?dòng)的時(shí)候會(huì)回調(diào)
* @param viewHolder
* @param actionState 當(dāng)前的狀態(tài)
*/
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
super.onSelectedChanged(viewHolder, actionState);
}
/**
* 當(dāng)用戶操作完畢某個(gè)item并且其動(dòng)畫也結(jié)束后會(huì)調(diào)用該方法撒顿,一般我們?cè)谠摲椒▋?nèi)恢復(fù)ItemView的初始狀態(tài),防止由于復(fù)用而產(chǎn)生的顯示錯(cuò)亂問題连茧。
* @param recyclerView
* @param viewHolder
*/
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
}
/**
* 實(shí)現(xiàn)我們自定義的交互規(guī)則或者自定義的動(dòng)畫效果
* @param c
* @param recyclerView
* @param viewHolder
* @param dX
* @param dY
* @param actionState
* @param isCurrentlyActive
*/
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
還有一些方法沒有做過多研究核蘸,這里就不列舉了。
注意事項(xiàng):調(diào)用onSwiped()
方法時(shí)啸驯,如果沒有配合RecyclerViewAdapter
的notifyItemRemoved()
方法則會(huì)讓Item的內(nèi)容給移除出去客扎,但是此Item還在原地,也就是會(huì)留下一個(gè)空白的view罚斗!
3.2 ItemTouchHelper的使用方式
3.2.1 RecyclerView
初始化
實(shí)現(xiàn)RecyclerViewAdapter
和ViewHolder
類徙鱼,RecyclerView設(shè)置LayoutManager
等,很常規(guī)的寫法,這里就不貼代碼了袱吆。
3.2.1 RecyclerView
設(shè)置ItemTouchHelper
ItemTouchHelperAdapter.MyItemTouchHelperCallback touchHelperCallback
= new ItemTouchHelperAdapter.MyItemTouchHelperCallback(adapter);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(touchHelperCallback);
itemTouchHelper.attachToRecyclerView(recyclerView);
3.3 ItemTouchHelper的使用場(chǎng)景簡(jiǎn)介
3.3.1 實(shí)現(xiàn)上下拖拽換位置功能和向左滑動(dòng)刪除功能
此功能需要重寫ItemTouchHelper.Callback
的getMovementFlags()
方法厌衙、onMove()
方法和onSwiped()
方法,并且要配合RecyclerViewAdapter
的notifyItemMoved()
和notifyItemRemoved()
方法使用绞绒。
ItemTouchHelper.Callback
關(guān)鍵代碼:
//RecyclerView的Adapter
private ItemTouchHelperAdapter mAdapter;
public MyItemTouchHelperCallback(ItemTouchHelperAdapter adapter){
this.mAdapter = adapter;
}
/**
* 允許哪個(gè)方向的拖拽和滑動(dòng)婶希,一般配合makeMovementFlags(int,int)去使用
* @param recyclerView
* @param viewHolder
* @return
*/
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
//允許上下拖拽,和從右向左滑動(dòng)
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
int swipeFlags = ItemTouchHelper.LEFT;
//計(jì)算拖拽和滑動(dòng)的方向
return makeMovementFlags(dragFlags,swipeFlags);
}
/**
* 拖拽一個(gè)item到新位置時(shí)會(huì)調(diào)用此方法蓬衡,一般配合Adapter的notifyItemMoved()方法來交換兩個(gè)ViewHolder的位置
* @param recyclerView
* @param viewHolder
* @param target
* @return true表示已經(jīng)到達(dá)移動(dòng)目的地
*/
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
//交換item位置
Collections.swap(mAdapter.mDatas,viewHolder.getAdapterPosition(),target.getAdapterPosition());
mAdapter.notifyItemMoved(viewHolder.getAdapterPosition(),target.getAdapterPosition());
return true;
}
/**
* 當(dāng)滑動(dòng)到一定程度時(shí)喻杈,松手會(huì)繼續(xù)滑動(dòng),然后調(diào)用此方法狰晚,反之item會(huì)回到原位筒饰,不調(diào)用此方法
* @param viewHolder
* @param direction 滑動(dòng)的方向
*/
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
Log.e("zw", "direction : " + direction);
//刪除此item
mAdapter.mDatas.remove(viewHolder.getAdapterPosition());
mAdapter.notifyItemRemoved(viewHolder.getAdapterPosition());
}
實(shí)現(xiàn)效果:
3.3.1 向左滑動(dòng)刪除時(shí)帶有動(dòng)畫功能(透明度)
此示例為向左滑動(dòng)時(shí),先顯示黑色的View壁晒,當(dāng)黑色的TextView顯示完全時(shí)瓷们,再向右滑動(dòng)時(shí),更改黑色TextView的文字顏色透明度秒咐。
先上顯示效果:
更改我們的布局文件谬晕,在item布局的屏幕右邊放置我們的TextView,布局文件如下:
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:layout_width="0dp"
android:layout_height="50dp"
android:padding="10dp"
android:gravity="center"
android:id="@+id/tv_item"
android:textStyle="bold"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintWidth_percent="0.5"
/>
<TextView
android:id="@+id/tv_item1"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:background="#000000"
android:gravity="center"
android:padding="10dp"
android:text="繼續(xù)滑動(dòng)刪除"
android:textColor="@android:color/white"
android:textStyle="bold"
app:layout_constraintLeft_toRightOf="parent"
/>
</android.support.constraint.ConstraintLayout>
其中用到了ConstraintLayout
反镇,這個(gè)控件對(duì)于布局十分的方便固蚤,并且性能也很優(yōu)秀,關(guān)于此控件的更多使用方法可以參考我這篇博客內(nèi)容: ConstraintLayout——約束性布局學(xué)習(xí)
這時(shí)候我們會(huì)發(fā)現(xiàn)向右側(cè)滑動(dòng)時(shí)歹茶,第二個(gè)TextView根本出不來夕玩,系統(tǒng)調(diào)用的是當(dāng)前屏幕可見部分布局的setTranslationX()
方法,因此無論如何惊豺,我們的第二個(gè)TextVeiw都不會(huì)顯示燎孟,因此我們要將setTranslationX()
改變成scrollTo()
方法。
另外尸昧,我們還要重寫clearView()
方法揩页,來將有動(dòng)畫效果的條目給復(fù)位到初始化狀態(tài),以免復(fù)用的時(shí)候產(chǎn)生顯示誤差烹俗。
ItemTouchHelper.Callback
關(guān)鍵代碼:
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
//僅僅針對(duì)Item的滑動(dòng)事件
if(actionState == ItemTouchHelper.ACTION_STATE_SWIPE){
TextView alphaView = viewHolder.itemView.findViewById(R.id.tv_item1);
int viewWidth = alphaView.getWidth();
if(Math.abs(dX) < viewWidth){
//滾動(dòng)
viewHolder.itemView.scrollTo((int) -dX,0);
} else if(Math.abs(dX) < recyclerView.getWidth() / 2){
//滑動(dòng)的透明度
float childAlpha = Math.abs(dX) - viewWidth;
float fatherAlpha = recyclerView.getWidth() / 2 - viewWidth;
float alpha = childAlpha / fatherAlpha;
int color = Color.argb((int)(alpha * 255),255,255,255);
alphaView.setTextColor(color);
}
} else {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
}
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
//復(fù)位
viewHolder.itemView.setScrollX(0);
TextView alphaView = viewHolder.itemView.findViewById(R.id.tv_item1);
alphaView.setTextColor(Color.parseColor("#00FFFFFF"));
}
3.4 小結(jié)
ItemTouchHelper是RecyclerView
自帶的一個(gè)幫助我們實(shí)現(xiàn)拖拽和滑動(dòng)的工具類爆侣,通過這個(gè)類我們可以很輕松的實(shí)現(xiàn)在RecyclerView
上炫酷的拖拽和滑動(dòng)效果!
總結(jié)
上面介紹了三種和觸摸有關(guān)的輔助類:
GestureDetector:用來監(jiān)聽我們觸摸屏幕的手勢(shì)幢妄。
ViewDragHelper:幫助我們處理子控件的拖拽事件兔仰。
ItemTouchHelper:
RecyclerView
上幫助我們拖拽和滑動(dòng)子條目的利器。
相信通過上述的例子蕉鸳,你應(yīng)該也學(xué)會(huì)了怎么使用它們乎赴,在工作中用上這些輔助類忍法,可以讓我們的工作效率大大提高,實(shí)現(xiàn)事半功倍的效果榕吼!
最后饿序,本示例所有效果,見此GitHub Demo