diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java index 9a4d69fd4e9ae..5878cad2f9a34 100644 --- a/core/java/android/widget/SearchView.java +++ b/core/java/android/widget/SearchView.java @@ -53,7 +53,10 @@ import android.util.TypedValue; import android.view.CollapsibleActionView; import android.view.KeyEvent; import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.TouchDelegate; import android.view.View; +import android.view.ViewConfiguration; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.AdapterView.OnItemClickListener; @@ -111,6 +114,12 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { private final ImageView mVoiceButton; private final View mDropDownAnchor; + private UpdatableTouchDelegate mTouchDelegate; + private Rect mSearchSrcTextViewBounds = new Rect(); + private Rect mSearchSrtTextViewBoundsExpanded = new Rect(); + private int[] mTemp = new int[2]; + private int[] mTemp2 = new int[2]; + /** Icon optionally displayed when the SearchView is collapsed. */ private final ImageView mCollapsedIcon; @@ -801,7 +810,48 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { break; } widthMode = MeasureSpec.EXACTLY; - super.onMeasure(MeasureSpec.makeMeasureSpec(width, widthMode), heightMeasureSpec); + + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); + + switch (heightMode) { + case MeasureSpec.AT_MOST: + case MeasureSpec.UNSPECIFIED: + height = Math.min(getPreferredHeight(), height); + break; + } + heightMode = MeasureSpec.EXACTLY; + + super.onMeasure(MeasureSpec.makeMeasureSpec(width, widthMode), + MeasureSpec.makeMeasureSpec(height, heightMode)); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + + if (changed) { + // Expand mSearchSrcTextView touch target to be the height of the parent in order to + // allow it to be up to 48dp. + getChildBoundsWithinSearchView(mSearchSrcTextView, mSearchSrcTextViewBounds); + mSearchSrtTextViewBoundsExpanded.set( + mSearchSrcTextViewBounds.left, 0, mSearchSrcTextViewBounds.right, bottom - top); + if (mTouchDelegate == null) { + mTouchDelegate = new UpdatableTouchDelegate(mSearchSrtTextViewBoundsExpanded, + mSearchSrcTextViewBounds, mSearchSrcTextView); + setTouchDelegate(mTouchDelegate); + } else { + mTouchDelegate.setBounds(mSearchSrtTextViewBoundsExpanded, mSearchSrcTextViewBounds); + } + } + } + + private void getChildBoundsWithinSearchView(View view, Rect rect) { + view.getLocationInWindow(mTemp); + getLocationInWindow(mTemp2); + final int top = mTemp[1] - mTemp2[1]; + final int left = mTemp[0] - mTemp2[0]; + rect.set(left , top, left + view.getWidth(), top + view.getHeight()); } private int getPreferredWidth() { @@ -809,6 +859,11 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { .getDimensionPixelSize(R.dimen.search_view_preferred_width); } + private int getPreferredHeight() { + return getContext().getResources() + .getDimensionPixelSize(R.dimen.search_view_preferred_height); + } + private void updateViewsVisibility(final boolean collapsed) { mIconified = collapsed; // Visibility of views that are visible when collapsed @@ -1749,6 +1804,101 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { } }; + private static class UpdatableTouchDelegate extends TouchDelegate { + /** + * View that should receive forwarded touch events + */ + private final View mDelegateView; + + /** + * Bounds in local coordinates of the containing view that should be mapped to the delegate + * view. This rect is used for initial hit testing. + */ + private final Rect mTargetBounds; + + /** + * Bounds in local coordinates of the containing view that are actual bounds of the delegate + * view. This rect is used for event coordinate mapping. + */ + private final Rect mActualBounds; + + /** + * mTargetBounds inflated to include some slop. This rect is to track whether the motion events + * should be considered to be be within the delegate view. + */ + private final Rect mSlopBounds; + + private final int mSlop; + + /** + * True if the delegate had been targeted on a down event (intersected mTargetBounds). + */ + private boolean mDelegateTargeted; + + public UpdatableTouchDelegate(Rect targetBounds, Rect actualBounds, View delegateView) { + super(targetBounds, delegateView); + mSlop = ViewConfiguration.get(delegateView.getContext()).getScaledTouchSlop(); + mTargetBounds = new Rect(); + mSlopBounds = new Rect(); + mActualBounds = new Rect(); + setBounds(targetBounds, actualBounds); + mDelegateView = delegateView; + } + + public void setBounds(Rect desiredBounds, Rect actualBounds) { + mTargetBounds.set(desiredBounds); + mSlopBounds.set(desiredBounds); + mSlopBounds.inset(-mSlop, -mSlop); + mActualBounds.set(actualBounds); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + final int x = (int) event.getX(); + final int y = (int) event.getY(); + boolean sendToDelegate = false; + boolean hit = true; + boolean handled = false; + + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + if (mTargetBounds.contains(x, y)) { + mDelegateTargeted = true; + sendToDelegate = true; + } + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_MOVE: + sendToDelegate = mDelegateTargeted; + if (sendToDelegate) { + if (!mSlopBounds.contains(x, y)) { + hit = false; + } + } + break; + case MotionEvent.ACTION_CANCEL: + sendToDelegate = mDelegateTargeted; + mDelegateTargeted = false; + break; + } + if (sendToDelegate) { + if (hit && !mActualBounds.contains(x, y)) { + // Offset event coordinates to be in the center of the target view since we + // are within the targetBounds, but not inside the actual bounds of + // mDelegateView + event.setLocation(mDelegateView.getWidth() / 2, + mDelegateView.getHeight() / 2); + } else { + // Offset event coordinates to the target view coordinates. + event.setLocation(x - mActualBounds.left, y - mActualBounds.top); + } + + handled = mDelegateView.dispatchTouchEvent(event); + } + return handled; + } + } + /** * Local subclass for AutoCompleteTextView. * @hide diff --git a/core/res/res/layout/search_view.xml b/core/res/res/layout/search_view.xml index 718e4a2dd604f..72588c71b7d98 100644 --- a/core/res/res/layout/search_view.xml +++ b/core/res/res/layout/search_view.xml @@ -48,11 +48,8 @@ @@ -81,7 +78,7 @@ android:layout_height="36dip" android:layout_width="0dp" android:layout_weight="1" - android:layout_gravity="bottom" + android:layout_gravity="center_vertical" android:paddingStart="@dimen/dropdownitem_text_padding_left" android:paddingEnd="@dimen/dropdownitem_text_padding_right" android:singleLine="true" diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 8200df875432f..ad7b7cc5662b3 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -184,8 +184,9 @@ 6dp - + 320dip + 48dip 27dip diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index a35b40910f104..60060a3702053 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -423,6 +423,7 @@ +