天天看點

Android群英傳知識點回顧——第五章:Android Scroll分析Android群英傳知識點回顧——第五章:Android Scroll分析

Android群英傳知識點回顧——第五章:Android Scroll分析

知識點目錄

  • 5.1 滑動效果是如何産生的
    • 5.1.1 Android坐标系
    • 5.1.2 視圖坐标系
    • 5.1.3 觸控事件——MotionEvent
  • 5.2 實作滑動的七種方法
    • 5.2.1 layout方法
    • 5.2.2 offsetLeftAndRight()與offsetTopAndBottom()
    • 5.2.3 LayoutParams
    • 5.2.4 scrollTo與scrollBy
    • 5.2.5 Scroller
    • 5.2.6 屬性動畫
    • 5.2.7 ViewDragHelper

知識點回顧

5.1 滑動效果是如何産生的

要實作View的滑動,就必須監聽使用者觸摸的事件,并根據事件傳入的坐标,動态且不斷地改變View的坐标,進而實作View跟随使用者觸摸的滑動而滑動

特别注意:View.getX()和event.getX()兩個不同的位置擷取的差別

5.1.1 Android坐标系

Android坐标系以螢幕左上角作為原點,可通過以下方法獲得x和y的坐标:
  • getRawX():擷取Android坐标系的x坐标,請注意這裡和event.getRawX()的差別
  • getRawY():擷取Android坐标系的y坐标,請注意這裡和event.getRawY()的差別
Android群英傳知識點回顧——第五章:Android Scroll分析Android群英傳知識點回顧——第五章:Android Scroll分析

5.1.2 視圖坐标系

視圖坐标系由子控件以父控件的左上角作為原點,可通過以下方法獲得x和y的坐标:
  • getX():擷取視圖坐标系的x坐标,請注意這裡和event.getX()的差別
  • getY():擷取視圖坐标系的y坐标,請注意這裡和event.getY()的差別
Android群英傳知識點回顧——第五章:Android Scroll分析Android群英傳知識點回顧——第五章:Android Scroll分析

5.1.3 觸控事件——MotionEvent

MotionEvent封裝的一些常用的事件常量:
  • ACTION_DOWN:單點觸摸按下的動作
  • ACTION_UP:單點觸摸離開的動作
  • ACTION_MOVE:單點觸摸移動的動作
  • ACTION_CANCEL:單點觸摸取消
  • ACTION_OUTSIDE:單點觸摸超出邊界
  • ACTION_POINTER_DOWN:多點觸摸按下的動作
  • ACTION_POINTER_UP:多點觸摸離開的動作
通常會在onTouchEvent(MotionEvent event)方法中通過event.getAction()方法擷取觸控事件類型,以下代碼為模闆式子:
@Override
public boolean onTouchEvent(MotionEvent event) {
    //擷取目前輸入點的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群英傳知識點回顧——第五章:Android Scroll分析Android群英傳知識點回顧——第五章:Android Scroll分析

系統海提供了非常多的方法來擷取坐标值、相對距離等:

View提供的擷取坐标方法:

  • getTop():擷取到的是View自身的頂邊到其父布局頂邊的距離
  • getLeft():擷取到的是View自身的左邊到其父布局左邊的距離
  • getRight():擷取到的是View自身的右邊到其父布局右邊的距離
  • getBottom():擷取到的是View自身的底邊到其父布局底邊的距離
MotionEvent提供的方法:
  • getX():擷取點選事件距離控件左邊的距離,即視圖坐标
  • getY():擷取點選事件距離控件頂邊的距離,即視圖坐标
  • getRawX:擷取點選事件距離整個螢幕左邊的距離,即絕對坐标
  • getRawY:擷取點選事件距離整個螢幕頂邊的距離,即絕對坐标

5.2 實作滑動的七種方法

無知識點

5.2.1 layout方法

當我們手指觸摸時記錄下觸摸點,在滑動時也記錄下觸摸點,通過兩次的內插補點,計算出偏移量,使用View本身的layout方法,讓他不斷的位移偏移量即可

Layout滑動……見經典代碼回顧案例一

5.2.2 offsetLeftAndRight()與offsetTopAndBottom()

offsetLeftAndRight滑動……見經典代碼回顧案例二

5.2.3 LayoutParams

通過getLayoutParams()方法來擷取布局參數,需要注意的是:
  • 不同的View應采用不同的View的getLayoutParams()方法
  • 前提必須要有一個父布局,否則系統無法擷取LayoutParams
  • 使用ViewGroup.MarginLayoutParams不需要考慮父布局類型
LayoutParams滑動……見經典代碼回顧案例三

5.2.4 scrollTo與scrollBy

scrollTo與scrollBy滑動……見經典代碼回顧案例四
  • scrollTo:移動到一個具體的坐标點(x,y)
  • scrollBy:表示移動的增量dx,dy
scrollTo、scrollBy方法移動的是View的content,即讓View的内容移動了,如果在ViewGroup中使用scrollTo、scrollBy方法,那麼移動的将是所有子View,例如:
  • TextView,content就是它的文本
  • ImageView,content就是它的drawable對象
也就是說我們要用getParent()來使用scrollTo、scrollBy方法,否則,TextView隻是偏移它的文本,ImageView隻是偏移它的圖檔

5.2.5 Scroller

Scroller使用步驟:
  • 初始化Scroller
  • 重寫computeScroll()方法
  • startScroll開啟滑動
Scroller滑動……見經典代碼回顧案例五

5.2.6 屬性動畫

無知識點

5.2.7 ViewDragHelper

ViewDragHelper使用步驟:
  • 初始化ViewDragHelper
  • 重寫攔截事件,将事件傳遞給ViewDragHelper處理
  • 處理computeScroll()
  • 處理回調ViewDragHelper.Callback
  • 開啟滑動,并更新UI
在ViewDragHelper.Callback中,系統定義了大量的監聽事件來幫助我們處理各種事件:
  • onViewCaptured() :使用者觸摸到view回調
  • onViewDragStateChanged():拖拽狀态改變時回調,比如idle,dragging等狀态
  • onViewPositionChanged():位置改變時回調,常用于滑動時更改scale進行縮放等效果
ViewDragHelper滑動……見經典代碼回顧案例六

經典代碼回顧

案例一:Layout滑動

public class DragView2 extends View {

    private int lastX;
    private int lastY;

    public DragView2(Context context) {
        super(context);
        ininView();
    }

    public DragView2(Context context, AttributeSet attrs) {
        super(context, attrs);
        ininView();
    }

    public DragView2(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        ininView();
    }

    private void ininView() {
        setBackgroundColor(Color.BLUE);
    }

    // 絕對坐标方式
    @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;
    }
}
           
一定要重新設定初始坐标,這樣才能準确地擷取偏移量

5.2.1-5.2.4 效果圖

Android群英傳知識點回顧——第五章:Android Scroll分析Android群英傳知識點回顧——第五章:Android Scroll分析

案例二:offsetLeftAndRight滑動

public class DragView1 extends View {

    private int lastX;
    private int lastY;

    public DragView1(Context context) {
        super(context);
        ininView();
    }

    public DragView1(Context context, AttributeSet attrs) {
        super(context, attrs);
        ininView();
    }

    public DragView1(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        ininView();
    }

    private void ininView() {
        // 給View設定背景顔色,便于觀察
        setBackgroundColor(Color.BLUE);
    }

    // 視圖坐标方式
    @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;
    }
}
           

案例三:LayoutParams滑動

public class DragView3 extends View {

    private int lastX;
    private int lastY;

    public DragView3(Context context) {
        super(context);
        ininView();
    }

    public DragView3(Context context, AttributeSet attrs) {
        super(context, attrs);
        ininView();
    }

    public DragView3(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        ininView();
    }

    private void ininView() {
        setBackgroundColor(Color.BLUE);
    }

    @Override
    public 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;
                ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
//                LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
                layoutParams.leftMargin = getLeft() + offsetX;
                layoutParams.topMargin = getTop() + offsetY;
                setLayoutParams(layoutParams);
                break;
        }
        return true;
    }
}
           

案例四:scrollTo與scrollBy滑動

public class DragView4 extends View {

    private int lastX;
    private int lastY;

    public DragView4(Context context) {
        super(context);
        ininView();
    }

    public DragView4(Context context, AttributeSet attrs) {
        super(context, attrs);
        ininView();
    }

    public DragView4(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        ininView();
    }

    private void ininView() {
        setBackgroundColor(Color.BLUE);
    }

    @Override
    public 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;
        }
        return true;
    }
}
           
要注意的是,scrollTo、scrollBy是以Android坐标系移動的,是以要使用我們正常的坐标系,則在x和y加一個負号

案例五:Scroller滑動

public class DragView5 extends View {

    private int lastX;
    private int lastY;
    private Scroller mScroller;

    public DragView5(Context context) {
        super(context);
        ininView(context);
    }

    public DragView5(Context context, AttributeSet attrs) {
        super(context, attrs);
        ininView(context);
    }

    public DragView5(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        ininView(context);
    }

    private void ininView(Context context) {
        setBackgroundColor(Color.BLUE);
        // 初始化Scroller
        mScroller = new Scroller(context);
    }

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

    @Override
    public 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;
    }
}
           

效果圖

Android群英傳知識點回顧——第五章:Android Scroll分析Android群英傳知識點回顧——第五章:Android Scroll分析

案例六:ViewDragHelper滑動

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();
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mMenuView = getChildAt();
        mMainView = getChildAt();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = mMenuView.getMeasuredWidth();
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return mViewDragHelper.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //将觸摸事件傳遞給ViewDragHelper,此操作必不可少
        mViewDragHelper.processTouchEvent(event);
        return true;
    }

    private void initView() {
        mViewDragHelper = ViewDragHelper.create(this, callback);
    }

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

                // 何時開始檢測觸摸事件
                @Override
                public boolean tryCaptureView(View child, int pointerId) {
                    //如果目前觸摸的child是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 ;
                }

                // 處理水準滑動
                @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() < ) {
                        //關閉菜單
                        //相當于Scroller的startScroll方法
                        mViewDragHelper.smoothSlideViewTo(mMainView, , );
                        ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
                    } else {
                        //打開菜單
                        mViewDragHelper.smoothSlideViewTo(mMainView, , );
                        ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
                    }
                }
            };

    @Override
    public void computeScroll() {
        if (mViewDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }
}
           
布局檔案必須放置兩個子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.handsome.qunyingzhuang.chapter_5.DragViewGroup
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/holo_blue_light">

            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Menu" />
        </FrameLayout>

        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/holo_orange_dark">

            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Main" />
        </FrameLayout>
    </com.handsome.qunyingzhuang.chapter_5.DragViewGroup>
</RelativeLayout>
           

效果圖

Android群英傳知識點回顧——第五章:Android Scroll分析Android群英傳知識點回顧——第五章:Android Scroll分析

經典回顧源碼下載下傳

github:https://github.com/CSDNHensen/QunYingZhuang