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()两个不同的位置获取的区别

  • getTop(),getBottom(),getLeft(),getRight()是相对于父控件的距离,单位为像素,
  • 看一张图就好理解了:     

 

  • event.getRowX():触摸点距离屏幕圆点的X坐标
  • event.getX():触摸点距离组件圆点的X坐标
  • 再看一张图,就也能迅速理解:

      

5.1.1 Android 坐标系

Android坐标系相当于第四象限,但是Y轴方向相反,向下是正方向

触控事件中通过getRawX()、getRawY()方法获得的坐标

就是该视图左上角在Android坐标系中的坐标,也就是距离屏幕圆点的坐标

一张图就明白了:

      

5.1.2 视图坐标系

视图坐标系也就是说父控件中的坐标,跟上面说的差不多,只不过上一个是指整个屏幕,而这一个是指组件

触控事件中通过getX()、getY()方法获得的坐标

就是该视图左上角在视图坐标系中的坐标,也就是距离组件圆点的坐标

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

很尴尬的是看到这里了,发现我上面已经总结了这里的知识点,在贴一遍吧:

系统还提供了非常多的方法来获取坐标值、相对距离等:

View提供的获取坐标方法:

  • getTop():获取到的是View自身的顶边到其父布局顶边的距离
  • getLeft():获取到的是View自身的左边到其父布局左边的距离
  • getRight():获取到的是View自身的右边到其父布局右边的距离
  • getBottom():获取到的是View自身的底边到其父布局底边的距离

MotionEvent提供的方法:

  • getX():获取点击事件距离控件左边的距离,即视图坐标
  • getY():获取点击事件距离控件顶边的距离,即视图坐标
  • getRawX:获取点击事件距离整个屏幕左边的距离,即绝对坐标
  • getRawY:获取点击事件距离整个屏幕顶边的距离,即绝对坐标

再来一张图,哈哈哈:

5.2 实现滑动的七种方法

5.2.1 layout方法

  1. 在View进行绘制时,会调用onLayout()来设置显示的位置
  2. 一定要重新设置初始坐标,这样才能准确地获取偏移量
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);
                // 重新设置初始坐标
                // 当你移动后肯定要设置此时的坐标为接下来移动后的last坐标
                lastX = rawX;
                lastY = rawY;
                break;
        }
        return true;
    }
}

5.2.2 offsetLeftAndRight()与offsetTopAndBottom()

  1. 这两个方法相当于提供一个对左右、上下移动的封装
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;
    }
}

5.2.3 LayoutParams

通过Margin()方法,设置params距离left和top的偏移量

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

5.2.4 scrollTo与scrollBy

  1. scrollTo指的是移动到一个具体的坐标点
  2. scrollBy指的是移动多少的距离
  3. 这两个方法只对内容有效,这里的内容其实就是指的是child,比如父布局中的子控件,TextView中的文本
  4. 最形象理解那就是RecyclerView当中调用这两个方法,它们的子View会滚动
  5. 要注意的是,scrollTo、scrollBy是以Android坐标系移动的,所以要使用我们正常的坐标系,则在x和y加一个负号(和正常相反)
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;
    }
}

5.2.5 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);
    }
    // 此方法可以理解为,只要view稍微动一点点,就会调用computeScroll()
    // getCurrX()是这个核心的方法,随时随地都能得到当前的位置,通过scrollTo进行滑动
    @Override
    public void computeScroll() {
        super.computeScroll();
        // 判断Scroller是否执行完毕
        // 当滑动停止后,返回的是false
        // 正在滑动时,调用invalidate()->draw()->computeScroll()会一直调用此方法
        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:
                // 手指离开时,执行滑动过程
                // 这个构造方法意为初始的X坐标,初始的Y坐标,滑动的X偏移量,滑动的Y偏移量
                // 实现了抬起手指后回到原位置的功能
                View viewGroup = ((View) getParent());
                mScroller.startScroll(
                        viewGroup.getScrollX(),
                        viewGroup.getScrollY(),
                        -viewGroup.getScrollX(),
                        -viewGroup.getScrollY());
                invalidate();
                break;
        }
        return true;
    }
}

5.2.6 属性动画

这个在后面会有更加详细的讲述,这里就不说了

5.2.7 ViewDragHelper

ViewDragHelper通常定义在一个ViewGroup的内部

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() {
        // 初始化ViewDragHelper,通过静态工厂方法
        mViewDragHelper = ViewDragHelper.create(this, callback);
    }

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

                // 何时开始检测触摸事件,指定哪个子View可以被拖动
                @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);
                }

                // 处理垂直滑动,返回0为不滑动
                @Override
                public int clampViewPositionVertical(View child, int top, int dy) {
                    return 0;
                }

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

                // 拖动结束后调用,当MainView移动后距离左边小于500像素,就回到初始状态
                // 否则就将侧边栏显示出来
                @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);
        }
    }
}

布局:

<?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的坐标系,以及触控事件,还有滑动的7种方法,然后对这些都有一个比较深的印象了,希望在接下来的学习当中能把这些东西运用自如,提高自己的技术水平!

猜你喜欢

转载自blog.csdn.net/pengbo6665631/article/details/81349218