Custom image view looses state on Marshmallow
S

1

0

I am using the PhotoView library in my Android project. The project contains the SaveStatePhotoView which is used to keep the state (zoom level, position) of the image view on configuration changes (rotation, ...).

// SaveStatePhotoView.java

@Override
protected void onRestoreInstanceState(Parcelable state) {
    if (!(state instanceof SavedState)) {
        super.onRestoreInstanceState(state);
        return;
    }

    final SavedState ss = (SavedState) state;
    super.onRestoreInstanceState(ss.getSuperState());

    getViewTreeObserver().addOnGlobalLayoutListener(
        new ViewTreeObserver.OnGlobalLayoutListener() {

        @Override
        public void onGlobalLayout() {
            restoreSavedState(ss);
            getViewTreeObserver().removeGlobalOnLayoutListener(this);
        }
    });
}

The view works as desired on Android 7.1.1 and Android 9.
On Android 6.0.1 the state is lost: the image view resets to its initial state when the device is rotated.

I prepared a simple project to demonstrate the issue. Please note that I am using PhotoView 1.3.1 on purpose because I cannot include transitive androidx dependencies at the moment.

Solatium answered 10/3, 2019 at 13:4 Comment(1)
I've found this answer which explains well how to handle the instance state loss on device rotation: https://mcmap.net/q/112039/-save-state-of-activity-when-orientation-changes-android, I think you could try to handle the restore of the imageview in the onCreate as it shows in the answer above, let me know if it helps!Vogler
C
1

N.B This does not seem to be an issue with PhotoView version 2.3.0.

PhotoView undergoes two layouts when recreated for API 23 and under. For API 24+, there is only one layout pass. What happens when there are two passes is that the scale (a matrix) that is restored in onRestoreInstanceState() of SaveStatePhotoView is reset. In your code, you are removing the global layout listener after the first pass so, when the matrix is reset on the second layout pass, you are not catching it. For API 24+, there is only one pass and the scale is restored and not reset. This is why you see an issue for API 23 and not for 24.

I think that the real fix is in PhotoView. A vanilla ImageView also undergoes two layout passes, so I don't think the extra layout pass is something that PhotoView causes. I do think, however, that PhotoView mishandles the scaling matrix for certain APIs.

You can address this issue by setting the scale on the second pass for API 24+ by doing something like this:

@Override
protected void onRestoreInstanceState(Parcelable state) {
    if (!(state instanceof SavedState)) {
        super.onRestoreInstanceState(state);
        return;
    }

    final SavedState ss = (SavedState) state;
    super.onRestoreInstanceState(ss.getSuperState());

    getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        private int invocationCount = 0;

        // Only one layout pass for M and up. Otherwise, we'll see two and the
        // scale set in the first pass is reset during the second pass, so the scale we
        // set doesn't stick until the 2nd pass.
        @Override
        public void onGlobalLayout() {
            setScale(Math.min(ss.scale, getMaximumScale()), getWidth() * ss.pivotX,
                    getHeight() * ss.pivotY, false);

            if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M || ++invocationCount > 1) {
                getViewTreeObserver().removeOnGlobalLayoutListener(this);
            }
        }
    });
}

The preceding is based upon the supplied demo app running PhotoView version 1.3.1.

Canaigre answered 14/3, 2019 at 13:42 Comment(3)
Great analysis! Thank you. - Where exactly did you see that PhotoView undergoes two layout passes on Marshmallow?Solatium
@Solatium Just override onLayout() in your custom view, rotate the device and count how many time it's called. For API 24+ it is just once and for lower APIs it is called twice. I did a quick test and it is the same for a straight ImageView.Canaigre
Thank you once again. The workaround seems to be successful. Merci.Solatium

© 2022 - 2024 — McMap. All rights reserved.