1 // Copyright 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.chrome.browser.infobar; 6 7 import android.animation.Animator; 8 import android.animation.ObjectAnimator; 9 import android.content.Context; 10 import android.content.res.Resources; 11 import android.graphics.Rect; 12 import android.view.Gravity; 13 import android.view.MotionEvent; 14 import android.view.View; 15 import android.view.ViewGroup; 16 import android.view.ViewParent; 17 import android.widget.FrameLayout; 18 19 import org.chromium.chrome.R; 20 21 import java.util.ArrayList; 22 23 /** 24 * A wrapper class designed to: 25 * - consume all touch events. This way the parent view (the FrameLayout ContentView) won't 26 * have its onTouchEvent called. If it does, ContentView will process the touch click. 27 * We don't want web content responding to clicks on the InfoBars. 28 * - allow swapping out of children Views for animations. 29 * 30 * Once an InfoBar has been hidden and removed from the InfoBarContainer, it cannot be reused 31 * because the main panel is discarded after the hiding animation. 32 */ 33 public class ContentWrapperView extends FrameLayout { 34 // Index of the child View that will get swapped out during transitions. 35 private static final int CONTENT_INDEX = 0; 36 37 private final int mGravity; 38 private final InfoBar mInfoBar; 39 40 private View mViewToHide; 41 private View mViewToShow; 42 43 /** 44 * Constructs a ContentWrapperView object. 45 * @param context The context to create this View with. 46 */ 47 public ContentWrapperView(Context context, InfoBar infoBar, View panel) { 48 // Set up this ViewGroup. 49 super(context); 50 mInfoBar = infoBar; 51 mGravity = Gravity.TOP; 52 53 // Set up this view. 54 Resources resources = context.getResources(); 55 LayoutParams wrapParams = new LayoutParams(LayoutParams.MATCH_PARENT, 56 LayoutParams.WRAP_CONTENT); 57 setLayoutParams(wrapParams); 58 setBackgroundColor(resources.getColor(R.color.infobar_background)); 59 60 // Add a separator line that delineates different InfoBars. 61 View separator = new View(context); 62 separator.setBackgroundColor(resources.getColor(R.color.infobar_background_separator)); 63 addView(separator, new LayoutParams(LayoutParams.MATCH_PARENT, getBoundaryHeight(context), 64 mGravity)); 65 66 // Add the InfoBar content. 67 addChildView(panel); 68 } 69 70 @Override 71 public boolean onInterceptTouchEvent(MotionEvent ev) { 72 return !mInfoBar.areControlsEnabled(); 73 } 74 75 @Override 76 public boolean onTouchEvent(MotionEvent event) { 77 // Consume all motion events so they do not reach the ContentView. 78 return true; 79 } 80 81 /** 82 * Calculates how tall the InfoBar boundary should be in pixels. 83 * XHDPI devices and above get a double-tall boundary. 84 * @return The height of the boundary. 85 */ 86 static int getBoundaryHeight(Context context) { 87 float density = context.getResources().getDisplayMetrics().density; 88 return density < 2.0f ? 1 : 2; 89 } 90 91 /** 92 * @return the current View representing the InfoBar. 93 */ 94 public boolean hasChildView() { 95 // If there's a View that can be replaced, there will be at least two children for the View. 96 // One of the Views will always be the InfoBar separator. 97 return getChildCount() > 1; 98 } 99 100 /** 101 * Detaches the View currently being shown and returns it for reparenting. 102 * @return the View that is currently being shown. 103 */ 104 public View detachCurrentView() { 105 assert getChildCount() > 1; 106 View view = getChildAt(CONTENT_INDEX); 107 removeView(view); 108 return view; 109 } 110 111 /** 112 * Adds a View to this layout, before the InfoBar separator. 113 * @param viewToAdd The View to add. 114 */ 115 private void addChildView(View viewToAdd) { 116 addView(viewToAdd, CONTENT_INDEX, new FrameLayout.LayoutParams( 117 LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT, mGravity)); 118 } 119 120 /** 121 * Prepares the animation needed to hide the current View and show the new one. 122 * @param viewToShow View that will replace the currently shown child of this FrameLayout. 123 */ 124 public void prepareTransition(View viewToShow) { 125 assert mViewToHide == null && mViewToShow == null; 126 127 // If it exists, the View that is being replaced will be the non-separator child and will 128 // we in the second position. 129 assert getChildCount() <= 2; 130 if (hasChildView()) { 131 mViewToHide = getChildAt(CONTENT_INDEX); 132 } 133 134 mViewToShow = viewToShow; 135 assert mViewToHide != null || mViewToShow != null; 136 assert mViewToHide != mViewToShow; 137 } 138 139 /** 140 * Called when the animation is starting. 141 */ 142 public void startTransition() { 143 if (mViewToShow != null) { 144 // Move the View to this container. 145 ViewParent parent = mViewToShow.getParent(); 146 assert parent != null && parent instanceof ViewGroup; 147 ((ViewGroup) parent).removeView(mViewToShow); 148 addChildView(mViewToShow); 149 150 // We're transitioning between two views; set the alpha so it doesn't pop in. 151 if (mViewToHide != null) mViewToShow.setAlpha(0.0f); 152 } 153 } 154 155 /** 156 * Called when the animation is done. 157 * At this point, we can get rid of the View that used to represent the InfoBar and re-enable 158 * controls. 159 */ 160 public void finishTransition() { 161 if (mViewToHide != null) { 162 removeView(mViewToHide); 163 } 164 getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT; 165 requestLayout(); 166 167 mViewToHide = null; 168 mViewToShow = null; 169 mInfoBar.setControlsEnabled(true); 170 } 171 172 /** 173 * Returns the height of the View being shown. 174 * If no new View is going to replace the current one (i.e. the InfoBar is being hidden), the 175 * height is 0. 176 */ 177 private int getViewToShowHeight() { 178 return mViewToShow == null ? 0 : mViewToShow.getHeight(); 179 } 180 181 /** 182 * Returns the height of the View being hidden. 183 * If there wasn't a View in the container (i.e. the InfoBar is being animated onto the screen), 184 * then the height is 0. 185 */ 186 private int getViewToHideHeight() { 187 return mViewToHide == null ? 0 : mViewToHide.getHeight(); 188 } 189 190 /** 191 * @return the difference in height between the View being shown and the View being hidden. 192 */ 193 public int getTransitionHeightDifference() { 194 return getViewToShowHeight() - getViewToHideHeight(); 195 } 196 197 /** 198 * Creates animations for transitioning between the two Views. 199 * @param animators ArrayList to append the transition Animators to. 200 */ 201 public void getAnimationsForTransition(ArrayList<Animator> animators) { 202 if (mViewToHide != null && mViewToShow != null) { 203 ObjectAnimator hideAnimator; 204 hideAnimator = ObjectAnimator.ofFloat(mViewToHide, "alpha", 1.0f, 0.0f); 205 animators.add(hideAnimator); 206 207 ObjectAnimator showAnimator; 208 showAnimator = ObjectAnimator.ofFloat(mViewToShow, "alpha", 0.0f, 1.0f); 209 animators.add(showAnimator); 210 } 211 } 212 213 /** 214 * Calculates a Rect that prevents this ContentWrapperView from overlapping its siblings. 215 * Because of the way the InfoBarContainer stores its children, Android will cause the InfoBars 216 * to overlap when a bar is slid towards the top of the screen. This calculates a bounding box 217 * around this ContentWrapperView that clips the InfoBar to be drawn solely in the space it was 218 * occupying before being translated anywhere. 219 * @return the calculated bounding box 220 */ 221 public Rect getClippingRect() { 222 int maxHeight = Math.max(getViewToHideHeight(), getViewToShowHeight()); 223 return new Rect(getLeft(), getTop(), getRight(), getTop() + maxHeight); 224 } 225 } 226