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

cur*_*zen 5 android invalidation screen-orientation android-imageview android-framelayout

编辑3:

由于历史原因,我在下面保留原始问题.但是,我发现这个问题并不孤立FrameLayout.因此,更新了标题.

我没有用更多的代码过度拥挤这篇文章,而是创建了一个示例项目来演示这个问题; 并将其上传到Google Project Hosting.

问题的摘要是这样的:

纵向可绘制,其上设置有特定边界,在改变方向并返回纵向时,不保留设定边界.相反,它恢复到原来的界限.尽管有力地设定了方向变化的明确界限.请注意,您遵守以后在Click等上设置的任何边界都会被遵守.

我还上传了一个版本的应用程序,其中包含2个单独的活动来说明问题.

  1. 纯粹的香草活动只是说明了这个问题.
  2. 使用自定义活动BitmapDrawableImageView-这一个打印边界时被更改日志.

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


原始问题:

我使用的是FrameLayout具有2个ImageView小号一个堆叠在另一个用于显示"电池状态"的图形的顶部.这是纵向模式.在横向模式下,我显示不同的布局(图表).

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

我尝试了各种各样的事情来弄清楚发生了什么.调试向我显示"顶部"图形的边界确实按预期设置.所以这似乎是一个无效的问题.我将介绍2个类和2个布局XML(全部简化)的代码,这有助于重现问题.还附加用于ImageView的占位符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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

编辑

我也试过调用setPercent()并传入一个保存的值displayPortraitView()- 当我们回到纵向模式时,基本上强制更新到已知状态.仍然没有运气.请注意,日志告诉我drawables的边界是正确的.我无法弄清楚为什么失效不会发生.


编辑2:

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

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

我们的想法是每当我们从横向模式返回到纵向模式时强制重绘和无效.再次 - 我确实看到电池"内容"drawable的界限被设置为所需,但UI拒绝跟上步伐.

Ran*_*ku' 2

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

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

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

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]  
Run Code Online (Sandbox Code Playgroud)

在阅读您的代码时,我发现仅仅为了绘制一个米而更改边界并存储边界等就太过分了。我可以建议以下之一:

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