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