Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License
     15  */
     16 
     17 package com.android.dialer.widget;
     18 
     19 import android.animation.ValueAnimator;
     20 import android.animation.ValueAnimator.AnimatorUpdateListener;
     21 import android.content.Context;
     22 import android.util.AttributeSet;
     23 import android.view.KeyEvent;
     24 import android.view.View;
     25 import android.widget.EditText;
     26 import android.widget.FrameLayout;
     27 
     28 import com.android.dialer.R;
     29 import com.android.dialer.util.DialerUtils;
     30 import com.android.phone.common.animation.AnimUtils;
     31 
     32 public class SearchEditTextLayout extends FrameLayout {
     33     private static final float EXPAND_MARGIN_FRACTION_START = 0.8f;
     34     private static final int ANIMATION_DURATION = 200;
     35 
     36     private OnKeyListener mPreImeKeyListener;
     37     private int mTopMargin;
     38     private int mBottomMargin;
     39     private int mLeftMargin;
     40     private int mRightMargin;
     41 
     42     private float mCollapsedElevation;
     43 
     44     /* Subclass-visible for testing */
     45     protected boolean mIsExpanded = false;
     46     protected boolean mIsFadedOut = false;
     47 
     48     private View mCollapsed;
     49     private View mExpanded;
     50     private EditText mSearchView;
     51     private View mSearchIcon;
     52     private View mCollapsedSearchBox;
     53     private View mVoiceSearchButtonView;
     54     private View mOverflowButtonView;
     55     private View mBackButtonView;
     56     private View mExpandedSearchBox;
     57     private View mClearButtonView;
     58 
     59     private ValueAnimator mAnimator;
     60 
     61     private OnBackButtonClickedListener mOnBackButtonClickedListener;
     62 
     63     /**
     64      * Listener for the back button next to the search view being pressed
     65      */
     66     public interface OnBackButtonClickedListener {
     67         public void onBackButtonClicked();
     68     }
     69 
     70     public SearchEditTextLayout(Context context, AttributeSet attrs) {
     71         super(context, attrs);
     72     }
     73 
     74     public void setPreImeKeyListener(OnKeyListener listener) {
     75         mPreImeKeyListener = listener;
     76     }
     77 
     78     public void setOnBackButtonClickedListener(OnBackButtonClickedListener listener) {
     79         mOnBackButtonClickedListener = listener;
     80     }
     81 
     82     @Override
     83     protected void onFinishInflate() {
     84         MarginLayoutParams params = (MarginLayoutParams) getLayoutParams();
     85         mTopMargin = params.topMargin;
     86         mBottomMargin = params.bottomMargin;
     87         mLeftMargin = params.leftMargin;
     88         mRightMargin = params.rightMargin;
     89 
     90         mCollapsedElevation = getElevation();
     91 
     92         mCollapsed = findViewById(R.id.search_box_collapsed);
     93         mExpanded = findViewById(R.id.search_box_expanded);
     94         mSearchView = (EditText) mExpanded.findViewById(R.id.search_view);
     95 
     96         mSearchIcon = findViewById(R.id.search_magnifying_glass);
     97         mCollapsedSearchBox = findViewById(R.id.search_box_start_search);
     98         mVoiceSearchButtonView = findViewById(R.id.voice_search_button);
     99         mOverflowButtonView = findViewById(R.id.dialtacts_options_menu_button);
    100         mBackButtonView = findViewById(R.id.search_back_button);
    101         mExpandedSearchBox = findViewById(R.id.search_box_expanded);
    102         mClearButtonView = findViewById(R.id.search_close_button);
    103 
    104         mSearchView.setOnFocusChangeListener(new OnFocusChangeListener() {
    105             @Override
    106             public void onFocusChange(View v, boolean hasFocus) {
    107                 if (hasFocus) {
    108                     DialerUtils.showInputMethod(v);
    109                 } else {
    110                     DialerUtils.hideInputMethod(v);
    111                 }
    112             }
    113         });
    114 
    115         findViewById(R.id.search_close_button).setOnClickListener(new OnClickListener() {
    116             @Override
    117             public void onClick(View v) {
    118                 mSearchView.setText(null);
    119             }
    120         });
    121 
    122         findViewById(R.id.search_back_button).setOnClickListener(new OnClickListener() {
    123             @Override
    124             public void onClick(View v) {
    125                 if (mOnBackButtonClickedListener != null) {
    126                     mOnBackButtonClickedListener.onBackButtonClicked();
    127                 }
    128             }
    129         });
    130 
    131         super.onFinishInflate();
    132     }
    133 
    134     @Override
    135     public boolean dispatchKeyEventPreIme(KeyEvent event) {
    136         if (mPreImeKeyListener != null) {
    137             if (mPreImeKeyListener.onKey(this, event.getKeyCode(), event)) {
    138                 return true;
    139             }
    140         }
    141         return super.dispatchKeyEventPreIme(event);
    142     }
    143 
    144     public void fadeOut() {
    145         fadeOut(null);
    146     }
    147 
    148     public void fadeOut(AnimUtils.AnimationCallback callback) {
    149         AnimUtils.fadeOut(this, ANIMATION_DURATION, callback);
    150         mIsFadedOut = true;
    151     }
    152 
    153     public void fadeIn() {
    154         AnimUtils.fadeIn(this, ANIMATION_DURATION);
    155         mIsFadedOut = false;
    156     }
    157 
    158     public void setVisible(boolean visible) {
    159         if (visible) {
    160             setAlpha(1);
    161             setVisibility(View.VISIBLE);
    162             mIsFadedOut = false;
    163         } else {
    164             setAlpha(0);
    165             setVisibility(View.GONE);
    166             mIsFadedOut = true;
    167         }
    168     }
    169     public void expand(boolean animate, boolean requestFocus) {
    170         updateVisibility(true /* isExpand */);
    171 
    172         if (animate) {
    173             AnimUtils.crossFadeViews(mExpanded, mCollapsed, ANIMATION_DURATION);
    174             mAnimator = ValueAnimator.ofFloat(EXPAND_MARGIN_FRACTION_START, 0f);
    175             setMargins(EXPAND_MARGIN_FRACTION_START);
    176             prepareAnimator(true);
    177         } else {
    178             mExpanded.setVisibility(View.VISIBLE);
    179             mExpanded.setAlpha(1);
    180             setMargins(0f);
    181             mCollapsed.setVisibility(View.GONE);
    182         }
    183 
    184         // Set 9-patch background. This owns the padding, so we need to restore the original values.
    185         int paddingTop = this.getPaddingTop();
    186         int paddingStart = this.getPaddingStart();
    187         int paddingBottom = this.getPaddingBottom();
    188         int paddingEnd = this.getPaddingEnd();
    189         setBackgroundResource(R.drawable.search_shadow);
    190         setElevation(0);
    191         setPaddingRelative(paddingStart, paddingTop, paddingEnd, paddingBottom);
    192 
    193         setElevation(0);
    194         if (requestFocus) {
    195             mSearchView.requestFocus();
    196         }
    197         mIsExpanded = true;
    198     }
    199 
    200     public void collapse(boolean animate) {
    201         updateVisibility(false /* isExpand */);
    202 
    203         if (animate) {
    204             AnimUtils.crossFadeViews(mCollapsed, mExpanded, ANIMATION_DURATION);
    205             mAnimator = ValueAnimator.ofFloat(0f, 1f);
    206             prepareAnimator(false);
    207         } else {
    208             mCollapsed.setVisibility(View.VISIBLE);
    209             mCollapsed.setAlpha(1);
    210             setMargins(1f);
    211             mExpanded.setVisibility(View.GONE);
    212         }
    213 
    214         mIsExpanded = false;
    215         setElevation(mCollapsedElevation);
    216         setBackgroundResource(R.drawable.rounded_corner);
    217     }
    218 
    219     /**
    220      * Updates the visibility of views depending on whether we will show the expanded or collapsed
    221      * search view. This helps prevent some jank with the crossfading if we are animating.
    222      *
    223      * @param isExpand Whether we are about to show the expanded search box.
    224      */
    225     private void updateVisibility(boolean isExpand) {
    226         int collapsedViewVisibility = isExpand ? View.GONE : View.VISIBLE;
    227         int expandedViewVisibility = isExpand ? View.VISIBLE : View.GONE;
    228 
    229         mSearchIcon.setVisibility(collapsedViewVisibility);
    230         mCollapsedSearchBox.setVisibility(collapsedViewVisibility);
    231         mVoiceSearchButtonView.setVisibility(collapsedViewVisibility);
    232         mOverflowButtonView.setVisibility(collapsedViewVisibility);
    233         mBackButtonView.setVisibility(expandedViewVisibility);
    234         // TODO: Prevents keyboard from jumping up in landscape mode after exiting the
    235         // SearchFragment when the query string is empty. More elegant fix?
    236         //mExpandedSearchBox.setVisibility(expandedViewVisibility);
    237         mClearButtonView.setVisibility(expandedViewVisibility);
    238     }
    239 
    240     private void prepareAnimator(final boolean expand) {
    241         if (mAnimator != null) {
    242             mAnimator.cancel();
    243         }
    244 
    245         mAnimator.addUpdateListener(new AnimatorUpdateListener() {
    246             @Override
    247             public void onAnimationUpdate(ValueAnimator animation) {
    248                 final Float fraction = (Float) animation.getAnimatedValue();
    249                 setMargins(fraction);
    250             }
    251         });
    252 
    253         mAnimator.setDuration(ANIMATION_DURATION);
    254         mAnimator.start();
    255     }
    256 
    257     public boolean isExpanded() {
    258         return mIsExpanded;
    259     }
    260 
    261     public boolean isFadedOut() {
    262         return mIsFadedOut;
    263     }
    264 
    265     /**
    266      * Assigns margins to the search box as a fraction of its maximum margin size
    267      *
    268      * @param fraction How large the margins should be as a fraction of their full size
    269      */
    270     private void setMargins(float fraction) {
    271         MarginLayoutParams params = (MarginLayoutParams) getLayoutParams();
    272         params.topMargin = (int) (mTopMargin * fraction);
    273         params.bottomMargin = (int) (mBottomMargin * fraction);
    274         params.leftMargin = (int) (mLeftMargin * fraction);
    275         params.rightMargin = (int) (mRightMargin * fraction);
    276         requestLayout();
    277     }
    278 }
    279