Index: chrome/android/java/src/org/chromium/chrome/browser/infobar/ContentWrapperView.java |
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/infobar/ContentWrapperView.java b/chrome/android/java/src/org/chromium/chrome/browser/infobar/ContentWrapperView.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..79e6d902056112dce37fb4d783c0ba9f87cadc6f |
--- /dev/null |
+++ b/chrome/android/java/src/org/chromium/chrome/browser/infobar/ContentWrapperView.java |
@@ -0,0 +1,254 @@ |
+// Copyright (c) 2013 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.infobar; |
+ |
+import android.animation.Animator; |
+import android.animation.ObjectAnimator; |
+import android.content.Context; |
+import android.content.res.Resources; |
+import android.graphics.Rect; |
+import android.view.Gravity; |
+import android.view.MotionEvent; |
+import android.view.View; |
+import android.view.ViewGroup; |
+import android.view.ViewParent; |
+import android.widget.FrameLayout; |
+ |
+import org.chromium.chrome.R; |
+ |
+import org.chromium.base.ApiCompatibilityUtils; |
+ |
+import java.util.ArrayList; |
+ |
+/** |
+ * A wrapper class designed to: |
+ * - consume all touch events. This way the parent view (the FrameLayout ContentView) won't |
+ * have its onTouchEvent called. If it does, ContentView will process the touch click. |
+ * We don't want web content responding to clicks on the InfoBars. |
+ * - allow swapping out of children Views for animations. |
+ * |
+ * Once an InfoBar has been hidden and removed from the InfoBarContainer, it cannot be reused |
+ * because the main panel is discarded after the hiding animation. |
+ */ |
+public class ContentWrapperView extends FrameLayout { |
+ private static final String TAG = "ContentWrapperView"; |
+ |
+ // Index of the child View that will get swapped out during transitions. |
+ private static final int CONTENT_INDEX = 0; |
+ |
+ private final int mGravity; |
+ private final boolean mInfoBarsFromTop; |
+ private final InfoBar mInfoBar; |
+ |
+ private View mViewToHide; |
+ private View mViewToShow; |
+ |
+ /** |
+ * Constructs a ContentWrapperView object. |
+ * @param context The context to create this View with. |
+ */ |
+ public ContentWrapperView(Context context, InfoBar infoBar, int backgroundType, View panel, |
+ boolean infoBarsFromTop) { |
+ // Set up this ViewGroup. |
+ super(context); |
+ mInfoBar = infoBar; |
+ mGravity = infoBarsFromTop ? Gravity.BOTTOM : Gravity.TOP; |
+ mInfoBarsFromTop = infoBarsFromTop; |
+ |
+ // Pull out resources we need for the backgrounds. Defaults to the INFO type. |
+ int separatorBackground = R.color.infobar_info_background_separator; |
+ int layoutBackground = R.drawable.infobar_info_background; |
+ if (backgroundType == InfoBar.BACKGROUND_TYPE_WARNING) { |
+ layoutBackground = R.drawable.infobar_warning_background; |
+ separatorBackground = R.color.infobar_warning_background_separator; |
+ } |
+ |
+ // Set up this view. |
+ Resources resources = context.getResources(); |
+ LayoutParams wrapParams = new LayoutParams(LayoutParams.MATCH_PARENT, |
+ LayoutParams.WRAP_CONTENT); |
+ setLayoutParams(wrapParams); |
+ ApiCompatibilityUtils.setBackgroundForView(this, resources.getDrawable(layoutBackground)); |
+ |
+ // Add a separator line that delineates different InfoBars. |
+ View separator = new View(context); |
+ separator.setBackgroundColor(resources.getColor(separatorBackground)); |
+ addView(separator, new LayoutParams(LayoutParams.MATCH_PARENT, getBoundaryHeight(context), |
+ mGravity)); |
+ |
+ // Add the InfoBar content. |
+ addChildView(panel); |
+ } |
+ |
+ @Override |
+ public boolean onInterceptTouchEvent(MotionEvent ev) { |
+ return !mInfoBar.areControlsEnabled(); |
+ } |
+ |
+ @Override |
+ public boolean onTouchEvent(MotionEvent event) { |
+ // Consume all motion events so they do not reach the ContentView. |
+ return true; |
+ } |
+ |
+ /** |
+ * Calculates how tall the InfoBar boundary should be in pixels. |
+ * XHDPI devices and above get a double-tall boundary. |
+ * @return The height of the boundary. |
+ */ |
+ private int getBoundaryHeight(Context context) { |
+ float density = context.getResources().getDisplayMetrics().density; |
+ return density < 2.0f ? 1 : 2; |
+ } |
+ |
+ /** |
+ * @return the current View representing the InfoBar. |
+ */ |
+ public boolean hasChildView() { |
+ // If there's a View that can be replaced, there will be at least two children for the View. |
+ // One of the Views will always be the InfoBar separator. |
+ return getChildCount() > 1; |
+ } |
+ |
+ /** |
+ * Detaches the View currently being shown and returns it for reparenting. |
+ * @return the View that is currently being shown. |
+ */ |
+ public View detachCurrentView() { |
+ assert getChildCount() > 1; |
+ View view = getChildAt(CONTENT_INDEX); |
+ removeView(view); |
+ return view; |
+ } |
+ |
+ /** |
+ * Adds a View to this layout, before the InfoBar separator. |
+ * @param viewToAdd The View to add. |
+ */ |
+ private void addChildView(View viewToAdd) { |
+ addView(viewToAdd, CONTENT_INDEX, new FrameLayout.LayoutParams( |
+ LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT, mGravity)); |
+ } |
+ |
+ /** |
+ * Prepares the animation needed to hide the current View and show the new one. |
+ * @param viewToShow View that will replace the currently shown child of this FrameLayout. |
+ */ |
+ public void prepareTransition(View viewToShow) { |
+ assert mViewToHide == null && mViewToShow == null; |
+ |
+ // If it exists, the View that is being replaced will be the non-separator child and will |
+ // we in the second position. |
+ assert getChildCount() <= 2; |
+ if (hasChildView()) { |
+ mViewToHide = getChildAt(CONTENT_INDEX); |
+ } |
+ |
+ mViewToShow = viewToShow; |
+ assert mViewToHide != null || mViewToShow != null; |
+ assert mViewToHide != mViewToShow; |
+ } |
+ |
+ /** |
+ * Called when the animation is starting. |
+ */ |
+ public void startTransition() { |
+ if (mViewToShow != null) { |
+ // Move the View to this container. |
+ ViewParent parent = mViewToShow.getParent(); |
+ assert parent != null && parent instanceof ViewGroup; |
+ ((ViewGroup) parent).removeView(mViewToShow); |
+ addChildView(mViewToShow); |
+ |
+ // We're transitioning between two views; set the alpha so it doesn't pop in. |
+ if (mViewToHide != null) mViewToShow.setAlpha(0.0f); |
+ |
+ // Because of layout scheduling, we need to move the child Views downward before it |
+ // occurs. Failure to do so results in the Views being located incorrectly during the |
+ // first few frames of the animation. |
+ if (mInfoBarsFromTop && getViewToShowHeight() > getViewToHideHeight()) { |
+ getLayoutParams().height = getViewToShowHeight(); |
+ |
+ int translation = getTransitionHeightDifference(); |
+ for (int i = 0; i < getChildCount(); ++i) { |
+ View v = getChildAt(i); |
+ v.setTop(v.getTop() + translation); |
+ v.setBottom(v.getBottom() + translation); |
+ } |
+ } |
+ } |
+ } |
+ |
+ /** |
+ * Called when the animation is done. |
+ * At this point, we can get rid of the View that used to represent the InfoBar and re-enable |
+ * controls. |
+ */ |
+ public void finishTransition() { |
+ if (mViewToHide != null) { |
+ removeView(mViewToHide); |
+ } |
+ getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT; |
+ requestLayout(); |
+ |
+ mViewToHide = null; |
+ mViewToShow = null; |
+ mInfoBar.setControlsEnabled(true); |
+ } |
+ |
+ /** |
+ * Returns the height of the View being shown. |
+ * If no new View is going to replace the current one (i.e. the InfoBar is being hidden), the |
+ * height is 0. |
+ */ |
+ private int getViewToShowHeight() { |
+ return mViewToShow == null ? 0 : mViewToShow.getHeight(); |
+ } |
+ |
+ /** |
+ * Returns the height of the View being hidden. |
+ * If there wasn't a View in the container (i.e. the InfoBar is being animated onto the screen), |
+ * then the height is 0. |
+ */ |
+ private int getViewToHideHeight() { |
+ return mViewToHide == null ? 0 : mViewToHide.getHeight(); |
+ } |
+ |
+ /** |
+ * @return the difference in height between the View being shown and the View being hidden. |
+ */ |
+ public int getTransitionHeightDifference() { |
+ return getViewToShowHeight() - getViewToHideHeight(); |
+ } |
+ |
+ /** |
+ * Creates animations for transitioning between the two Views. |
+ * @param animators ArrayList to append the transition Animators to. |
+ */ |
+ public void getAnimationsForTransition(ArrayList<Animator> animators) { |
+ if (mViewToHide != null && mViewToShow != null) { |
+ ObjectAnimator hideAnimator; |
+ hideAnimator = ObjectAnimator.ofFloat((Object)mViewToHide, "alpha", 1.0f, 0.0f); |
+ animators.add(hideAnimator); |
+ |
+ ObjectAnimator showAnimator; |
+ showAnimator = ObjectAnimator.ofFloat((Object)mViewToShow, "alpha", 0.0f, 1.0f); |
+ animators.add(showAnimator); |
+ } |
+ } |
+ |
+ /** |
+ * Calculates a Rect that prevents this ContentWrapperView from overlapping its siblings. |
+ * Because of the way the InfoBarContainer stores its children, Android will cause the InfoBars |
+ * to overlap when a bar is slid towards the top of the screen. This calculates a bounding box |
+ * around this ContentWrapperView that clips the InfoBar to be drawn solely in the space it was |
+ * occupying before being translated anywhere. |
+ * @return the calculated bounding box |
+ */ |
+ public Rect getClippingRect() { |
+ int maxHeight = Math.max(getViewToHideHeight(), getViewToShowHeight()); |
+ return new Rect(getLeft(), getTop(), getRight(), getTop() + maxHeight); |
+ } |
+} |