Restore state of view before applying XML attributes
Asked Answered
C

2

8

I have a custom view, let's say this is its code:

public class CustomView extends View {

    boolean visible;
    boolean enabled;

    public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);

        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomView, 0, 0);
        try {
            visible = a.getBoolean(R.styleable.CustomView_visible, true);
            enabled = a.getBoolean(R.styleable.CustomView_enabled, true);
        } finally {
            a.recycle();
        }

        // Apply XML attributes here
    }

    @Override
    public Parcelable onSaveInstanceState() {
        // Save instance state
        Bundle bundle = new Bundle();
        bundle.putParcelable("superState", super.onSaveInstanceState());
        bundle.putBoolean("visible", visible);
        bundle.putBoolean("enabled", enabled);

        return bundle;
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        // Restore instance state
        // This is called after constructor
        if (state instanceof Bundle) {
            Bundle bundle = (Bundle) state;
            visible = bundle.getBoolean("visible");
            enabled = bundle.getBoolean("enabled");

            state = bundle.getParcelable("superState");
        }
        super.onRestoreInstanceState(state);
    }
}

Pretty straightforward. My custom view reads attributes from XML and applies them. These attributes are saved and restored on configuration changes.

But if I have two different layout, for example for two different orientations:

[layout-port/view.xml]
<CustomView
    custom:visible="true"
    custom:enabled="true"

[layout-land/view.xml]
<CustomView
    custom:visible="false"
    custom:enabled="false"

My problem is that when changing the device orientation, view state is saved as visible and enabled, but now XML layout states the view shouldn't have either. Constructor gets called before onRestoreInstanceState and the XML attributes are getting overwritten by the saved state. I don't want that, XML has priority over saved state.

I am doing something wrong? What would be the best way to solve this ?

Cheesecloth answered 17/4, 2017 at 20:57 Comment(5)
store the xml values in other variables and reapply them after restoration. You can also don't apply restoration, so the values will always be those defined in xmlCask
@Cask This is probably what I end up doing. I just thought maybe there was a more direct way to do that, a way to restore state after parsing XML. What I thought I could do is to save the AttributeSet to a variable then parse XML at the end of onRestoreInstanteState. But is onRestoreInstanteState is not called when view is first created.Cheesecloth
android parses the xml and applies its attributes in the view constructor, so the xml is always processed before the restore state. If you want to change this order, you'll have to manually set the variables valuesCask
Don't save & restore particularly these two attributes. They reflect state of some content or piece of data or model in general. So the state of view should be set at runtime.Cowie
@EugenPechanec That's what I ended up doing.Cheesecloth
C
0

In your case you have to store current orientation in Parcelable along with other attributes, and apply those restored attributes only in the case if restored orientation is equal to the current orientation (i.e. activity was destroyed and restored by OS). In your case I would use android:tag for defining current orientation like this:

[layout-port/view.xml]
<CustomView
    android:tag="port"
    custom:visible="true"
    custom:enabled="true"

[layout-land/view.xml]
<CustomView
    android:tag="land"
    custom:visible="false"
    custom:enabled="false"

And then your custom view class would be like:

public class ScheduleView extends View {

    String orientation;
    boolean visible;
    boolean enabled;

    public ScheduleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);

        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomView, 0, 0);
        try {
            visible = a.getBoolean(R.styleable.CustomView_visible, true);
            enabled = a.getBoolean(R.styleable.CustomView_enabled, true);
        } finally {
            a.recycle();
        }

        orientation = (String) getTag();
    }

    @Override
    public Parcelable onSaveInstanceState() {
        // Save instance state
        Bundle bundle = new Bundle();
        bundle.putParcelable("superState", super.onSaveInstanceState());
        bundle.putBoolean("visible", visible);
        bundle.putBoolean("enabled", enabled);
        bundle.putString("orientation", orientation);

        return bundle;
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        // Restore instance state
        // This is called after constructor
        if (state instanceof Bundle) {
            Bundle bundle = (Bundle) state;

            String restoredOrientation = bundle.getString("orientation");
            if (restoredOrientation.equals(orientation)) {
                visible = bundle.getBoolean("visible");
                enabled = bundle.getBoolean("enabled");
            }

            state = bundle.getParcelable("superState");
        }
        super.onRestoreInstanceState(state);
    }
}

Haven't tested it properly, but it should work. Hope it will be helpful.

Canoodle answered 22/4, 2017 at 17:21 Comment(4)
I want to apply some saved attributes, those that are not in XML. Same thing applies to azizbekian's answer.Cheesecloth
Sorry, but I don't get you problem. You actually could apply those out-of-xml attributes outside if block in onRestoreInstanceState method.Canoodle
onRestoreInstanceState is not always called this is the problem.Cheesecloth
Well, then I guess you need to explain your problem better. You said saved attributes - by saved I understand that you want to apply those attributes after onRestoreInstanceState called.Canoodle
D
0

Basically, you need to separate states to portrait and landscape, meaning you also have to save particular configuration state also.

final boolean isPortrait = 
            getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
bundle.putBoolean("isPortrait", isPortrait);

Then when restoring the state:

final boolean savedOrientation = bundle.getBoolean("isPortrait");
final boolean currentOrientation = 
            getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;

if (savedOrientation == currentOrientation) {
    // now retrieve saved values
} else {
    // do nothing, values are initialized in constructor
}
Disembody answered 22/4, 2017 at 20:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.