Commit d8149dd5 authored by David Trainor's avatar David Trainor Committed by Commit Bot

Fix some versioning issues with the Download Home v2 UI

The CircularProgressView button was built using View#setForeground(),
which is unavailable on early versions of Android.  This patch addresses
that by adding a helper class that can give a View setForeground-like
support.

This patch also adds an AutoAminatorDrawable, which will start and stop
Drawable animations when the Drawable is visible or not.

BUG=868554

Change-Id: I4a11921962aeac9dcee701f7e3584ba70ac96def
Reviewed-on: https://chromium-review.googlesource.com/1231295Reviewed-by: default avatarShakti Sahu <shaktisahu@chromium.org>
Reviewed-by: default avatarDavid Trainor <dtrainor@chromium.org>
Commit-Queue: David Trainor <dtrainor@chromium.org>
Cr-Commit-Position: refs/heads/master@{#593559}
parent 57a4994c
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.download.home.list.view;
import android.graphics.drawable.Drawable;
import android.support.v7.graphics.drawable.DrawableWrapper;
/**
* A helper {@link Drawable} that wraps another {@link Drawable} and starts/stops any
* {@link Animatable} {@link Drawable}s in the {@link Drawable} hierarchy when this {@link Drawable}
* is shown or hidden.
*/
public class AutoAnimatorDrawable extends DrawableWrapper {
// Since Drawables default visible to true by default, we might not get a change and start the
// animation on the first visibility request.
private boolean mGotVisibilityCall;
public AutoAnimatorDrawable(Drawable drawable) {
super(drawable);
}
// DrawableWrapper implementation.
@Override
public boolean setVisible(boolean visible, boolean restart) {
boolean changed = super.setVisible(visible, restart);
if (visible) {
if (changed || restart || !mGotVisibilityCall) UiUtils.startAnimatedDrawables(this);
} else {
UiUtils.stopAnimatedDrawables(this);
}
mGotVisibilityCall = true;
return changed;
}
}
\ No newline at end of file
...@@ -6,15 +6,13 @@ package org.chromium.chrome.browser.download.home.list.view; ...@@ -6,15 +6,13 @@ package org.chromium.chrome.browser.download.home.list.view;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.graphics.drawable.Animatable; import android.graphics.Canvas;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.support.annotation.DrawableRes;
import android.support.annotation.IntDef; import android.support.annotation.IntDef;
import android.support.annotation.StringRes; import android.support.annotation.StringRes;
import android.support.annotation.StyleableRes;
import android.support.v7.content.res.AppCompatResources;
import android.support.v7.widget.AppCompatImageButton; import android.support.v7.widget.AppCompatImageButton;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.View;
import org.chromium.chrome.browser.util.MathUtils; import org.chromium.chrome.browser.util.MathUtils;
import org.chromium.chrome.download.R; import org.chromium.chrome.download.R;
...@@ -29,8 +27,8 @@ import java.lang.annotation.RetentionPolicy; ...@@ -29,8 +27,8 @@ import java.lang.annotation.RetentionPolicy;
* The determinate {@link Drawable} will have it's level set via {@link Drawable#setLevel(int)} * The determinate {@link Drawable} will have it's level set via {@link Drawable#setLevel(int)}
* based on the progress (0 - 10,000). * based on the progress (0 - 10,000).
* *
* The indeterminate {@link Drawable} supports {@link Animatable} drawables and the animation will * The indeterminate and determinate {@link Drawable}s support {@link Animatable} drawables and the
* be started/stopped when shown/hidden respectively. * animation will be started/stopped when shown/hidden respectively.
*/ */
public class CircularProgressView extends AppCompatImageButton { public class CircularProgressView extends AppCompatImageButton {
/** /**
...@@ -61,9 +59,7 @@ public class CircularProgressView extends AppCompatImageButton { ...@@ -61,9 +59,7 @@ public class CircularProgressView extends AppCompatImageButton {
private final Drawable mPauseButtonSrc; private final Drawable mPauseButtonSrc;
private final Drawable mRetryButtonSrc; private final Drawable mRetryButtonSrc;
// Tracking this here as the API {@link View#getForeground()} is not available in all supported private final ForegroundDrawableCompat mForegroundHelper;
// Android versions.
private Drawable mForegroundDrawable;
/** /**
* Creates an instance of a {@link CircularProgressView}. * Creates an instance of a {@link CircularProgressView}.
...@@ -73,22 +69,24 @@ public class CircularProgressView extends AppCompatImageButton { ...@@ -73,22 +69,24 @@ public class CircularProgressView extends AppCompatImageButton {
public CircularProgressView(Context context, AttributeSet attrs) { public CircularProgressView(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
TypedArray types = mForegroundHelper = new ForegroundDrawableCompat(this);
context.obtainStyledAttributes(attrs, R.styleable.CircularProgressView, 0, 0);
try { TypedArray types = attrs == null
mIndeterminateProgress = getDrawable( ? null
context, types, R.styleable.CircularProgressView_indeterminateProgress); : context.obtainStyledAttributes(attrs, R.styleable.CircularProgressView, 0, 0);
mDeterminateProgress = getDrawable(
context, types, R.styleable.CircularProgressView_determinateProgress); mIndeterminateProgress = UiUtils.autoAnimateDrawable(UiUtils.getDrawable(
mResumeButtonSrc = context, types, R.styleable.CircularProgressView_indeterminateProgress));
getDrawable(context, types, R.styleable.CircularProgressView_resumeSrc); mDeterminateProgress = UiUtils.autoAnimateDrawable(UiUtils.getDrawable(
mPauseButtonSrc = context, types, R.styleable.CircularProgressView_determinateProgress));
getDrawable(context, types, R.styleable.CircularProgressView_pauseSrc); mResumeButtonSrc =
mRetryButtonSrc = UiUtils.getDrawable(context, types, R.styleable.CircularProgressView_resumeSrc);
getDrawable(context, types, R.styleable.CircularProgressView_retrySrc); mPauseButtonSrc =
} finally { UiUtils.getDrawable(context, types, R.styleable.CircularProgressView_pauseSrc);
types.recycle(); mRetryButtonSrc =
} UiUtils.getDrawable(context, types, R.styleable.CircularProgressView_retrySrc);
types.recycle();
} }
/** /**
...@@ -101,21 +99,13 @@ public class CircularProgressView extends AppCompatImageButton { ...@@ -101,21 +99,13 @@ public class CircularProgressView extends AppCompatImageButton {
*/ */
public void setProgress(int progress) { public void setProgress(int progress) {
if (progress == INDETERMINATE) { if (progress == INDETERMINATE) {
if (mForegroundDrawable != mIndeterminateProgress) { if (mForegroundHelper.getDrawable() != mIndeterminateProgress) {
setForeground(mIndeterminateProgress); mForegroundHelper.setDrawable(mIndeterminateProgress);
if (mIndeterminateProgress instanceof Animatable) {
((Animatable) mIndeterminateProgress).start();
}
} }
} else { } else {
progress = MathUtils.clamp(progress, 0, 100); progress = MathUtils.clamp(progress, 0, 100);
mDeterminateProgress.setLevel(progress * MAX_LEVEL / 100); mDeterminateProgress.setLevel(progress * MAX_LEVEL / 100);
setForeground(mDeterminateProgress); mForegroundHelper.setDrawable(mDeterminateProgress);
}
// Stop any animation that might have previously been running.
if (mForegroundDrawable != mIndeterminateProgress) {
((Animatable) mIndeterminateProgress).stop();
} }
} }
...@@ -150,16 +140,25 @@ public class CircularProgressView extends AppCompatImageButton { ...@@ -150,16 +140,25 @@ public class CircularProgressView extends AppCompatImageButton {
// View implementation. // View implementation.
@Override @Override
public void setForeground(Drawable foreground) { public void draw(Canvas canvas) {
mForegroundDrawable = foreground; super.draw(canvas);
super.setForeground(foreground); mForegroundHelper.draw(canvas);
} }
private static final Drawable getDrawable( @Override
Context context, TypedArray attrs, @StyleableRes int attrId) { protected void onVisibilityChanged(View changedView, int visibility) {
@DrawableRes super.onVisibilityChanged(changedView, visibility);
int resId = attrs.getResourceId(attrId, -1); mForegroundHelper.onVisibilityChanged(changedView, visibility);
if (resId == -1) return null; }
return AppCompatResources.getDrawable(context, resId);
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
mForegroundHelper.drawableStateChanged();
}
@Override
protected boolean verifyDrawable(Drawable dr) {
return super.verifyDrawable(dr) || mForegroundHelper.verifyDrawable(dr);
} }
} }
\ No newline at end of file
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.download.home.list.view;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v4.view.ViewCompat;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
import android.view.View.OnLayoutChangeListener;
import android.view.ViewGroup;
import android.view.ViewParent;
/**
* A helper class to simulate {@link View#getForeground()} on older versions of Android. This class
* requires specific setup to work properly. The following methods must be overridden and forwarded
* from the underly {@link View}:
*
* 1) {@link View#draw(Canvas)}.
* 2) {@link View#onVisibilityChanged(View,boolean)}
* 3) {@link View#drawableStateChanged()}.
* 4) {@link View#verifyDrawable(Drawable)}.
*
* Here is a rough example of how to use this below to extend an ImageView (replace with any View):
*
* public class ForegroundEnabledView extends ImageView {
* private final ForegroundDrawableCompat mCompat;
*
* public class ForegroundEnabledView(Context context) {
* super(context);
* mCompat = new ForegroundDrawableCompat(this);
* }
*
* public void someHelperMethod(Drawable drawable) {
* mCompat.setDrawable(drawable);
* }
*
* // ImageView implementation.
* @Override
* public void draw(Canvas canvas) {
* super.draw(canvas);
*
* // It is important to make sure the foreground drawable draws *after* other view content.
* mCompat.draw(canvas);
* }
*
* @Override
* protected void onVisibilityChanged(View changedView, int visibility) {
* super.onVisibilityChanged(changedView, visibility);
* mCompat.onVisibilityChanged(changedView, visibility);
* }
*
* @Override
* protected void drawableStateChanged() {
* super.drawableStateChanged();
* mCompat.drawableStateChanged();
* }
*
* @Override
* protected boolean verifyDrawable(Drawable dr) {
* return super.verifyDrawable(dr) || mCompat.verifyDrawable(dr);
* }
* }
*/
public class ForegroundDrawableCompat
implements OnAttachStateChangeListener, OnLayoutChangeListener {
private final View mView;
private boolean mOnBoundsChanged;
private Drawable mDrawable;
/**
* Builds a {@link ForegroundDrawableCompat} around {@code View}. This will enable setting a
* {@link Drawable} to draw on top of {@link View} at draw time.
* @param view The {@link View} to add foreground {@link Drawable} support to.
*/
public ForegroundDrawableCompat(View view) {
mView = view;
mView.addOnAttachStateChangeListener(this);
mView.addOnLayoutChangeListener(this);
}
/**
* Sets {@code drawable} to draw on top of the {@link View}.
* @param drawable The {@link Drawable} to set as a foreground {@link Drawable} on the
* {@link View}.
*/
public void setDrawable(Drawable drawable) {
if (mDrawable == drawable) return;
if (mDrawable != null) {
if (ViewCompat.isAttachedToWindow(mView)) mDrawable.setVisible(false, false);
mDrawable.setCallback(null);
mView.unscheduleDrawable(mDrawable);
mDrawable = null;
}
mDrawable = drawable;
if (mDrawable != null) {
mOnBoundsChanged = true;
DrawableCompat.setLayoutDirection(mDrawable, ViewCompat.getLayoutDirection(mView));
if (mDrawable.isStateful()) mDrawable.setState(mView.getDrawableState());
// TODO(dtrainor): Support tint?
if (ViewCompat.isAttachedToWindow(mView)) {
mDrawable.setVisible(
mView.getWindowVisibility() == View.VISIBLE && mView.isShown(), false);
}
mDrawable.setCallback(mView);
}
mView.requestLayout();
mView.invalidate();
}
/** @return The currently set foreground {@link Drawable}. */
public Drawable getDrawable() {
return mDrawable;
}
/** Meant to be called from {@link View#onDraw(Canvas)}. */
public void draw(Canvas canvas) {
if (mDrawable == null) return;
if (mOnBoundsChanged) {
mOnBoundsChanged = false;
mDrawable.setBounds(0, 0, mView.getWidth(), mView.getHeight());
}
mDrawable.draw(canvas);
}
/** Meant to be called from {@link View#onVisibilityChanged(View,visibility)}. */
public void onVisibilityChanged(View view, int visibility) {
if (mView != view || mDrawable == null) return;
ViewParent parent = mView.getParent();
boolean parentVisible = parent != null
&& (!(parent instanceof ViewGroup) || ((ViewGroup) parent).isShown());
if (parentVisible && mView.getWindowVisibility() == View.VISIBLE) {
mDrawable.setVisible(visibility == View.VISIBLE, false);
}
}
/** Meant to be called from {@link View#drawableStateChanged()}. */
public void drawableStateChanged() {
if (mDrawable == null) return;
if (mDrawable.setState(mView.getDrawableState())) mView.invalidate();
}
/** Meant to be called from {@link View#verifyDrawable(Drawable)}. */
public boolean verifyDrawable(Drawable drawable) {
return drawable != null && mDrawable == drawable;
}
// OnAttachStateChangeListener implementation.
@Override
public void onViewAttachedToWindow(View v) {
if (mView.isShown() && mView.getWindowVisibility() != View.GONE) {
mDrawable.setVisible(mView.getVisibility() == View.VISIBLE, false);
}
}
@Override
public void onViewDetachedFromWindow(View v) {
if (mView.isShown() && mView.getWindowVisibility() != View.GONE) {
mDrawable.setVisible(false, false);
}
}
// OnLayoutChangeListener implementation.
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
int oldTop, int oldRight, int oldBottom) {
if (mDrawable == null) return;
int width = right - left;
int height = bottom - top;
if (width != mDrawable.getBounds().width() || height != mDrawable.getBounds().height()) {
mOnBoundsChanged = true;
}
}
}
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.download.home.list.view;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.DrawableWrapper;
import android.graphics.drawable.InsetDrawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.RotateDrawable;
import android.graphics.drawable.ScaleDrawable;
import android.os.Build;
import android.support.annotation.DrawableRes;
import android.support.annotation.Nullable;
import android.support.annotation.StyleableRes;
import android.support.v7.content.res.AppCompatResources;
import org.chromium.base.Callback;
/** A set of helper methods to make interacting with the Android UI easier. */
public final class UiUtils {
private UiUtils() {}
/**
* Loads a {@link Drawable} from an attribute. Uses {@link AppCompatResources} to support all
* modern {@link Drawable} types.
* @return A new {@link Drawable} or {@code null} if the attribute wasn't set.
*/
public static @Nullable Drawable getDrawable(
Context context, @Nullable TypedArray attrs, @StyleableRes int attrId) {
if (attrs == null) return null;
@DrawableRes
int resId = attrs.getResourceId(attrId, -1);
if (resId == -1) return null;
return UiUtils.getDrawable(context, resId);
}
/**
* Loads a {@link Drawable} from a resource Id. Uses {@link AppCompatResources} to support all
* modern {@link Drawable} types.
* @return A new {@link Drawable}.
*/
public static Drawable getDrawable(Context context, @DrawableRes int resId) {
return AppCompatResources.getDrawable(context, resId);
}
/**
* Wraps {@code drawable} in a {@link AutoAnimatorDrawable}.
* @return A wrapped {@code Drawable} or {@code null} if {@code drawable} is null.
*/
public static @Nullable Drawable autoAnimateDrawable(@Nullable Drawable drawable) {
return drawable == null ? null : new AutoAnimatorDrawable(drawable);
}
/**
* Recursively searches {@code drawable} for all {@link Animatable} instances and starts them.
* @param drawable The {@link Drawable} to start animating.
*/
public static void startAnimatedDrawables(@Nullable Drawable drawable) {
animatedDrawableHelper(drawable, animatable -> animatable.start());
}
/**
* Recursively searches {@code drawable} for all {@link Animatable} instances and stops them.
* @param drawable The {@link Drawable} to stop animating.
*/
public static void stopAnimatedDrawables(@Nullable Drawable drawable) {
animatedDrawableHelper(drawable, animatable -> animatable.stop());
}
@TargetApi(Build.VERSION_CODES.KITKAT)
private static void animatedDrawableHelper(
@Nullable Drawable drawable, Callback<Animatable> consumer) {
if (drawable == null) return;
if (drawable instanceof Animatable) {
consumer.onResult((Animatable) drawable);
// Assume Animatable drawables can handle animating their own internals/sub drawables.
return;
}
if (drawable != drawable.getCurrent()) {
// Check obvious cases where the current drawable isn't actually being shown. This
// should support all {@link DrawableContainer} instances.
UiUtils.animatedDrawableHelper(drawable.getCurrent(), consumer);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && drawable instanceof DrawableWrapper) {
// Support all modern versions of drawables that wrap other ones. This won't cover old
// versions of Android (see below for other if/else blocks).
animatedDrawableHelper(((DrawableWrapper) drawable).getDrawable(), consumer);
} else if (drawable instanceof LayerDrawable) {
LayerDrawable layerDrawable = (LayerDrawable) drawable;
for (int i = 0; i < layerDrawable.getNumberOfLayers(); i++) {
animatedDrawableHelper(layerDrawable.getDrawable(i), consumer);
}
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
&& drawable instanceof InsetDrawable) {
// Support legacy versions of InsetDrawable.
animatedDrawableHelper(((InsetDrawable) drawable).getDrawable(), consumer);
} else if (drawable instanceof RotateDrawable) {
// Support legacy versions of RotateDrawable.
animatedDrawableHelper(((RotateDrawable) drawable).getDrawable(), consumer);
} else if (drawable instanceof ScaleDrawable) {
// Support legacy versions of ScaleDrawable.
animatedDrawableHelper(((ScaleDrawable) drawable).getDrawable(), consumer);
}
}
}
\ No newline at end of file
...@@ -517,7 +517,10 @@ chrome_java_sources = [ ...@@ -517,7 +517,10 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/download/home/list/holder/SeparatorViewHolder.java", "java/src/org/chromium/chrome/browser/download/home/list/holder/SeparatorViewHolder.java",
"java/src/org/chromium/chrome/browser/download/home/list/holder/ThumbnailAwareViewHolder.java", "java/src/org/chromium/chrome/browser/download/home/list/holder/ThumbnailAwareViewHolder.java",
"java/src/org/chromium/chrome/browser/download/home/list/holder/VideoViewHolder.java", "java/src/org/chromium/chrome/browser/download/home/list/holder/VideoViewHolder.java",
"java/src/org/chromium/chrome/browser/download/home/list/view/AutoAnimatorDrawable.java",
"java/src/org/chromium/chrome/browser/download/home/list/view/CircularProgressView.java", "java/src/org/chromium/chrome/browser/download/home/list/view/CircularProgressView.java",
"java/src/org/chromium/chrome/browser/download/home/list/view/ForegroundDrawableCompat.java",
"java/src/org/chromium/chrome/browser/download/home/list/view/UiUtils.java",
"java/src/org/chromium/chrome/browser/download/home/list/ListProperties.java", "java/src/org/chromium/chrome/browser/download/home/list/ListProperties.java",
"java/src/org/chromium/chrome/browser/download/home/list/ListPropertyViewBinder.java", "java/src/org/chromium/chrome/browser/download/home/list/ListPropertyViewBinder.java",
"java/src/org/chromium/chrome/browser/download/home/list/ListUtils.java", "java/src/org/chromium/chrome/browser/download/home/list/ListUtils.java",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment