ImageView при изменении ориентации - Drawable игнорирует setBounds и возвращает в исходное состояние - PullRequest
5 голосов
/ 11 февраля 2012

РЕДАКТИРОВАТЬ 3:

Я поддерживаю первоначальный вопрос ниже по историческим причинам. Однако я обнаружил, что проблема не изолирована до FrameLayout. Следовательно, обновлен заголовок.

Вместо того, чтобы переполнять этот пост большим количеством кода, я создал пример проекта, который демонстрирует проблему; и загрузил в Google Project Hosting.

Краткое описание проблемы:

Рисование в портретной ориентации с определенными границами, на изменение ориентации и возврат к портрету, не сохраняет установить границы. Вместо этого он возвращается к своим первоначальным границам. Это в несмотря на принудительную установку явных границ изменения ориентации. Обратите внимание, что любые ограничения, которые вы позже установили для Click и т. Д., Выполняются.

Я также загрузил версию приложения , которая содержит 2 отдельных действия, иллюстрирующих проблему.

  1. Обычная ванильная активность, которая только иллюстрирует проблему.
  2. Активность, использующая пользовательский BitmapDrawable для ImageView - этот выводится в журнал при каждом изменении границ.

Вторая версия ясно показывает, что даже если я установлю границы на Drawable, эти границы не будут соблюдаться в onBoundsChange().


Оригинальный вопрос:

Я использую FrameLayout с 2 ImageView s, расположенными один над другим для отображения графики «Состояние батареи». Это в портретном режиме. В ландшафтном режиме я отображаю другой макет (график).

Моя проблема заключается в следующем - предположим, отображается состояние батареи - скажем, 30%. Теперь я поворачиваю экран и отображаю график. Когда я возвращаюсь в книжную ориентацию, изображение батареи возвращается в исходное положение (что является "полным").

Я пробовал все виды вещей, чтобы попытаться выяснить, что происходит. Отладка показывает мне, что границы для «верхней» графики действительно устанавливаются в соответствии с ожиданиями. Так что, похоже, это проблема недействительности. Я представляю код для 2 классов и 2 макетов XML (все упрощенно), который помогает воспроизвести проблему. Также прикрепление заполнителей PNG, используемых для ImageViews.

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 (XML для FrameLayout, используемый в пользовательском представлении)

<?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>

EDIT

Я также пытался вызвать setPercent() и передать сохраненное значение в displayPortraitView() - в основном принудительно обновляя известное состояние всякий раз, когда мы возвращаемся в портретный режим. Все еще не повезло. Обратите внимание, что журналы говорят мне, что границы рисования правильны. Я не могу понять, почему недействительность не происходит.


РЕДАКТИРОВАТЬ 2:

  1. В ValueIndicator.java я ввел переменную-член mPercent который всегда хранит последнее известное процентное значение.
  2. Обновлен код setPercent() для обновления переменной-члена mPercent; а затем вызовите метод updateIndicateUi().
  3. updateIndicatorUi() (который теперь является public методом) теперь использует состояние (т.е. mPercent) для своей работы.
  4. Всякий раз, когда мы возвращаемся в портретный режим, я звоню updateIndicatorUi(). Это заставляет само изображение батареи обновляться.

Я также обновил код, чтобы отразить эти изменения.

Идея состоит в том, чтобы принудительно перерисовывать и аннулировать всякий раз, когда мы возвращаемся из ландшафтного в портретный режим. Опять же - я вижу, что границы элемента «содержимого» батареи устанавливаются по желанию, но пользовательский интерфейс отказывается идти в ногу.

Ответы [ 3 ]

2 голосов
/ 16 февраля 2012

Я проверил ваш код на 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) вместо границ его рисования.
  2. Вместо использования ImageView создайте класс с расширением View и переопределите onDraw(Canvas), а затем нарисуйте красный прямоугольник, используя drawRect.
0 голосов
/ 21 февраля 2012

Я решил эту проблему, используя старый добрый пользовательский объект View. В onDraw() я рисую растровые изображения до Canvas и изменяю размеры в соответствии с процентом батареи. Выложил архив для рабочего проекта здесь на Google Project Hosting. Этот ответ помог мне на моем пути, и поэтому я принял и наградил награду.

Я до сих пор не убежден в необходимости использования пользовательского View для этого. ИМО, я должен быть в состоянии поместить ImageView в Layout, и это все еще должно работать. Я просто не могу придумать, как сказать макету, чтобы мои Drawable остались одни!

Кроме того, этот рисунок батареи изначально предназначался для использования вместе с другими графиками (температура, сила сигнала ...) в виде таблицы с двумя столбцами. Таким образом, применимость этого решения к этим случаям еще предстоит выяснить.

Кроме того, в моем решении есть небольшая ошибка - батарея всегда выглядит пустой в первый раз. Я загружу решение в тот же проект, когда найду решение для этого.

0 голосов
/ 11 февраля 2012

Просто предположение, но в RotateActivity попробуйте изменить порядок супер-вызова следующим образом:

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