Index: chrome/android/java/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuViewPager.java |
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuViewPager.java b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuViewPager.java |
index dd76948cbbe1e9b0f01ef264b72dd508555bea10..65217495301ef22d158d64e19da7885cab01cb7b 100644 |
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuViewPager.java |
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuViewPager.java |
@@ -4,11 +4,19 @@ |
package org.chromium.chrome.browser.contextmenu; |
+import android.animation.Animator; |
+import android.animation.AnimatorListenerAdapter; |
+import android.animation.ValueAnimator; |
+import android.animation.ValueAnimator.AnimatorUpdateListener; |
import android.content.Context; |
+import android.graphics.Canvas; |
+import android.graphics.drawable.Drawable; |
import android.support.v4.view.ViewPager; |
+import android.support.v4.view.animation.LinearOutSlowInInterpolator; |
import android.util.AttributeSet; |
import android.view.View; |
+import org.chromium.base.ApiCompatibilityUtils; |
import org.chromium.chrome.R; |
/** |
@@ -16,15 +24,24 @@ import org.chromium.chrome.R; |
* pager. |
*/ |
public class TabularContextMenuViewPager extends ViewPager { |
+ private static final int ANIMATION_DURATION_MS = 250; |
+ |
private final int mContextMenuMinimumPaddingPx = |
getResources().getDimensionPixelSize(R.dimen.context_menu_min_padding); |
+ private final Drawable mBackgroundDrawable = ApiCompatibilityUtils.getDrawable( |
+ getResources(), R.drawable.white_with_rounded_corners); |
- public TabularContextMenuViewPager(Context context) { |
- super(context); |
- } |
+ private ValueAnimator mAnimator; |
+ private int mOldHeight; |
+ private int mCanvasWidth; |
+ private int mClipHeight; |
+ |
+ private int mDifferenceInHeight; |
+ private int mPreviousChildIndex = 1; |
public TabularContextMenuViewPager(Context context, AttributeSet attrs) { |
super(context, attrs); |
+ mBackgroundDrawable.mutate(); |
} |
/** |
@@ -33,49 +50,120 @@ public class TabularContextMenuViewPager extends ViewPager { |
*/ |
@Override |
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
- int menuHeight = 0; |
int tabHeight = 0; |
- |
- // getCurrentItem() does not take into account the tab layout unlike getChildCount(). |
- int currentItemsIndex = getCurrentItem() + 1; |
+ int menuHeight = 0; |
// The width of the context menu is defined so that it leaves space between itself and the |
// screen's edges. It is also bounded to a max size to prevent the menu from stretching |
// across a large display (e.g. a tablet screen). |
- int deviceWidthPx = getResources().getDisplayMetrics().widthPixels; |
- int contextMenuWidth = Math.min(deviceWidthPx - 2 * mContextMenuMinimumPaddingPx, |
+ int appWindowWidthPx = getResources().getDisplayMetrics().widthPixels; |
+ int contextMenuWidth = Math.min(appWindowWidthPx - 2 * mContextMenuMinimumPaddingPx, |
getResources().getDimensionPixelSize(R.dimen.context_menu_max_width)); |
widthMeasureSpec = MeasureSpec.makeMeasureSpec(contextMenuWidth, MeasureSpec.EXACTLY); |
- // The height of the context menu is calculated as the sum of: |
- // 1. The tab bar's height, which is only visible when the context menu requires it |
- // (i.e. an ImageLink is clicked) |
- // 2. The height of the View being displayed for the current tab. |
- for (int i = 0; i < getChildCount(); i++) { |
- View child = getChildAt(i); |
- |
- child.measure( |
- widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); |
- int measuredHeight = child.getMeasuredHeight(); |
- |
- // The ViewPager also considers the tab layout one of its children, and needs to be |
- // treated separately from getting the largest height. |
- if (child.getId() == R.id.tab_layout && child.getVisibility() != GONE) { |
- tabHeight = measuredHeight; |
- } else if (i == currentItemsIndex) { |
- menuHeight = child.getMeasuredHeight(); |
- break; |
+ // getCurrentItem() returns the index of the current page in the pager's pages. |
+ // It does not take into account the tab layout like getChildCount(), so we add 1. |
+ int currentChildIndex = getCurrentItem() + 1; |
+ |
+ // Handles the case where this is called while the pager is scrolling between views. |
+ // The height should remain the same as the height of the last view seen before scrolling. |
+ if (getScrollX() != 0 && getScrollX() != mCanvasWidth) { |
+ heightMeasureSpec = MeasureSpec.makeMeasureSpec(mOldHeight, MeasureSpec.EXACTLY); |
+ } else { |
+ // The height of the context menu is calculated as the sum of: |
+ // 1. The tab bar's height, which is only visible when the context menu requires it |
+ // (i.e. an ImageLink is clicked) |
+ // 2. The height of the View being displayed for the current tab. |
+ for (int i = 0; i < getChildCount(); i++) { |
+ View child = getChildAt(i); |
+ child.measure( |
+ widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); |
+ int measuredHeight = child.getMeasuredHeight(); |
+ |
+ // The ViewPager also considers the tab layout one of its children, and needs to be |
+ // treated separately from getting the largest height. |
+ if (child.getId() == R.id.tab_layout && child.getVisibility() != GONE) { |
+ tabHeight = measuredHeight; |
+ } else if (i == currentChildIndex) { |
+ menuHeight = child.getMeasuredHeight(); |
+ break; |
+ } |
+ } |
+ int fullHeight = menuHeight + tabHeight; |
+ int appWindowHeightPx = getResources().getDisplayMetrics().heightPixels; |
+ fullHeight = Math.min(fullHeight, appWindowHeightPx - 2 * mContextMenuMinimumPaddingPx); |
+ mDifferenceInHeight = fullHeight - mOldHeight; |
+ |
+ if (currentChildIndex == mPreviousChildIndex) { |
+ // Handles the snapping of the view when its height changes |
+ // (i.e. an image finished loading or the link became fully visible). |
+ // The pager will immediately snap to the new height. |
+ mClipHeight = fullHeight; |
+ if (menuHeight != 0) mOldHeight = fullHeight; |
+ heightMeasureSpec = MeasureSpec.makeMeasureSpec(fullHeight, MeasureSpec.EXACTLY); |
+ } else { |
+ // Handles the case where the view pager has completely scrolled to a different |
+ // child. It will measure to the larger height so the clipping is visible. |
+ initAnimator(); |
+ heightMeasureSpec = MeasureSpec.makeMeasureSpec( |
+ Math.max(mOldHeight, fullHeight), MeasureSpec.EXACTLY); |
} |
} |
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec); |
+ mPreviousChildIndex = currentChildIndex; |
+ // The animation only runs when switching to a tab with a different height. |
+ if (mAnimator != null) mAnimator.start(); |
+ } |
- // Cap the height of the context menu so that it fits on the screen without touching the |
- // screen's edges. |
- int fullHeight = menuHeight + tabHeight; |
- int deviceHeightPx = getResources().getDisplayMetrics().heightPixels; |
- fullHeight = Math.min(fullHeight, deviceHeightPx - 2 * mContextMenuMinimumPaddingPx); |
+ private void initAnimator() { |
+ if (mAnimator != null) return; |
+ mAnimator = ValueAnimator.ofFloat(0f, 1f); |
+ mAnimator.setDuration(ANIMATION_DURATION_MS); |
+ mAnimator.setInterpolator(new LinearOutSlowInInterpolator()); |
+ mAnimator.addUpdateListener(new AnimatorUpdateListener() { |
- heightMeasureSpec = MeasureSpec.makeMeasureSpec(fullHeight, MeasureSpec.EXACTLY); |
- super.onMeasure(widthMeasureSpec, heightMeasureSpec); |
+ @Override |
+ public void onAnimationUpdate(ValueAnimator animation) { |
+ float animatedValue = (float) animation.getAnimatedValue(); |
+ if (mDifferenceInHeight < 0) { |
+ setTranslationY(animatedValue * -mDifferenceInHeight / 2); |
+ } else { |
+ setTranslationY((1 - animatedValue) * mDifferenceInHeight / 2); |
+ } |
+ mClipHeight = mOldHeight + (int) (mDifferenceInHeight * animatedValue); |
+ invalidate(); |
+ } |
+ }); |
+ mAnimator.addListener(new AnimatorListenerAdapter() { |
+ @Override |
+ public void onAnimationEnd(Animator animation) { |
+ mOldHeight = mClipHeight; |
+ setTranslationY(0); |
+ if (mDifferenceInHeight < 0) requestLayout(); |
+ } |
+ }); |
+ } |
+ |
+ @Override |
+ public void onDraw(Canvas canvas) { |
+ mCanvasWidth = canvas.getWidth(); |
+ int backgroundOffsetX = getScrollX(); |
+ mBackgroundDrawable.setBounds( |
+ backgroundOffsetX, 0, canvas.getWidth() + backgroundOffsetX, mClipHeight); |
+ mBackgroundDrawable.draw(canvas); |
+ |
+ boolean clipped = false; |
+ if (mClipHeight != 0) { |
+ canvas.save(); |
+ canvas.clipRect(0, 0, mCanvasWidth, mClipHeight); |
+ clipped = true; |
+ } |
+ |
+ super.onDraw(canvas); |
+ |
+ if (clipped) { |
+ canvas.restore(); |
+ } |
} |
} |