【问题标题】:ImageView on orientation change - Drawable ignores setBounds and returns to original stateImageView 上的方向改变 - Drawable 忽略 setBounds 并返回到原始状态
【发布时间】:2012-02-11 04:57:39
【问题描述】:

编辑 3:

出于历史原因,我保留下面的原始问题。但是,我发现这个问题并不局限于FrameLayout。因此,更新了标题。

我创建了一个示例项目来演示这个问题,而不是用更多的代码来过度拥挤这篇文章;和uploaded它在 Google Project Hosting 上。

问题的总结是这样的:

一个纵向的drawable,上面设置了一定的边界,on 改变方向并返回纵向,不保留 设定界限。相反,它恢复到原来的界限。这是在 尽管强行设置了方向变化的明确界限。 请注意,您稍后在 Click 等上设置的任何界限都会被遵守。

我还有 uploaded a version of the app,其中包含 2 个单独的活动来说明问题。

  1. 只是说明问题的普通活动。
  2. ImageView 上使用自定义 BitmapDrawable 的活动 - 每当更改边界时,此活动就会打印到日志中。

第二个版本清楚地表明,即使我在 Drawable 上设置了边界,onBoundsChange() 也不会遵守这些边界。


原问题:

我正在使用 FrameLayout 和 2 个 ImageViews 堆叠在一起以显示“电池状态”图形。这是纵向模式。在横向模式下,我显示不同的布局(图表)。

我的问题是——假设正在显示电池状态——比如 30%。现在,我旋转屏幕并显示图表。当我回到纵向时,电池图形会回到原来的位置(即“充满”)。

我尝试了各种各样的方法来试图弄清楚发生了什么。调试显示“顶部”图形的边界确实按预期设置。所以这似乎是一个失效问题。我正在展示 2 个类和 2 个布局 XML(均已简化)的代码,这有助于重现该问题。还附加了用于 ImageViews 的占位符 PNG。

谁能发现错误?要重新创建问题,请运行应用程序,然后单击“更新”按钮。图形将被“填充”到一定程度。然后,切换到横向,然后再回到纵向。该图形不记得它之前的值。

活动:

public class RotateActivity extends Activity {

    private View portraitView, landscapeView;
    private LayoutInflater li;
    private Configuration mConfig;
    private ValueIndicator indicator;
    private Button btn;
    private Random random = new java.util.Random();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        li = LayoutInflater.from(this);
        portraitView = li.inflate(R.layout.portrait, null);
        landscapeView = li.inflate(R.layout.landscape, null);
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        mConfig = newConfig;
        initialize();

    }

    @Override
    protected void onResume() {
        mConfig = getResources().getConfiguration();
        initialize();
        super.onResume();
    }

    private void initialize(){
        if(mConfig.orientation == Configuration.ORIENTATION_LANDSCAPE){
            displayLandscape();
        } else {
            displayPortrait();
        }
    }

    private void displayLandscape() {
        setContentView(landscapeView);
    }

    private void displayPortrait() {
        setContentView(portraitView);
        btn = (Button)portraitView.findViewById(R.id.button1);
        indicator = (ValueIndicator)portraitView.findViewById(R.id.valueIndicator1);
    /*
     * Forcing the graphic to perform redraw to its known state when we return to portrait view.
     */
    indicator.updateIndicatorUi();  



        btn.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                updateIndicator(random.nextInt(100));
            }
        });

    }

    private void updateIndicator(int newValue){
        indicator.setPercent(newValue);
    }
}

肖像.xml:

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

    <com.gs.kiran.trial.inval.ValueIndicator
                android:id="@+id/valueIndicator1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="10dp" />

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Update" />

</LinearLayout>

ValueIndicator.java

public class ValueIndicator extends FrameLayout {

    private ImageView ivFrame, ivContent;
    private Drawable drFrame, drContent;
    private Rect mBounds;
    private int currentTop;
    private int mPercent;

    public ValueIndicator(Context context, AttributeSet attrs) {
        super(context, attrs);
        LayoutInflater li = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View root = li.inflate(R.layout.indicator, this, true);

        ivFrame = (ImageView) root.findViewById(R.id.ivFrame);
        ivContent = (ImageView) root.findViewById(R.id.ivContent);

        drFrame = ivFrame.getDrawable();
        drContent = ivContent.getDrawable();

        mBounds = drFrame.getBounds();

        Log.d(Constants.LOG_TAG, "Constructor of ValueIndicator");

    }

    public void setPercent(int newPercent){
       this.mPercent = newPercent;
       updateIndicatorUi();
    }

    public void updateIndicatorUi(){
        Rect newBounds = new Rect(mBounds);
        newBounds.top = mBounds.bottom - (int)(this.mPercent * mBounds.height() / 100);
        currentTop = newBounds.top;
        Log.d(Constants.LOG_TAG, "currentTop = "+currentTop);
        drContent.setBounds(newBounds);
        //invalidateDrawable(drContent);
        invalidate();
    }
}

indicator.xml(自定义视图中使用的 FrameLayout 的 XML)

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/frameLayout1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" >

    <ImageView
        android:id="@+id/ivFrame"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/frame" />

    <ImageView
        android:id="@+id/ivContent"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/content" />

</FrameLayout>

landscape.xml(虚拟占位符 - 足以重现问题)

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

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Landscape" />

</LinearLayout>

AndroidManifest.xml sn-p:

<activity
            android:label="@string/app_name"
            android:name=".RotateActivity" 
            android:configChanges="orientation|keyboardHidden">
            <intent-filter >
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

编辑

我还尝试调用setPercent() 并在displayPortraitView() 中传递一个保存的值 - 基本上每当我们回到纵向模式时都会强制更新到已知状态。仍然没有运气。请注意,日志告诉我可绘制对象的边界是正确的。我无法弄清楚为什么没有发生失效。


编辑 2:

  1. 在ValueIndicator.java中,我引入了一个成员变量mPercent 它始终存储最后一个已知的百分比值。
  2. 更新setPercent()代码更新成员变量mPercent;然后调用updateIndicateUi() 方法。
  3. updateIndicatorUi()(现在是 public 方法)现在使用状态(即 mPercent)来完成其工作。
  4. 每当我们回到纵向模式时,我都会打电话给updateIndicatorUi()。这会强制电池图形自行更新。

我还更新了代码以反映这些变化。

我们的想法是在我们从横向模式返回纵向模式时强制重绘和无效。再一次 - 我确实看到根据需要设置了可绘制的电池“内容”的边界,但 UI 拒绝跟上。

【问题讨论】:

    标签: android screen-orientation android-imageview invalidation android-framelayout


    【解决方案1】:

    我在 Google Code Hosting 上检查了您的代码(感谢您如此彻底地记录代码),我发现当您返回时,Drawable 上设置的边界确实发生了变化再次从横向到纵向。

    Drawable 的边界不是由您的代码更改,而是由ImageView 的布局方法更改。当您放置一个新布局 (setContentView) 时,所有布局代码都会运行,包括 ImageView 的。 ImageView 更改它包含的可绘制对象的边界,这就是为什么将可绘制对象的边界更改为原始边界的原因。

    导致绑定更改的堆栈跟踪是:

    Thread [<1> main] (Suspended (entry into method onBoundsChange in BitmapDrawable))  
        BitmapDrawable.onBoundsChange(Rect) line: 293   
        BitmapDrawable(Drawable).setBounds(int, int, int, int) line: 131    
        ImageView.configureBounds() line: 769   
        ImageView.setFrame(int, int, int, int) line: 742    
        ImageView(View).layout(int, int, int, int) line: 7186   
        LinearLayout.setChildFrame(View, int, int, int, int) line: 1254 
        LinearLayout.layoutVertical() line: 1130    
        LinearLayout.onLayout(boolean, int, int, int, int) line: 1047   
        LinearLayout(View).layout(int, int, int, int) line: 7192    
        FrameLayout.onLayout(boolean, int, int, int, int) line: 338 
        FrameLayout(View).layout(int, int, int, int) line: 7192 
        LinearLayout.setChildFrame(View, int, int, int, int) line: 1254 
        LinearLayout.layoutVertical() line: 1130    
        LinearLayout.onLayout(boolean, int, int, int, int) line: 1047   
        LinearLayout(View).layout(int, int, int, int) line: 7192    
        PhoneWindow$DecorView(FrameLayout).onLayout(boolean, int, int, int, int) line: 338  
        PhoneWindow$DecorView(View).layout(int, int, int, int) line: 7192   
        ViewRoot.performTraversals() line: 1145 
        ViewRoot.handleMessage(Message) line: 1865  
        ViewRoot(Handler).dispatchMessage(Message) line: 99 
        Looper.loop() line: 130 
        ActivityThread.main(String[]) line: 3835    
        Method.invokeNative(Object, Object[], Class, Class[], Class, int, boolean) line: not available [native method]  
        Method.invoke(Object, Object...) line: 507  
        ZygoteInit$MethodAndArgsCaller.run() line: 847  
        ZygoteInit.main(String[]) line: 605 
        NativeStart.main(String[]) line: not available [native method]  
    

    在阅读您的代码时,我发现更改边界和存储边界等只是为了画一个仪表而过大。我可以建议以下其中一项:

    1. 更改ImageView 本身的大小(使用setLayoutParams)而不是其drawable 的边界。
    2. 不使用 ImageView,而是创建一个扩展 View 的类并覆盖 onDraw(Canvas),然后使用 drawRect 绘制红色矩形。

    【讨论】:

    • 感谢您的意见。万分感激。实际上,这是一个“虚拟”图像。我的真实应用程序使用 FrameLayout 显示电池状态,其中我有 3 张高质量图像 - 电池“框架”、黑色电池“背景”和绿色电池“内容”。我对在 Android 中以图形方式显示电池状态的最佳方式进行了相当多的研究,并得出结论认为setBounds 是要走的路。因此,我认为带有onDraw 的普通自定义View 解决方案可能不适合我。我将尝试建议 1 并报告。
    • 只是大声思考:即使ImageView在布局时改变了边界;我仍然强制设置界限之后 setContentView()。为什么不遵守我指定的新界限?我注意到setImageDrawable() 又调用requestLayout() - 所以要减轻这种情况;我将setImageDrawable() 代码移动到onCreate() 而不是displayPortrait()。仍然没有改善。
    • 更新:我正在尝试扩展View 的解决方案,然后使用graphics.drawBitmap() 将图像直接绘制到屏幕上。这可能有点复杂,因为我需要为onMeasure() 确定正确的策略。我希望在赏金到期之前有一些东西。
    • 我已将“工作”代码提交给谷歌代码项目托管。感谢您为我指明了正确的方向。我使用自定义Viewcanvas.drawBitmap() 来创建电池图形。我将在原始问题的答案中添加更多详细信息。
    【解决方案2】:

    只是一个猜测,但在RotateActivity中,尝试将super调用的顺序更改为:

    super.onConfigurationChanged(newConfig); // super called before initialize()
    mConfig = newConfig;
    initialize();
    

    【讨论】:

    • 好收获!虽然不起作用。我尝试了一些我在问题中更新的东西。
    • 我真的不想通读您的所有代码,但是您为什么不设置电池图像的大小,就像每次重新加载时第一次制作时一样?只需保存值(可能在 instansteState 中)并重复操作。
    • 是的 - 我已经试过了。这就是我在编辑中提到的。每次我回到纵向时,我都会强制设置电池百分比。正如我所提到的,边界已正确更新。只是它们没有“生效”。我什至试图在自定义视图的onAttachedToWindow() 中强制更新 - 仍然没有用。
    • 哎呀..我编辑了问题,但没有编辑代码本身。稍后再做。
    【解决方案3】:

    我通过使用一个很好的旧自定义 View 对象解决了这个问题。在onDraw() 中,我将位图绘制到Canvas,并根据电池百分比调整边界大小。在 Google Project Hosting 上传了工作项目 here 的存档。 This answer 帮助了我,因此我接受并授予了赏金。

    我仍然不相信为此使用自定义View 的必要性。 IMO,我应该能够将ImageView 放在Layout 中,这应该仍然有效。我只是想不出一种方法来告诉布局不要管我的Drawables!

    此外,此电池图形最初旨在与其他图形(温度、信号强度...)一起使用,采用 2 列表格布局。因此,该解决方案对这些情况的适用性还有待观察。

    另外,我的解决方案有一个小错误 - 电池第一次总是显示为空。当我找到解决方案时,我会将解决方案上传到同一个项目。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-01-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-09-02
      相关资源
      最近更新 更多