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 // Convert a long click into a click to expand the search box, and then long click on the 105 // search view. This accelerates the long-press scenario for copy/paste. 106 mCollapsedSearchBox.setOnLongClickListener(new OnLongClickListener() { 107 @Override 108 public boolean onLongClick(View view) { 109 mCollapsedSearchBox.performClick(); 110 mSearchView.performLongClick(); 111 return false; 112 } 113 }); 114 115 mSearchView.setOnFocusChangeListener(new OnFocusChangeListener() { 116 @Override 117 public void onFocusChange(View v, boolean hasFocus) { 118 if (hasFocus) { 119 DialerUtils.showInputMethod(v); 120 } else { 121 DialerUtils.hideInputMethod(v); 122 } 123 } 124 }); 125 126 findViewById(R.id.search_close_button).setOnClickListener(new OnClickListener() { 127 @Override 128 public void onClick(View v) { 129 mSearchView.setText(null); 130 } 131 }); 132 133 findViewById(R.id.search_back_button).setOnClickListener(new OnClickListener() { 134 @Override 135 public void onClick(View v) { 136 if (mOnBackButtonClickedListener != null) { 137 mOnBackButtonClickedListener.onBackButtonClicked(); 138 } 139 } 140 }); 141 142 super.onFinishInflate(); 143 } 144 145 @Override dispatchKeyEventPreIme(KeyEvent event)146 public boolean dispatchKeyEventPreIme(KeyEvent event) { 147 if (mPreImeKeyListener != null) { 148 if (mPreImeKeyListener.onKey(this, event.getKeyCode(), event)) { 149 return true; 150 } 151 } 152 return super.dispatchKeyEventPreIme(event); 153 } 154 fadeOut()155 public void fadeOut() { 156 fadeOut(null); 157 } 158 fadeOut(AnimUtils.AnimationCallback callback)159 public void fadeOut(AnimUtils.AnimationCallback callback) { 160 AnimUtils.fadeOut(this, ANIMATION_DURATION, callback); 161 mIsFadedOut = true; 162 } 163 fadeIn()164 public void fadeIn() { 165 AnimUtils.fadeIn(this, ANIMATION_DURATION); 166 mIsFadedOut = false; 167 } 168 setVisible(boolean visible)169 public void setVisible(boolean visible) { 170 if (visible) { 171 setAlpha(1); 172 setVisibility(View.VISIBLE); 173 mIsFadedOut = false; 174 } else { 175 setAlpha(0); 176 setVisibility(View.GONE); 177 mIsFadedOut = true; 178 } 179 } expand(boolean animate, boolean requestFocus)180 public void expand(boolean animate, boolean requestFocus) { 181 updateVisibility(true /* isExpand */); 182 183 if (animate) { 184 AnimUtils.crossFadeViews(mExpanded, mCollapsed, ANIMATION_DURATION); 185 mAnimator = ValueAnimator.ofFloat(EXPAND_MARGIN_FRACTION_START, 0f); 186 setMargins(EXPAND_MARGIN_FRACTION_START); 187 prepareAnimator(true); 188 } else { 189 mExpanded.setVisibility(View.VISIBLE); 190 mExpanded.setAlpha(1); 191 setMargins(0f); 192 mCollapsed.setVisibility(View.GONE); 193 } 194 195 // Set 9-patch background. This owns the padding, so we need to restore the original values. 196 int paddingTop = this.getPaddingTop(); 197 int paddingStart = this.getPaddingStart(); 198 int paddingBottom = this.getPaddingBottom(); 199 int paddingEnd = this.getPaddingEnd(); 200 setBackgroundResource(R.drawable.search_shadow); 201 setElevation(0); 202 setPaddingRelative(paddingStart, paddingTop, paddingEnd, paddingBottom); 203 204 setElevation(0); 205 if (requestFocus) { 206 mSearchView.requestFocus(); 207 } 208 mIsExpanded = true; 209 } 210 collapse(boolean animate)211 public void collapse(boolean animate) { 212 updateVisibility(false /* isExpand */); 213 214 if (animate) { 215 AnimUtils.crossFadeViews(mCollapsed, mExpanded, ANIMATION_DURATION); 216 mAnimator = ValueAnimator.ofFloat(0f, 1f); 217 prepareAnimator(false); 218 } else { 219 mCollapsed.setVisibility(View.VISIBLE); 220 mCollapsed.setAlpha(1); 221 setMargins(1f); 222 mExpanded.setVisibility(View.GONE); 223 } 224 225 mIsExpanded = false; 226 setElevation(mCollapsedElevation); 227 setBackgroundResource(R.drawable.rounded_corner); 228 } 229 230 /** 231 * Updates the visibility of views depending on whether we will show the expanded or collapsed 232 * search view. This helps prevent some jank with the crossfading if we are animating. 233 * 234 * @param isExpand Whether we are about to show the expanded search box. 235 */ updateVisibility(boolean isExpand)236 private void updateVisibility(boolean isExpand) { 237 int collapsedViewVisibility = isExpand ? View.GONE : View.VISIBLE; 238 int expandedViewVisibility = isExpand ? View.VISIBLE : View.GONE; 239 240 mSearchIcon.setVisibility(collapsedViewVisibility); 241 mCollapsedSearchBox.setVisibility(collapsedViewVisibility); 242 mVoiceSearchButtonView.setVisibility(collapsedViewVisibility); 243 mOverflowButtonView.setVisibility(collapsedViewVisibility); 244 mBackButtonView.setVisibility(expandedViewVisibility); 245 // TODO: Prevents keyboard from jumping up in landscape mode after exiting the 246 // SearchFragment when the query string is empty. More elegant fix? 247 //mExpandedSearchBox.setVisibility(expandedViewVisibility); 248 mClearButtonView.setVisibility(expandedViewVisibility); 249 } 250 prepareAnimator(final boolean expand)251 private void prepareAnimator(final boolean expand) { 252 if (mAnimator != null) { 253 mAnimator.cancel(); 254 } 255 256 mAnimator.addUpdateListener(new AnimatorUpdateListener() { 257 @Override 258 public void onAnimationUpdate(ValueAnimator animation) { 259 final Float fraction = (Float) animation.getAnimatedValue(); 260 setMargins(fraction); 261 } 262 }); 263 264 mAnimator.setDuration(ANIMATION_DURATION); 265 mAnimator.start(); 266 } 267 isExpanded()268 public boolean isExpanded() { 269 return mIsExpanded; 270 } 271 isFadedOut()272 public boolean isFadedOut() { 273 return mIsFadedOut; 274 } 275 276 /** 277 * Assigns margins to the search box as a fraction of its maximum margin size 278 * 279 * @param fraction How large the margins should be as a fraction of their full size 280 */ setMargins(float fraction)281 private void setMargins(float fraction) { 282 MarginLayoutParams params = (MarginLayoutParams) getLayoutParams(); 283 params.topMargin = (int) (mTopMargin * fraction); 284 params.bottomMargin = (int) (mBottomMargin * fraction); 285 params.leftMargin = (int) (mLeftMargin * fraction); 286 params.rightMargin = (int) (mRightMargin * fraction); 287 requestLayout(); 288 } 289 } 290