Android 中view的移动

Android中实现view移动有很多方法。以下使一些sdk的介绍和demo实现。文章末尾有demo的github地址,需要的同学请自取。

先介绍一下Android的坐标系

1417629-6081c0d9534c1647_看图王.web.png

View提供的获取的坐标以及距离的方法:
getTop() 获取到的是view自身的顶边到其父布局顶边的距离
getLeft() 获取到的是view自身的左边到其父布局左边的距离
getRight() 获取到的是view自身的右边到其父布局左边的距离
getBottom() 获取到的是view自身底边到其父布局顶边的距离

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

1.通过view.layout(int l, int t, int r, int b) 方法移动view,

具体逻辑为:在ontouch方法中,获得偏移距离,判断偏移量是否超出边界,如果未超出然后做位置移动

    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Log.e("ACTION_DOWN", x + "");
                lastX = x;
                lastY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                //计算移动的距离
                int offX = x - lastX;
                if (noReachParentBorder()) {
                    int left = this.getLeft() + offX;
                    coreMoveLogic(left);
                }
                break;
        }
        return true;
    }

    private void coreMoveLogic(int left) {
        left = left >= 0 ? left : 0;
        int right = left + mWidth;
        if (right < mParentWidth) {

        } else {
            right = mParentWidth;
            left = mParentWidth - mWidth;
        }
        Log.e("offX", right + "--" + left);
        this.layout(left, this.getTop(), right, this.getBottom());
    }

    public boolean noReachParentBorder() {
        ((View) getParent()).getLocationOnScreen(parentPos);
        this.getLocationOnScreen(selfPos);
        Log.e("pos", parentPos[0] + "--" + selfPos[0] + "--" + mParentWidth + "--" + mWidth);
        if (parentPos[0] <= selfPos[0] &&
                (mParentWidth + parentPos[0]) >= (selfPos[0] + mWidth)) {
            return true;
        }
        return false;
    }

2.通过设置ViewGroup.MarginLayoutParams移动view

相同的逻辑,只是修改coreMoveLogic的实现,使用ViewGroup.MarginLayoutParams替换view.layout方法

private void coreMoveLogic(int left) {
        left = left >= 0 ? left : 0;
        int right = left + mWidth;
        if (right < mParentWidth) {

        } else {
            right = mParentWidth;
            left = mParentWidth - mWidth;
        }
        Log.e("offX", right + "--" + left);
        ViewGroup.MarginLayoutParams mlp =
                (ViewGroup.MarginLayoutParams) getLayoutParams();
        mlp.leftMargin = left;
        setLayoutParams(mlp);
    }

3.通过offsetLeftAndRight,offsetTopAndBottom方法移动view,

依然修改coreMoveLogic的实现,使用offsetLeftAndRight替换view.layout方法

private void coreMoveLogic(int left) {
        left = left >= 0 ? left : 0;

        int right = left + mWidth;
        if (right < mParentWidth) {

        } else {
            right = mParentWidth;
            left = mParentWidth - mWidth;
        }
        Log.e("offX", right + "--" + left);
        offsetLeftAndRight(left-getLeft());
    }

4.通过父view的scrollBy,scrollTo方法移动view,

该方法只是使View中的内容整体滑动,并没有滑动View的实际位置
修改下实现逻辑,这次在移动之前提前计算好偏移量。

public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Log.e("ACTION_DOWN", x + "");
                lastX = x;
                lastY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                //计算移动的距离
                int offX = x - lastX;
                CallBean callBean = noReachParentBorder(offX);
                if (callBean.noReachBorder) {
                    int left = this.getLeft() + callBean.lastOffX;
                    coreMoveLogic(left);
                }
                break;
        }
        return true;
    }

    private void coreMoveLogic(int left) {
        int right = left + mWidth;
        if (right < mParentWidth) {

        } else {
            right = mParentWidth;
            left = mParentWidth - mWidth;
        }
        Log.e("offX", right + "--" + left);
        ((View) getParent()).scrollBy(getLeft()-left,0);
    }

    public CallBean noReachParentBorder(int offX) {
        this.getLocationOnScreen(selfPos);
        CallBean callBean = new CallBean();
        callBean.lastOffX = offX;
        Log.e("pos", selfPos[0] + "--" + selfPosStart[0] + "--" + offX + "--" + selfPos[1]+ "--" + selfPosStart[1]+ "--" +mParentWidth+ "--" +mWidth );
        if (selfPos[0]>= selfPosStart[0] &&
                selfPos[0] <= (mParentWidth-mWidth)) {
            callBean.noReachBorder = true;
            if (selfPos[0]+offX<=selfPosStart[0]){
                callBean.lastOffX = selfPosStart[0]-selfPos[0];
            }if (selfPos[0]+offX>=(mParentWidth-mWidth)){
                callBean.lastOffX =(mParentWidth-mWidth)-selfPos[0];
            }
        }else {
            callBean.noReachBorder = false;
        }
        Log.e("callBean",callBean.toString());
        return callBean;
    }

    class CallBean {
        boolean noReachBorder;
        int lastOffX;
        @Override
        public String toString() {
            return "CallBean{" +
                    "noReachBorder=" + noReachBorder +
                    ", lastOffX=" + lastOffX +
                    '}';
        }
    }

5.通过Scroller.startScroll(int startX, int startY, int dx, int dy)移动view

startScroll方法会移动到指定位置,类似scrollTo,
同样该方法只是使View中的内容整体滑动,并没有滑动View的实际位置
以下代码中,随手势移动view,抬手时调用startScroll回到初始位置,
注意,要使startScroll生效需要重写computeScroll,并且执行重绘

public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Log.e("ACTION_DOWN", x + "");
                lastX = x;
                lastY = y;
                break;
            case MotionEvent.ACTION_UP:
                View viewGroup= (View) getParent();
                mScroller.startScroll(viewGroup.getScrollX(),
                        viewGroup.getScrollY(),
                        -viewGroup.getScrollX(),
                        -viewGroup.getScrollY());//返回原来的位置
                //通过重绘不断调用computeScroll
                invalidate();
                break;
            case MotionEvent.ACTION_MOVE:

                //计算移动的距离
                int offsetX=x-lastX;
                int offsetY=y-lastY;
                ((View)getParent()).scrollBy(-offsetX,-offsetY);

                break;
        }
        return true;
    }
    @Override
    public void computeScroll() {
        super.computeScroll();
        if(mScroller.computeScrollOffset()){
            ((View)getParent()).scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
            //通过重绘不断调用computeScroll
            invalidate();
        }
    }

6.通过ViewDragHelper移动view

ViewDragHelper是通过静态工厂方法创建实例,可以简单方便的指定拖动的方向、检测到是否触及到边缘,内部通过代理当前viewgroup的onInterceptTouchEvent和onTouchEvent去控制子view

核心功能都是通过ViewDragHelper.Callback实现的,

boolean tryCaptureView(View child, int pointerId) 
获取子view,返回true表示允许进行移动操作
int clampViewPositionHorizontal(View child, int left, int dx)
返回对应child的新的left就可以水平移动子view了
int clampViewPositionVertical(View child, int top, int dy) 
返回对应child的新的top就可以水平移动子view了
int getViewVerticalDragRange(View child)
控制垂直移动的边界范围
int getViewHorizontalDragRange(View child) 
控制水平移动的边界范围
void onViewReleased(View releasedChild, float xvel, float yvel) 
当releasedChild被释放的时候会调用该方法,xvel和yvel是x和y方向的加速度
void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) 
当changedView的位置发生变化时调用

以下代码简单的示例:

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

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mViewDragHelper.processTouchEvent(event);
        return true;
    }

    @Override
    public void computeScroll() {
        if (mViewDragHelper.continueSettling(true)) {
            invalidate();
        }
    }

    class MyDragHelper extends ViewDragHelper.Callback{

        @Override
        public boolean tryCaptureView(@NonNull View child, int pointerId) {
            //true :child view can move
            mLeft = child.getLeft();
            mTop = child.getTop();
            return true;
        }

        @Override
        public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) {
            if (child==getChildAt(0)||child==getChildAt(2)){
                return  left;
            }
            return super.clampViewPositionHorizontal(child, left, dx);
        }

        @Override
        public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
            if (child==getChildAt(1)||child==getChildAt(2)){
                return  top;
            }
            return super.clampViewPositionVertical(child, top, dy);
        }

        @Override
        public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {
            if (releasedChild==getChildAt(2)){
                Log.e("onViewReleased","onViewReleased"+mLeft+"--"+mTop);
                //smoothSlideViewTo、settleCapturedViewAt、flingCapturedView这三个方法类似,在内部是用的ScrollerCompat来实现滑动的。
                //需要重写computeScroll,使scroll生效
                mViewDragHelper.settleCapturedViewAt(mLeft, mTop);
                invalidate();
            }else {
                super.onViewReleased(releasedChild, xvel, yvel);
            }

        }

    }

demo效果如下
20200603074404 00_00_00-00_00_30.gif

github传送门在此

你可能感兴趣的:(Android 中view的移动)