Commit 92f0b13b authored by jaekyun's avatar jaekyun Committed by Commit bot

Upstream SmoothProgressBar and ToolbarProgressBar

BUG=453188

Review URL: https://codereview.chromium.org/898483006

Cr-Commit-Position: refs/heads/master@{#314654}
parent a665b308
// Copyright 2015 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.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ProgressBar;
import org.chromium.base.ApiCompatibilityUtils;
/**
* A progress bar that smoothly animates incremental updates.
* <p>
* Consumers of this class need to be aware that calls to {@link #getProgress()} will return
* the currently visible progress value and not the one set in the last call to
* {@link #setProgress(int)}.
*/
public class SmoothProgressBar extends ProgressBar {
private static final int MAX = 100;
// The amount of time between subsequent progress updates. 16ms is chosen to make 60fps.
private static final long PROGRESS_UPDATE_DELAY_MS = 16;
private boolean mIsAnimated = false;
private int mTargetProgress;
// Since the progress bar is being animated, the internal progress bar resolution should be
// at least fine as the width, not MAX. This multiplier will be applied to input progress
// to convert to a finer scale.
private int mResolutionMutiplier = 1;
private Runnable mUpdateProgressRunnable = new Runnable() {
@Override
public void run() {
if (getProgress() == mTargetProgress) return;
if (!mIsAnimated) {
setProgressInternal(mTargetProgress);
return;
}
// Every time, the progress bar get's at least 20% closer to mTargetProcess.
// Add 3 to guarantee progressing even if they only differ by 1.
setProgressInternal(getProgress() + (mTargetProgress - getProgress() + 3) / 4);
ApiCompatibilityUtils.postOnAnimationDelayed(
SmoothProgressBar.this, this, PROGRESS_UPDATE_DELAY_MS);
}
};
/**
* Create a new progress bar with range 0...100 and initial progress of 0.
* @param context the application environment.
* @param attrs the xml attributes that should be used to initialize this view.
*/
public SmoothProgressBar(Context context, AttributeSet attrs) {
super(context, attrs);
setMax(MAX * mResolutionMutiplier);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
int normalizedProgress = getProgress() / mResolutionMutiplier;
// Choose an integer resolution multiplier that makes the scale at least fine as the width.
mResolutionMutiplier = Math.max(1, (w + MAX - 1) / MAX);
setMax(mResolutionMutiplier * MAX);
setProgressInternal(normalizedProgress * mResolutionMutiplier);
}
@Override
public void setProgress(int progress) {
final int targetProgress = progress * mResolutionMutiplier;
if (mTargetProgress == targetProgress) return;
mTargetProgress = targetProgress;
removeCallbacks(mUpdateProgressRunnable);
ApiCompatibilityUtils.postOnAnimation(this, mUpdateProgressRunnable);
}
/**
* Sets whether to animate incremental updates or not.
* @param isAnimated True if it is needed to animate incremental updates.
*/
public void setAnimated(boolean isAnimated) {
mIsAnimated = isAnimated;
}
/**
* Called to update the progress visuals.
* @param progress The progress value to set the visuals to.
*/
protected void setProgressInternal(int progress) {
super.setProgress(progress);
}
}
// Copyright 2015 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.widget;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.util.AttributeSet;
import android.view.View;
import org.chromium.base.VisibleForTesting;
import org.chromium.ui.interpolators.BakedBezierInterpolator;
/**
* Progress bar for use in the Toolbar view.
*/
public class ToolbarProgressBar extends SmoothProgressBar {
private static final long PROGRESS_CLEARING_DELAY_MS = 200;
private static final int SHOW_HIDE_DURATION_MS = 100;
private final Runnable mClearLoadProgressRunnable;
private int mDesiredVisibility;
private Animator mShowAnimator;
private Animator mHideAnimator;
/**
* Creates a toolbar progress bar.
* @param context the application environment.
* @param attrs the xml attributes that should be used to initialize this view.
*/
public ToolbarProgressBar(Context context, AttributeSet attrs) {
super(context, attrs);
// The base constructor will trigger a progress change and alter the expected
// visibility, so force a visibility change to reset the state.
setVisibility(VISIBLE);
mClearLoadProgressRunnable = new Runnable() {
@Override
public void run() {
setProgress(0);
}
};
// Hide the background portion of the system progress bar.
Drawable progressDrawable = getProgressDrawable();
if (progressDrawable instanceof LayerDrawable) {
Drawable progressBackgroundDrawable =
((LayerDrawable) progressDrawable)
.findDrawableByLayerId(android.R.id.background);
if (progressBackgroundDrawable != null) {
progressBackgroundDrawable.setVisible(false, false);
progressBackgroundDrawable.setAlpha(0);
}
}
}
@Override
public void setSecondaryProgress(int secondaryProgress) {
super.setSecondaryProgress(secondaryProgress);
setVisibilityForProgress();
}
@Override
protected void setProgressInternal(int progress) {
super.setProgressInternal(progress);
if (progress == getMax()) {
postDelayed(mClearLoadProgressRunnable, PROGRESS_CLEARING_DELAY_MS);
}
setVisibilityForProgress();
}
@Override
public void setVisibility(int v) {
mDesiredVisibility = v;
setVisibilityForProgress();
}
private void setVisibilityForProgress() {
if (mDesiredVisibility != VISIBLE) {
super.setVisibility(mDesiredVisibility);
return;
}
int progress = Math.max(getProgress(), getSecondaryProgress());
super.setVisibility(progress == 0 ? INVISIBLE : VISIBLE);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// Some versions of Android have a bug where they don't properly update the drawables with
// the correct bounds. setProgressDrawable has been overridden to properly push the bounds
// but on rotation they weren't always being set. Forcing a bounds update on size changes
// fixes the problem.
setProgressDrawable(getProgressDrawable());
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
buildAnimators();
setPivotY(getHeight());
}
@Override
public void setProgressDrawable(Drawable d) {
Drawable currentDrawable = getProgressDrawable();
super.setProgressDrawable(d);
if (currentDrawable != null && d instanceof LayerDrawable) {
LayerDrawable ld = (LayerDrawable) d;
for (int i = 0; i < ld.getNumberOfLayers(); i++) {
ld.getDrawable(i).setBounds(currentDrawable.getBounds());
}
}
}
/**
* @return Whether or not this progress bar has animations running for showing/hiding itself.
*/
@VisibleForTesting
boolean isAnimatingForShowOrHide() {
return (mShowAnimator != null && mShowAnimator.isStarted())
|| (mHideAnimator != null && mHideAnimator.isStarted());
}
private void buildAnimators() {
if (mShowAnimator != null && mShowAnimator.isRunning()) mShowAnimator.end();
if (mHideAnimator != null && mHideAnimator.isRunning()) mHideAnimator.end();
mShowAnimator = ObjectAnimator.ofFloat(this, View.SCALE_Y, 0.f, 1.f);
mShowAnimator.setDuration(SHOW_HIDE_DURATION_MS);
mShowAnimator.setInterpolator(BakedBezierInterpolator.FADE_IN_CURVE);
mShowAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
setSecondaryProgress(getMax());
}
});
mHideAnimator = ObjectAnimator.ofFloat(this, View.SCALE_Y, 1.f, 0.f);
mHideAnimator.setDuration(SHOW_HIDE_DURATION_MS);
mHideAnimator.setInterpolator(BakedBezierInterpolator.FADE_OUT_CURVE);
mHideAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
setSecondaryProgress(0);
}
});
}
@Override
public void setProgress(int progress) {
// If the show animator has started, the progress bar needs to be tracked as if it is
// currently showing. This makes sure we trigger the proper hide animation and cancel the
// show animation if we show/hide the bar very fast. See crbug.com/453360.
boolean isShowing =
getProgress() > 0 || (mShowAnimator != null && mShowAnimator.isStarted());
boolean willShow = progress > 0;
removeCallbacks(mClearLoadProgressRunnable);
super.setProgress(progress);
if (isShowing != willShow) {
if (mShowAnimator == null || mHideAnimator == null) buildAnimators();
if (mShowAnimator.isRunning()) mShowAnimator.end();
if (mHideAnimator.isRunning()) mHideAnimator.end();
if (willShow) {
mShowAnimator.start();
} else {
mHideAnimator.start();
}
}
}
}
// Copyright 2015 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.widget;
import static org.chromium.base.test.util.Restriction.RESTRICTION_TYPE_PHONE;
import android.test.suitebuilder.annotation.MediumTest;
import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.Restriction;
import org.chromium.chrome.shell.ChromeShellTestBase;
import org.chromium.chrome.shell.R;
import org.chromium.content.browser.test.util.Criteria;
import org.chromium.content.browser.test.util.CriteriaHelper;
import java.util.concurrent.atomic.AtomicReference;
/**
* Tests related to the ToolbarProgressBar.
*/
public class ToolbarProgressBarTest extends ChromeShellTestBase {
/**
* Test that calling progressBar.setProgress(# > 0) followed by progressBar.setProgress(0)
* results in a hidden progress bar (the secondary progress needs to be 0).
* @throws InterruptedException
*/
@Feature({"Android-Toolbar"})
@MediumTest
@Restriction(RESTRICTION_TYPE_PHONE)
public void testProgressBarDisappearsAfterFastShowHide() throws InterruptedException {
launchChromeShellWithUrl("about:blank");
waitForActiveShellToBeDoneLoading();
final AtomicReference<ToolbarProgressBar> progressBar =
new AtomicReference<ToolbarProgressBar>();
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
progressBar.set((ToolbarProgressBar) getActivity().findViewById(R.id.progress));
}
});
// Wait for the progress bar to be reset.
CriteriaHelper.pollForUIThreadCriteria(new Criteria() {
@Override
public boolean isSatisfied() {
return progressBar.get().getProgress() == 0;
}
});
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
assertEquals("Progress bar should be hidden to start.", 0,
progressBar.get().getProgress());
progressBar.get().setProgress(10);
assertTrue("Progress bar did not start animating",
progressBar.get().isAnimatingForShowOrHide());
progressBar.get().setProgress(0);
}
});
// Wait for the progress bar to finish any and all animations.
CriteriaHelper.pollForUIThreadCriteria(new Criteria() {
@Override
public boolean isSatisfied() {
return !progressBar.get().isAnimatingForShowOrHide();
}
});
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
// The secondary progress should be gone.
assertEquals("Progress bar background still visible.", 0,
progressBar.get().getSecondaryProgress());
}
});
}
}
\ No newline at end of file
...@@ -7,7 +7,6 @@ package org.chromium.chrome.shell; ...@@ -7,7 +7,6 @@ package org.chromium.chrome.shell;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.graphics.drawable.ClipDrawable;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.MotionEvent; import android.view.MotionEvent;
...@@ -28,6 +27,7 @@ import org.chromium.chrome.browser.TabObserver; ...@@ -28,6 +27,7 @@ import org.chromium.chrome.browser.TabObserver;
import org.chromium.chrome.browser.UrlUtilities; import org.chromium.chrome.browser.UrlUtilities;
import org.chromium.chrome.browser.appmenu.AppMenuButtonHelper; import org.chromium.chrome.browser.appmenu.AppMenuButtonHelper;
import org.chromium.chrome.browser.appmenu.AppMenuHandler; import org.chromium.chrome.browser.appmenu.AppMenuHandler;
import org.chromium.chrome.browser.widget.SmoothProgressBar;
import org.chromium.chrome.shell.omnibox.SuggestionPopup; import org.chromium.chrome.shell.omnibox.SuggestionPopup;
import org.chromium.content.common.ContentSwitches; import org.chromium.content.common.ContentSwitches;
...@@ -40,14 +40,14 @@ public class ChromeShellToolbar extends LinearLayout { ...@@ -40,14 +40,14 @@ public class ChromeShellToolbar extends LinearLayout {
private final Runnable mClearProgressRunnable = new Runnable() { private final Runnable mClearProgressRunnable = new Runnable() {
@Override @Override
public void run() { public void run() {
mProgressDrawable.setLevel(0); mProgressBar.setProgress(0);
} }
}; };
private final Runnable mUpdateProgressRunnable = new Runnable() { private final Runnable mUpdateProgressRunnable = new Runnable() {
@Override @Override
public void run() { public void run() {
mProgressDrawable.setLevel(100 * mProgress); mProgressBar.setProgress(mProgress);
if (mLoading) { if (mLoading) {
mStopReloadButton.setImageResource( mStopReloadButton.setImageResource(
R.drawable.btn_close); R.drawable.btn_close);
...@@ -60,7 +60,7 @@ public class ChromeShellToolbar extends LinearLayout { ...@@ -60,7 +60,7 @@ public class ChromeShellToolbar extends LinearLayout {
}; };
private EditText mUrlTextView; private EditText mUrlTextView;
private ClipDrawable mProgressDrawable; private SmoothProgressBar mProgressBar;
private ChromeShellTab mTab; private ChromeShellTab mTab;
private final TabObserver mTabObserver; private final TabObserver mTabObserver;
...@@ -140,7 +140,7 @@ public class ChromeShellToolbar extends LinearLayout { ...@@ -140,7 +140,7 @@ public class ChromeShellToolbar extends LinearLayout {
protected void onFinishInflate() { protected void onFinishInflate() {
super.onFinishInflate(); super.onFinishInflate();
mProgressDrawable = (ClipDrawable) findViewById(R.id.toolbar).getBackground(); mProgressBar = (SmoothProgressBar) findViewById(R.id.progress);
initializeUrlField(); initializeUrlField();
initializeTabSwitcherButton(); initializeTabSwitcherButton();
initializeMenuButton(); initializeMenuButton();
......
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2012 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.
-->
<clip xmlns:android="http://schemas.android.com/apk/res/android">
<shape>
<solid android:color="@color/material_deep_teal_500" />
</shape>
</clip>
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2015 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.
-->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/secondaryProgress">
<clip android:gravity="left">
<shape>
<solid android:color="@color/material_deep_teal_200" />
</shape>
</clip>
</item>
<item android:id="@android:id/progress">
<clip android:gravity="left">
<shape>
<solid android:color="@color/material_deep_teal_500" />
</shape>
</clip>
</item>
</layer-list>
\ No newline at end of file
...@@ -14,50 +14,61 @@ ...@@ -14,50 +14,61 @@
<org.chromium.chrome.shell.ChromeShellToolbar android:id="@+id/toolbar" <org.chromium.chrome.shell.ChromeShellToolbar android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="vertical">
android:background="@drawable/progress"> <LinearLayout
<org.chromium.chrome.browser.widget.TintedImageButton android:layout_width="match_parent"
android:id="@+id/add_button"
android:layout_width="38dp"
android:layout_height="38dp"
android:src="@android:drawable/ic_menu_add"
android:visibility="gone"
android:scaleType="center"/>
<org.chromium.chrome.browser.widget.TintedImageButton
android:id="@+id/stop_reload_button"
android:layout_width="38dp"
android:layout_height="38dp"
android:src="@drawable/btn_close"
android:background="?attr/selectableItemBackground"
android:scaleType="center"/>
<EditText android:id="@+id/url"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:orientation="horizontal">
android:gravity="bottom" <org.chromium.chrome.browser.widget.TintedImageButton
android:textSize="18sp" android:id="@+id/add_button"
android:autoText="true" android:layout_width="38dp"
android:capitalize="sentences" android:layout_height="38dp"
android:singleLine="true" android:src="@android:drawable/ic_menu_add"
android:selectAllOnFocus="true" android:visibility="gone"
android:hint="@string/url_hint" android:scaleType="center"/>
android:inputType="textUri" <org.chromium.chrome.browser.widget.TintedImageButton
android:imeOptions="actionGo|flagNoExtractUi" /> android:id="@+id/stop_reload_button"
<org.chromium.chrome.browser.widget.TintedImageButton android:layout_width="38dp"
android:id="@+id/tab_switcher" android:layout_height="38dp"
android:layout_width="38dp" android:src="@drawable/btn_close"
android:layout_height="38dp" android:background="?attr/selectableItemBackground"
android:src="@drawable/btn_tabswitcher" android:scaleType="center"/>
android:background="?attr/selectableItemBackground" <EditText android:id="@+id/url"
android:scaleType="center" android:layout_width="0dp"
android:contentDescription="@null"/> android:layout_height="wrap_content"
<org.chromium.chrome.browser.widget.TintedImageButton android:layout_weight="1"
android:id="@+id/menu_button" android:gravity="bottom"
android:layout_width="38dp" android:textSize="18sp"
android:layout_height="38dp" android:autoText="true"
android:src="@drawable/btn_menu" android:capitalize="sentences"
android:background="?attr/selectableItemBackground" android:singleLine="true"
android:scaleType="center"/> android:selectAllOnFocus="true"
android:hint="@string/url_hint"
android:inputType="textUri"
android:imeOptions="actionGo|flagNoExtractUi" />
<org.chromium.chrome.browser.widget.TintedImageButton
android:id="@+id/tab_switcher"
android:layout_width="38dp"
android:layout_height="38dp"
android:src="@drawable/btn_tabswitcher"
android:background="?attr/selectableItemBackground"
android:scaleType="center"
android:contentDescription="@null"/>
<org.chromium.chrome.browser.widget.TintedImageButton
android:id="@+id/menu_button"
android:layout_width="38dp"
android:layout_height="38dp"
android:src="@drawable/btn_menu"
android:background="?attr/selectableItemBackground"
android:scaleType="center"/>
</LinearLayout>
<org.chromium.chrome.browser.widget.ToolbarProgressBar
android:id="@+id/progress"
style="@android:style/Widget.Holo.Light.ProgressBar.Horizontal"
android:progressDrawable="@drawable/progress_bar"
android:layout_width="match_parent"
android:layout_height="2dp"
android:progress="0" />
</org.chromium.chrome.shell.ChromeShellToolbar> </org.chromium.chrome.shell.ChromeShellToolbar>
<FrameLayout android:id="@+id/content_container" <FrameLayout android:id="@+id/content_container"
android:layout_width="match_parent" android:layout_width="match_parent"
......
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