android进阶-Android Scroll分析

来源:互联网 发布:手机网络初始化失败 编辑:程序博客网 时间:2024/06/06 00:39

这里写链接内容1.1 Android坐标系

在android 中,将屏幕最左上角的顶点作为Android坐标系的原点,从这个点向右是X轴正方向,从这个点向下是Y轴正方向。
这里写图片描述

1.2 视图坐标系

描述子视图在父视图中的位置关系。
原点不是android坐标系中的屏幕最左上角,而是以父视图左上角为坐标原点
这里写图片描述
在触控事件中,通过getX()、getY()所获取戴尔坐标就是视图坐标系中的坐标。

1.3触控事件——MotionEvent

MotionEvent中封装的一些常用得病事件常量,它定义了触控事件的不同类型。

这里写图片描述
通常情况下我们会在onTouchEvent(MotionEvent event)方法中通过event.getAction()方法来获取触控事件的类型.

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

获取坐标值的方法
这里写图片描述

2.实现滑动的七种方式

2.1 layout方法

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

2.2 offsetLeftAndRight()与offsetTopAndBottom()
这个方法相当于系统提供的一个对左右、上下移动的API的封装。

//同时对left和right进行偏移 offsetLeftAndRight(offsetX); //同时对top和bottom进行偏移 offsetTopAndBottom(offsetY);

2.3 LayoutParams
通过改变 LayoutParams来改变一个view的位置时,通常改变的是这个view的Margin属性,所以除了使用布局的LayoutParams之外,还可以使用ViewGroup.MarginLayoutParams,这个不需要考虑父布局的类型。

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

2.4 crollTo与scrollBy

 case MotionEvent.ACTION_MOVE:                int offsetX = x - lastX;                int offsetY = y - lastY;                ((View) getParent()).scrollBy(-offsetX, -offsetY);                break;

这里写图片描述
这里写图片描述
通过上面的分析可以发现,如果将scrollBy中的参数dx和dy设置为正数,那么content将向坐标轴负方向移动;如果将scrollBy中的参数dx和dy设置为负数,那么content将向坐标轴正方向移动。

2.5 Scroller
Scroller类与crollTo、crollBy方式十分相似。通过Scroller类可以实现平滑移动的效果,而不是瞬间完成动作。
使用Scroller的三个步骤
(1)初始化Scroller
通过构造方法创建一个Scroller对象
// 初始化Scroller
mScroller = new Scroller(context);
(2)重写computeScroll()方法,实现模拟滑动

 // 判断Scroller是否执行完毕        if (mScroller.computeScrollOffset()) {            ((View) getParent()).scrollTo(                    mScroller.getCurrX(),                    mScroller.getCurrY());            // 通过重绘来不断调用computeScroll            invalidate();        }

(3)startScroll开启模拟过程
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)

   case MotionEvent.ACTION_UP:                // 手指离开时,执行滑动过程                View viewGroup = ((View) getParent());                mScroller.startScroll(                        viewGroup.getScrollX(),                        viewGroup.getScrollY(),                        -viewGroup.getScrollX(),                        -viewGroup.getScrollY());                invalidate();                break;

2.6 ViewDragHelper
通过viewDragHelper,基本可以实现各种不同的滑动,拖动需求。
1 .初始化viewDragHelper
通常定义在一个ViewGroup的内部,并通过7️⃣静态工厂方法进行初始化。

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

2.拦截事件

 @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        return mViewDragHelper.shouldInterceptTouchEvent(ev);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        //将触摸事件传递给ViewDragHelper,此操作必不可少        mViewDragHelper.processTouchEvent(event);        return true;    }

3.处理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时开始检测                    return mMainView == child;                }

在实例中自定义了一个ViewGroup,里面定义了两个子View-MenuView和MainView,当指定如上代码时,则只有MainVierw是可以被拖动的。
下面看具体的滑动方法:
clampViewPositionVertical和clampViewPositionHorizontal

   // 处理垂直滑动                @Override                public int clampViewPositionVertical(View child, int top, int dy) {                    return 0;                }                // 处理水平滑动                @Override                public int clampViewPositionHorizontal(View child, int left, int dx) {                    return left;                }

当手指离开屏幕后,子View滑动回初始位置。通过onViewReleased()来实现

    // 拖动结束后调用                @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);                    }                }

在onSizeChange()方法中获取View的宽度,根据View宽度处理滑动后的效果。

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

完整实例:

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(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();    }    @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 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);                    }                }            };    @Override    public void computeScroll() {        if (mViewDragHelper.continueSettling(true)) {            ViewCompat.postInvalidateOnAnimation(this);        }    }}
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:paddingLeft="@dimen/activity_horizontal_margin"    android:paddingRight="@dimen/activity_horizontal_margin"    android:paddingTop="@dimen/activity_vertical_margin"    android:paddingBottom="@dimen/activity_vertical_margin"    tools:context=".MainActivity">    <com.imooc.dragviewtest.DragViewGroup        android:layout_width="match_parent"        android:layout_height="match_parent"        android:id="@+id/view">        <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.imooc.dragviewtest.DragViewGroup></RelativeLayout>

仿QQ侧拉效果

Demo地址:
下载地址