Android 滑動詳解-思維導圖版
滑動
版本:2018/05/12-1(11:11)
基礎知識
View
1、什麼是View
- View是所有控制元件的基類
- View有一個特殊子類ViewGroup,ViewGroup能包含一組View,但ViewGroup的本身也是View。
- 由於View和ViewGourp的存在,意味著View可以是單個控制元件也可以是一組控制元件。這種結構形成了View樹。
2、View的位置引數:top,left,right,bottom
- top-左上角的y軸座標(全部是相對座標,相對於父容器)
- left-左上角的x軸座標
- right-右下角的x軸座標
- bottom-右下角的y軸座標
- 在View中獲取這些成員變數的方法,是getLeft(),getRight(),getTop(),getBottom()即可
3、View從3.0開始新增的引數:x,y,translationX,translationY
- x,y是View當前左上角的座標
- translationX,translationY是在滑動/動畫後,View當前位置和View最原始位置的距離。
- 因此得出等式:x(View左上角當前位置) = left(View左上角初始位置) + translationX(View左上角偏移的距離)
- View平移時top、left等引數不變,改變的是x,y,tranlsationX和tranlsationY
座標系
4、Android座標系
- Android座標系以
螢幕左上角
為原點,向右X軸為正半軸,向下Y軸為正半軸- 觸控事件中getRawX()和getRawY()獲得的就是Android座標系的座標
- Android中通過
getLocationOnScreen(intlocation[])
能獲得當前檢視的左上
5、View座標系
- View座標系是以當前檢視的
父檢視的左上角
作為原點建立的座標系,方向和Android座標系一致- 觸控事件中getX()和getY()獲得的就是檢視座標系中的座標
MotionEvent
6、MotionEvent的作用
MotionEvent
用於記錄移動事件
- 包括滑鼠、手機、traceball、pen的移動事件。
7、MotionEvent包含的手指觸控事件
- ACTION_DOWN\MOVE\UP對應三個觸控事件。
- getX/getY能獲得觸控點的座標,相當於當前View左上角的(x,y)
- getRawX/getRawY,獲得觸控點相當於手機左上角的(x,y)座標
滑動的7種實現方法
8、View滑動的7種方法:
- layout:對View進行重新佈局定位。在onTouchEvent()方法中獲得控制元件滑動前後的偏移。然後通過layout方法重新設定。
- offsetLeftAndRight和offsetTopAndBottom:系統提供上下/左右同時偏移的API。onTouchEvent()中呼叫
- LayoutParams: 更改自身佈局引數
- scrollTo/scrollBy: 本質是移動View的內容,需要通過父容器的該方法來滑動當前View
- Scroller: 平滑滑動,通過過載
computeScroll()
,使用scrollTo/scrollBy
完成滑動效果。- 屬性動畫: 動畫對View進行滑動
- ViewDragHelper: 谷歌提供的輔助類,用於完成各種拖拽效果。
15、Layout實現滑動
/*================================*
* onTouchEvent-進行偏移計算,之後呼叫layout
*================================*/
public boolean onTouchEvent(MotionEvent event) {
float curX = event.getX(); //手指實時位置的X
float curY = event.getY(); //Y
switch(event.getAction()){
case MotionEvent.ACTION_MOVE:
int offsetX = (int)(curX - downX); //X偏移
int offsetY = (int)(curY - downY); //Y偏移
/**=============================================
* 變化後的距離=getLeft(當前控制元件距離父控制元件左邊的距離)+偏移量——呼叫layout重新佈局
*============================================*/
layout(getLeft() + offsetX, getTop() + offsetY, getRight() + offsetX, getBottom() + offsetY);
break;
case MotionEvent.ACTION_DOWN:
downX = curX; //按下時的座標
downY = curY;
break;
}
return true;
}
16、offsetLeftAndRight和offsetTopAndBottom實現滑動
/*================================*
* onTouchEvent-進行偏移計算,直接呼叫
*================================*/
public boolean onTouchEvent(MotionEvent event) {
float curX = event.getX(); //手指實時位置的X
float curY = event.getY(); //Y
switch(event.getAction()){
case MotionEvent.ACTION_MOVE:
int offsetX = (int)(curX - downX); //X偏移
int offsetY = (int)(curY - downY); //Y偏移
/**=============================================
* 對left和right, top和bottom同時偏移
*============================================*/
offsetLeftAndRight(offsetX);
offsetTopAndBottom(offsetY);
break;
case MotionEvent.ACTION_DOWN:
downX = curX; //按下時的座標
downY = curY;
break;
}
return true;
}
17、LayoutParams實現滑動:
- 通過父控制元件設定View在父控制元件的位置,但需要指定父佈局的型別,不好
- 用ViewGroup的MariginLayoutParams的方法去設定margin
//方法一:通過佈局設定在父控制元件的位置。但是必須要有父控制元件, 而且要指定父佈局的型別,不好的方法。
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);
/**===============================================
* 方法二:用ViewGroup的MarginLayoutParams的方法去設定marign
* 優點:相比於上面方法, 就不需要知道父佈局的型別。
* 缺點:滑動到右側控制元件會縮小
*===============================================*/
ViewGroup.MarginLayoutParams mlayoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
mlayoutParams.leftMargin = getLeft() + offsetX;
mlayoutParams.topMargin = getTop() + offsetY;
setLayoutParams(mlayoutParams);
18、scrollTo\scrollBy實現滑動
- 都是View提供的方法。
- scrollTo-直接到新的x,y座標處。
- scrollBy-基於當前位置的相對滑動。
- scrollBy-內部是呼叫scrollTo.
scrollTo\scrollBy
, 效果是移動View的內容,因此需要在View的父控制元件中呼叫。
// 1、移動到目標位置
((View)getParent()).scrollTo(dstX, dstY);
// 2、相對滑動:且scrollBy是父容器進行滑動,因此偏移量需要取負
((View)getParent()).scrollBy(-offsetX, -offsetY);
19、scrollTo/By內部的mScrollX和mScrollY的意義
- mScrollX的值,相當於手機螢幕相對於View左邊緣向右移動的距離,手機螢幕向右移動時,mScrollX的值為正;手機螢幕向左移動(等價於View向右移動),mScrollX的值為負。
- mScrollY和X的情況相似,手機螢幕向下移動,mScrollY為+正值;手機螢幕向上移動,mScrollY為-負值。
- mScrollX/Y是根據第一次滑動前的位置來獲得的,例如:第一次向左滑動200(等於手機螢幕向右滑動200),mScrollX = 200;第二次向右滑動50, mScrollX = 200 + (-50)= 150,而不是(-50)。
20、動畫實現滑動的方法
- 可以通過傳統動畫或者屬性動畫的方式實現
- 傳統動畫需要通過設定fillAfter為true來保留動畫後的狀態(但是無法在動畫後的位置進行點選操作,這方面還是屬性動畫好)
- 屬性動畫會保留動畫後的狀態,能夠點選。
21、ViewDragHelper
- 通過
ViewDragHelper
去自定義ViewGroup
讓其子View
具有滑動效果。
彈性滑動
Scroller
1、Scroller的作用
- 用於
封裝滑動
- 提供了
基於時間的滑動偏移值
,但是實際滑動需要我們去負責。
1、Scroller的要點
- 呼叫startScroll方法時,
Scroller只是單純的儲存引數
- 之後的invalidate方法導致的View重繪
- View重繪之後draw方法會呼叫自己實現的computeScroll(),才真正實現了滑動
1、Scroller的使用
// 1、初始化
Scroller mScroller = new Scroller(getContext());
// 2、重寫View的方法computeScroll
public void computeScroll() {
super.computeScroll();
//判斷scroller是否執行完畢。
if(mScroller.computeScrollOffset()){
((View)getParent()).scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
//通過重繪來不斷呼叫 computeScroll
invalidate();
}
}
// 3、開始滑動
case MotionEvent.ACTION_UP:
View viewGroup = (View) getParent();
mScroller.startScroll(viewGroup.getScrollX(), viewGroup.getScrollY(),
-viewGroup.getScrollX(), -viewGroup.getScrollY());
invalidate();
break;
1、Scroller工作原理
- Scroller本身不能實現View的滑動,需要配合View的computeScroll方法實現彈性滑動
- 不斷讓View重繪,每一次重繪距離滑動的開始時間有一個時間間隔,通過該時間可以得到View當前的滑動距離
- View的每次重繪都會導致View的小幅滑動,多次小幅滑動就組成了彈性滑動
動畫
4、通過動畫實現彈性滑動
延時策略
5、通過延時策略實現彈性滑動。
- 通過handler、View的postDelayed、或者執行緒的sleep方法。
- 實現思路:例如將View滑動100畫素,通過Handler可以每100ms傳送一次訊息讓其滑動10畫素,最終會在1000ms內滑動100畫素。
側滑選單
DraweLayout
1、DrawerLayout是什麼?
側滑選單
。
2、DrawerLayout的使用
側滑選單
的佈局需要用layout_gravity
屬性指定。主體View
的佈局中寬高
需要為match_parent
且不能有layout_gravity屬性
//佈局檔案
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
android:id="@+id/md_drawerlayout"
xxx>
<Button
android:id="@+id/md_slidemenu_text"
android:layout_width="match_parent"
android:layout_height="match_parent"
xxx
android:layout_gravity="start"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
xxx主體xxx
</LinearLayout>
</android.support.v4.widget.DrawerLayout>
DrawerLayout drawerLayout = findViewById(R.id.md_drawerlayout);
Button button = findViewById(R.id.md_slidemenu_text);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
drawerLayout.closeDrawer(button); //關閉側滑選單
}
});
3、DrawerLayout中android:layout_gravity
屬性
left/start
:選單位於左側top/bottom
:選單位於右側
4、DrawerLayout的方法
1-開啟
drawerLayout.openDrawer(button);
2-關閉
drawerLayout.closeDrawer(button);
3-設定監聽器(DrawerListener)
drawerLayout.setDrawerListener(new DrawerLayout.DrawerListener() {
//滑動時
@Override
public void onDrawerSlide(View drawerView, float slideOffset) {
}
//開啟時
@Override
public void onDrawerOpened(View drawerView) {
}
//關閉時
@Override
public void onDrawerClosed(View drawerView) {
}
//狀態改變時:{@link #STATE_IDLE}, {@link #STATE_DRAGGING} or {@link #STATE_SETTLING}.
@Override
public void onDrawerStateChanged(int newState) {
}
});
4-設定監聽器(SimpleDrawerListener)
//可以選擇性實現其中的部分回撥介面
drawerLayout.setDrawerListener(new DrawerLayout.SimpleDrawerListener() {
@Override
public void onDrawerSlide(View drawerView, float slideOffset) {
super.onDrawerSlide(drawerView, slideOffset);
}
});
SlidingPanelLayout
1、SlidingPaneLayout是什麼
- 提供一種類似於
DrawerLayout
的側滑選單效果,“效果並不好”xml
佈局中第一個ChildView
就是左側選單的內容
,第二個ChildView
就是主體內容
2、SlidingPaneLayout的使用
<android.support.v4.widget.SlidingPaneLayout
android:id="@+id/md_slidingpanelayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/md_slidemenu_text"
android:layout_width="150dp"
android:layout_height="match_parent"
xxx左側內容xxx/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
xxx主體內容xxx
</LinearLayout>
</android.support.v4.widget.SlidingPaneLayout>
SlidingPaneLayout slidingPaneLayout = findViewById(R.id.md_slidingpanelayout);
Button button = findViewById(R.id.md_slidemenu_text);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//關閉Pane
slidingPaneLayout.closePane();
}
});
3、SlidingPaneLayout的方法
// 1. 開啟Pane
slidingPaneLayout.openPane();
// 2. 關閉Pane
slidingPaneLayout.closePane();
// 3. 右側主體頁面縮排去的陰影漸變色
slidingPaneLayout.setSliderFadeColor(Color.BLUE);
// 4. 左側面板縮排去的陰影漸變色
slidingPaneLayout.setCoveredFadeColor(Color.GRAY);
// 5. 監聽器
slidingPaneLayout.setPanelSlideListener(new SlidingPaneLayout.PanelSlideListener() {
/**
* 左側面板在滑動
* @param panel 被移走的主體View
* @param slideOffset 滑動的百分比(0~1)
*/
@Override
public void onPanelSlide(View panel, float slideOffset) {
}
//左側Pane已經開啟
@Override
public void onPanelOpened(View panel) {
}
//左側Pane已經關閉
@Override
public void onPanelClosed(View panel) {
}
});
NavigationView
NavigationView的作用
- 配合
DrawerLayout
使用用於實現其中的左側選單效果
- Google在5.0之後推出NavigationView,
左側選單效果
整體上分為兩部分,上面一部分叫做HeaderLayout
,下面的那些點選項都是menu
。
ViewDragHelper
1、ViewDragHelper的作用
- 用於
編寫自定義ViewGroup
的工具類
- 位於
android.support.v4.widget.
。- 提供一系列
操作和狀態追蹤
用於幫助使用者進行拖拽和定位子View
2、ViewDragHelper的簡單例項
實現ChildView可以自由拖拽的ViewGroup
1. 建立ViewDragHelper
2. 將ViewGroup
的事件處理交給ViewDragHelper
3. 自定義ViewDragHelper.Callback
實現一些觸控回撥,用於實現效果。
public class ScrollViewGroup extends LinearLayout{
private ViewDragHelper mViewDragHelper;
public ScrollViewGroup(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
/**==============================================
* 1、建立ViewDragHelper
*===========================================*/
mViewDragHelper = ViewDragHelper.create(
this, //ViewGroup
1f, //設定touchSlop-sensitivity越大,touchslop越小
new MyViewDragHelperCallback()); //使用者觸控事件的回撥
}
/**==============================================
* 2、ViewGroup的事件處理都交給ViewDragHelper
*===========================================*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {\
//轉交中斷處理權
return mViewDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//交由處理事件,且返回true表示處理後續事件。
mViewDragHelper.processTouchEvent(event);
return true;
}
/**==============================================
* 3、ViewDragHelper.Callback
*===========================================*/
class MyViewDragHelperCallback extends ViewDragHelper.Callback{
/**
* 3.1-決定哪些View可以捕獲
* @return true-捕獲該child; false-不處理
*/
@Override
public boolean tryCaptureView(View child, int pointerId) {
return true;
}
/**
* 3.2-控制Child在水平方向上的邊界
* @return 範圍限制後的新left(當前child的left)
*/
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
// left範圍為 leftPadding ~ (getWidth() - getPaddingRight() - child.getWidth())
final int leftMinBound = getPaddingLeft();
final int leftMaxBound = getWidth() - getPaddingRight() - child.getWidth();
final int newLeft = Math.min(Math.max(left, leftMinBound), leftMaxBound);
return newLeft;
}
/**
* 3.3-控制Child在垂直方向上的邊界
* @return 範圍限制後的新top
*/
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
final int topMinBound = getPaddingTop();
final int topMaxBound = getHeight() - getPaddingBottom() - child.getHeight();
final int newLeft = Math.min(Math.max(top, topMinBound), topMaxBound);
return newLeft;
}
}
}
3、ChilView為Button或者clickable = true
時無法拖動的解決辦法
- 正常流程: 如果子View不消耗事件,那麼整個手勢(DOWN-MOVE-UP)都是直接進入onTouchEvent,在onTouchEvent的DOWN的時候就確定了captureView。
- 子View消耗事件:會先走onInterceptTouchEvent方法,判斷是否可以捕獲,而在判斷的過程中會去判斷另外兩個回撥的方法:getViewHorizontalDragRange和getViewVerticalDragRange,只有這兩個方法返回大於0的值才能正常的捕獲。
/**
* 返回子View水平滑動範圍。
* return 0: 則該ChildView不會滑動。
*/
@Override
public int getViewHorizontalDragRange(View child)
{
return getMeasuredWidth()-child.getMeasuredWidth();
}
/**
* 返回子View垂直滑動範圍。
* return 0: 則該ChildView不會滑動。
*/
@Override
public int getViewVerticalDragRange(View child)
{
return getMeasuredHeight()-child.getMeasuredHeight();
}
ViewDragHelper.Callback
1、ViewDragHelper.Callback的方法和作用
方法 | 作用 |
---|---|
onViewDragStateChanged() | 當ViewDragHelper狀態發生變化時回撥(IDLE ,DRAGGING ,SETTING-自動滾動時 ) |
onViewPositionChanged() | ChildView位置改變時回撥 |
onViewCaptured() | 捕獲ChildView時回撥 |
onViewReleased() | 鬆開ChildView時回撥 |
onEdgeTouched() | 當觸控到邊界時回撥 |
onEdgeLock() | true的時候會鎖住當前的邊界,false則unLock。 |
onEdgeDragStarted() | 邊緣拖拽開始時回撥 |
getOrderedChildIndex() | 在同一個座標(x,y)下應該去獲取哪一個View。(mViewDragHelper.findTopChildUnder中需要用到) |
getViewHorizontalDragRange() | 獲取水平方向上的拖拽範圍 |
getViewVerticalDragRange() | 獲取垂直方向上的拖拽範圍 |
tryCaptureView() | 判斷是否捕獲當前View |
clampViewPositionHorizontal() | 控制Child在水平方向上的邊界 |
clampViewPositionVertical() | 控制Child在垂直方向上的邊界 |
/**
* ChildView不在被拖拽的時候呼叫。
*
* 1. 想要將ChilView 安置到某個位置, 需要呼叫{@link ViewDragHelper#settleCapturedViewAt(int, int)}
* 2. 想要將ChilView fling到某個位置, 需要呼叫{@link ViewDragHelper#flingCapturedView(int, int, int, int)}
*
* 注意:
* 1. 如果呼叫這些方法, ViewDragHelper會進入{@link ViewDragHelper#STATE_SETTLING}模式,
* 此時直到View完全停止, View的捕獲都不會停止。
* 2. 如果不呼叫這些方法,View會停止且ViewDragHelper會處於{@link ViewDragHelper#STATE_IDLE}模式
*
* {@link View#computeScroll()}
*
* @param xvel 手指離開螢幕時-X軸速度(畫素/秒)
* @param yvel 手指離開螢幕時-Y軸速度(畫素/秒)
*/
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
if (releasedChild == mAutoBackView){
mViewDragHelper.settleCapturedViewAt(mAuthoBackOriginPoint.x, mAuthoBackOriginPoint.y);
invalidate();
}
}
/**
* 觸控到邊緣
*/
@Override
public void onEdgeTouched(int edgeFlags, int pointerId) {}
/**
* true的時候會鎖住當前的邊界,false則unLock。
*/
@Override
public boolean onEdgeLock(int edgeFlags) {
return false;
}
/**
* 邊緣拖動的時候回撥。能繞過“tryCaptureView”
*
* @param edgeFlags A combination of edge flags describing the edge(s) dragged
* @param pointerId ID of the pointer touching the described edge(s)
* @see #EDGE_LEFT
* @see #EDGE_TOP
* @see #EDGE_RIGHT
* @see #EDGE_BOTTOM
*/
@Override
public void onEdgeDragStarted(int edgeFlags, int pointerId) {
super.onEdgeDragStarted(edgeFlags, pointerId);
//主動通過captureChildView進行捕獲
mViewDragHelper.captureChildView(mEdgeDragView, pointerId);
}
/**
* 決定ChildView的Z軸上的順序。(mViewDragHelper.findTopChildUnder(x, y)中需要獲取到座標(x,y)上最上層的子View)
*
* @param index 查詢的呼叫位置
* @return 在呼叫位置上View的index
*/
@Override
public int getOrderedChildIndex(int index) {
return index;
}
/**
* 返回子View水平滑動範圍。
* return 0: 則該ChildView不會滑動。
*/
@Override
public int getViewHorizontalDragRange(View child)
{
return getMeasuredWidth()-child.getMeasuredWidth();
}
/**
* 返回子View垂直滑動範圍。
* return 0: 則該ChildView不會滑動。
*/
@Override
public int getViewVerticalDragRange(View child)
{
return getMeasuredHeight()-child.getMeasuredHeight();
}
/**
* 決定哪些View可以捕獲
* @return true-捕獲該child; false-不處理
*/
@Override
public boolean tryCaptureView(View child, int pointerId) {
if(child == mEdgeDragView) return false;
return true;
}
/**
* 控制Child在水平方向上的邊界
* @return 範圍限制後的新left(當前child的left)
*/
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
// left範圍為 leftPadding ~ (getWidth() - getPaddingRight() - child.getWidth())
final int leftMinBound = getPaddingLeft();
final int leftMaxBound = getWidth() - getPaddingRight() - child.getWidth();
final int newLeft = Math.min(Math.max(left, leftMinBound), leftMaxBound);
return newLeft;
}
/**
* 控制Child在垂直方向上的邊界
* @return 範圍限制後的新top
*/
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
final int topMinBound = getPaddingTop();
final int topMaxBound = getHeight() - getPaddingBottom() - child.getHeight();
final int newLeft = Math.min(Math.max(top, topMinBound), topMaxBound);
return newLeft;
}
@Override
public void onViewDragStateChanged(int state) {}
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {}
@Override
public void onViewCaptured(View capturedChild, int activePointerId) {}
2、shouldInterceptTouchEvent中方法回撥順序
DOWN:
getOrderedChildIndex(findTopChildUnder)
->onEdgeTouched
MOVE:
getOrderedChildIndex(findTopChildUnder)
->getViewHorizontalDragRange & getViewVerticalDragRange(checkTouchSlop)(MOVE中可能不止一次)
->clampViewPositionHorizontal & clampViewPositionVertical
->onEdgeDragStarted
->tryCaptureView
->onViewCaptured
->onViewDragStateChanged
3、processTouchEvent中方法回撥順序
DOWN:
getOrderedChildIndex(findTopChildUnder)
->tryCaptureView
->onViewCaptured
->onViewDragStateChanged
->onEdgeTouched
MOVE:
->STATE==DRAGGING:dragTo
->STATE!=DRAGGING:
onEdgeDragStarted
->getOrderedChildIndex(findTopChildUnder)
->getViewHorizontalDragRange & getViewVerticalDragRange(checkTouchSlop)
->tryCaptureView
->onViewCaptured
->onViewDragStateChanged
擴充套件例項
1、ViewDragHelper例項:拖拽返回、邊緣拖拽
2、ViewGroup如何去獲取子控制元件
View mNormalView;
View mAutoBackView;
View mEdgeDragView;
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mNormalView = getChildAt(0);
mAutoBackView = getChildAt(1);
mEdgeDragView = getChildAt(2);
}
3、ViewGroup如何去獲取某ChildView的初始座標
Point mAuthoBackOriginPoint = new Point();
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
mAuthoBackOriginPoint.x = mAutoBackView.getLeft();
mAuthoBackOriginPoint.y = mAutoBackView.getTop();
}
4、ViewGroup如何進行拖拽返回
1-Callback的
onViewReleased
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
//1、改變位置
if (releasedChild == mAutoBackView){
mViewDragHelper.settleCapturedViewAt(mAuthoBackOriginPoint.x, mAuthoBackOriginPoint.y);
invalidate();
}
}
2-內部是
mScroller.startScroll
因此需要computeScroll
配合
@Override
public void computeScroll()
{
if(mViewDragHelper.continueSettling(true))
{
invalidate();
}
}
5、ViewDragHelper的邊緣拖動
/**
* 遮蔽"目標控制元件"的滑動效果
*/
@Override
public boolean tryCaptureView(View child, int pointerId) {
if(child == mEdgeDragView) return false;
return true;
}
/**
* 邊緣拖動的時候回撥。能繞過“tryCaptureView”
*/
@Override
public void onEdgeDragStarted(int edgeFlags, int pointerId) {
super.onEdgeDragStarted(edgeFlags, pointerId);
//主動通過captureChildView進行捕獲
mViewDragHelper.captureChildView(mEdgeDragView, pointerId);
}
// 設定邊緣追蹤
mViewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT | ViewDragHelper.EDGE_TOP);
QQ側滑選單
public class DragViewGroup extends FrameLayout {
//側滑類
private ViewDragHelper mViewDragHelper;
private View mMenuView,mMainView;
private int mWidth;
public DragViewGroup(Context context) {
super(context);
initView();
}
public DragViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public DragViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
/**-------------------------------------------
* 1、初始化資料:呼叫ViewDragHelper.create方法
* ------------------------------------------*/
private void initView() {
mViewDragHelper = ViewDragHelper.create(this,callback); //需要監聽的View和回撥callback
}
/**-------------------------------
* 2、事件攔截和觸控事件全部交給ViewDragHelper進行處理
* ------------------------------*/
//事件攔截
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mViewDragHelper.shouldInterceptTouchEvent(ev);
}
//觸控事件
@Override
public boolean onTouchEvent(MotionEvent event) {
//將觸控事件傳遞給ViewDragHelper
mViewDragHelper.processTouchEvent(event);
return true;
}
/**--------------------------------------------
* 3、也需要重寫computeScroll()
* 內部也是通過scroller來進行平移滑動, 這個模板可以照搬
* -------------------------------------------*/
@Override
public void computeScroll() {
if(mViewDragHelper.continueSettling(true)){
ViewCompat.postInvalidateOnAnimation(this);
}
}
/**------------------------------
* 4、處理的回撥:側滑回調
* ----------------------------*/
private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {
/*-------------------------------
* 何時開始觸控:
* 1.指定哪一個子View可以被移動.
* 2.如果直接返回true,在該佈局之內的所有子View都可以隨意划動
* ------------------------------*/
@Override
public boolean tryCaptureView(View child, int pointerId) {
//如果當前觸控的child是mMainView開始檢測
return mMainView == child;
}
/*-------------------------------
* 處理水平滑動:
* 1. 返回值預設為0,如果為0則不處理該方向的滑動。
* 2. 一般直接返回left,當需要精準計算pading等值時,可以先對left處理再返回
* ------------------------------*/
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return left;
}
/*-------------------------------
* 處理垂直滑動:
* 1. 返回值預設為0,如果為0則不處理該方向的滑動。
* 2. 一般直接返回top,,當需要精準計算pading等值時,可以先對left處理再返回
* ------------------------------*/
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
return 0;
}
/*---------------------------------------------
* 拖動結束後呼叫,類似ACTION_UP。
* 這裡是實現側滑選單,一般滑動可以不用這段程式碼
* ---------------------------------------------*/
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
//手指擡起後緩慢的移動到指定位置
if(mMainView.getLeft() <500){
//關閉選單
mViewDragHelper.smoothSlideViewTo(mMainView,0,0);
ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
}else{
//開啟選單
mViewDragHelper.smoothSlideViewTo(mMainView,300,0);
ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
}
}
};
/**---------------------------------------------------
* 5、獲取子控制元件用於處理
* 1. 上面完成了滑動功能,這裡簡單的按照第1、2的順序指定子控制元件View的內容
* 2. onSizeChanged能夠獲得menu等子控制元件的寬度等資訊,有需求可以後續處理
* ----------------------------------------------*/
//XML載入組建後回撥
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mMenuView = getChildAt(0);
mMainView = getChildAt(1);
}
//元件大小改變時回撥
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = mMenuView.getMeasuredWidth();
}
}
使用(作為父控制元件,裡面依次放menu和main):
<com.example.xxxx.DragViewGroup
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorAccent"/>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary"/>
</com.example.xxxx.DragViewGroup>
GestureDetector
1、GestureDetector作用和注意點
- 探測
手勢
和事件
,需要通過提供的MotionEvent
- 該類僅能用於
touch觸控
提供的MotionEvent
,不能用於traceball events(追蹤球事件)
自定義View
中可以重寫onTouchEvent()
方法並在裡面用GestureDetector
接管。
OnGestureListener
2、OnGestureListener作用
- 用於在
手勢
產生時,去通知監聽者。- 該
監聽器
會監聽所有的手勢,如果只需要監聽一部分可以使用SimpleOnGestureListener
3、OnGestureListener能監聽哪些手勢(5種)?
public interface OnGestureListener {
/**
* 1、按下操作。且其他任何事件之前都會觸發該方法。
* @param e Down MotionEvent
*/
boolean onDown(MotionEvent e);
/**
* 2、按下之後,Move和Up之前。用於提供視覺反饋告訴使用者已經捕獲了他們的行為。
* @param e Down MotionEvent
*/
void onShowPress(MotionEvent e);
/**
* 2、擡起操作。
* @param e Up MotionEvent
*/
boolean onSingleTapUp(MotionEvent e);
/**
* 3、滑動操作(由Down MotionEvent e1觸發,當前是Move MotionEvent e2)
*
* @param e1 開啟滑動的按下操作。
* @param e2 觸發onScroll的滑動操作。
* @param distanceX 最近一次onScroll和當前onScroll之間的X滑動距離。
* @param distanceY 最近一次onScroll和當前onScroll之間的Y滑動距離。
*/
boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);
/**
* 4、長按操作。
*
* @param e 開始長按的Down操作.
*/
void onLongPress(MotionEvent e);
/**
* 5、猛扔操作。
*
* @param e1 開始fling操作的Down MotionEvent
* @param e2 觸發onFling的Move MotionEvent
* @param velocityX X軸的速度(pixels / second 畫素/每秒)
* @param velocityY Y軸的速度(pixels / second 畫素/每秒)
*/
boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
}
上面所有有返回值的方法,
return true
-消耗該事件;return false
-不消耗該事件
4、OnGestureListener的使用方法。
/**====================================================
* 1、GestureDetector通過context和onGestureListener構造
*======================================================*/
GestureDetector gestureDetector = new GestureDetector(getApplicationContext(), new GestureDetector.OnGestureListener() {
//實現5種回撥方法
});
/**======================================
* 2、在View的touch方法中進行攔截。
*=======================================*/
imageView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
//交給GestureDetector進行處理
return gestureDetector.onTouchEvent(event);
}
});
OnDoubleTapListener
5、OnDoubleTapListener作用
- 監聽“雙擊操作”
- 監聽“確認的單擊操作”—該單擊操作之後的操作無法構成一次雙擊。
6、OnDoubleTapListener能監聽哪些手勢(3種)?
/**
* 雙擊或者單擊(後續操作無法導致雙擊)
*/
public interface OnDoubleTapListener {
/**
* 1、單擊操作。 不會產生雙擊行為的單擊操作才會觸發。
*
* @param e Down MotionEvent
* @return true-消耗事件; false-不消耗事件。
*/
boolean onSingleTapConfirmed(MotionEvent e);
/**
* 2、雙擊操作.
*
* @param e 雙擊操作的第一個按下操作。
* @return true-消耗事件; false-不消耗事件。
*/
boolean onDoubleTap(MotionEvent e);
/**
* 3、雙擊操作之間發生了down、move或者up事件。
*
* @param e 雙擊操作期間產生的MotionEvent
* @return true-消耗事件; false-不消耗事件。
*/
boolean onDoubleTapEvent(MotionEvent e);
}
7、OnDoubleTapListener的使用方法
GestureDetector gestureDetector = new GestureDetector(...);
gestureDetector.setOnDoubleTapListener(new GestureDetector.OnDoubleTapListener() {
// 三種