Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android群英傳第五章筆記·Android Scroll分析

Android群英傳第五章筆記·Android Scroll分析

編輯:關於Android編程

發生滑動效果的原因

Android坐標系

圖片名稱
獲取view在屏幕上的坐標(view左上角的坐標)

    View view = (View) findViewById(R.id.view);  
    int []location=new int[2];  
    view.getLocationOnScreen(location);  
    int x=location[0];//獲取當前位置的橫坐標  
    int y=location[1];//獲取當前位置的縱坐標  

觸控事件使用getRawX(),getRawY()獲得也是Android坐標系的坐標。

視圖坐標系

圖片名稱
獲取view相對父view的坐標(以父view左上角為坐標原點)

    View view = (View) findViewById(R.id.view);  
    int []location=new int[2];  
    view.getLocationInWindow(location);  
    int x=location[0];//獲取當前位置的橫坐標  
    int y=location[1];//獲取當前位置的縱坐標  

觸控事件中getX(),getY()獲得坐標是視圖坐標系中的坐標。

View坐標、距離API總結

圖片名稱
拓展閱讀Android View體系

實現滑動的七種方法

實現思路:觸摸view時,記下當前觸摸點坐標和移動後的觸摸點坐標,從而獲取到偏移量,並通過偏移量來修改View的坐標,從而實現滑動過程。

layout

view在繪制之前,會調用onLayout方法設置顯示的位置。那麼可以改變view的left,top,bottom,right值來改變view的位置。

//視圖坐標方式
@Override
public 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;
            // 在當前left、top、right、bottom的基礎上加上偏移量
            layout(getLeft() + offsetX,
                    getTop() + offsetY,
                    getRight() + offsetX,
                    getBottom() + offsetY);
//                        offsetLeftAndRight(offsetX);
//                        offsetTopAndBottom(offsetY);
            break;
    }
    return true;
}

 // 絕對坐標方式
@Override
public boolean onTouchEvent(MotionEvent event) {
    int rawX = (int) (event.getRawX());
    int rawY = (int) (event.getRawY());
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            // 記錄觸摸點坐標
            lastX = rawX;
            lastY = rawY;
            break;
        case MotionEvent.ACTION_MOVE:
            // 計算偏移量
            int offsetX = rawX - lastX;
            int offsetY = rawY - lastY;
            // 在當前left、top、right、bottom的基礎上加上偏移量
            layout(getLeft() + offsetX,
                    getTop() + offsetY,
                    getRight() + offsetX,
                    getBottom() + offsetY);
            // 重新設置初始坐標
            lastX = rawX;
            lastY = rawY;
            break;
    }
    return true;
}

offsetLeftAndRight和offsetTopAndBottom

這個方法相當於系統提供的一個左右、上下移動的API封裝。只需要把計算出的偏移量傳入即可完成view的移動。偏移量計算與上一個方法相同。

//同時對left和right進行偏移
offsetLeftAndRight(offsetX);
//同時對top和bottom進行偏移
offsetTopAndBottom(offsetY);

LayoutParams

LayoutParams保存了一個view的布局參數。通過改變LayoutParams的參數來改變view的位置。

// ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);

要注意的是,通過getLayoutParams獲取LayoutParams時,要根據view的父view的類型設置不同的類型。例如父布局是LinearLayout時,就可以使用LinearLayout.LayoutParams。而且,前提是要有一個父布局,不然無法獲取LayoutParams。
另外一種方法是使用ViewGroup.MarginLayoutParams,不需要考慮父布局的類型。

scrollTo與scrollBy

srcollTo表示將移動到一個具體的坐標點,scrollBy表示移動偏移量。
但是,scrollTo和scrollBy移動的是view的content(ViewGroup的content是子view,textview的content是文本,imageview的content是drawable對象)。所以要移動一個view時,應該使用父view的scrollTo和scrollBy方法。
另外一個問題是,父view和子view的移動方向是相反的。例如子view想要實現向右移動的效果,偏移量為10。如果調用父view的scrollBy(10,0),子view實際上是向左偏移10單位。原因是父view和子view的相對位置是相反的:父view向右時,子view相對父view是向左的。

((View) getParent()).scrollBy(-offsetX, -offsetY);

Note:書上讀到這兒有點困惑:一邊說scrollBy移動的是viewGroup的內容(即view),一邊又說移動的是ViewGroup本身(所以才有相對位移)。

Scroller

前面提到的方法都是在觸摸移動過程中不斷修改view的坐標來實現移動,因此移動過程看上去是平滑的。如果要實現這樣一種效果:點擊一個按鈕,讓一個view移動。那麼這個移動過程將是瞬間完成的。要使這樣一個移動過程也變得平滑,就要使用Scroller類。
步驟:
1. 初始化Scroller

    mScroller = new Scroller(context);

2. 重寫computeScroll()方法,實現模擬滑動

    @Override
 public void computeScroll() {
    super.computeScroll();
    // 判斷Scroller是否執行完畢
    if (mScroller.computeScrollOffset()) {
        ((View) getParent()).scrollTo(
                mScroller.getCurrX(),
                mScroller.getCurrY());
        // 通過重繪來不斷調用computeScroll
        invalidate();
        }
 }

invalidate()方法的作用是循環調用computeScroll。執行流程為:invalidate()–> draw()–> computeScroll()。從而實現不斷調用scrollTo移動一小段距離,實現模擬滑動的效果。當滑動完成後,循環結束。
3. startScroll開啟模擬過程
使用以下兩個重載方法:

    public void startScroll(int startX,int startY,int dx,int dy,int duration);
    public void startScroll(int startX,int startY,int dx,int dy);

在需要開始移動的方法裡寫入以下代碼:

     View viewGroup = ((View) getParent());
            mScroller.startScroll(
                    viewGroup.getScrollX(),
                    viewGroup.getScrollY(),
                    -viewGroup.getScrollX(),
                    -viewGroup.getScrollY());//偏移量仍為相反數
            invalidate();

屬性動畫

一個功能強大的類:ViewDragHelper。通過它可以實現各種不同的滑動、拖放需求。以下以實現滑動展開側邊菜單欄效果為例說明ViewDragHelper的使用。整個布局分為mMenuView和mMainView。功能為監聽mMainView的觸摸事件實現拖動,拖動完成後根據拖動距離使mMenuView滑動。
1. 初始化ViewDragHelper

    mViewDragHelper = ViewDragHelper.create(this, callback);

第一個參數是要監聽的view,通常需要時一個ViewGroup。第二個參數是一個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()
ViewDragHelper也是使用Scroller來實現平滑移動的。因此也需要重寫computeScroll方法。

    @Override
      public void computeScroll() {
        if (mViewDragHelper.continueSettling(true)) {
         ViewCompat.postInvalidateOnAnimation(this);
        }
  }

4. 處理回調callback

    private ViewDragHelper.Callback callback =
        new ViewDragHelper.Callback() {

            // 何時開始檢測觸摸事件
            @Override
            public boolean tryCaptureView(View child, int pointerId) {
                //如果當前觸摸的child是mMainView時開始檢測,即只有mMainView是可以被拖動的
                return mMainView == child;
            }

            // 觸摸到View後回調
            @Override
            public void onViewCaptured(View capturedChild,
                                       int activePointerId) {
                super.onViewCaptured(capturedChild, activePointerId);
            }

            // 當拖拽狀態改變,比如idle,dragging
            @Override
            public void onViewDragStateChanged(int state) {
                super.onViewDragStateChanged(state);
            }

            // 當位置改變的時候調用,常用與滑動時更改scale等
            @Override
            public void onViewPositionChanged(View changedView,
                                              int left, int top, int dx, int dy) {
                super.onViewPositionChanged(changedView, left, top, dx, dy);
            }

            // 處理垂直滑動
            @Override
            public int clampViewPositionVertical(View child, int top, int dy) {
                return 0;
            }

            // 處理水平滑動
            @Override
            public int clampViewPositionHorizontal(View child, int left, int dx) {
                return left;
            }

            // 拖動結束後調用
            @Override
            public void onViewReleased(View releasedChild, float xvel, float yvel) {
                super.onViewReleased(releasedChild, xvel, yvel);
                //手指抬起後緩慢移動到指定位置
                if (mMainView.getLeft() < 500) {
                    //關閉菜單
                    //相當於Scroller的startScroll方法
                    mViewDragHelper.smoothSlideViewTo(mMainView, 0, 0);
                    ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
                } else {
                    //打開菜單
                    mViewDragHelper.smoothSlideViewTo(mMainView, 300, 0);
                    ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
                }
            }
        };
clampViewPositionVertical和clampViewPositionHorizontal方法分別對應垂直和水平方向上的滑動。這兩個類必須重寫。因為它們默認返回0,即不滑動。clampViewPositionVertical中的參數top,代表在垂直方向上child移動的距離,dy表示比較前一次的增量。通常情況下,只需要返回top和left即可。需要更加精確的時候,才對top和left進行一些處理。 onViewReleased在拖動結束後,即手指抬起後的處理。這裡實現的是view的移動(繼續展開或者撤回),實現原理和Scroller一樣。 ViewDragHelper其他回調事件舉例:
onViewCaptured():觸摸到View後回調 onViewDragStateChanged():拖曳狀態改變時回調,如idle,dragging等狀態 onViewPositinChanged():位置改變時回調,常用於滑動時更改scale進行縮放等效果。
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved