Android中實現(xiàn)滑動的七種方式總結(jié)
在Android中想要實現(xiàn)實現(xiàn)滑動有很多方法,這篇博客將提供一些實現(xiàn)滑動的思路,希望可以幫助到有需要的人。
一、Android坐標體系
在講解滑動之前,我們有必要簡單提一下Android的坐標體系,因為滑動的實質(zhì)就是坐標的不斷改變,所以我們先來了解一下Android坐標系和視圖坐標系兩個概念。直接放上兩張圖片吧,一目了然。

Android坐標系

視圖坐標系
從上面的兩張圖可以看出,Android坐標系的坐標原點位于屏幕的左上角,而視圖坐標系的原點位于父視圖的左上角,既然提供了兩種不同的坐標系,那么我們?nèi)绾蝸慝@取坐標呢,Android已經(jīng)給我們提供了一些方法用于獲取這些坐標,看下面的圖便一目了然。

Android獲取坐標的各種方法
二、layout方法
在View進行繪制時,是調(diào)用onLayout()方法來確定View的位置的,同樣我們也可以調(diào)用layout()方法來傳入我們滑動后的坐標便可以實現(xiàn)View的滑動,當然坐標的獲取我們可以在觸控事件中進行獲取,下面我們做一個View隨手指進行滑動的小例子來進行說明。
public class DragView extends View {
private int mLastX;
private int mLastY;
public DragView(Context context) {
this(context, null);
}
public DragView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DragView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
int lastX = 0, lastY = 0;
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
mLastX = x;
mLastY = y;
break;
case MotionEvent.ACTION_MOVE:
int offsetX = x - mLastX;
int offsetY = y - mLastY;
layout(getLeft() + offsetX, getTop() + offsetY,
getRight() + offsetX, getBottom() + offsetY);
break;
}
return true;
}
}
上面我們在觸控事件中獲取到獲取到手指按下時的坐標(lastX, lastY),然后在手指移動時不斷計算X和Y方向上的偏移量,然后再調(diào)用layout()方法來改變View的位置從而實現(xiàn)滑動。當然上面我們是通過getX()和getY()來獲取視圖坐標來進行修改,我們也可以通過getRawX()和getRawY()來獲取絕對坐標來實現(xiàn)上面的效果。代碼如下:
private int mLastX;
private int mLastY;
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getRawX();
int y = (int) event.getRawY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
mLastX = x;
mLastY = y;
break;
case MotionEvent.ACTION_MOVE:
int offsetX = x - mLastX;
int offsetY = y - mLastY;
layout(getLeft() + offsetX, getTop() + offsetY,
getRight() + offsetX, getBottom() + offsetY);
//重新設置初始坐標
mLastX = x;
mLastY = y;
break;
}
return true;
}
上面一定要注意,我們在改變完View的位置后必須調(diào)用設置初始坐標,這樣才能準確獲取偏移量。
三、offsetLeftAndRight和offsetTopAndBottom
這一種方法和上一種方法大部分步驟都是相同的,只是在移動View上有所差別,代碼如下:
offsetLeftAndRight(offsetX); offsetTopAndBottom(offsetY);
上面的這種方法只是多了一層封裝,可以實現(xiàn)比上面實現(xiàn)同樣的效果。
四、設置LayoutParams
LayoutParams可以通過改變的布局參數(shù),我們可以通過下面的代碼實現(xiàn)上面同樣的效果。
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams(); layoutParams.leftMargin = getLeft() + offsetX; layoutParams.topMargin = getTop() + offsetY; setLayoutParams(layoutParams);
注意:我們的LayoutParams可以通過getLayoutParams()方法來獲取,但是要注意,如果View的父布局是LinearLayout,那么我們的LayoutParams就是LinearLayout.LayoutParams,如果View的父布局是RelativeLayout,則我們的LayoutParams就是RelativeLayout.LayoutParams。當然我們還有一種簡單的方法,不用再管父布局的布局方式。代碼如下:
ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams(); marginLayoutParams.leftMargin = getLeft() + offsetX; marginLayoutParams.topMargin = getTop() + offsetY; setLayoutParams(marginLayoutParams);
上面的這種方法不用管父布局的類型,使用起來更加方便。
五、scrollTo和scrollBy方法
關于這兩個方法我們需要仔細說一下其中的一些注意事項
1 . scrollTo的參數(shù)是具體的一個坐標點(x, y), 而scrollBy的參數(shù)是在x, y方向上的坐標偏移
2 . scrollTo和scrollBy移動的是View的內(nèi)容。這一點很重要!!!!
如果我們對ViewGroup使用scrollTo和scrollBy則移動的是內(nèi)部的所有子View, 如果對TextView使用scrollTo和scrollBy則移動的是其中額文本。
3 . 視圖移動還有一個不太好理解的地方在于坐標,我們下面結(jié)合圖片來說明一下:

視圖移動1

視圖移動2
我們可以這樣理解,我們的手機屏幕作為一個蓋板,在手機屏幕下面是一個巨大的畫布,我們的手機屏幕這個蓋板是透明的,導致只有和手機屏幕重合的畫布部分才會被我們看到,我們調(diào)用scrollTo和scrollBy也可以理解為是在移動手機上面的蓋板。如圖中所示,按鈕在ViewGroup中的坐標是(20, 10),當我們調(diào)用scrollBy(20, 10)之后,就相當于移動了屏幕上的蓋板,然后我們看到的按鈕就到了ViewGroup的左上角。這樣如果我們想讓按鈕在水平和豎直方向上各移動20和10個單位,我們就必須調(diào)用scrollBy(-20, -10)
經(jīng)過了上面的知識準備,我們這里也使用scrollBy來實現(xiàn)前面實現(xiàn)的那個View隨手指移動的小例子:
((View)getParent()).scrollBy(-offsetX, -offsetY);
六、使用Scroller
Scroller也是滑動中很重要的一個角色,進過前面的scrollTo和scrollBy大家也會發(fā)現(xiàn),它們的移動時瞬間完成的,滑動顯得十分突兀,Google為了改善用戶體驗,便給出了Scroller,它可以實現(xiàn)平滑的移動,從而使滑動過程更加真實,用戶體驗更好,下面我們先簡單說說Scroller的實現(xiàn)原理。
Scroller也是滑動中很重要的一個角色,進過前面的scrollTo和scrollBy大家也會發(fā)現(xiàn),它們的移動時瞬間完成的,滑動顯得十分突兀,Google為了改善用戶體驗,便給出了Scroller,它可以實現(xiàn)平滑的移動,從而使滑動過程更加真實,用戶體驗更好,下面我們先簡單說說Scroller的實現(xiàn)原理。
Scroller的實現(xiàn)方式類似于scrollTo和scrollBy,scrollTo和scrollBy的移動都是從一個坐標點瞬間移動到另一個左邊點,而Scroller則是將移動的這段距離切分成好幾段的微小的位移,然后每一段調(diào)用scrollTo來不斷移動這些微小的位移,由于人眼的視覺暫留效果,就會給人平滑移動的視覺效果。
下面我們在上一步的基礎上增加一個小功能,第一部分還是View隨手指移動,但是當我們松開手指時,讓View自己平滑移動到最初始的位置(屏幕左上角),下面我們就來一步步介紹Scroller的用法
1 . 聲明Scroller變量,并在構(gòu)造方法中進行初始化
2 . 在觸控事件的ACTION_UP(手指抬起)事件中傳入開始滑動的坐標和需要滑動的距離并觸發(fā)Scroller的滑動事件
3 . 重寫computeScroll(),實現(xiàn)真正的滑動
下面是完整的代碼示例:
public class DragView extends View {
private int mLastX;
private int mLastY;
//聲明Scroller變量
private Scroller mScroller;
public DragView(Context context) {
this(context, null);
}
public DragView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DragView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//在構(gòu)造方法中初始化Scroller變量
mScroller = new Scroller(context);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getRawX();
int y = (int) event.getRawY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
mLastX = x;
mLastY = y;
break;
case MotionEvent.ACTION_MOVE:
int offsetX = x - mLastX;
int offsetY = y - mLastY;
//實現(xiàn)View跟隨手指移動的效果
((View)getParent()).scrollBy(-offsetX, -offsetY);
//重新設置初始坐標
mLastX = x;
mLastY = y;
break;
case MotionEvent.ACTION_UP:
//當手指抬起時執(zhí)行滑動過程
View view = (View) getParent();
mScroller.startScroll(view.getScrollX(), view.getScrollY(),
view.getScrollX(), view.getScrollY(), 5000);
//調(diào)用重繪來間接調(diào)用computeScroll()方法
invalidate();
break;
}
return true;
}
@Override
public void computeScroll() {
super.computeScroll();
//判斷滑動過程是否完成
if (mScroller.computeScrollOffset()){
((View)getParent()).scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
//通過重繪來不斷調(diào)用computeScroll()方法
invalidate();
}
}
}
上面的代碼View隨手指移動的代碼部分是與前面相同的,我們只說說Scroller的部分以及一些注意事項
1 . startScroll()方法各參數(shù)的意義,我們可以看看下面的源碼:
/** * Start scrolling by providing a starting point, the distance to travel, * and the duration of the scroll. * * @param startX Starting horizontal scroll offset in pixels. Positive * numbers will scroll the content to the left. * @param startY Starting vertical scroll offset in pixels. Positive numbers * will scroll the content up. * @param dx Horizontal distance to travel. Positive numbers will scroll the * content to the left. * @param dy Vertical distance to travel. Positive numbers will scroll the * content up. * @param duration Duration of the scroll in milliseconds. */ public void startScroll(int startX, int startY, int dx, int dy, int duration)
可以看出startX和startY參數(shù)就是開始滾動的(x, y)坐標,那么我們就可以通過ViewGroup(子View的父視圖)的getScrollX()和getScrollY()來獲取,這里一定要注意,我們在滑動時的content就是子View,所以我們通過子View的父視圖(ViewGroup)的getScrollX()和getScrollY()獲取到的就是子View在X和Y方向上滑動的距離,即就是我們需要的當我們手指抬起時子View的(x, y)坐標。而如果我們對子View調(diào)用getScrollX()和getScrollY()方法,則獲得的是子View內(nèi)部的視圖的滑動距離及坐標。
dx和dy分別是在X和Y方向上的偏移量,而且注釋中說了,如果我們傳入的dx和dy的值是正值,那么將會向上向左移動這個content(其實就是我們這里的View),即我們就可以讓子View回到左上角,這里我們還是可以借助于上一小節(jié)中提到的視圖移動的概念,我們想讓子View向坐上方移動,其實就是想讓覆蓋在上面的蓋板向右下角移動,我們可以將dx和dy理解為父視圖(覆蓋在上面的蓋板)的偏移量。
假設我們剛開始是讓子View隨手指向右下方移動,那么相當于覆蓋在上面的蓋板是向左上方移動,所以我們通過getScrollX()和getScrollY()獲得的值是負值,我們現(xiàn)在松開手指想讓子View向左上方移動(即回到屏幕左上角),那么就相當于蓋板向右下角移動,所以我們的dx和dy的值必須是-getScrollX()和-getScrollY(),此時的兩個值都是正值。
2 . 由于我們的computeScroll()方法不會主動調(diào)用,但是我們又需要它不斷調(diào)用從而不斷進行微小移動從而實現(xiàn)平滑的滑動,所以我們可以通過下面的方法。
這三個按照以下順序進行調(diào)用 invalidate()--->onDraw()--->computeScroll(),所以我們可以可以在ACTION_UP中調(diào)用完startScroll()方法后調(diào)用invalidate()方法,然后在computeScroll()方法中判斷滑動是否結(jié)束,如果沒結(jié)束,則通過getCurrX()和getCurrY()來獲得當前需要移動的微小的位移的坐標點,然后傳入scrollTo()方法中,這時候子View還只是移動了一小段距離,然后我們再次調(diào)用invalidate()方法,然后接著調(diào)用onDraw()方法,然后再次進入computeScroll()中再次讓子View移動一小段距離,直到滑動結(jié)束,computeScrollOffset()返回false,則這個循環(huán)調(diào)用的過程結(jié)束,從而完成平滑移動的過程。
七、屬性動畫
屬性動畫一樣可以實現(xiàn)View的滑動,但是由于屬性動畫涉及到的知識點也是眾多,這里不再展開來寫,只是提供一個思路,后續(xù)后專門寫一篇博客來說。
八、ViewDragHelper
ViewDragHelper可以幫助我們實現(xiàn)各種滑動需求,但是它的使用也相對較復雜,所以準備專門寫一篇博客來介紹他,這里只是給出一個概念
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
Android socket實現(xiàn)原理詳解 服務端和客戶端如何搭建
這篇文章主要為大家詳細介紹了Android socket實現(xiàn)原理詳解,以及服務端和客戶端的搭建方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-08-08
基于Android開發(fā)支持表情的實現(xiàn)詳解
本篇文章是對在Android開發(fā)中支持表情的實現(xiàn)代碼進行了介紹。需要的朋友參考下2013-05-05
RecyclerView中使用CheckBox出現(xiàn)勾選混亂的解決方法
這篇文章主要為大家詳細介紹了RecyclerView中使用CheckBox出現(xiàn)勾選混亂的解決方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-12-12
android編程實現(xiàn)局部界面動態(tài)切換的方法
這篇文章主要介紹了android編程實現(xiàn)局部界面動態(tài)切換的方法,以實例形式較為詳細的分析了Android局部切換的布局及功能實現(xiàn)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-11-11
Android 使用FragmentTabHost實現(xiàn)底部菜單功能
這篇文章主要介紹了Android 使用FragmentTabHost實現(xiàn)底部菜單功能,詳細給大家介紹了FragmentTabHost配合Fragment的使用方法,需要的朋友可以參考下2017-12-12
Android開發(fā)TextView內(nèi)的文字實現(xiàn)自動換行
這篇文章主要為大家介紹了Android開發(fā)TextView內(nèi)的文字實現(xiàn)自動換行,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-06-06

