方向更改时的ImageView - Drawable忽略setBounds并返回原始状态

时间:2012-02-11 04:57:40

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

编辑3:

由于历史原因,我保留下面的原始问题。但是,我发现问题与FrameLayout无关。因此,更新了标题。

我没有用更多代码过度拥挤这篇文章,而是创建了一个展示问题的示例项目;并在Google Project Hosting上uploaded

问题摘要如下:

  

纵向方向的绘图,上面有一定的边界   改变方向并回归肖像,不保留   设定界限。相反,它恢复到原来的界限。这是在   尽管强行设置方向变化的显式界限。   请注意,您之后在Click etc上设置的任何边界都会被遵守。

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

  1. 简单的香草活动,只是说明了这个问题。
  2. BitmapDrawable上使用自定义ImageView的活动 - 无论何时更改边界,都会打印此日志。
  3. 第二个版本清楚地表明,即使我在Drawable上设置了边界,onBoundsChange()也没有遵守这些边界。


    原始问题:

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

    我的问题是这个 - 假设电池状态正在显示 - 比如说30%。现在,我旋转屏幕并显示图表。当我回到纵向方向时,电池图形将恢复到原始位置(即“已满”)。

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

    Placeholder Battery Frame graphic Placeholder Battery Content graphic

    有人能发现错误吗?要重新创建该问题,请运行该应用程序,然后单击“更新”按钮。图形将被“填充”到一定水平。然后,切换到横向,然后返回到肖像。图形不记得它的早期值。

    活动:

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

    portrait.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片段:

    <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()中传入一个已保存的值 - 基本上只要我们回到纵向模式,就会强制更新已知状态。仍然没有运气。请注意,日志告诉我drawables的边界是正确的。我无法弄清楚为什么失效不会发生。


    编辑2:

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

      我们的想法是每当我们从横向模式返回到纵向模式时强制重绘和无效。再次 - 我确实看到电池“内容”可绘制的范围被设置为所需,但UI拒绝跟上。

3 个答案:

答案 0 :(得分:2)

我已经检查了您在Google Code Hosting上的代码(感谢您为如此彻底地记录代码所做的努力),并且我发现当您返回时,Drawable上设置的界限确实已经再次来自横向的肖像。

Drawable的边界不是由您的代码更改,而是由ImageView的布局方法更改。放置新布局(setContentView)时,将运行所有布局代码,包括ImageViewImageView 更改它包含的drawable的边界,这就是为什么你将drawable的边界更改为原始的边界。

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

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绘制红色矩形。

答案 1 :(得分:0)

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

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

答案 2 :(得分:0)

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

我仍然不相信为此需要使用自定义View。 IMO,我应该能够将ImageView放在Layout中,这应该仍然有效。我只是想办法告诉布局让Drawable单独留下来!

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

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