Android MarginLayoutParams问题记录

本文详细探讨了如何利用MarginLayoutParams实现View跟随手指精确滑动,分析了marginTop参数的真实含义,并提供了一种有效的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 使用MarginLayoutParams来实现View跟随手指滑动的问题

如果搜索View滑动
百度搜出来的都是下面这段:

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

但是这样写运行后会发现
每次手指滑动一点。View却滑动很大的距离
运行下面代码后

  //手指按下的纵坐标
    private float startY;

    //手指滑动的距离
    private float dy;
    ViewGroup.MarginLayoutParams layoutParams;

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if(layoutParams==null){
            layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
        }
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                startY = ev.getRawY();
                super.dispatchTouchEvent(ev);
                return true;
            case MotionEvent.ACTION_MOVE:
                dy = ev.getRawY() - startY;
                layoutParams.topMargin = (int) dy+getTop();

                Log.i("TAG","dy="+dy);
                Log.i("TAG","getTop="+getTop());
                Log.i("TAG","topMargin="+layoutParams.topMargin);

                setLayoutParams(layoutParams);
                return true;
            case MotionEvent.ACTION_UP:
                requestLayout();
                break;
            default:
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

滑动前

11-13 01:35:36.254 4750-4750/? I/TAG: dy=16.748535
11-13 01:35:36.254 4750-4750/? I/TAG: getTop=0
11-13 01:35:36.254 4750-4750/? I/TAG: topMargin=16

滑动后

11-13 01:35:36.663 4750-4750/? I/TAG: dy=37.980194
11-13 01:35:36.663 4750-4750/? I/TAG: getTop=513
11-13 01:35:36.663 4750-4750/? I/TAG: topMargin=550

可以看到我只滑动了37像素但是topMargin却变成了550.
这是因为在滑动过程中,每次滑动都会多次调用 ACTION_MOVE里面的内容。
getTop的值时因为topMargin变而变

所以这一行代码

layoutParams.topMargin = (int) dy+getTop();

就相当于
layoutParams.topMargin = layoutParams.topMargin+(int) dy;

而每次滑动的时候,就相当于迭代累加layoutParams.topMargin的值。
比如你只滑动了40个像素。那么可能调用了10次ACTION_MOVE里面的内容。
那么layoutParams.topMargin实际上就会叠加10次。并不是只加40。

2 marginTop参数并不一定是View到ViewGroup的距离
/**
         * The top margin in pixels of the child. Margin values should be positive.
         * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
         * to this field.
         */
        @ViewDebug.ExportedProperty(category = "layout")
        public int topMargin;

源码中对marginTop的解释是:

* The top margin in pixels of the child. Margin values should be positive.

child顶部(到父布局)的像素距离。Margin值应该是正直

Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value

当重新制定(Margin)值时,要调用setLayoutParams()方法(来保存);
源码的解释和官方API写的一样。并没有其他的解释。
layoutParams.topMargins的真实含义
例如
不设置margin

<com.example.test05_layoutparmas.MyTextView
        android:id="@+id/textView"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:background="#f00"/>
  //手指滑动的距离
    private float dy;
    ViewGroup.MarginLayoutParams layoutParams;

    //记录上一次滑动的距离
    int lastDy;

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if(layoutParams==null){
            layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
        }
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                startY = ev.getRawY();
                super.dispatchTouchEvent(ev);
                return true;
            case MotionEvent.ACTION_MOVE:
                Log.i("TAG","before topMargin="+layoutParams.topMargin); 
        }
        return super.dispatchTouchEvent(ev);
    }

打印结果

11-13 02:03:10.103 5432-5432/com.example.test05_layoutparmas I/TAG: before topMargin=0

设置margin

<com.example.test05_layoutparmas.MyTextView
        android:id="@+id/textView"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_marginTop="20dp"
        android:background="#f00"/>```
 //手指按下的纵坐标
    private float startY;

    //手指滑动的距离
    private float dy;
    ViewGroup.MarginLayoutParams layoutParams;

    //记录上一次滑动的距离
    int lastDy;

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if(layoutParams==null){
            layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
        }
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                startY = ev.getRawY();
                super.dispatchTouchEvent(ev);
                return true;
            case MotionEvent.ACTION_MOVE:
                Log.i("TAG","before topMargin="+layoutParams.topMargin);
                return true;
      
        }
        return super.dispatchTouchEvent(ev);
    }

打印结果

11-13 02:08:43.155 6108-6108/com.example.test05_layoutparmas I/TAG: before topMargin=53

可以看出layoutParams.topMargins并不是margin的值。

遗留问题: layoutParams.topMargins的真实距离 和影响此参数的因素

3 最终解决

    //手指按下的纵坐标
    private float startY;

    //手指滑动的距离
    private float dy;
    ViewGroup.MarginLayoutParams layoutParams;

    //记录上一次滑动的距离
    int lastDy;

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if(layoutParams==null){
            layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
        }
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                startY = ev.getRawY();
                super.dispatchTouchEvent(ev);
                return true;
            case MotionEvent.ACTION_MOVE:
                dy = (ev.getRawY() - startY)*2;
                layoutParams.topMargin = (int) dy+lastDy;

                setLayoutParams(layoutParams);
                return true;
            case MotionEvent.ACTION_UP:
                lastDy = (int)dy+lastDy;
                requestLayout();
                break;
            default:
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

最终通过上面的代码解决了我的滑动问题。但是还留下一个问题:
问题:dy并不是实际滑动的距离。每次还要X2。这样手指滑动的距离才是View滑动的距离。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值