欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

自定义控件拖拽View界面刷新保持原位置

程序员文章站 2022-11-30 19:04:50
下午接到一个很简单的需求,在首页有一个按钮,可以拖拽。产品让估工期,我随口说晚上下班前给你看效果。因为真的是很普遍的需求,百度一下,自定义View一大堆。直接使用,和原业务也没有耦合,本来信心慢慢,想着10分钟完成,又可以摸鱼一下午爽呆呆。结果差点翻车~随手百度的一个自定View 里面的代码注释很清晰,但是当VC大法进项目之后发现,拖动的控件,但是页面刷新,View位置会重置到原位置。public class FreeView extends ImageView { private i...

周五下午接到一个很简单的需求,在首页有一个按钮,可以拖拽。产品让估工期,我随口说晚上下班前给你看效果。

因为真的是很普遍的需求,百度一下,自定义View一大堆。直接使用,和原业务也没有耦合,本来信心慢慢,想着10分钟完成,又可以摸鱼一下午爽呆呆。结果差点翻车~

随手百度的一个自定View 里面的代码注释很清晰,但是当VC大法进项目之后发现,拖动的控件,但是页面刷新,View位置会重置到原位置。

public class FreeView extends ImageView {
    private int width; //  测量宽度 FreeView的宽度
    private int height; // 测量高度 FreeView的高度
    private int maxWidth; // 最大宽度 window 的宽度
    private int maxHeight; // 最大高度 window 的高度
    private Context context;
    private float downX; //点击时的x坐标
    private float downY;  // 点击时的y坐标
    //是否拖动标识
    private boolean isDrag = false;

    // 处理点击事件和滑动时间冲突时使用 返回是否拖动标识
    public boolean isDrag() {
        return isDrag;
    }

    // 初始化属性
    public FreeView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;


    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 获取屏宽高 和 可是适用范围 (我的需求是可在屏幕内拖动 不超出范围 也不需要隐藏)
        width = getMeasuredWidth();
        height = getMeasuredHeight();
        maxWidth = getMaxWidth(context);
        maxHeight = getMaxHeight(context) - getStatusBarHeight();// 此时减去状态栏高度 注意如果有状态栏 要减去状态栏 如下行 得到的是可活动的高度
        //maxHeight = UiUtil.getMaxHeight(context)-getStatusBarHeight() - getNavigationBarHeight();
    }

    // 获取状态栏高度
    public int getStatusBarHeight() {
        int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
        return getResources().getDimensionPixelSize(resourceId);
    }

    // 获取导航栏高度
    public int getNavigationBarHeight() {
        int rid = getResources().getIdentifier("config_showNavigationBar", "bool", "android");
        if (rid != 0) {
            int resourceId = context.getResources().getIdentifier("navigation_bar_height", "dimen", "android");
            return context.getResources().getDimensionPixelSize(resourceId);
        } else
            return 0;

    }


    /**
     * 处理事件分发
     *
     * @param event
     * @return
     */

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        if (this.isEnabled()) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN: // 点击动作处理 每次点击时将拖动状态改为 false 并且记录下点击时的坐标 downX downY
                    isDrag = false;
                    downX = event.getX(); // 点击触屏时的x坐标 用于离开屏幕时的x坐标作计算
                    downY = event.getY(); // 点击触屏时的y坐标 用于离开屏幕时的y坐标作计算
                    break;
                case MotionEvent.ACTION_MOVE: // 滑动动作处理 记录离开屏幕时的 moveX  moveY 用于计算距离 和 判断滑动事件和点击事件 并作出响应
                    final float moveX = event.getX() - downX;
                    final float moveY = event.getY() - downY;
                    int l, r, t, b; // 上下左右四点移动后的偏移量
                    //计算偏移量 设置偏移量 = 3 时 为判断点击事件和滑动事件的峰值
                    if (Math.abs(moveX) > 3 || Math.abs(moveY) > 3) { // 偏移量的绝对值大于 3 为 滑动时间 并根据偏移量计算四点移动后的位置
                        l = (int) (getLeft() + moveX);
                        r = l + width;
                        t = (int) (getTop() + moveY);
                        b = t + height;
                        //不划出边界判断,最大值为边界值
                        // 如果你的需求是可以划出边界 此时你要计算可以划出边界的偏移量 最大不能超过自身宽度或者是高度  如果超过自身的宽度和高度 view 划出边界后 就无法再拖动到界面内了 注意
                        if (l < 0) { // left 小于 0 就是滑出边界 赋值为 0 ; right 右边的坐标就是自身宽度 如果可以划出边界 left right top bottom 最小值的绝对值 不能大于自身的宽高
                            l = 0;
                            r = l + width;
                        } else if (r > maxWidth) { // 判断 right 并赋值
                            r = maxWidth;
                            l = r - width;
                        }
                        if (t < 0) { // top
                            t = 0;
                            b = t + height;
                        } else if (b > maxHeight) { // bottom
                            b = maxHeight;
                            t = b - height;
                        }
                        
                    this.layout(l, t, r, b); // 重置view在layout 中位置
    isDrag = true;  // 重置 拖动为 true
                    } else {
 isDrag = false; // 小于峰值3时 为点击事件
                    }
                    break;
                case MotionEvent.ACTION_UP: // 不处理
          setPressed(false);
                    break;
                case MotionEvent.ACTION_CANCEL: // 不处理
                    setPressed(false);
                    break;
            }
            return true;
        }
        return false;
    }

    public OnclickListener mOnclicklistener;

    public interface OnclickListener {
        void onMove();
    }


    public void setOnClickListener(OnclickListener mOnclicklistener) {
        this.mOnclicklistener = mOnclicklistener;
    }

    public static int getMaxWidth(Context context) {
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics dm = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(dm);
        return dm.widthPixels;
    }


    public static int getMaxHeight(Context context) {
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics dm = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(dm);
        return dm.heightPixels;
    }
}

想想也知道了,在我们计算完控件挪动距离之后,仅仅调用了layout方法进行摆放。也仅仅针对控件本身。那当我们的界面刷新的时候,例如列表滚动,轮播图刷新的时候,父控件回一层一层的调用layout摆放。而我们调用的ViewGroup的setTop(),setLeft(),layout()等方法,而这些方法只被建议用在ViewGroup内部。其根本原因可能是这些方法对View的操作类似于帧动画,仅仅改变View当时的位置,但是View的属性并没有改变,当界面刷新重新绘制或者重新加载的时候,View会根据其自身的属性的恢复位置。

知道原因了,那么就更改吧,

我想到方法

1、记录View 最后的位置,进来之后直接摆放

2、利用PopWindow实现

3、既然不认可这种方式操作view,那我就使用你认可的方式,setLayoutParams()方式

好了,这篇文章不探讨 1/2 有兴趣的同学可以自己百度下资料,我使用第三种方式实现

public class FreeView extends ImageView {
    private int width; //  测量宽度 FreeView的宽度
    private int height; // 测量高度 FreeView的高度
    private int maxWidth; // 最大宽度 window 的宽度
    private int maxHeight; // 最大高度 window 的高度
    private Context context;
    private float downX; //点击时的x坐标
    private float downY;  // 点击时的y坐标

    private float x, y;


    //是否拖动标识
    private boolean isDrag = false;

    // 处理点击事件和滑动时间冲突时使用 返回是否拖动标识
    public boolean isDrag() {
        return isDrag;
    }

    // 初始化属性
    public FreeView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;


    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 获取屏宽高 和 可是适用范围 (我的需求是可在屏幕内拖动 不超出范围 也不需要隐藏)
        width = getMeasuredWidth();
        height = getMeasuredHeight();
        maxWidth = getMaxWidth(context);
        maxHeight = getMaxHeight(context) - getStatusBarHeight();// 此时减去状态栏高度 注意如果有状态栏 要减去状态栏 如下行 得到的是可活动的高度
        //maxHeight = UiUtil.getMaxHeight(context)-getStatusBarHeight() - getNavigationBarHeight();
    }

    // 获取状态栏高度
    public int getStatusBarHeight() {
        int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
        return getResources().getDimensionPixelSize(resourceId);
    }

    // 获取导航栏高度
    public int getNavigationBarHeight() {
        int rid = getResources().getIdentifier("config_showNavigationBar", "bool", "android");
        if (rid != 0) {
            int resourceId = context.getResources().getIdentifier("navigation_bar_height", "dimen", "android");
            return context.getResources().getDimensionPixelSize(resourceId);
        } else
            return 0;

    }


    /**
     * 处理事件分发
     *
     * @param event
     * @return
     */

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        if (this.isEnabled()) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN: // 点击动作处理 每次点击时将拖动状态改为 false 并且记录下点击时的坐标 downX downY
                    isDrag = false;
                    downX = event.getRawX(); // 点击触屏时的x坐标 用于离开屏幕时的x坐标作计算
                    downY = event.getRawY(); // 点击触屏时的y坐标 用于离开屏幕时的y坐标作计算
                    x = event.getRawX(); // 用于计算是点击还是滑动
                    y = event.getRawY(); // 用于计算是点击还是滑动


                    this.bringToFront();
                    break;
                case MotionEvent.ACTION_MOVE: // 滑动动作处理 记录离开屏幕时的 moveX  moveY 用于计算距离 和 判断滑动事件和点击事件 并作出响应
                    final int moveX = (int) (event.getRawX() - downX);
                    final int moveY = (int) (event.getRawY() - downY);


                    Logger.e("-X-" + moveX + "-Y-" + moveY);
                        int l, r, t, b; // 上下左右四点移动后的偏移量
                        l = (int) (getLeft() + moveX);
                        r = getRight() + moveX;
                        t = (int) (getTop() + moveY);
                        b = getBottom() + moveY;
                        if (l < 0) { // left 小于 0 就是滑出边界 赋值为 0 ; right 右边的坐标就是自身宽度 如果可以划出边界 left right top bottom 最小值的绝对值 不能大于自身的宽高
                            l = 0;
                            r = l + width;
                        } else if (r > maxWidth) { // 判断 right 并赋值
                            r = maxWidth;
                            l = r - width;
                        }
                        if (t < 0) { // top
                            t = 0;
                            b = t + height;
                        } else if (b > maxHeight) { // bottom
                            b = maxHeight;
                            t = b - height;
                        }
                        setRelativeViewLocation(this, l, t, r, b); // 重置view在layout 中位置
                        downX = event.getRawX();
                        downY = event.getRawY();
                    break;
                case MotionEvent.ACTION_UP:
                    final int mX = (int) (event.getRawX() - x);
                    final int mY = (int) (event.getRawY() - y);
                    if (Math.abs(mX) < 10 || Math.abs(mY) < 10){
                        isDrag = true;  // 重置 拖动为 true
                    }else{
                        isDrag = false;  // 重置 拖动为 true
                    }
                    break;
                case MotionEvent.ACTION_CANCEL: // 不处理
                    setPressed(false);
                    break;
            }
            return true;
        }
        return false;
    }

    public OnclickListener mOnclicklistener;

    public interface OnclickListener {
        void onMove();
    }


    public void setOnClickListener(OnclickListener mOnclicklistener) {
        this.mOnclicklistener = mOnclicklistener;
    }

    public static int getMaxWidth(Context context) {
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics dm = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(dm);
        return dm.widthPixels;
    }


    public static int getMaxHeight(Context context) {
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics dm = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(dm);
        return dm.heightPixels;
    }

    private void setRelativeViewLocation(View view, int left, int top, int right, int bottom) {
        RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(right - left, bottom - top);
        params.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);
        params.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE);
        params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE);
        params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, RelativeLayout.TRUE);
        ViewParent parent = view.getParent();
        View p = (View) parent;
        int marginRight = p.getWidth() - right;
        int marginBottom = p.getHeight() - bottom;
        params.setMargins(left, top, marginRight, marginBottom);
        view.setLayoutParams(params);
    }

}

 

还是自定义控件,直接VC进入项目,就可以使用,点击事件可以使用回调,可以使用View的setOnclicklistener()方式,暴露了一个boolean值控制。

自测还没有发现什么问题

 

 

 

 

 

 

 

 

本文地址:https://blog.csdn.net/qq_30974087/article/details/107128690