Android中级——滑动分析

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

Srcoll

Android坐标系

将屏幕左上角的顶点作为Android坐标系的原点向右为X轴正方向向下为Y轴正方向

在这里插入图片描述

可通过如下方式获取控件在Android坐标系的(x, y)坐标

int[] location = new int[2];
getLocationOnScreen(location);

或通过

@Override
public boolean onTouchEvent(MotionEvent event) {
    int x = (int) event.getRawX();
    int y = (int) event.getRawY();
}

视图坐标系

描述子视图在父视图中的位置关系将父视图左上角作为坐标系原点

在这里插入图片描述
可通过如下方式获取控件在视图坐标系的(x, y)坐标

@Override
public boolean onTouchEvent(MotionEvent event) {
    int x = (int) event.getX();
    int y = (int) event.getY();
}

常见方法

View中获取坐标的方法有

  • getTop()View顶边到其父布局顶边的距离
  • getLeft()View左边到其父布局左边的距离
  • getTop()View右边到其父布局左边的距离
  • getTop()View底边到其父布局顶边的距离

在这里插入图片描述

MotionEvent中获取坐标的方法有

  • getX()点击事件距离控件左边的距离即视图坐标
  • getX()点击事件距离控件顶边的距离即视图坐标
  • getX()点击事件距离屏幕左边的距离即绝对坐标
  • getX()点击事件距离控件顶边的距离即绝对坐标

实现滑动

基本思想是当触摸View时记下坐标当手指移动时记下移动后的坐标从而获取偏移量修改View的坐标不断重复实现滑动

layout()

布局如下

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
    tools:context=".MainActivity">

    <com.demo.demo0.MyView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:background="@color/colorPrimaryDark" />

</LinearLayout>

在View移动时计算偏移量并加到layout()方法中若使用绝对坐标每次都需重新设置初始坐标

public class MyView extends View {

    private static final String TAG = MyView.class.getSimpleName();
    private int lastX;
    private int lastY;

    public MyView(Context context) {
        this(context, null);
    }

    public MyView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        /*int x = (int) event.getRawX();
        int y = (int) event.getRawY();*/
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                lastX = x;
                lastY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                int offsetX = x - lastX;
                int offsetY = y - lastY;
                layout(getLeft() + offsetX,
                        getTop() + offsetY,
                        getRight() + offsetX,
                        getBottom() + offsetY);
                /*lastX = x;
                lastY = y;*/
                break;
        }
        return true;
    }
}

offsetLeftAndRight()和offsetTopAndBottom()

这两个方法只需传入偏移量效果与Layout()一样

offsetLeftAndRight(offsetX);
offsetTopAndBottom(offsetY);

LayoutParams

使用LayoutParams修改Margin加上偏移量效果同上

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

如上获取LayoutParams时需要根据父布局的类型或通过ViewGroup.MarginLayoutParams这样就无需考虑父布局

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

scrollTo()与scrollBy()

  • scrollTo(x, y) 表示移动到坐标点(x, y)
  • scrollBy(dx, dy) 表示移动的增量为 dx、dy

但上面方法移动对象是控件的content

  • 对于ViewGroupcontent是其所有子View
  • 对于Viewcontent是其内容如TextView中的文本

故如果要移动View应该要调用其父类的scrollBy()

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

但是如上无法得到想要的结果原因是scrollBy()移动的是屏幕的可视区域
在这里插入图片描述

如上后面的矩形为画布中间的矩形则为屏幕的可视区域若调用scrollBy(20,10)则结果为可视区域向右下方移动而对于Button则是向左上方移动如下图

在这里插入图片描述

故在传入参数时应设置为负数才会使控件的content向正方向移动

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

同理scrollTo()也是如此

Scroller

  • 若在点击事件中使用scrollBy()或scrollTo()其会在瞬间完成
  • 而ACTION_MOVE中会不断获取偏移量从而形成连续移动效果
  • Scroller的出现就是为了实现平滑移动原理也是通过不断的偏移
public class MyView extends View {

    private static final String TAG = MyView.class.getSimpleName();
    private int lastX;
    private int lastY;
    private Scroller mScroller;

    public MyView(Context context) {
        this(context, null);
    }

    public MyView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        mScroller = new Scroller(context);
    }

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

    @Override
    public void computeScroll() {
        super.computeScroll();
        if (mScroller.computeScrollOffset()) {    //判断是否完成滑动
            ((View) getParent()).scrollTo(
                    mScroller.getCurrX(),
                    mScroller.getCurrY());
            invalidate();   //computeScroll()会在draw()方法中调用通过不断重绘实现平滑移动
        }
    }
}

如上实现在ACTION_UP时将控件回归原位利用Scroller和computeScroll()通过不断获取当前滚动值调用scrollTo()实现平滑移动

VierDragHepler

如下实现在ViewGroup中将其2个子View分为Menu和Main当侧滑拖动Main时显示Menu

public class DragViewGroup extends FrameLayout {

    private ViewDragHelper mViewDragHelper;
    private View mMenuView, mMainView;
    private int mWidth;

    public DragViewGroup(Context context) {
        this(context, null);
    }

    public DragViewGroup(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public DragViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }

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

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mMenuView = getChildAt(0);  //将传入的View分为Menu和Main
        mMainView = getChildAt(1);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = mMenuView.getMeasuredWidth();  //获取Menu的宽度暂未使用
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        //将事件拦截传给ViewDragHelper
        return mViewDragHelper.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //将触摸事件传给ViewDragHelper
        mViewDragHelper.processTouchEvent(event);
        return true;
    }

    private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            //指示哪个View可以被拖动为true则所有View都可
            return mMainView == child;
        }

        //是否需要水平滑动为0则不需要当不需要处理padding时可直接返回left
        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            return left;
        }

        //是否需要垂直滑动同上
        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            return 0;
        }

        //拖动结束后调用
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            super.onViewReleased(releasedChild, xvel, yvel);
            if (mMainView.getLeft() < 500) {    //滑动距离不超过500菜单将回滚
                mViewDragHelper.smoothSlideViewTo(mMainView, 0, 0);
            } else {
                //打开菜单
                mViewDragHelper.smoothSlideViewTo(mMainView, 300, 0);
            }
            ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
        }
    };

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

布局如下在内部放置2个不同颜色全屏的TextView

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
    tools:context=".MainActivity">
    <com.demo.demo0.DragViewGroup
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/menu"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/colorAccent" />

        <TextView
            android:id="@+id/main"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/colorPrimary" />
    </com.demo.demo0.DragViewGroup>

</LinearLayout>

对其进行侧滑效果如图

在这里插入图片描述

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: android