OSDN Git Service

Fixes for ImageView drawable visibility dispatch
authorAdam Powell <adamp@google.com>
Thu, 25 Aug 2016 00:09:01 +0000 (17:09 -0700)
committerAdam Powell <adamp@google.com>
Fri, 26 Aug 2016 22:59:39 +0000 (22:59 +0000)
Some apps rely on their drawables not getting not-visible hints via
setVisible when the window visibility changes. This manifests as
additional animations, such as crossfading from placeholders when the
window becomes visible again.

Apps should be able to handle this case in the future now that we have
more detailed reporting via onVisibilityAggregated, but to keep
existing apps working as-is, ImageView now operates in a compatibility
mode for targetSdkVersion < N and will only dispatch visibility
signals based on the same triggers used in M. New apps get the more
detailed signals.

Fix a bug where window visibility dispatch via onVisibilityAggregated
would double-dispatch "not visible" when the window is transitioning
from GONE => INVISIBLE or INVISIBLE => GONE.

Make the growing set of compatibility check fields in ImageView
static, matching the pattern from View.

Bug 30216207

Change-Id: I88875260bf6aaa23687c7d51353de8d633383531

core/java/android/view/View.java
core/java/android/view/ViewRootImpl.java
core/java/android/widget/ImageView.java

index 872168a..c42ad2e 100644 (file)
@@ -10384,7 +10384,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
      *                  ancestors or by window visibility
      * @return true if this view is visible to the user, not counting clipping or overlapping
      */
-    @Visibility boolean dispatchVisibilityAggregated(boolean isVisible) {
+    boolean dispatchVisibilityAggregated(boolean isVisible) {
         final boolean thisVisible = getVisibility() == VISIBLE;
         // If we're not visible but something is telling us we are, ignore it.
         if (thisVisible || !isVisible) {
@@ -15527,7 +15527,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
         if (vis != GONE) {
             onWindowVisibilityChanged(vis);
             if (isShown()) {
-                // Calling onVisibilityChanged directly here since the subtree will also
+                // Calling onVisibilityAggregated directly here since the subtree will also
                 // receive dispatchAttachedToWindow and this same call
                 onVisibilityAggregated(vis == VISIBLE);
             }
index 7494b94..1bb4c08 100644 (file)
@@ -52,7 +52,6 @@ import android.hardware.input.InputManager;
 import android.media.AudioManager;
 import android.os.Binder;
 import android.os.Build;
-import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
 import android.os.Debug;
 import android.os.Handler;
@@ -1468,6 +1467,8 @@ public final class ViewRootImpl implements ViewParent,
         final int viewVisibility = getHostVisibility();
         final boolean viewVisibilityChanged = !mFirst
                 && (mViewVisibility != viewVisibility || mNewSurfaceNeeded);
+        final boolean viewUserVisibilityChanged = !mFirst &&
+                ((mViewVisibility == View.VISIBLE) != (viewVisibility == View.VISIBLE));
 
         WindowManager.LayoutParams params = null;
         if (mWindowAttributesChanged) {
@@ -1541,13 +1542,7 @@ public final class ViewRootImpl implements ViewParent,
         if (viewVisibilityChanged) {
             mAttachInfo.mWindowVisibility = viewVisibility;
             host.dispatchWindowVisibilityChanged(viewVisibility);
-
-            // Prior to N we didn't have dispatchVisibilityAggregated to give a more accurate
-            // view into when views are visible to the user or not. ImageView never dealt with
-            // telling its drawable about window visibility, among other things. Some apps cause
-            // an additional crossfade animation when windows become visible if they get this
-            // additional call, so only send it to new apps to avoid new visual jank.
-            if (host.getContext().getApplicationInfo().targetSdkVersion >= VERSION_CODES.N) {
+            if (viewUserVisibilityChanged) {
                 host.dispatchVisibilityAggregated(viewVisibility == View.VISIBLE);
             }
             if (viewVisibility != View.VISIBLE || mNewSurfaceNeeded) {
index ea0afb9..aa67c82 100644 (file)
@@ -22,7 +22,6 @@ import android.annotation.Nullable;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.ColorStateList;
-import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -115,11 +114,17 @@ public class ImageView extends View {
     private int mBaseline = -1;
     private boolean mBaselineAlignBottom = false;
 
-    // AdjustViewBounds behavior will be in compatibility mode for older apps.
-    private boolean mAdjustViewBoundsCompat = false;
+    /** Compatibility modes dependent on targetSdkVersion of the app. */
+    private static boolean sCompatDone;
+
+    /** AdjustViewBounds behavior will be in compatibility mode for older apps. */
+    private static boolean sCompatAdjustViewBounds;
 
     /** Whether to pass Resources when creating the source from a stream. */
-    private boolean mUseCorrectStreamDensity;
+    private static boolean sCompatUseCorrectStreamDensity;
+
+    /** Whether to use pre-Nougat drawable visibility dispatching conditions. */
+    private static boolean sCompatDrawableVisibilityDispatch;
 
     private static final ScaleType[] sScaleTypeArray = {
         ScaleType.MATRIX,
@@ -206,9 +211,13 @@ public class ImageView extends View {
         mMatrix = new Matrix();
         mScaleType = ScaleType.FIT_CENTER;
 
-        final int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
-        mAdjustViewBoundsCompat = targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR1;
-        mUseCorrectStreamDensity = targetSdkVersion > Build.VERSION_CODES.M;
+        if (!sCompatDone) {
+            final int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
+            sCompatAdjustViewBounds = targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR1;
+            sCompatUseCorrectStreamDensity = targetSdkVersion > Build.VERSION_CODES.M;
+            sCompatDrawableVisibilityDispatch = targetSdkVersion < Build.VERSION_CODES.N;
+            sCompatDone = true;
+        }
     }
 
     @Override
@@ -881,8 +890,8 @@ public class ImageView extends View {
             InputStream stream = null;
             try {
                 stream = mContext.getContentResolver().openInputStream(uri);
-                return Drawable.createFromResourceStream(
-                        mUseCorrectStreamDensity ? getResources() : null, null, stream, null);
+                return Drawable.createFromResourceStream(sCompatUseCorrectStreamDensity
+                        ? getResources() : null, null, stream, null);
             } catch (Exception e) {
                 Log.w(LOG_TAG, "Unable to open content: " + uri, e);
             } finally {
@@ -917,10 +926,13 @@ public class ImageView extends View {
             mRecycleableBitmapDrawable.setBitmap(null);
         }
 
+        boolean sameDrawable = false;
+
         if (mDrawable != null) {
+            sameDrawable = mDrawable == d;
             mDrawable.setCallback(null);
             unscheduleDrawable(mDrawable);
-            if (isAttachedToWindow()) {
+            if (!sCompatDrawableVisibilityDispatch && !sameDrawable && isAttachedToWindow()) {
                 mDrawable.setVisible(false, false);
             }
         }
@@ -933,8 +945,12 @@ public class ImageView extends View {
             if (d.isStateful()) {
                 d.setState(getDrawableState());
             }
-            d.setVisible(isAttachedToWindow() && getWindowVisibility() == VISIBLE && isShown(),
-                    true);
+            if (!sameDrawable || sCompatDrawableVisibilityDispatch) {
+                final boolean visible = sCompatDrawableVisibilityDispatch
+                        ? getVisibility() == VISIBLE
+                        : isAttachedToWindow() && getWindowVisibility() == VISIBLE && isShown();
+                d.setVisible(visible, true);
+            }
             d.setLevel(mLevel);
             mDrawableWidth = d.getIntrinsicWidth();
             mDrawableHeight = d.getIntrinsicHeight();
@@ -1057,7 +1073,7 @@ public class ImageView extends View {
                                 pleft + pright;
 
                         // Allow the width to outgrow its original estimate if height is fixed.
-                        if (!resizeHeight && !mAdjustViewBoundsCompat) {
+                        if (!resizeHeight && !sCompatAdjustViewBounds) {
                             widthSize = resolveAdjustedSize(newWidth, mMaxWidth, widthMeasureSpec);
                         }
 
@@ -1073,7 +1089,7 @@ public class ImageView extends View {
                                 ptop + pbottom;
 
                         // Allow the height to outgrow its original estimate if width is fixed.
-                        if (!resizeWidth && !mAdjustViewBoundsCompat) {
+                        if (!resizeWidth && !sCompatAdjustViewBounds) {
                             heightSize = resolveAdjustedSize(newHeight, mMaxHeight,
                                     heightMeasureSpec);
                         }
@@ -1512,11 +1528,40 @@ public class ImageView extends View {
     @Override
     public void onVisibilityAggregated(boolean isVisible) {
         super.onVisibilityAggregated(isVisible);
-        if (mDrawable != null) {
+        // Only do this for new apps post-Nougat
+        if (mDrawable != null && !sCompatDrawableVisibilityDispatch) {
             mDrawable.setVisible(isVisible, false);
         }
     }
 
+    @RemotableViewMethod
+    @Override
+    public void setVisibility(int visibility) {
+        super.setVisibility(visibility);
+        // Only do this for old apps pre-Nougat; new apps use onVisibilityAggregated
+        if (mDrawable != null && sCompatDrawableVisibilityDispatch) {
+            mDrawable.setVisible(visibility == VISIBLE, false);
+        }
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        // Only do this for old apps pre-Nougat; new apps use onVisibilityAggregated
+        if (mDrawable != null && sCompatDrawableVisibilityDispatch) {
+            mDrawable.setVisible(getVisibility() == VISIBLE, false);
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        // Only do this for old apps pre-Nougat; new apps use onVisibilityAggregated
+        if (mDrawable != null && sCompatDrawableVisibilityDispatch) {
+            mDrawable.setVisible(false, false);
+        }
+    }
+
     @Override
     public CharSequence getAccessibilityClassName() {
         return ImageView.class.getName();