OLD | NEW |
1 // Copyright 2017 The Chromium Authors. All rights reserved. | 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 package org.chromium.chrome.browser.contextmenu; | 5 package org.chromium.chrome.browser.contextmenu; |
6 | 6 |
| 7 import android.animation.Animator; |
| 8 import android.animation.AnimatorListenerAdapter; |
| 9 import android.animation.ValueAnimator; |
| 10 import android.animation.ValueAnimator.AnimatorUpdateListener; |
7 import android.content.Context; | 11 import android.content.Context; |
| 12 import android.graphics.Canvas; |
| 13 import android.graphics.drawable.Drawable; |
8 import android.support.v4.view.ViewPager; | 14 import android.support.v4.view.ViewPager; |
| 15 import android.support.v4.view.animation.LinearOutSlowInInterpolator; |
9 import android.util.AttributeSet; | 16 import android.util.AttributeSet; |
10 import android.view.View; | 17 import android.view.View; |
11 | 18 |
| 19 import org.chromium.base.ApiCompatibilityUtils; |
12 import org.chromium.chrome.R; | 20 import org.chromium.chrome.R; |
13 | 21 |
14 /** | 22 /** |
15 * When there is more than one view for the context menu to display, it wraps th
e display in a view | 23 * When there is more than one view for the context menu to display, it wraps th
e display in a view |
16 * pager. | 24 * pager. |
17 */ | 25 */ |
18 public class TabularContextMenuViewPager extends ViewPager { | 26 public class TabularContextMenuViewPager extends ViewPager { |
| 27 private static final int ANIMATION_DURATION_MS = 250; |
| 28 |
19 private final int mContextMenuMinimumPaddingPx = | 29 private final int mContextMenuMinimumPaddingPx = |
20 getResources().getDimensionPixelSize(R.dimen.context_menu_min_paddin
g); | 30 getResources().getDimensionPixelSize(R.dimen.context_menu_min_paddin
g); |
| 31 private final Drawable mBackgroundDrawable = ApiCompatibilityUtils.getDrawab
le( |
| 32 getResources(), R.drawable.white_with_rounded_corners); |
21 | 33 |
22 public TabularContextMenuViewPager(Context context) { | 34 private ValueAnimator mAnimator; |
23 super(context); | 35 private int mOldHeight; |
24 } | 36 private int mCanvasWidth; |
| 37 private int mClipHeight; |
| 38 |
| 39 private int mDifferenceInHeight; |
| 40 private int mPreviousChildIndex = 1; |
25 | 41 |
26 public TabularContextMenuViewPager(Context context, AttributeSet attrs) { | 42 public TabularContextMenuViewPager(Context context, AttributeSet attrs) { |
27 super(context, attrs); | 43 super(context, attrs); |
| 44 mBackgroundDrawable.mutate(); |
28 } | 45 } |
29 | 46 |
30 /** | 47 /** |
31 * Used to show the full ViewPager dialog. Without this the dialog would hav
e no height or | 48 * Used to show the full ViewPager dialog. Without this the dialog would hav
e no height or |
32 * width. | 49 * width. |
33 */ | 50 */ |
34 @Override | 51 @Override |
35 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { | 52 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
| 53 int tabHeight = 0; |
36 int menuHeight = 0; | 54 int menuHeight = 0; |
37 int tabHeight = 0; | |
38 | |
39 // getCurrentItem() does not take into account the tab layout unlike get
ChildCount(). | |
40 int currentItemsIndex = getCurrentItem() + 1; | |
41 | 55 |
42 // The width of the context menu is defined so that it leaves space betw
een itself and the | 56 // The width of the context menu is defined so that it leaves space betw
een itself and the |
43 // screen's edges. It is also bounded to a max size to prevent the menu
from stretching | 57 // screen's edges. It is also bounded to a max size to prevent the menu
from stretching |
44 // across a large display (e.g. a tablet screen). | 58 // across a large display (e.g. a tablet screen). |
45 int deviceWidthPx = getResources().getDisplayMetrics().widthPixels; | 59 int appWindowWidthPx = getResources().getDisplayMetrics().widthPixels; |
46 int contextMenuWidth = Math.min(deviceWidthPx - 2 * mContextMenuMinimumP
addingPx, | 60 int contextMenuWidth = Math.min(appWindowWidthPx - 2 * mContextMenuMinim
umPaddingPx, |
47 getResources().getDimensionPixelSize(R.dimen.context_menu_max_wi
dth)); | 61 getResources().getDimensionPixelSize(R.dimen.context_menu_max_wi
dth)); |
48 | 62 |
49 widthMeasureSpec = MeasureSpec.makeMeasureSpec(contextMenuWidth, Measure
Spec.EXACTLY); | 63 widthMeasureSpec = MeasureSpec.makeMeasureSpec(contextMenuWidth, Measure
Spec.EXACTLY); |
50 | 64 |
51 // The height of the context menu is calculated as the sum of: | 65 // getCurrentItem() returns the index of the current page in the pager's
pages. |
52 // 1. The tab bar's height, which is only visible when the context menu
requires it | 66 // It does not take into account the tab layout like getChildCount(), so
we add 1. |
53 // (i.e. an ImageLink is clicked) | 67 int currentChildIndex = getCurrentItem() + 1; |
54 // 2. The height of the View being displayed for the current tab. | |
55 for (int i = 0; i < getChildCount(); i++) { | |
56 View child = getChildAt(i); | |
57 | 68 |
58 child.measure( | 69 // Handles the case where this is called while the pager is scrolling be
tween views. |
59 widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec
.UNSPECIFIED)); | 70 // The height should remain the same as the height of the last view seen
before scrolling. |
60 int measuredHeight = child.getMeasuredHeight(); | 71 if (getScrollX() != 0 && getScrollX() != mCanvasWidth) { |
| 72 heightMeasureSpec = MeasureSpec.makeMeasureSpec(mOldHeight, MeasureS
pec.EXACTLY); |
| 73 } else { |
| 74 // The height of the context menu is calculated as the sum of: |
| 75 // 1. The tab bar's height, which is only visible when the context m
enu requires it |
| 76 // (i.e. an ImageLink is clicked) |
| 77 // 2. The height of the View being displayed for the current tab. |
| 78 for (int i = 0; i < getChildCount(); i++) { |
| 79 View child = getChildAt(i); |
| 80 child.measure( |
| 81 widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, Measure
Spec.UNSPECIFIED)); |
| 82 int measuredHeight = child.getMeasuredHeight(); |
61 | 83 |
62 // The ViewPager also considers the tab layout one of its children,
and needs to be | 84 // The ViewPager also considers the tab layout one of its childr
en, and needs to be |
63 // treated separately from getting the largest height. | 85 // treated separately from getting the largest height. |
64 if (child.getId() == R.id.tab_layout && child.getVisibility() != GON
E) { | 86 if (child.getId() == R.id.tab_layout && child.getVisibility() !=
GONE) { |
65 tabHeight = measuredHeight; | 87 tabHeight = measuredHeight; |
66 } else if (i == currentItemsIndex) { | 88 } else if (i == currentChildIndex) { |
67 menuHeight = child.getMeasuredHeight(); | 89 menuHeight = child.getMeasuredHeight(); |
68 break; | 90 break; |
| 91 } |
| 92 } |
| 93 int fullHeight = menuHeight + tabHeight; |
| 94 int appWindowHeightPx = getResources().getDisplayMetrics().heightPix
els; |
| 95 fullHeight = Math.min(fullHeight, appWindowHeightPx - 2 * mContextMe
nuMinimumPaddingPx); |
| 96 mDifferenceInHeight = fullHeight - mOldHeight; |
| 97 |
| 98 if (currentChildIndex == mPreviousChildIndex) { |
| 99 // Handles the snapping of the view when its height changes |
| 100 // (i.e. an image finished loading or the link became fully visi
ble). |
| 101 // The pager will immediately snap to the new height. |
| 102 mClipHeight = fullHeight; |
| 103 if (menuHeight != 0) mOldHeight = fullHeight; |
| 104 heightMeasureSpec = MeasureSpec.makeMeasureSpec(fullHeight, Meas
ureSpec.EXACTLY); |
| 105 } else { |
| 106 // Handles the case where the view pager has completely scrolled
to a different |
| 107 // child. It will measure to the larger height so the clipping i
s visible. |
| 108 initAnimator(); |
| 109 heightMeasureSpec = MeasureSpec.makeMeasureSpec( |
| 110 Math.max(mOldHeight, fullHeight), MeasureSpec.EXACTLY); |
69 } | 111 } |
70 } | 112 } |
| 113 super.onMeasure(widthMeasureSpec, heightMeasureSpec); |
| 114 mPreviousChildIndex = currentChildIndex; |
| 115 // The animation only runs when switching to a tab with a different heig
ht. |
| 116 if (mAnimator != null) mAnimator.start(); |
| 117 } |
71 | 118 |
72 // Cap the height of the context menu so that it fits on the screen with
out touching the | 119 private void initAnimator() { |
73 // screen's edges. | 120 if (mAnimator != null) return; |
74 int fullHeight = menuHeight + tabHeight; | 121 mAnimator = ValueAnimator.ofFloat(0f, 1f); |
75 int deviceHeightPx = getResources().getDisplayMetrics().heightPixels; | 122 mAnimator.setDuration(ANIMATION_DURATION_MS); |
76 fullHeight = Math.min(fullHeight, deviceHeightPx - 2 * mContextMenuMinim
umPaddingPx); | 123 mAnimator.setInterpolator(new LinearOutSlowInInterpolator()); |
| 124 mAnimator.addUpdateListener(new AnimatorUpdateListener() { |
77 | 125 |
78 heightMeasureSpec = MeasureSpec.makeMeasureSpec(fullHeight, MeasureSpec.
EXACTLY); | 126 @Override |
79 super.onMeasure(widthMeasureSpec, heightMeasureSpec); | 127 public void onAnimationUpdate(ValueAnimator animation) { |
| 128 float animatedValue = (float) animation.getAnimatedValue(); |
| 129 if (mDifferenceInHeight < 0) { |
| 130 setTranslationY(animatedValue * -mDifferenceInHeight / 2); |
| 131 } else { |
| 132 setTranslationY((1 - animatedValue) * mDifferenceInHeight /
2); |
| 133 } |
| 134 mClipHeight = mOldHeight + (int) (mDifferenceInHeight * animated
Value); |
| 135 invalidate(); |
| 136 } |
| 137 }); |
| 138 mAnimator.addListener(new AnimatorListenerAdapter() { |
| 139 @Override |
| 140 public void onAnimationEnd(Animator animation) { |
| 141 mOldHeight = mClipHeight; |
| 142 setTranslationY(0); |
| 143 if (mDifferenceInHeight < 0) requestLayout(); |
| 144 } |
| 145 }); |
| 146 } |
| 147 |
| 148 @Override |
| 149 public void onDraw(Canvas canvas) { |
| 150 mCanvasWidth = canvas.getWidth(); |
| 151 int backgroundOffsetX = getScrollX(); |
| 152 mBackgroundDrawable.setBounds( |
| 153 backgroundOffsetX, 0, canvas.getWidth() + backgroundOffsetX, mCl
ipHeight); |
| 154 mBackgroundDrawable.draw(canvas); |
| 155 |
| 156 boolean clipped = false; |
| 157 if (mClipHeight != 0) { |
| 158 canvas.save(); |
| 159 canvas.clipRect(0, 0, mCanvasWidth, mClipHeight); |
| 160 clipped = true; |
| 161 } |
| 162 |
| 163 super.onDraw(canvas); |
| 164 |
| 165 if (clipped) { |
| 166 canvas.restore(); |
| 167 } |
80 } | 168 } |
81 } | 169 } |
OLD | NEW |