• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
ContentWrapperView(Context context, InfoBar infoBar, View panel)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
onInterceptTouchEvent(MotionEvent ev)71     public boolean onInterceptTouchEvent(MotionEvent ev) {
72         return !mInfoBar.areControlsEnabled();
73     }
74 
75     @Override
onTouchEvent(MotionEvent event)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      */
getBoundaryHeight(Context context)86     private 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      */
hasChildView()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      */
detachCurrentView()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      */
addChildView(View viewToAdd)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      */
prepareTransition(View viewToShow)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      */
startTransition()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      */
finishTransition()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      */
getViewToShowHeight()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      */
getViewToHideHeight()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      */
getTransitionHeightDifference()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      */
getAnimationsForTransition(ArrayList<Animator> animators)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      */
getClippingRect()221     public Rect getClippingRect() {
222         int maxHeight = Math.max(getViewToHideHeight(), getViewToShowHeight());
223         return new Rect(getLeft(), getTop(), getRight(), getTop() + maxHeight);
224     }
225 }
226