Android Scroll分析
概述
相對于Android2.x版本中常見的長按、點擊操作,滑動的方式具有更友好的用戶體驗性。因此從4.x的版本開始,滑動操作大量出現在Android系統中。
我們在這里主要闡述兩個問題
滑動效果分析
滑動一個View,本質上就是移動一個View。
改變其當前所處的位置,它的原理和動畫效果的實現非常相似,都是通過不斷的改變View的坐標來實現這一個效果。
所以要實現View的滑動,必須要監聽用戶的觸摸事件,并根據事件傳入的坐標,動態且不斷的改變View的坐標,從而實現View跟隨用戶觸摸的滑動而滑動。
在此之前,我們需要先了解下Android中的窗口坐標體系和屏幕的觸控事件MotionEvent。
Android坐標系
所謂滑動,正是相對于參考系的運動。
在Android中,將屏幕最左上角的頂點作為Android坐標系的原點,從這個點向右是X軸的正方向,從這個點向下是Y軸的正方向。
系統提供了getLocationOnScreen(int location[])這樣的方法來獲取Android坐標系中點的位置,即該視圖左上角在Android坐標系中的坐標。
另外在觸控事件中使用 getRawX(),getRawY()方法所獲得的坐標同樣是Android坐標系中的坐標。
視圖坐標系
Android還有一個視圖坐標系,它描述的是子視圖在父視圖中的位置關系。
和上面的Android坐標系相輔相成。
和Android坐標系類似,視圖坐標系同樣是以原點方向向右為X軸正方向,以原點向下為Y軸正方向,只是這個原點不再是Android坐標系中屏幕的左上角,而是父視圖左上角為坐標原點。
在觸摸事件中,通過getX()和getY()所獲得的坐標就是視圖坐標系中的坐標。
觸控事件-MotionEvent
觸控事件MotionEvent在用戶交互中,占據著舉足輕重的位置。
首先我們來看下MotionEvent中封裝的一些常用的事件變量,它定義了觸控事件的不同的類型。
// 單點觸摸按下動作 public static final int ACTION_DOWN = 0 ; // 單點觸摸離開動作 public static final int ACTION_UP = 1 ; // 觸摸點移動動作 public static final int ACTION_MOVE = 2 ; // 觸摸動作取消 public static final int ACTION_CANCEL = 3 ; // 觸摸動作超出邊界 public static final int ACTION_OUTSIDE = 4 ; // 多點觸摸按下動作 public static final int ACTION_POINTER_DOWN = 5 ; // 多點離開動作 public static final int ACTION_POINTER_UP = 6 ;通常情況下,我們會在onTouchEvent(MotionEvnet event)方法中通過event.getAction()方法來獲取觸控事件的類型,并使用switch-case方法來進行篩選,這個代碼的模式基本固定,如下
@Override public boolean onTouchEvent(MotionEvent envnt){// 獲取當前輸入點的X、Y坐標(視圖坐標)int x = (int)event.getX();int y = (int)event.getY();switch(event.getAction()){case MotionEvent.ACTION_DOWN://處理輸入的按下事件break;case MotionEvent.ACTION_MOVE:// 處理輸入的移動事件break;case MotionEvent.ACTION_UP:// 處理輸入的離開事件break;}return true ; }在不涉及多點操作的情況下,通常可以使用以上代碼來完成觸控事件的監聽,上述僅僅是一個代碼模板~
在Android中提供了很多獲取坐標值,相對舉例的方法,我們來梳理一下。
View 提供的獲取坐標的方法
getTop():獲取到的是View自身的頂邊到其父布局頂邊的距離
getLeft():獲取到的是View自身的左邊到其父布局左邊的距離
getRight():獲取到的是View自身的右邊到其父布局左邊的距離
getBottom():獲取到的是View自身的底邊到其父布局頂邊的距離
MotionEvent 提供的方法
getX():獲取點擊事件距離控件左邊的距離,即視圖坐標
getY():獲取點擊事件距離控件頂邊的距離,即視圖坐標
getRawX():獲取點擊事件距離整個屏幕左邊的距離,即絕對坐標
getRawY():獲取點擊事件距離整個屏幕頂邊的距離,即絕對坐標
實現滑動的七種方法
不管使用何種方法,其實現的基本思路是一致的:當觸摸View時,系統記下當前觸摸點坐標,當手指移動時,系統記下移動后的觸摸點坐標,從而獲取到相對于前一次坐標點的偏移量,并通過偏移量來修改View的坐標,這樣不斷地重復,從而實現滑動的過程。
下面我們通過例子來看看Android是如何實現滑動效果的。
首先我們自定義一個View,置于布局文件中,實現一個簡單的布局。
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><com.turing.base.android_hero.chapter5_Scroll.DragView android:layout_width="100dp"android:layout_height="100dp"/></RelativeLayout>layout方法
概述
在View繪制時,會調用onLayout()方法來設置顯示的位置。
同樣,可以通過修改View的 left top right bottom四個屬性來控制View的坐標。
在每次回調onTouchEvent方法的時候,我們都來獲取一下觸摸點的坐標。
Code
自定義DragView,重寫onTouchEvent方法
package com.turing.base.android_hero.chapter5_Scroll;import android.content.Context; import android.graphics.Color; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View;/*** MyApp** @author Mr.Yang on 2016-04-09 21:11.* @version 1.0* 自定義View*/ public class DragView extends View {// 定義上次觸摸的位置private int lastX;private int lastY;/*** 構造函數中調用 initViewColor方法** @param context*/public DragView(Context context) {super(context);initViewBackGroundColor();}public DragView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);initViewBackGroundColor();}public DragView(Context context, AttributeSet attrs) {super(context, attrs);initViewBackGroundColor();}public void initViewBackGroundColor() {// 給View設置背景顏色,便于觀察setBackgroundColor(Color.BLUE);}/*** 重寫 onTouchEvent方法** @param event* @return*/@Overridepublic boolean onTouchEvent(MotionEvent event) {int x = (int) event.getX();int y = (int) event.getY();int rawx = (int) event.getRawX();int rawy = (int) event.getRawY();switch (event.getAction()) {case MotionEvent.ACTION_DOWN:// 按下的時候 記錄觸摸點坐標 scrollByXY // lastX = x; // lastY = y ;lastX = rawx;lastY = rawy;break;case MotionEvent.ACTION_MOVE:// scrollByXY(x,y);scrollyByRawXY(rawx, rawy);break;}return true;}/*** 使用Android坐標系 絕對坐標來計算偏移量,并移動View* 使用絕對坐標系,在每次執行完ACTION_MOVE的邏輯后一定要重新設置初始坐標,* 這樣才能準確的獲取到偏移量* @param rawx* @param rawy*/public void scrollyByRawXY(int rawx, int rawy) {// 計算偏移量int offsetX = rawx - lastX;int offsetY = rawy - lastY;// 增加偏移量layout(getLeft() + offsetX,getTop() + offsetY,getRight() + offsetX,getBottom() + offsetY);// 重新設置初坐標lastX = rawx;lastY = rawy;}/*** 使用視圖坐標系 計算偏移量,并移動View*/public void scrollByXY(int x, int y) {// 計算偏移量int offsetX = x - lastX;int offsetY = y - lastY;// 在當前left right top bottom的基礎上增加偏移量layout(getLeft() + offsetX,getTop() + offsetY,getRight() + offsetX,getBottom() + offsetY);} }Scroll_Layout
package com.turing.base.android_hero.chapter5_Scroll;import android.os.Bundle; import android.support.v7.app.AppCompatActivity;import com.turing.base.R;public class Scroll_Layout extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_scroll__layout);} }效果圖
offsetLeftAndRight 和 offsetTopAndBottom
這個方法相當于系統提供了一個對左右和上下移動的API的封裝。
當計算出偏移量之后,只需要使用如下代碼完成View的重新布局,效果和使用layout方法一樣
// 同時對left和right進行偏移 offsetLeftAndRigth(offsetX); // 同時對top和bottom進行偏移 offsetTopAndBottom(offsetY);效果同layout 就不貼代碼和運行效果圖了。
LayoutParams
概述
LayoutParams保存了一個View的布局參數。因此可以通過改變LayoutParms來動態的修改一個 布局的位置參數,從而達到改變View位置的效果。
我們可以通過getLayoutParams()來獲取一個View的LayoutParams. 當然了計算偏移量和Layout方法中計算offset也是一樣的,當獲取到偏移量之后,就可以通過setLayoutParams來改變LayoutParams.
代碼如下:
// 獲取偏移量int offsetX = x - lastX;int offsetY = y - lastY;RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();layoutParams.leftMargin = getLeft() + offsetX;layoutParams.topMargin = getTop() + offsetY;setLayoutParams(layoutParams);注意事項:
- 通過getLayoutParams獲取LayoutParams時,需要根據View在父布局中的類型來設置不同的類型,比如這里我們把View放到了RelativeLayout中,那么就是RelativeLayout.LayoutParams ,同樣的道理 如果放到了LinearLayout中,則為LinearLayout.LayoutParams
- 通過getLayoutParams的方式獲取布局參數,前提是必須要有一個父布局,否則系統無法獲取。
Code
關鍵自定義類
package com.turing.base.android_hero.chapter5_Scroll;import android.content.Context; import android.graphics.Color; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.widget.RelativeLayout;/*** MyApp** @author Mr.Yang on 2016-04-10 10:23.* @version 1.0* @desc*/ public class DragView_LayoutParams extends View {private int lastX, lastY;public DragView_LayoutParams(Context context) {super(context);initViewBackgroudCoclor();}public DragView_LayoutParams(Context context, AttributeSet attrs) {super(context, attrs);initViewBackgroudCoclor();}public DragView_LayoutParams(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);initViewBackgroudCoclor();}private void initViewBackgroudCoclor() {// 給View設置背景顏色,便于觀察setBackgroundColor(Color.BLUE);}@Overridepublic boolean onTouchEvent(MotionEvent event) {int x = (int) event.getX();int y = (int) event.getY();switch (event.getAction()) {case MotionEvent.ACTION_DOWN:// 記錄當前的觸摸坐標lastX = x;lastY = y;break;case MotionEvent.ACTION_MOVE:// 獲取偏移量int offsetX = x - lastX;int offsetY = y - lastY;RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();layoutParams.leftMargin = getLeft() + offsetX;layoutParams.topMargin = getTop() + offsetY;setLayoutParams(layoutParams);break;default:break;}return true;} }效果圖
效果圖同上~
方法二-ViewGroup.MarginLayoutParams
在通過LayoutParams來改變一個變量的位置的時候,通常改變的是這個View的margin屬性,所以除了使用布局的LayoutParams之外,我們還可以使用ViewGroup.MarginLaoutParams來實現同樣的功能。
// 計算偏移量int offsetX = x - lastX;int offsetY = y - lastY;ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();layoutParams.leftMargin = getLeft() + offsetX;layoutParams.topMargin = getTop() + offsetY;setLayoutParams(layoutParams);break;使用ViewGroup.MarginLayoutParams更加方便,不需要考慮父布局的類型,當然了這兩者的本質都是一樣的。
scrollTo和scrollBy
概述
在一個View中,系統提供了scrollTo 、scrollBy兩種方式來改變一個View的位置。
顧名思義,
- scrollTo(x,y)表示移動到一個具體的坐標點 (x,y).
- scrollBy(dx,dy)表示移動的增量為dx,dy.
需要注意的是:
- scrollTo和scrollBy方法移動的是View的content,即讓View中的內容移動,如果在ViewGroup中使用scrollTo和scrollBy方法,那么移動的將是所有的子View,但如果在View中使用,那么移動的將是View的內容,比如TextView,content就是它的文本,ImageView,content就是它的Drawable對象。
- 要實現跟隨手指移動而滑動的效果,必須將偏移量設置為負值。
- 如果將scrollBy中的參數dx和dy設置為正數,那么content將向坐標的負方向移動,設置為負數,content將向坐標軸的正方向移動。
- 在使用絕對坐標系時,也可以通過scrollTo來實現相同的效果
Code
關鍵自定義View
package com.turing.base.android_hero.chapter5_Scroll;import android.content.Context; import android.graphics.Color; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View;/*** MyApp** @author Mr.Yang on 2016-04-10 12:32.* @version 1.0* @desc*/ public class DragView_scrollToscrollBy extends View {private int lastX;private int lastY;public DragView_scrollToscrollBy(Context context) {super(context);initViewBackgroundColor();}public DragView_scrollToscrollBy(Context context, AttributeSet attrs) {super(context, attrs);initViewBackgroundColor();}public DragView_scrollToscrollBy(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);initViewBackgroundColor();}private void initViewBackgroundColor() {setBackgroundColor(Color.BLUE);}@Overridepublic boolean onTouchEvent(MotionEvent event) {int x = (int) event.getX();int y = (int) event.getY();switch (event.getAction()) {case MotionEvent.ACTION_DOWN:lastX = x;lastY = y;break;case MotionEvent.ACTION_MOVE:int offsetX = x - lastX;int offsetY = y - lastY;((View) getParent()).scrollBy(-offsetX,-offsetY);break;}return true;} }效果圖同上~
Scroller
概述
上面說到了scrollTo、scrollby方法,就不得不提一下Scroller類。
總體來講,scrollTo scrollBy方法,子View的移動都是瞬間的,在事件執行的時候平移已經完成了,而Scroller類可以實現平滑移動的效果,而不是在瞬間完成的移動。
演示:
子View隨著手指的滑動而滑動,在手指離開屏幕時,讓子View平滑的移動到初始位置,即屏幕的左上角。
使用Scroller一般需要三個步驟:
Code
package com.turing.base.android_hero.chapter5_Scroll;import android.content.Context; import android.graphics.Color; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.widget.Scroller;/*** MyApp** @author Mr.Yang on 2016-04-10 13:15.* @version 1.0* @desc*/ public class DragViewScroller extends View {private int lastX;private int lastY;private Scroller mScroller;public DragViewScroller(Context context) {super(context);ininView(context);}public DragViewScroller(Context context, AttributeSet attrs) {super(context, attrs);ininView(context);}public DragViewScroller(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);ininView(context);}private void ininView(Context context) {setBackgroundColor(Color.BLUE);// 初始化ScrollermScroller = new Scroller(context);}@Overridepublic void computeScroll() {super.computeScroll();// 判斷Scroller是否執行完畢if (mScroller.computeScrollOffset()) {((View) getParent()).scrollTo(mScroller.getCurrX(),mScroller.getCurrY());// 通過重繪來不斷調用computeScrollinvalidate();}}@Overridepublic boolean onTouchEvent(MotionEvent event) {int x = (int) event.getX();int y = (int) event.getY();switch (event.getAction()) {case MotionEvent.ACTION_DOWN:lastX = (int) event.getX();lastY = (int) event.getY();break;case MotionEvent.ACTION_MOVE:int offsetX = x - lastX;int offsetY = y - lastY;((View) getParent()).scrollBy(-offsetX, -offsetY);break;case MotionEvent.ACTION_UP:// 手指離開時,執行滑動過程View viewGroup = ((View) getParent());mScroller.startScroll(viewGroup.getScrollX(),viewGroup.getScrollY(),-viewGroup.getScrollX(),-viewGroup.getScrollY());invalidate();break;}return true;} }效果圖
屬性動畫
待出一篇博客詳述~
ViewDragHelper
概述
步驟:
- 初始化ViewDragHelper
- 攔截事件
- 處理computerScroll
- 處理回調Callback
Code
請移步 本人Github-DragViewGroup
效果圖
總結
以上是生活随笔為你收集整理的Android Scroll分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CardView的那点事儿
- 下一篇: Android绘图机制与处理技巧-更新中