2015-08-18 18:27:18 +00:00
|
|
|
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
|
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
|
|
|
|
package org.mozilla.gecko.gfx;
|
|
|
|
|
|
|
|
import org.mozilla.gecko.PrefsHelper;
|
|
|
|
import org.mozilla.gecko.util.FloatUtils;
|
|
|
|
import org.mozilla.gecko.util.ThreadUtils;
|
|
|
|
|
2015-08-18 18:27:18 +00:00
|
|
|
import android.graphics.PointF;
|
2015-08-18 18:27:20 +00:00
|
|
|
import android.support.v4.view.ViewCompat;
|
2015-08-18 18:27:18 +00:00
|
|
|
import android.util.Log;
|
|
|
|
import android.view.animation.DecelerateInterpolator;
|
2015-08-18 18:27:18 +00:00
|
|
|
import android.view.MotionEvent;
|
2015-08-18 18:27:18 +00:00
|
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.List;
|
|
|
|
|
|
|
|
public class DynamicToolbarAnimator {
|
|
|
|
private static final String LOGTAG = "GeckoDynamicToolbarAnimator";
|
|
|
|
private static final String PREF_SCROLL_TOOLBAR_THRESHOLD = "browser.ui.scroll-toolbar-threshold";
|
|
|
|
|
|
|
|
// The duration of the animation in ns
|
|
|
|
private static final long ANIMATION_DURATION = 250000000;
|
|
|
|
|
|
|
|
private final GeckoLayerClient mTarget;
|
|
|
|
private final List<LayerView.DynamicToolbarListener> mListeners;
|
|
|
|
|
|
|
|
/* The translation to be applied to the toolbar UI view. This is the
|
|
|
|
* distance from the default/initial location (at the top of the screen,
|
|
|
|
* visible to the user) to where we want it to be. This variable should
|
|
|
|
* always be between 0 (toolbar fully visible) and the height of the toolbar
|
|
|
|
* (toolbar fully hidden), inclusive.
|
|
|
|
*/
|
|
|
|
private float mToolbarTranslation;
|
|
|
|
|
|
|
|
/* The translation to be applied to the LayerView. This is the distance from
|
|
|
|
* the default/initial location (just below the toolbar, with the bottom
|
|
|
|
* extending past the bottom of the screen) to where we want it to be.
|
|
|
|
* This variable should always be between 0 and the height of the toolbar,
|
|
|
|
* inclusive.
|
|
|
|
*/
|
|
|
|
private float mLayerViewTranslation;
|
|
|
|
|
|
|
|
/* This stores the maximum translation that can be applied to the toolbar
|
|
|
|
* and layerview when scrolling. This is populated with the height of the
|
|
|
|
* toolbar. */
|
|
|
|
private float mMaxTranslation;
|
|
|
|
|
|
|
|
/* If this boolean is true, scroll changes will not affect translation */
|
|
|
|
private boolean mPinned;
|
|
|
|
|
|
|
|
/* This interpolator is used for the above mentioned animation */
|
|
|
|
private DecelerateInterpolator mInterpolator;
|
|
|
|
|
|
|
|
/* This is the proportion of the viewport rect that needs to be travelled
|
|
|
|
* while scrolling before the translation will start taking effect.
|
|
|
|
*/
|
|
|
|
private float SCROLL_TOOLBAR_THRESHOLD = 0.20f;
|
|
|
|
/* The ID of the prefs listener for the scroll-toolbar threshold */
|
|
|
|
private Integer mPrefObserverId;
|
|
|
|
|
2015-08-18 18:27:20 +00:00
|
|
|
/* While we are resizing the viewport to account for the toolbar, the Java
|
|
|
|
* code and painted layer metrics in the compositor have different notions
|
|
|
|
* of the CSS viewport height. The Java value is stored in the
|
|
|
|
* GeckoLayerClient's viewport metrics, and the Gecko one is stored here.
|
|
|
|
* This allows us to adjust fixed-pos items correctly.
|
|
|
|
* You must synchronize on mTarget.getLock() to read/write this. */
|
|
|
|
private Integer mHeightDuringResize;
|
|
|
|
|
|
|
|
/* This tracks if we should trigger a "snap" on the next composite. A "snap"
|
|
|
|
* is when we simultaneously move the LayerView and change the scroll offset
|
|
|
|
* in the compositor so that everything looks the same on the screen but
|
|
|
|
* has really been shifted.
|
|
|
|
* You must synchronize on |this| to read/write this. */
|
|
|
|
private boolean mSnapRequired = false;
|
|
|
|
|
2015-08-18 18:27:18 +00:00
|
|
|
/* The task that handles showing/hiding toolbar */
|
|
|
|
private DynamicToolbarAnimationTask mAnimationTask;
|
|
|
|
|
2015-08-18 18:27:18 +00:00
|
|
|
/* The start point of a drag, used for scroll-based dynamic toolbar
|
|
|
|
* behaviour. */
|
|
|
|
private PointF mTouchStart;
|
|
|
|
private float mLastTouch;
|
|
|
|
|
2015-08-18 18:27:18 +00:00
|
|
|
public DynamicToolbarAnimator(GeckoLayerClient aTarget) {
|
|
|
|
mTarget = aTarget;
|
|
|
|
mListeners = new ArrayList<LayerView.DynamicToolbarListener>();
|
|
|
|
|
|
|
|
mInterpolator = new DecelerateInterpolator();
|
|
|
|
|
|
|
|
// Listen to the dynamic toolbar pref
|
|
|
|
mPrefObserverId = PrefsHelper.getPref(PREF_SCROLL_TOOLBAR_THRESHOLD, new PrefsHelper.PrefHandlerBase() {
|
|
|
|
@Override
|
|
|
|
public void prefValue(String pref, int value) {
|
|
|
|
SCROLL_TOOLBAR_THRESHOLD = value / 100.0f;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean isObserver() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
public void destroy() {
|
|
|
|
if (mPrefObserverId != null) {
|
|
|
|
PrefsHelper.removeObserver(mPrefObserverId);
|
|
|
|
mPrefObserverId = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void addTranslationListener(LayerView.DynamicToolbarListener aListener) {
|
|
|
|
mListeners.add(aListener);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void removeTranslationListener(LayerView.DynamicToolbarListener aListener) {
|
|
|
|
mListeners.remove(aListener);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void fireListeners() {
|
|
|
|
for (LayerView.DynamicToolbarListener listener : mListeners) {
|
|
|
|
listener.onTranslationChanged(mToolbarTranslation, mLayerViewTranslation);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-18 18:27:18 +00:00
|
|
|
void onPanZoomStopped() {
|
|
|
|
for (LayerView.DynamicToolbarListener listener : mListeners) {
|
|
|
|
listener.onPanZoomStopped();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-18 18:27:19 +00:00
|
|
|
void onMetricsChanged(ImmutableViewportMetrics aMetrics) {
|
|
|
|
for (LayerView.DynamicToolbarListener listener : mListeners) {
|
|
|
|
listener.onMetricsChanged(aMetrics);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-18 18:27:18 +00:00
|
|
|
public void setMaxTranslation(float maxTranslation) {
|
|
|
|
ThreadUtils.assertOnUiThread();
|
|
|
|
if (maxTranslation < 0) {
|
|
|
|
Log.e(LOGTAG, "Got a negative max-translation value: " + maxTranslation + "; clamping to zero");
|
|
|
|
mMaxTranslation = 0;
|
|
|
|
} else {
|
|
|
|
mMaxTranslation = maxTranslation;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-28 21:22:17 +00:00
|
|
|
public float getMaxTranslation() {
|
|
|
|
return mMaxTranslation;
|
|
|
|
}
|
|
|
|
|
2015-08-18 18:27:18 +00:00
|
|
|
public float getToolbarTranslation() {
|
|
|
|
return mToolbarTranslation;
|
|
|
|
}
|
|
|
|
|
2015-08-18 18:27:18 +00:00
|
|
|
public void setPinned(boolean pinned) {
|
|
|
|
mPinned = pinned;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isPinned() {
|
|
|
|
return mPinned;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void showToolbar(boolean immediately) {
|
2015-08-18 18:27:20 +00:00
|
|
|
animateToolbar(true, immediately);
|
2015-08-18 18:27:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public void hideToolbar(boolean immediately) {
|
2015-08-18 18:27:20 +00:00
|
|
|
animateToolbar(false, immediately);
|
2015-08-18 18:27:18 +00:00
|
|
|
}
|
|
|
|
|
2015-08-18 18:27:20 +00:00
|
|
|
private void animateToolbar(final boolean showToolbar, boolean immediately) {
|
2015-08-18 18:27:18 +00:00
|
|
|
ThreadUtils.assertOnUiThread();
|
|
|
|
|
|
|
|
if (mAnimationTask != null) {
|
|
|
|
mTarget.getView().removeRenderTask(mAnimationTask);
|
|
|
|
mAnimationTask = null;
|
|
|
|
}
|
|
|
|
|
2015-08-18 18:27:20 +00:00
|
|
|
float desiredTranslation = (showToolbar ? 0 : mMaxTranslation);
|
|
|
|
Log.v(LOGTAG, "Requested " + (immediately ? "immediate " : "") + "toolbar animation to translation " + desiredTranslation);
|
|
|
|
if (FloatUtils.fuzzyEquals(mToolbarTranslation, desiredTranslation)) {
|
2015-08-18 18:27:18 +00:00
|
|
|
// If we're already pretty much in the desired position, don't bother
|
|
|
|
// with a full animation; do an immediate jump
|
|
|
|
immediately = true;
|
|
|
|
Log.v(LOGTAG, "Changing animation to immediate jump");
|
|
|
|
}
|
|
|
|
|
2015-08-18 18:27:20 +00:00
|
|
|
if (showToolbar && immediately) {
|
|
|
|
// Special case for showing the toolbar immediately: some of the call
|
|
|
|
// sites expect this to happen synchronously, so let's do that. This
|
|
|
|
// is safe because if we are showing the toolbar from a hidden state
|
|
|
|
// there is no chance of showing garbage
|
|
|
|
mToolbarTranslation = desiredTranslation;
|
2015-08-18 18:27:18 +00:00
|
|
|
fireListeners();
|
2015-08-18 18:27:20 +00:00
|
|
|
// And then proceed with the normal flow (some of which will be
|
|
|
|
// a no-op now)...
|
2015-08-18 18:27:18 +00:00
|
|
|
}
|
|
|
|
|
2015-08-18 18:27:20 +00:00
|
|
|
if (!showToolbar) {
|
|
|
|
// If we are hiding the toolbar, we need to move the LayerView first,
|
|
|
|
// so that we don't end up showing garbage under the toolbar when
|
|
|
|
// it is hidden. In the case that we are showing the toolbar, we
|
|
|
|
// move the LayerView after the toolbar is shown - the
|
|
|
|
// DynamicToolbarAnimationTask calls that upon completion.
|
|
|
|
shiftLayerView(desiredTranslation);
|
|
|
|
}
|
|
|
|
|
|
|
|
mAnimationTask = new DynamicToolbarAnimationTask(desiredTranslation, immediately, showToolbar);
|
2015-08-18 18:27:18 +00:00
|
|
|
mTarget.getView().postRenderTask(mAnimationTask);
|
|
|
|
}
|
|
|
|
|
2015-08-18 18:27:20 +00:00
|
|
|
private synchronized void shiftLayerView(float desiredTranslation) {
|
|
|
|
float layerViewTranslationNeeded = desiredTranslation - mLayerViewTranslation;
|
|
|
|
mLayerViewTranslation = desiredTranslation;
|
|
|
|
synchronized (mTarget.getLock()) {
|
|
|
|
mHeightDuringResize = new Integer(mTarget.getViewportMetrics().viewportRectHeight);
|
|
|
|
mSnapRequired = mTarget.setViewportSize(
|
|
|
|
mTarget.getView().getWidth(),
|
|
|
|
mTarget.getView().getHeight() - Math.round(mMaxTranslation - mLayerViewTranslation),
|
|
|
|
new PointF(0, -layerViewTranslationNeeded));
|
|
|
|
if (!mSnapRequired) {
|
|
|
|
mHeightDuringResize = null;
|
|
|
|
ThreadUtils.postToUiThread(new Runnable() {
|
|
|
|
// Post to run it outside of the synchronize blocks. The
|
|
|
|
// delay shouldn't hurt.
|
|
|
|
@Override
|
|
|
|
public void run() {
|
|
|
|
fireListeners();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
// Request a composite, which will trigger the snap.
|
|
|
|
mTarget.getView().requestRender();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-18 18:27:19 +00:00
|
|
|
IntSize getViewportSize() {
|
2015-08-21 17:21:58 +00:00
|
|
|
ThreadUtils.assertOnUiThread();
|
|
|
|
|
2015-08-18 18:27:19 +00:00
|
|
|
int viewWidth = mTarget.getView().getWidth();
|
|
|
|
int viewHeight = mTarget.getView().getHeight();
|
2015-08-21 17:21:58 +00:00
|
|
|
float toolbarTranslation = mToolbarTranslation;
|
|
|
|
if (mAnimationTask != null) {
|
|
|
|
// If we have an animation going, mToolbarTranslation may be in flux
|
|
|
|
// and we should use the final value it will settle on.
|
|
|
|
toolbarTranslation = mAnimationTask.getFinalToolbarTranslation();
|
|
|
|
}
|
|
|
|
int viewHeightVisible = viewHeight - Math.round(mMaxTranslation - toolbarTranslation);
|
2015-08-18 18:27:19 +00:00
|
|
|
return new IntSize(viewWidth, viewHeightVisible);
|
|
|
|
}
|
|
|
|
|
2015-08-18 18:27:20 +00:00
|
|
|
boolean isResizing() {
|
|
|
|
return mHeightDuringResize != null;
|
|
|
|
}
|
2015-08-18 18:27:18 +00:00
|
|
|
|
2015-08-18 18:27:20 +00:00
|
|
|
private final Runnable mSnapRunnable = new Runnable() {
|
|
|
|
private int mFrame = 0;
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public final void run() {
|
|
|
|
// It takes 2 frames for the view translation to take effect, at
|
|
|
|
// least on a Nexus 4 device running Android 4.2.2. So we wait for
|
|
|
|
// two frames before doing the notifyAll(), otherwise we get a
|
|
|
|
// short user-visible glitch.
|
|
|
|
// TODO: find a better way to do this, if possible.
|
|
|
|
if (mFrame == 1) {
|
|
|
|
synchronized (this) {
|
|
|
|
this.notifyAll();
|
|
|
|
}
|
|
|
|
mFrame = 0;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mFrame == 0) {
|
|
|
|
fireListeners();
|
|
|
|
}
|
|
|
|
|
|
|
|
ViewCompat.postOnAnimation(mTarget.getView(), this);
|
|
|
|
mFrame++;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
void scrollChangeResizeCompleted() {
|
2015-08-18 18:27:18 +00:00
|
|
|
synchronized (mTarget.getLock()) {
|
2015-08-18 18:27:20 +00:00
|
|
|
Log.v(LOGTAG, "Scrollchange resize completed");
|
|
|
|
mHeightDuringResize = null;
|
2015-08-18 18:27:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-18 18:27:18 +00:00
|
|
|
/**
|
|
|
|
* "Shrinks" the absolute value of aValue by moving it closer to zero by
|
|
|
|
* aShrinkAmount, but prevents it from crossing over zero. If aShrinkAmount
|
|
|
|
* is negative it is ignored.
|
|
|
|
* @return The shrunken value.
|
|
|
|
*/
|
|
|
|
private static float shrinkAbs(float aValue, float aShrinkAmount) {
|
|
|
|
if (aShrinkAmount <= 0) {
|
|
|
|
return aValue;
|
|
|
|
}
|
|
|
|
float shrinkBy = Math.min(Math.abs(aValue), aShrinkAmount);
|
|
|
|
return (aValue < 0 ? aValue + shrinkBy : aValue - shrinkBy);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This function takes in a scroll amount and decides how much of that
|
|
|
|
* should be used up to translate things on screen because of the dynamic
|
|
|
|
* toolbar behaviour. It returns the maximum amount that could be used
|
|
|
|
* for translation purposes; the rest must be used for scrolling.
|
|
|
|
*/
|
|
|
|
private float decideTranslation(float aDelta,
|
|
|
|
ImmutableViewportMetrics aMetrics,
|
|
|
|
float aTouchTravelDistance) {
|
|
|
|
|
|
|
|
float exposeThreshold = aMetrics.getHeight() * SCROLL_TOOLBAR_THRESHOLD;
|
|
|
|
float translation = aDelta;
|
|
|
|
|
|
|
|
if (translation < 0) { // finger moving upwards
|
|
|
|
translation = shrinkAbs(translation, aMetrics.getOverscroll().top);
|
|
|
|
|
|
|
|
// If the toolbar is in a state between fully hidden and fully shown
|
|
|
|
// (i.e. the user is actively translating it), then we want the
|
|
|
|
// translation to take effect right away. Or if the user has moved
|
|
|
|
// their finger past the required threshold (and is not trying to
|
|
|
|
// scroll past the bottom of the page) then also we want the touch
|
|
|
|
// to cause translation.
|
|
|
|
boolean inBetween = (mToolbarTranslation != 0 && mToolbarTranslation != mMaxTranslation);
|
|
|
|
boolean reachedThreshold = -aTouchTravelDistance >= exposeThreshold;
|
|
|
|
boolean atBottomOfPage = aMetrics.viewportRectBottom() >= aMetrics.pageRectBottom;
|
|
|
|
if (inBetween || (reachedThreshold && !atBottomOfPage)) {
|
|
|
|
return translation;
|
|
|
|
}
|
|
|
|
} else { // finger moving downwards
|
|
|
|
translation = shrinkAbs(translation, aMetrics.getOverscroll().bottom);
|
|
|
|
|
|
|
|
// Ditto above comment, but in this case if they reached the top and
|
|
|
|
// the toolbar is not shown, then we do want to allow translation
|
|
|
|
// right away.
|
|
|
|
boolean inBetween = (mToolbarTranslation != 0 && mToolbarTranslation != mMaxTranslation);
|
|
|
|
boolean reachedThreshold = aTouchTravelDistance >= exposeThreshold;
|
|
|
|
boolean atTopOfPage = aMetrics.viewportRectTop <= aMetrics.pageRectTop;
|
|
|
|
boolean isToolbarTranslated = (mToolbarTranslation != 0);
|
|
|
|
if (inBetween || reachedThreshold || (atTopOfPage && isToolbarTranslated)) {
|
|
|
|
return translation;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
boolean onInterceptTouchEvent(MotionEvent event) {
|
|
|
|
if (mPinned) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Animations should never co-exist with the user touching the screen.
|
|
|
|
if (mAnimationTask != null) {
|
|
|
|
mTarget.getView().removeRenderTask(mAnimationTask);
|
|
|
|
mAnimationTask = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// we only care about single-finger drags here; any other kind of event
|
|
|
|
// should reset and cause us to start over.
|
|
|
|
if (event.getActionMasked() != MotionEvent.ACTION_MOVE ||
|
|
|
|
event.getPointerCount() != 1)
|
|
|
|
{
|
|
|
|
if (mTouchStart != null) {
|
|
|
|
Log.v(LOGTAG, "Resetting touch sequence due to non-move");
|
|
|
|
mTouchStart = null;
|
|
|
|
}
|
2015-08-20 20:29:01 +00:00
|
|
|
|
|
|
|
if (event.getActionMasked() == MotionEvent.ACTION_UP) {
|
|
|
|
// We need to do this even if the toolbar is already fully
|
|
|
|
// visible or fully hidden, because this is what triggers the
|
|
|
|
// viewport resize in content and updates the viewport metrics.
|
|
|
|
boolean toolbarMostlyVisible = mToolbarTranslation < (mMaxTranslation / 2);
|
|
|
|
Log.v(LOGTAG, "All fingers lifted, completing " + (toolbarMostlyVisible ? "show" : "hide"));
|
|
|
|
animateToolbar(toolbarMostlyVisible, false);
|
|
|
|
}
|
2015-08-18 18:27:18 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mTouchStart != null) {
|
|
|
|
float prevDir = mLastTouch - mTouchStart.y;
|
|
|
|
float newDir = event.getRawY() - mLastTouch;
|
|
|
|
if (prevDir != 0 && newDir != 0 && ((prevDir < 0) != (newDir < 0))) {
|
|
|
|
Log.v(LOGTAG, "Direction changed: " + mTouchStart.y + " -> " + mLastTouch + " -> " + event.getRawY());
|
|
|
|
// If the direction of movement changed, reset the travel
|
|
|
|
// distance properties.
|
|
|
|
mTouchStart = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mTouchStart == null) {
|
|
|
|
mTouchStart = new PointF(event.getRawX(), event.getRawY());
|
|
|
|
mLastTouch = event.getRawY();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
float deltaY = event.getRawY() - mLastTouch;
|
|
|
|
mLastTouch = event.getRawY();
|
|
|
|
float travelDistance = event.getRawY() - mTouchStart.y;
|
|
|
|
|
|
|
|
ImmutableViewportMetrics metrics = mTarget.getViewportMetrics();
|
|
|
|
|
2015-08-18 18:27:19 +00:00
|
|
|
if (metrics.getPageHeight() <= mTarget.getView().getHeight() &&
|
|
|
|
mToolbarTranslation == 0) {
|
|
|
|
// If the page is short and the toolbar is already visible, don't
|
|
|
|
// allow translating it out of view.
|
2015-08-18 18:27:18 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
float translation = decideTranslation(deltaY, metrics, travelDistance);
|
|
|
|
Log.v(LOGTAG, "Got vertical translation " + translation);
|
|
|
|
|
|
|
|
float oldToolbarTranslation = mToolbarTranslation;
|
|
|
|
float oldLayerViewTranslation = mLayerViewTranslation;
|
|
|
|
mToolbarTranslation = FloatUtils.clamp(mToolbarTranslation - translation, 0, mMaxTranslation);
|
|
|
|
mLayerViewTranslation = FloatUtils.clamp(mLayerViewTranslation - translation, 0, mMaxTranslation);
|
|
|
|
|
|
|
|
if (oldToolbarTranslation == mToolbarTranslation &&
|
|
|
|
oldLayerViewTranslation == mLayerViewTranslation) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
fireListeners();
|
|
|
|
mTarget.getView().requestRender();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-08-18 18:27:20 +00:00
|
|
|
public PointF getVisibleEndOfLayerView() {
|
|
|
|
return new PointF(mTarget.getView().getWidth(),
|
|
|
|
mTarget.getView().getHeight() - mMaxTranslation + mLayerViewTranslation);
|
|
|
|
}
|
|
|
|
|
2015-08-18 18:27:19 +00:00
|
|
|
private float bottomOfCssViewport(ImmutableViewportMetrics aMetrics) {
|
2015-08-18 18:27:20 +00:00
|
|
|
return (isResizing() ? mHeightDuringResize : aMetrics.getHeight())
|
|
|
|
+ mMaxTranslation - mLayerViewTranslation;
|
|
|
|
}
|
|
|
|
|
|
|
|
private synchronized boolean getAndClearSnapRequired() {
|
|
|
|
boolean snapRequired = mSnapRequired;
|
|
|
|
mSnapRequired = false;
|
|
|
|
return snapRequired;
|
2015-08-18 18:27:19 +00:00
|
|
|
}
|
|
|
|
|
2015-08-18 18:27:20 +00:00
|
|
|
void populateViewTransform(ViewTransform aTransform, ImmutableViewportMetrics aMetrics) {
|
|
|
|
if (getAndClearSnapRequired()) {
|
|
|
|
synchronized (mSnapRunnable) {
|
|
|
|
ViewCompat.postOnAnimation(mTarget.getView(), mSnapRunnable);
|
|
|
|
try {
|
|
|
|
// hold the in-progress composite until the views have been
|
|
|
|
// translated because otherwise there is a visible glitch.
|
|
|
|
// don't hold for more than 100ms just in case.
|
|
|
|
mSnapRunnable.wait(100);
|
|
|
|
} catch (InterruptedException ie) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
aTransform.x = aMetrics.viewportRectLeft;
|
|
|
|
aTransform.y = aMetrics.viewportRectTop;
|
|
|
|
aTransform.width = aMetrics.viewportRectWidth;
|
|
|
|
aTransform.height = aMetrics.viewportRectHeight;
|
|
|
|
aTransform.scale = aMetrics.zoomFactor;
|
|
|
|
|
2015-08-18 18:27:19 +00:00
|
|
|
aTransform.fixedLayerMarginTop = mLayerViewTranslation - mToolbarTranslation;
|
|
|
|
float bottomOfScreen = mTarget.getView().getHeight();
|
|
|
|
// We want to move a fixed item from "bottomOfCssViewport" to
|
|
|
|
// "bottomOfScreen". But also the bottom margin > 0 means that bottom
|
|
|
|
// fixed-pos items will move upwards.
|
|
|
|
aTransform.fixedLayerMarginBottom = bottomOfCssViewport(aMetrics) - bottomOfScreen;
|
2015-08-18 18:27:20 +00:00
|
|
|
//Log.v(LOGTAG, "ViewTransform is x=" + aTransform.x + " y=" + aTransform.y
|
|
|
|
// + " z=" + aTransform.scale + " t=" + aTransform.fixedLayerMarginTop
|
|
|
|
// + " b=" + aTransform.fixedLayerMarginBottom);
|
2015-08-18 18:27:19 +00:00
|
|
|
}
|
|
|
|
|
2015-08-18 18:27:18 +00:00
|
|
|
class DynamicToolbarAnimationTask extends RenderTask {
|
|
|
|
private final float mStartTranslation;
|
|
|
|
private final float mEndTranslation;
|
2015-08-18 18:27:20 +00:00
|
|
|
private final boolean mImmediate;
|
|
|
|
private final boolean mShiftLayerView;
|
2015-08-18 18:27:18 +00:00
|
|
|
private boolean mContinueAnimation;
|
|
|
|
|
2015-08-18 18:27:20 +00:00
|
|
|
public DynamicToolbarAnimationTask(float aTranslation, boolean aImmediate, boolean aShiftLayerView) {
|
|
|
|
super(false);
|
2015-08-18 18:27:18 +00:00
|
|
|
mContinueAnimation = true;
|
|
|
|
mStartTranslation = mToolbarTranslation;
|
|
|
|
mEndTranslation = aTranslation;
|
2015-08-18 18:27:20 +00:00
|
|
|
mImmediate = aImmediate;
|
|
|
|
mShiftLayerView = aShiftLayerView;
|
2015-08-18 18:27:18 +00:00
|
|
|
}
|
|
|
|
|
2015-08-21 17:21:58 +00:00
|
|
|
float getFinalToolbarTranslation() {
|
|
|
|
return mEndTranslation;
|
|
|
|
}
|
|
|
|
|
2015-08-18 18:27:18 +00:00
|
|
|
@Override
|
|
|
|
public boolean internalRun(long timeDelta, long currentFrameStartTime) {
|
|
|
|
if (!mContinueAnimation) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calculate the progress (between 0 and 1)
|
2015-08-18 18:27:20 +00:00
|
|
|
final float progress = mImmediate
|
|
|
|
? 1.0f
|
|
|
|
: mInterpolator.getInterpolation(
|
2015-08-18 18:27:18 +00:00
|
|
|
Math.min(1.0f, (System.nanoTime() - getStartTime())
|
|
|
|
/ (float)ANIMATION_DURATION));
|
|
|
|
|
|
|
|
// This runs on the compositor thread, so we need to post the
|
|
|
|
// actual work to the UI thread.
|
|
|
|
ThreadUtils.assertNotOnUiThread();
|
|
|
|
|
|
|
|
ThreadUtils.postToUiThread(new Runnable() {
|
|
|
|
@Override
|
|
|
|
public void run() {
|
|
|
|
// Move the toolbar as per the animation
|
|
|
|
mToolbarTranslation = FloatUtils.interpolate(mStartTranslation, mEndTranslation, progress);
|
|
|
|
fireListeners();
|
|
|
|
|
2015-08-18 18:27:20 +00:00
|
|
|
if (mShiftLayerView && progress >= 1.0f) {
|
|
|
|
shiftLayerView(mEndTranslation);
|
2015-08-18 18:27:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
mTarget.getView().requestRender();
|
|
|
|
if (progress >= 1.0f) {
|
|
|
|
mContinueAnimation = false;
|
|
|
|
}
|
|
|
|
return mContinueAnimation;
|
|
|
|
}
|
|
|
|
}
|
2015-08-18 18:27:20 +00:00
|
|
|
|
|
|
|
class SnapMetrics {
|
|
|
|
public final int viewportWidth;
|
|
|
|
public final int viewportHeight;
|
|
|
|
public final float scrollChangeY;
|
|
|
|
|
|
|
|
SnapMetrics(ImmutableViewportMetrics aMetrics, float aScrollChange) {
|
|
|
|
viewportWidth = aMetrics.viewportRectWidth;
|
|
|
|
viewportHeight = aMetrics.viewportRectHeight;
|
|
|
|
scrollChangeY = aScrollChange;
|
|
|
|
}
|
|
|
|
}
|
2015-08-18 18:27:18 +00:00
|
|
|
}
|