• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
onBackButtonClicked()67         public void onBackButtonClicked();
68     }
69 
SearchEditTextLayout(Context context, AttributeSet attrs)70     public SearchEditTextLayout(Context context, AttributeSet attrs) {
71         super(context, attrs);
72     }
73 
setPreImeKeyListener(OnKeyListener listener)74     public void setPreImeKeyListener(OnKeyListener listener) {
75         mPreImeKeyListener = listener;
76     }
77 
setOnBackButtonClickedListener(OnBackButtonClickedListener listener)78     public void setOnBackButtonClickedListener(OnBackButtonClickedListener listener) {
79         mOnBackButtonClickedListener = listener;
80     }
81 
82     @Override
onFinishInflate()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
dispatchKeyEventPreIme(KeyEvent event)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 
fadeOut()144     public void fadeOut() {
145         fadeOut(null);
146     }
147 
fadeOut(AnimUtils.AnimationCallback callback)148     public void fadeOut(AnimUtils.AnimationCallback callback) {
149         AnimUtils.fadeOut(this, ANIMATION_DURATION, callback);
150         mIsFadedOut = true;
151     }
152 
fadeIn()153     public void fadeIn() {
154         AnimUtils.fadeIn(this, ANIMATION_DURATION);
155         mIsFadedOut = false;
156     }
157 
setVisible(boolean visible)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     }
expand(boolean animate, boolean requestFocus)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 
collapse(boolean animate)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      */
updateVisibility(boolean isExpand)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 
prepareAnimator(final boolean expand)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 
isExpanded()257     public boolean isExpanded() {
258         return mIsExpanded;
259     }
260 
isFadedOut()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      */
setMargins(float fraction)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