Почему onLayout и onSizeChanged вызываются дважды при изменении ориентации? - PullRequest
10 голосов
/ 14 марта 2012

Я заметил, что onLayout и onSizeChanged вызывают дважды сразу после изменения ориентации, либо из альбомной - портретной или из портретной -> ландшафтной, при обработке изменения конфигурации из действия.Кроме того, первый onLayout / onSizeChanged содержит старые размеры (до поворота), а второй onLayout / onSizeChanged содержит новые (правильные) измерения.

Кто-нибудь знает почему и / или как обойти это?Кажется, что, возможно, изменение размера экрана происходит через некоторое время после изменения конфигурации, то есть размеры не верны сразу после изменения конфигурации, когда вызывается onConfigurationChanged?

Вот отладочный вывод кода ниже, показывая оба вызова onLayout / onSizeChanged после поворота из Портретного в Пейзаж (обратите внимание, что устройство имеет размер 540x960, поэтому ширина пейзажа должна быть 960, а ширина портрета 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

Обратите также внимание, чтоfirst onSizeChanged oldwidth и oldheight равны 0, что указывает на то, что мы только что были добавлены в иерархию представлений - но с неправильными измерениями для ландшафта!

А вот код, который иллюстрирует это поведение:

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);
    }
}

MyHor horizontalScrollView.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> 

1 Ответ

7 голосов
/ 08 ноября 2012

Я сам очень долго задавался этим вопросом.

То, как я ответил на вопрос - потому что я верю, что ответ, это зависит от обстоятельств, - это добавление try/catch иливедение журнала в методе requestLayout.Это позволяет вам видеть, когда выполняются запросы на повторное измерение и повторную выкладку, и в случае try/catch, кем.

Способ раскладки в Android работает так, что вы помечаете представление какс грязной компоновкой с requestLayout.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 содержит больше информации об измерениях и планировке, но, к сожалению, не хватает описанных выше особенностей.

Обработка событий и создание потоков

БазовыйЦикл представления выглядит следующим образом:

  1. Событие приходит и отправляется соответствующему представлению.Представление обрабатывает событие и уведомляет всех слушателей.
  2. Если в ходе обработки события, возможно, потребуется изменить границы представления, представление вызовет requestLayout ().
  3. Аналогично,если в ходе обработки события может потребоваться изменить внешний вид представления, представление вызовет invalidate ().
  4. Если были вызваны requestLayout () или invalidate (), инфраструктура позаботится об измерении, разметка и отрисовка дерева соответствующим образом.

Примечание. Все дерево вида является однопоточным.Вы должны всегда быть в потоке пользовательского интерфейса при вызове любого метода в любом представлении.Если вы выполняете работу над другими потоками и хотите обновить состояние представления из этого потока, вам следует использовать обработчик.

...