View的getLeft()、getTranslationX()和getX()的区别

本文深入探讨了Android中View的位置信息,包括mLeft、mTop、translationX、translationY、getX()和getY()的区别。通过代码示例和源码分析,揭示了mTop的赋值过程以及与translationY的关系。实验结果显示,translationY影响了点击事件的响应范围,而mTop则决定了父容器中View的位置,不会因动画而改变。

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

1. View的位置信息

通过查看View的源代码,我们知道关于View的位置信息有以下:
1. mLeft、mTop、mRight、mBottom
2. translationX、translationY
3. getX()、getY()

2. demo验证

直接放了两个TextView在布局中

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/text1"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:gravity="center"
        android:background="#ff0000"
        android:textSize="15sp"
        android:textColor="#ffffff"
        android:text="text1"
        />

    <TextView
        android:id="@+id/text2"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:gravity="center"
        android:background="#00ff00"
        android:textSize="15sp"
        android:textColor="#ffffff"
        android:text="text2"
        />

</LinearLayout>

然后设置监听器,点击text2就触动text1的动画,点击text1就打印日志

final TextView text1 = (TextView) findViewById(R.id.text1);
text1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Log.i("cc", "mLeft = " + text1.getLeft() + ",mTop = " + text1.getTop() + ",mRight = " + text1.getRight() + ",mBottom = " + text1.getBottom());
        Log.i("cc", "translationX = " + text1.getTranslationX() + ",translationY = " + text1.getTranslationY());
        Log.i("cc", "x = " + text1.getX() + ",y = " + text1.getY());
    }
});

final TextView text2 = (TextView) findViewById(R.id.text2);
text2.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        ObjectAnimator anim = ObjectAnimator.ofFloat(text1, "translationY", 0, 300);
        anim.setDuration(2000);
        anim.start();
    }
});

开始运行,此时的界面如下:

image

text1在text2的上面,嗯,界面没有问题

此时,我直接点击text1,打印日志如下:

image

可以看出mTop的值为0,translationY的值也为0,y的值也为0,嗯。没有问题。然后我点击text2,界面如下

image

此时我再次点击text1,打印日志如下:

image

发现问题没有,mTop依然为0,但是translationY和y都已经变成了300.下面开始分析

3. 分析

我们翻看View的源码,直接搜索“mTop =”,看具体有哪儿对mTop进行了赋值。
搜索结果有两个地方,一个是在setTop()方法,一个叫setFrame的方法中看见了对mTop的赋值。
其中setTop方法是public方法,意味着是暴露给外界调用的,不过一般不会调用,因为会改变控件的高度。原因如下:

@ViewDebug.ExportedProperty(category = "layout")
public final int getHeight() {
    return mBottom - mTop;
}

那么我们只需要关心setFrame方法了,因为该方法较长,我只截取了部分代码:

protected boolean setFrame(int left, int top, int right, int bottom) {
    …………

    if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
        changed = true;

        // Remember our drawn bit
        int drawn = mPrivateFlags & PFLAG_DRAWN;

        int oldWidth = mRight - mLeft;
        int oldHeight = mBottom - mTop;
        int newWidth = right - left;
        int newHeight = bottom - top;
        boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);

        // Invalidate our old position
        invalidate(sizeChanged);

        mLeft = left;
        mTop = top;
        mRight = right;
        mBottom = bottom;
        …………
    }
    return changed;
}

可以看到这儿对mTop进行了赋值,那么我们继续搜索setFrame方法在哪些地方被调用了。搜索结果有三处地方:setLeftTopRightBottom、setOpticalFrame和layout方法。首先setLeftTopRightBottom方法也是public方法,也是提供给外界调用的,不过一般开发者不会去调用它,原因上面已经阐明。那么我们只需要关心setOpticalFrame和layout方法。

我们查看下setOpticalFrame的源码

private boolean setOpticalFrame(int left, int top, int right, int bottom) {
    Insets parentInsets = mParent instanceof View ?
            ((View) mParent).getOpticalInsets() : Insets.NONE;
    Insets childInsets = getOpticalInsets();
    return setFrame(
            left   + parentInsets.left - childInsets.left,
            top    + parentInsets.top  - childInsets.top,
            right  + parentInsets.left + childInsets.right,
            bottom + parentInsets.top  + childInsets.bottom);
}

平平常常,并没有什么什么奇特之处,只是简单得调用setFrame而已,然后我们继续搜索代码中哪儿调用了setOpticalFrame方法,搜索结果只有一个,就是上面提到的layout方法。一下是layout的源码。

public void layout(int l, int t, int r, int b) {
    if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
        onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
        mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
    }

    int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;

    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        onLayout(changed, l, t, r, b);
        mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnLayoutChangeListeners != null) {
            ArrayList<OnLayoutChangeListener> listenersCopy =
                    (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
            int numListeners = listenersCopy.size();
            for (int i = 0; i < numListeners; ++i) {
                listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
            }
        }
    }

    mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
    mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}

主要是下面这一句在起作用:

boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

也就是说,系统对于mTop的修改,仅仅限于在layout方法内部,而layout方法我们都知道,是View在onMeasure测量之后,对自身以及子控件进行位置布局的。因此我们刚才写的demo代码对translationY开启的属性动画,并不会影响到mTop的值。那么为什么y的值跟随translationY一起改动了呢?我们接着看源码。

public float getY() {
    return mTop + getTranslationY();
}

public void setY(float y) {
    setTranslationY(y - mTop);
}

是不是一下子就真相大白了,View根本就没有x、y的成员变量,不过是根据translationY和mTop的值计算出来的而已。

那么通过上面的demo,我们是否就只能得出这些信息呢?答案是NO,大家是否注意到了两个现象:

  1. 当我点击text2时,text1的位置往下移动了,但是text2的位置并没有被挤下去,我们的布局容器是LinearLayout,按道理text1往下移动了,text2也应该跟着往下移动。
  2. 当我点击text2后,text1移动到了text2的下面,此时我点击text1,发现删除了日志,也就是说text1接受了点击事件。做过动画的朋友都知道,如果我们用View动画来移动View的话,移动的只是View的影响,在动画结束的位置是无法响应点击事件的,而在最开始的位置能响应点击事件。

首先我们解释第一个现象,上面也说了,系统对于mTop的修改仅仅位于layout方法内,而我们开启动画是不能改变mTop的值的。那么mTop的值的意义是什么?是告诉父容器,我的控件的位置在哪儿。那么这个有什么意义呢?意义就是,父容器在onMeasure和onLayout的时候,是以mTop的值作为基准的,这也就是为什么text1往下移动了,text2没有被跟着挤下去。原因就是父容器LinearLayout认为text1的位置依然在mTop的位置。

好,那么如果我们就想改变mTop的值怎么办呢?有两个方法:
1. 直接调用setTop()方法进行设置,不过你要承受住控件变形的风险。
2. 调用setLayoutParam方法进行对控件的margin、width、height、left、top等设置。这个方法是改变的mTop的值。

然后我们解释第二个现象,我们发现text1的mTop等于0,translationY和y都等于300,并且能够在300的地方响应点击事件,这就说明了系统点击事件的范围判断是根据translationY来判断的,而不是通过mTop。

因此我们可以总结一下

  1. 如果想要改变View对于父容器的影响,那么影响的是mLeft、mTop、width、height等属于父容器的属性(mLeft、mTop、width、height等都是父容器的属性,而不属于本View)。
  2. 如果想要改变View自身的影响,比如添加监听器,添加动画等等,那么影响的是translationX、translationY等属于自身的属性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值