如何在onConfigurationChanged中获取根布局的新宽度/高度?

22

我们的一个视图以 ScrollView 作为其根布局。当设备旋转并调用 onConfigurationChanged() 时,我们想要能够获取 ScrollView 的新宽度/高度。我们的代码如下:

@Override
public void onConfigurationChanged(Configuration newConfig) {
    Log.d(TAG, "Width: '" + findViewById(R.id.scrollview).getWidth() + "'");
    Log.d(TAG, "Height: '" + findViewById(R.id.scrollview).getHeight() + "'");

    super.onConfigurationChanged(newConfig);

    Log.d(TAG, "Width: '" + findViewById(R.id.scrollview).getWidth() + "'");
    Log.d(TAG, "Height: '" + findViewById(R.id.scrollview).getHeight() + "'");
}

我们的 AndroidManifest.xml 文件中相关部分如下:

<activity android:name=".SomeActivity"
    android:configChanges="keyboardHidden|orientation">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
    </intent-filter>
</activity>

最后,我们布局的相关部分如下:

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/scrollview"
    android:layout_height="fill_parent"
    android:layout_width="fill_parent"
    >
    <LinearLayout android:id="@+id/container"
        android:orientation="vertical"
        android:layout_height="fill_parent"
        android:minHeight="200dip"
        android:layout_width="fill_parent"
        >

在我们的安卓设备上,我们预期在切换到横屏时看到ScrollView的宽度变为854,在切换回竖屏时变为480(高度也做相应的变化,不包括菜单栏)。然而,我们看到的结果是相反的。以下是我们的LogCat:

// Switching to landscape:
03-26 11:26:16.490: DEBUG/ourtag(17245): Width: '480'  // Before super
03-26 11:26:16.490: DEBUG/ourtag(17245): Height: '778' // Before super
03-26 11:26:16.529: DEBUG/ourtag(17245): Width: '480'  // After super
03-26 11:26:16.536: DEBUG/ourtag(17245): Height: '778' // After super

// Switching to portrait:
03-26 11:26:28.724: DEBUG/ourtag(17245): Width: '854'  // Before super
03-26 11:26:28.740: DEBUG/ourtag(17245): Height: '404' // Before super
03-26 11:26:28.740: DEBUG/ourtag(17245): Width: '854'  // After super
03-26 11:26:28.740: DEBUG/ourtag(17245): Height: '404' // After super

很明显,当我们从竖屏切换到横屏时,我们得到的是纵向尺寸,而当我们从横屏切换到竖屏时,我们得到的是横向尺寸。我们做错了什么吗?我们可以使用hacky方法解决这个问题,但我觉得我们可能忽略了一个简单的解决方案。

3个回答

38

对于那些想要更详细的解决方案描述的人:您可以使用视图的ViewTreeObserver并注册一个OnGlobalLayoutListener

@Override
public void onConfigurationChanged(Configuration newConfiguration) {
    super.onConfigurationChanged(newConfiguration);
    final View view = findViewById(R.id.scrollview);

    ViewTreeObserver observer = view.getViewTreeObserver();
    observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

        @Override
        public void onGlobalLayout() {
            Log.v(TAG,
                    String.format("new width=%d; new height=%d", view.getWidth(),
                            view.getHeight()));
            view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
        }
    });
}

6
为了避免onGlobalLayout方法的无限循环调用,在根据视图新尺寸进行计算后,我必须移除监听器:view.getViewTreeObserver().removeGlobalOnLayoutListener(this); - Sébastien
1
谢谢,我忘记了viewTreeObservers...另外为了澄清:removeGlobalOnLayoutListener已经被弃用,请使用"removeOnGlobalLayoutListener"代替。 - j2emanue
6
这会导致结果不一致。有时候做对了,有时候做错了。在我看来,在自定义“View”中监听调整大小事件可能是正确的方法。 - Christopher Perry
很好的方式来达成交易,而且在旋转过程中也非常顺畅。 - Alessandro Cabutto
同意@ChristopherPerry的观点,尝试过这个方法,但有时候它并没有任何效果,而且会返回相同的值。最好使用自定义视图并覆盖onSizeChanged事件,这样就能始终正常工作。 - KunalK

16

当onGlobalLayout被调用时,并不确定视图是否已根据新的布局方向进行了调整,所以对我来说,只有以下解决方案有效:

@Override
public void onConfigurationChanged(Configuration newConfig) {
final int oldHeight = mView.getHeight();
final int oldWidth = mView.getWidth();

mView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            if (mView.getHeight() != oldHeight && mView.getWidth() != oldWidth) {
                mView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                //mView now has the correct dimensions, continue with your stuff
            }
        }
    });
    super.onConfigurationChanged(newConfig);}

谢谢,我还得检查尺寸是否改变才能移除监听器。现在onGlobalLayout会被调用两次,我只有在第二次才能得到正确的尺寸。不过我不知道为什么 :/ - Tim Autin
应该是被接受的答案。虽然检查高度和宽度两者都不是必要的,但只检查其中一个就足够了。 - 0101100101
如果用户调整了手机设置中的动画速度,则绝对需要注意。 - rupps
如果onConfigurationChanged被调用的情况不仅仅是旋转,那该怎么办? - cylon

7

getWidth()返回View布局时的宽度,这意味着您需要等待其绘制到屏幕上。在重新绘制新配置的视图之前,会调用onConfigurationChanged,因此我认为您直到稍后才能获得新宽度。


这正是我们担心的。我们能想到的第一个解决方案是生成一个线程,等待100毫秒或者其他时间,然后获取宽度/高度。有没有比那更好的方法呢? - jakeboxer
13
创建一个线程?哦不,不要这样做!你可以在Handler中发布一个Runnable(或者在UI线程中使用View.post())来实现。你也可以在ViewTreeObserver中注册一个监听器,在自定义View中等待大小改变事件等等。 - Romain Guy
听ViewTreeObserver对我来说似乎是最好的解决方案,因为我发现等待方法有点不太专业。 - jqpubliq
啊,太好了。我们以前从未听说过ViewTreeObserver。Romain Guy,如果你把它作为答案发布,我会投票并接受它。否则,我只能接受这个答案。 - jakeboxer
1
如果你不能使用它来获取新的高度和宽度,那么将一个名为newConfig的配置对象传递到方法中的意义是什么?Configuration类甚至有screenWidthDp和screenHeightDp类成员。 - Marty Miller
runOnUiThread 在布局完成之前立即被调用,post 方法似乎也是如此,所以如果使用等待方法,则需要生成一个新线程。使用 ViewTreeObserver 更为规范。 - Lassi Kinnunen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接