Home | History | Annotate | Download | only in infobar
      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