为什么onLayout和onSizeChanged获得在改变方向时叫了两声?叫了、两声、方向、onLayout

2023-09-06 09:12:02 作者:扯不断的是回忆

我已经注意到,onLayout和onSizeChanged被调用的两次的紧接着连续的方向变化后,无论是从landscape->纵向还是从纵向状态>景观,处理来自一个活动的配置更改时, 。此外,第一onLayout / onSizeChanged包含的旧的尺寸(旋转前),而第二onLayout / onSizeChanged包含的新的(正确的)尺寸。

有谁知道为什么,和/或如何解决此问题?看起来也许是屏幕尺寸的变化发生了相当一段时间的配置更改后 - 即尺寸不会立即在 onConfigurationChanged 被称为配置更改后正确。

下面是的code以下调试输出,同时显示onLayout / onSizeChanged调用从纵向到横向旋转后(注意,该设备是540x960,这样的景观宽度应该是960,而纵向宽度为540)

  03-13 17:36:21.140:DEBUG / RotateTest(27765):onConfigurationChanged:风景
03-13 17:36:21.169:DEBUG / RotateTest(27765):onSizeChanged:540,884,0,0
03-13 17:36:21.189:DEBUG / RotateTest(27765):onLayout:真0,0,540,884
03-13 17:36:21.239:DEBUG / RotateTest(27765):onSizeChanged:960464540884
03-13 17:36:21.259:DEBUG / RotateTest(27765):onLayout:真0,0,960,464
 

还要注意的是第一onSizeChanged oldwidth和oldheight是0,这表明我们只是添加到视图层次! - 但与的错误的尺寸景观

这里是code,它阐释了这一点:

MyActivity.java

 包com.example;

进口android.app.Activity;
进口android.content.res.Configuration;
进口android.os.Bundle;
进口android.util.Log;
进口android.widget.FrameLayout;

公共类MyActivity扩展活动
{
    私有静态字符串变量=RotateTest;

    @覆盖
    公共无效onConfigurationChanged(配置NEWCONFIG){
        Log.d(TAG,onConfigurationChanged:+(newConfig.orientation == 1纵向:风景));
        super.onConfigurationChanged(NEWCONFIG);
        _setView();
    }

    @覆盖
    公共无效的onCreate(包savedInstanceState)
    {
        Log.d(TAG的onCreate);
        super.onCreate(savedInstanceState);
        _setView();
    }

    私人无效_setView(){
        MyHorizo​​ntalScrollView滚动视图=新MyHorizo​​ntalScrollView(这一点,NULL);
        的setContentView(滚动视图);
    }
}
 
日清工作法 落实责任和目标的完美方法 绝对受用

MyHorizo​​ntalScrollView.java

 包com.example;

进口android.content.Context;
进口android.util.AttributeSet;
进口android.util.Log;
进口android.widget.Horizo​​ntalScrollView;

公共类MyHorizo​​ntalScrollView扩展Horizo​​ntalScrollView {

    私有静态字符串变量=RotateTest;

    公共MyHorizo​​ntalScrollView(上下文的背景下,ATTRS的AttributeSet){
        超(背景下,ATTRS);
    }

    @覆盖
    保护无效onLayout(布尔改变,诠释L,INT T,INT R,int b)在{
        super.onLayout(改变,L,T,R,B);
        Log.d(TAG,onLayout:+的String.Format(%S-%D,%D,%D,%D,改变了,L,T,R,B));
    }

    @覆盖
    保护无效onSizeChanged(INT W,INT小时,INT oldw,诠释oldh){
        super.onSizeChanged(W,H,oldw,oldh);
        Log.d(TAG,onSizeChanged:+的String.Format(%D,%D,%D,%D,W,H,oldw,oldh));
    }
}
 

的Andr​​oidManifest.xml

 < XML版本=1.0编码=UTF-8&GT?;
<舱单的xmlns:机器人=htt​​p://schemas.android.com/apk/res/android
      包=为com.example
      安卓版code =1
      机器人:VERSIONNAME =1.0
        >

    <使用-SDK安卓的minSdkVersion =8的Andr​​oid版本:targetSdkVersion =9/>

    <应用机器人:标签=@字符串/ APP_NAME
            >
        <活动机器人:名称=MyActivity
                  机器人:标签=@字符串/ APP_NAME
                  机器人:configChanges =方向
                >
            <意向滤光器>
                <作用机器人:名称=android.intent.action.MAIN/>
                <类机器人:名称=android.intent.category.LAUNCHER/>
            &所述; /意图滤光器>
        < /活性GT;
    < /用途>
< /舱单>
 

解决方案

我一直在想这个自己很长一段时间。

我回答了这个问题的方式 - 因为我相信答案是,这取决于 - 是通过增加一个尝试/捕获或记录语句 requestLayout 方法。这可以让你看的时候的重新测量和重新布局的要求制成,并且在的情况下, try / catch语句,是谁。

的方式布置在Android的工作原理是,你标记一个视图具有一个肮脏的布局 requestLayout 。它总是运行在一定的时间间隔在UI线程在Android尺蠖将重新测量和重新布局的树视图已标记为脏一些不确定点的未来。

我大胆猜测, onConfigurationChanged ,你得到几个 requestLayout 电话和弯针呼吁 onMeasure 地方在他们中间。

这是什么样的记录看起来像我:

  11-07 15:39:13.624:W / YARIAN(30006):requestLayout
11-07 15:39:13.632:W / YARIAN(30006):requestLayout
11-07 15:39:13.640:W / YARIAN(30006):requestLayout
11-07 15:39:13.647:W / YARIAN(30006):requestLayout
11-07 15:39:13.686:W / YARIAN(30006):requestLayout
11-07 15:39:13.718:W / YARIAN(30006):requestLayout
11-07 15:39:13.827:W / YARIAN(30006):requestLayout
11-07 15:39:14.108:W / YARIAN(30006):onLayout
11-07 15:39:14.155:W / YARIAN(30006):requestLayout
11-07 15:39:14.272:W / YARIAN(30006):onLayout
 

Android的文档中有更多信息的衡量和布局,但可悲的是短于我上面描述的具体细节。

  

事件处理和线程

     

的图中的基本循环是如下:

        在一个事件进来,被分派到相应的视图。视图处理该事件,并通知所有侦听器。   如果在处理事件的过程中,该视图的边界可能需要改变,该视图将调用requestLayout()。   同样,如果在处理视图的外观可能需要事件的过程中被改变,认为将调用无效()。   如果任一requestLayout()或无效()被调用时,该框架将负责测量,布局和绘制的   树为宜。         

注:整个视图树是单线程的。你必须时刻   在UI线程调用的任何视图的任何方法时。如果你正在做   在其他线程工作,要更新从一个视图状态   线程,你应该使用一个处理器。

I've noticed that onLayout and onSizeChanged get called twice in immediate succession after an orientation change, either from landscape->portrait or from portrait->landscape, when handling the configuration change from an Activity. Furthermore, the first onLayout/onSizeChanged contain the old dimensions (before the rotate), while the 2nd onLayout/onSizeChanged contain the new (correct) dimensions.

Does anyone know why, and/or how to work around this? It seems like perhaps the screen size change happens quite some time after the configuration change - i.e., the dimensions are not correct immediately after the configuration change when onConfigurationChanged is called?

Here's the debug output of the code below, showing both onLayout/onSizeChanged calls after a rotation from Portrait to Landscape (note that the device is 540x960, so the landscape width should be 960 while the portrait width is 540):

03-13 17:36:21.140: DEBUG/RotateTest(27765): onConfigurationChanged: LANDSCAPE
03-13 17:36:21.169: DEBUG/RotateTest(27765): onSizeChanged:540,884,0,0
03-13 17:36:21.189: DEBUG/RotateTest(27765): onLayout:true-0,0,540,884
03-13 17:36:21.239: DEBUG/RotateTest(27765): onSizeChanged:960,464,540,884
03-13 17:36:21.259: DEBUG/RotateTest(27765): onLayout:true-0,0,960,464

Note also that the first onSizeChanged oldwidth and oldheight are 0, indicating that we were just added to the view hierarchy - yet with the wrong dimensions for landscape!

And here is the code that illustrates this behavior:

MyActivity.java

package com.example;

import android.app.Activity;
import android.content.res.Configuration;
import android.os.Bundle;
import android.util.Log;
import android.widget.FrameLayout;

public class MyActivity extends Activity
{
    private static String TAG = "RotateTest";

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        Log.d(TAG, "onConfigurationChanged: " + (newConfig.orientation == 1 ? "PORTRAIT" : "LANDSCAPE"));
        super.onConfigurationChanged(newConfig);
        _setView();
    }

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        Log.d(TAG, "onCreate");
        super.onCreate(savedInstanceState);
        _setView();
    }

    private void _setView() {
        MyHorizontalScrollView scrollView = new MyHorizontalScrollView(this, null);
        setContentView(scrollView);
    }
}

MyHorizontalScrollView.java

package com.example;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.HorizontalScrollView;

public class MyHorizontalScrollView extends HorizontalScrollView {

    private static String TAG = "RotateTest";

    public MyHorizontalScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        Log.d(TAG, "onLayout:" + String.format("%s-%d,%d,%d,%d", changed, l, t, r, b));
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        Log.d(TAG, "onSizeChanged:" + String.format("%d,%d,%d,%d", w, h, oldw, oldh));
    }
}

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.example"
      android:versionCode="1"
      android:versionName="1.0"
        >

    <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="9"/>

    <application android:label="@string/app_name"
            >
        <activity android:name="MyActivity"
                  android:label="@string/app_name"
                  android:configChanges="orientation"
                >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest> 

解决方案

I've been wondering this myself for a very long time.

The way I answered the question--because I do believe the answer is, it depends--is by adding a try/catch or logging statement in the requestLayout method. This allows you to see when the requests for remeasuring and re-laying out are made, and in the case of the try/catch, by whom.

The way laying out in Android works is that you mark a view as having a dirty layout with requestLayout. An Android looper which always runs on the UI thread on some interval will remeasure and re-layout views in the tree that have been marked as dirty at some indeterminate point in the future.

I venture to guess that onConfigurationChanged, you are getting several requestLayout calls and the looper is calling onMeasure somewhere in the middle of them.

This is what the logging looked like for me:

11-07 15:39:13.624: W/YARIAN(30006): requestLayout
11-07 15:39:13.632: W/YARIAN(30006): requestLayout
11-07 15:39:13.640: W/YARIAN(30006): requestLayout
11-07 15:39:13.647: W/YARIAN(30006): requestLayout
11-07 15:39:13.686: W/YARIAN(30006): requestLayout
11-07 15:39:13.718: W/YARIAN(30006): requestLayout
11-07 15:39:13.827: W/YARIAN(30006): requestLayout
11-07 15:39:14.108: W/YARIAN(30006): onLayout
11-07 15:39:14.155: W/YARIAN(30006): requestLayout
11-07 15:39:14.272: W/YARIAN(30006): onLayout

The Android documentation has more information on measuring and laying out, but is sadly short on the specifics I described above.

Event Handling and Threading

The basic cycle of a view is as follows:

An event comes in and is dispatched to the appropriate view. The view handles the event and notifies any listeners. If in the course of processing the event, the view's bounds may need to be changed, the view will call requestLayout(). Similarly, if in the course of processing the event the view's appearance may need to be changed, the view will call invalidate(). If either requestLayout() or invalidate() were called, the framework will take care of measuring, laying out, and drawing the tree as appropriate.

Note: The entire view tree is single threaded. You must always be on the UI thread when calling any method on any view. If you are doing work on other threads and want to update the state of a view from that thread, you should use a Handler.