android layout过程分析,Andriod 从 0 开始自定义控件之 View 的 layout 过程 (八)

前言

在上一篇文章了,我们学习了 View 三大流程之一的 measure 过程,当 measure 过程完成后,View 的大小就测量好了。接下来就到了 layout 的过程了,layout 的过程就是用于确定 View 的位置。下面通过查看源码,来更深入的了解下 layout 的整个过程。

源码分析

View 的 layout 过程是从 ViewRoot 开始的,ViewRoot 的 performTraversals 方法会在 measure 过程执行完后继续执行 performLayout 方法,在该方法中又执行了 View 的 layout 方法:

host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());复制代码

该方法接收四个参数,分别是 left、top、right、bottom 四个坐标,代表 View 的四个顶点位置,其中 left、top 的参数为 0,right、bottom 则把测量好的宽、高传入了进来,下面继续看下 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;

}

// 记录 View 的原始位置

int oldL = mLeft;

int oldT = mTop;

int oldB = mBottom;

int oldR = mRight;

// 判断 isLayoutModeOptical 方法的返回值, true 则执行 setOpticalFrame 方法,否则执行 setFrame

// setOpticalFrame 方法最终也走了 setFrame 方法,所以最终都会执行 setFrame 方法

// setFrame 方法会判断 View 位置是否发生改变, 如果发生改变, 会将 View 新的 left、top、right、bottom 值赋值给成员变量,并返回一个 boolean 值,表示位置是否改变

// 当 setFrame 完成后,表示 View 本身的位置已经确定

boolean changed = isLayoutModeOptical(mParent) ?

setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

// 如果位置发生改变,执行 onLayout 方法,该方法在 View 中是个空实现,需要继承 ViewGroup 的类实现

// onLayout 方法的作用是 ViewGroup 确定子 View 的位置

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 listenersCopy =

(ArrayList)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;

}复制代码

layout 方法首先通过调用 setFrame 方法判断 View 的位置是否发生改变,如果发生改变,则将新位置的值 left、top、right、bottom 赋值给 mLeft、mTop、mRight、mBottom 这几个成员变量,这四个值保存着 View 的位置信息,我们可以通过 getLeft、getTop、getRight、getBottom 方法获取到。

所以我们如果想要得到 View 的位置信息,那么必须在 setFrame 方法执行完毕后获取,比如说在 onLayout 方法中获取,因为通过看源码我们已经知道,onLayout 方法是在 setFrame 方法之后执行的。

当 setFrame 方法执行完成后,会返回一个 View 位置是否发生改变的 boolean 值,如果发生改变,那么就会走 onLayout 方法,该方法在 ViewGroup 中调用,用于确定子 View 的位置。

由于具体每种布局的实现效果都不同,所以 onLayout 的默认实现,和上一篇 measure 测量过程中 ViewGroup 的 onMeasure 方法一样,是空实现,具体怎么确定子 View 的位置,由 ViewGroup 的具体实现类去作不同实现。

下面,我们可以自定义一个只能包含一个 View 的布局,来实现下 onLayout 方法,有兴趣的同学也可以自己去看看 LinearLayout 等布局的 onLayout 实现。

实例 (单布局)

代码实现:

public class SingleLayout extends ViewGroup {

public SingleLayout(Context context, AttributeSet attrs) {

super(context, attrs);

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

if(getChildCount() > 0){

View childAt = getChildAt(0);

measureChild(childAt, widthMeasureSpec, heightMeasureSpec);

}

}

@Override

protected void onLayout(boolean changed, int l, int t, int r, int b) {

if(getChildCount() > 0){

View childAt = getChildAt(0);

childAt.layout(0, 0, childAt.getMeasuredWidth(), childAt.getMeasuredHeight());

}

}

}复制代码

该自定义布局很简单,首先在 onMeasure 方法中判断是否有子 View,如果有,那么只测量第一个 View。接着在 onLayout 中判断是否有子 View,如果有,则调用了第一个子 View 的 layout 方法,分别传入 0, 0, childAt.getMeasuredWidth(), childAt.getMeasuredHeight() 四个参数,这四个参数分别代表了子 View 在 当前自定义布局中的位置,也就是放置在当前自定义布局的左上角。

当子 VIew 在 layout 方法中确定了自己的位置后,如果子 View 是 ViewGroup 那么又会去调用 onLayout 方法去确定它子 View 的位置。这样一层一层传递下去,就完成了整个 View 数的 layout 过程。

下面我们把刚刚写的布局运行下,看下效果:

android:layout_width="match_parent"

android:layout_height="wrap_content">

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="Hello World!"/>

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="Hello World2!"/>

复制代码

运行结果:

118ca6e08cb896f38133242f848bff2a.png

可以看到,我们尽管在布局下面放了两个 View,但最终显示的只有一个 View。

getMeasuredWidth 和 getWidth 的区别

getMeasuredWidth / getMeasuredHeight 的值是在 onMeausre 方法结束后可以获取到的,getWidth / getHeight 的值是在 onLayout 方法结束后可以获取到的。

通过查看 View 的 getWidth 、getHeight 源码:

public final int getWidth() {

return mRight - mLeft;

}

public final int getHeight() {

return mBottom - mTop;

}复制代码

结合 mLeft、mTop、mRight、mBottom 这四个成员变量的赋值过程来看,getWidth 方法的返回值刚好就是 getMeasuredWidth,因为 getMeasuredWidth - 0 不就还是 getMeasuredWidth 吗。

但是在某些特殊情况下,还是会导致两者的返回值不相同,比如说以刚刚自定义的 SingleLayout 的例子看来,我如果将调用子 View 的 layout 方法修改为:

childAt.layout(100, 100, childAt.getMeasuredWidth(), childAt.getMeasuredHeight());复制代码

那么最终获取的结果,会发现 getWidth 的值比 getMeasuredWidth 的值少 100px。虽然这样做没有啥意义,但是证明了测量宽高并不一定会等于最终的宽高。

还有一种情况,就是当 View 需要多次测量才能确定自己的测量宽高时,那么在前几次的测量过程中得出的测量宽高有可能并不等于最终的测量宽高,这时获取的测量宽高并不一定与最终宽高相等。

参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的 Android Studio 计算器应用程序的编程过程和代码: 1. 打开 Android Studio,创建一个新项目。 2. 在 Project 面板中,找到 app -> java -> com.example.yourappname 文件夹,打开 MainActivity.java 文件。 3. 在 onCreate 方法中,添加以下代码: ``` @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final TextView resultTextView = findViewById(R.id.resultTextView); final Button addButton = findViewById(R.id.addButton); final Button subButton = findViewById(R.id.subButton); final Button mulButton = findViewById(R.id.mulButton); final Button divButton = findViewById(R.id.divButton); final EditText num1EditText = findViewById(R.id.num1EditText); final EditText num2EditText = findViewById(R.id.num2EditText); addButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { double num1 = Double.parseDouble(num1EditText.getText().toString()); double num2 = Double.parseDouble(num2EditText.getText().toString()); double result = num1 + num2; resultTextView.setText(Double.toString(result)); } }); subButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { double num1 = Double.parseDouble(num1EditText.getText().toString()); double num2 = Double.parseDouble(num2EditText.getText().toString()); double result = num1 - num2; resultTextView.setText(Double.toString(result)); } }); mulButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { double num1 = Double.parseDouble(num1EditText.getText().toString()); double num2 = Double.parseDouble(num2EditText.getText().toString()); double result = num1 * num2; resultTextView.setText(Double.toString(result)); } }); divButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { double num1 = Double.parseDouble(num1EditText.getText().toString()); double num2 = Double.parseDouble(num2EditText.getText().toString()); double result = num1 / num2; resultTextView.setText(Double.toString(result)); } }); } ``` 这段代码的作用是在应用程序启动时,设置布局文件,然后找到文本框、按钮等件,并设置点击监听器。当用户点击加、减、乘、除按钮时,会从文本框中获取两个数值,进行相应的计算,然后在结果文本框中显示计算结果。 4. 在 res -> layout 文件夹中,打开 activity_main.xml 文件,添加以下件: ``` <EditText android:id="@+id/num1EditText" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Enter a number" /> <EditText android:id="@+id/num2EditText" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Enter another number" /> <Button android:id="@+id/addButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="+" /> <Button android:id="@+id/subButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="-" /> <Button android:id="@+id/mulButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="*" /> <Button android:id="@+id/divButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="/" /> <TextView android:id="@+id/resultTextView" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="0" android:textAlignment="center" android:textSize="24sp" /> ``` 这段代码的作用是在应用程序启动时,在界面上添加两个文本框、四个按钮和一个文本框,用于输入两个数值、进行加减乘除运算和显示计算结果。 5. 运行应用程序,输入两个数值,点击加、减、乘、除按钮,会在结果文本框中显示计算结果。 以上就是一个简单的 Android Studio 计算器应用程序的编程过程和代码,希望能对你有所帮助。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值