Defer loading SearchView until needed
And:
- Don't load Search/Settings assets on all MenuItems
- Don't restartInput() every time the keyboard is shown.
Bug: 146228321
Test: Manually
Change-Id: Iff1ce8f22677a9026dfa8fe821dd2b74b8be4fbf
diff --git a/car-ui-lib/res/layout/car_ui_toolbar.xml b/car-ui-lib/res/layout/car_ui_toolbar.xml
index 2d54431..dc5e997 100644
--- a/car-ui-lib/res/layout/car_ui_toolbar.xml
+++ b/car-ui-lib/res/layout/car_ui_toolbar.xml
@@ -135,11 +135,10 @@
app:layout_constraintBottom_toTopOf="@id/car_ui_toolbar_bottom_guideline"
app:layout_constraintEnd_toStartOf="@+id/car_ui_toolbar_end_guideline"/>
- <com.android.car.ui.toolbar.SearchView
- android:id="@+id/car_ui_toolbar_search_view"
+ <FrameLayout
+ android:id="@+id/car_ui_toolbar_search_view_container"
android:layout_width="0dp"
android:layout_height="@dimen/car_ui_toolbar_search_height"
- android:visibility="gone"
app:layout_constraintTop_toTopOf="@id/car_ui_toolbar_top_guideline"
app:layout_constraintBottom_toTopOf="@id/car_ui_toolbar_bottom_guideline"
app:layout_constraintStart_toEndOf="@+id/car_ui_toolbar_nav_icon_container"
diff --git a/car-ui-lib/res/layout/car_ui_toolbar_two_row.xml b/car-ui-lib/res/layout/car_ui_toolbar_two_row.xml
index bc334bf..64f3ece 100644
--- a/car-ui-lib/res/layout/car_ui_toolbar_two_row.xml
+++ b/car-ui-lib/res/layout/car_ui_toolbar_two_row.xml
@@ -128,15 +128,14 @@
app:layout_constraintStart_toEndOf="@id/car_ui_toolbar_title_logo_container"
app:layout_constraintEnd_toStartOf="@id/car_ui_toolbar_menu_items_container"/>
- <com.android.car.ui.toolbar.SearchView
- android:id="@+id/car_ui_toolbar_search_view"
+ <FrameLayout
+ android:id="@+id/car_ui_toolbar_search_view_container"
android:layout_width="0dp"
android:layout_height="@dimen/car_ui_toolbar_search_height"
- android:visibility="gone"
app:layout_constraintTop_toTopOf="@id/car_ui_toolbar_top_guideline"
app:layout_constraintBottom_toTopOf="@id/car_ui_toolbar_row_separator"
- app:layout_constraintStart_toEndOf="@id/car_ui_toolbar_nav_icon_container"
- app:layout_constraintEnd_toStartOf="@id/car_ui_toolbar_menu_items_container"/>
+ app:layout_constraintStart_toEndOf="@+id/car_ui_toolbar_nav_icon_container"
+ app:layout_constraintEnd_toStartOf="@+id/car_ui_toolbar_menu_items_container"/>
<LinearLayout
android:id="@+id/car_ui_toolbar_menu_items_container"
diff --git a/car-ui-lib/src/com/android/car/ui/toolbar/MenuItem.java b/car-ui-lib/src/com/android/car/ui/toolbar/MenuItem.java
index 45f34ec..ef142ec 100644
--- a/car-ui-lib/src/com/android/car/ui/toolbar/MenuItem.java
+++ b/car-ui-lib/src/com/android/car/ui/toolbar/MenuItem.java
@@ -308,10 +308,11 @@
/** Builder class */
public static final class Builder {
private final Context mContext;
- private final CharSequence mSearchTitle;
- private final CharSequence mSettingsTitle;
- private final Drawable mSearchIcon;
- private final Drawable mSettingsIcon;
+
+ private CharSequence mSearchTitle;
+ private CharSequence mSettingsTitle;
+ private Drawable mSearchIcon;
+ private Drawable mSettingsIcon;
private int mId = View.NO_ID;
private CharSequence mTitle;
@@ -335,10 +336,6 @@
// Must use getApplicationContext to avoid leaking activities when the MenuItem
// is held onto for longer than the Activity's lifecycle
mContext = c.getApplicationContext();
- mSearchTitle = mContext.getString(R.string.car_ui_toolbar_menu_item_search_title);
- mSettingsTitle = mContext.getString(R.string.car_ui_toolbar_menu_item_settings_title);
- mSearchIcon = mContext.getDrawable(R.drawable.car_ui_icon_search);
- mSettingsIcon = mContext.getDrawable(R.drawable.car_ui_icon_settings);
}
/** Builds a {@link MenuItem} from the current state of the Builder */
@@ -531,6 +528,8 @@
* <p>If using this, you should only change the id, visibility, or onClickListener.
*/
public Builder setToSearch() {
+ mSearchTitle = mContext.getString(R.string.car_ui_toolbar_menu_item_search_title);
+ mSearchIcon = mContext.getDrawable(R.drawable.car_ui_icon_search);
mIsSearch = true;
setTitle(mSearchTitle);
setIcon(mSearchIcon);
@@ -547,6 +546,8 @@
* <p>If using this, you should only change the id, visibility, or onClickListener.
*/
public Builder setToSettings() {
+ mSettingsTitle = mContext.getString(R.string.car_ui_toolbar_menu_item_settings_title);
+ mSettingsIcon = mContext.getDrawable(R.drawable.car_ui_icon_settings);
mIsSettings = true;
setTitle(mSettingsTitle);
setIcon(mSettingsIcon);
diff --git a/car-ui-lib/src/com/android/car/ui/toolbar/SearchView.java b/car-ui-lib/src/com/android/car/ui/toolbar/SearchView.java
index 54048c6..d7240eb 100644
--- a/car-ui-lib/src/com/android/car/ui/toolbar/SearchView.java
+++ b/car-ui-lib/src/com/android/car/ui/toolbar/SearchView.java
@@ -32,22 +32,23 @@
import com.android.car.ui.R;
-import java.util.HashSet;
+import java.util.Collections;
import java.util.Set;
/**
* A search view used by {@link Toolbar}.
*/
public class SearchView extends ConstraintLayout {
+ private final InputMethodManager mInputMethodManager;
private final ImageView mIcon;
private final EditText mSearchText;
private final View mCloseIcon;
private final int mStartPaddingWithoutIcon;
private final int mStartPadding;
private final int mEndPadding;
- private final Set<Toolbar.OnSearchListener> mSearchListeners = new HashSet<>();
- private final Set<Toolbar.OnSearchCompletedListener> mSearchCompletedListeners =
- new HashSet<>();
+ private Set<Toolbar.OnSearchListener> mSearchListeners = Collections.emptySet();
+ private Set<Toolbar.OnSearchCompletedListener> mSearchCompletedListeners =
+ Collections.emptySet();
private final TextWatcher mTextWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
@@ -76,6 +77,9 @@
public SearchView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
+ mInputMethodManager = (InputMethodManager)
+ getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+
LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.car_ui_toolbar_search_view, this, true);
@@ -83,6 +87,7 @@
mIcon = requireViewById(R.id.car_ui_toolbar_search_icon);
mCloseIcon = requireViewById(R.id.car_ui_toolbar_search_close);
mCloseIcon.setOnClickListener(view -> mSearchText.getText().clear());
+ mCloseIcon.setVisibility(View.GONE);
mStartPaddingWithoutIcon = mSearchText.getPaddingStart();
mStartPadding = context.getResources().getDimensionPixelSize(
@@ -94,13 +99,10 @@
mSearchText.setOnFocusChangeListener(
(view, hasFocus) -> {
- InputMethodManager manager = ((InputMethodManager)
- context.getSystemService(Context.INPUT_METHOD_SERVICE));
if (hasFocus) {
- manager.restartInput(view); // needed to detect changes to imeOptions
- manager.showSoftInput(view, 0);
+ mInputMethodManager.showSoftInput(view, 0);
} else {
- manager.hideSoftInputFromWindow(view.getWindowToken(), 0);
+ mInputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
});
@@ -138,32 +140,16 @@
* Adds a listener for the search text changing.
* See also {@link #unregisterOnSearchListener(Toolbar.OnSearchListener)}
*/
- public void registerOnSearchListener(Toolbar.OnSearchListener listener) {
- mSearchListeners.add(listener);
+ public void setSearchListeners(Set<Toolbar.OnSearchListener> listeners) {
+ mSearchListeners = listeners;
}
/**
* Removes a search listener.
* See also {@link #registerOnSearchListener(Toolbar.OnSearchListener)}
*/
- public boolean unregisterOnSearchListener(Toolbar.OnSearchListener listener) {
- return mSearchListeners.remove(listener);
- }
-
- /**
- * Adds a search completed listener
- * See also {@link #registerOnSearchCompletedListener(Toolbar.OnSearchCompletedListener)}
- */
- public void registerOnSearchCompletedListener(Toolbar.OnSearchCompletedListener listener) {
- mSearchCompletedListeners.add(listener);
- }
-
- /**
- * Removes a search completed listener.
- * See also {@link #unregisterOnSearchCompletedListener(Toolbar.OnSearchCompletedListener)}
- */
- public boolean unregisterOnSearchCompletedListener(Toolbar.OnSearchCompletedListener listener) {
- return mSearchCompletedListeners.remove(listener);
+ public void setSearchCompletedListeners(Set<Toolbar.OnSearchCompletedListener> listeners) {
+ mSearchCompletedListeners = listeners;
}
/**
@@ -227,6 +213,9 @@
mIcon.setVisibility(View.VISIBLE);
}
mIsPlainText = plainText;
+
+ // Needed to detect changes to imeOptions
+ mInputMethodManager.restartInput(mSearchText);
}
}
diff --git a/car-ui-lib/src/com/android/car/ui/toolbar/Toolbar.java b/car-ui-lib/src/com/android/car/ui/toolbar/Toolbar.java
index 32b2ae1..d4b1f5d 100644
--- a/car-ui-lib/src/com/android/car/ui/toolbar/Toolbar.java
+++ b/car-ui-lib/src/com/android/car/ui/toolbar/Toolbar.java
@@ -49,6 +49,7 @@
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
/**
@@ -144,11 +145,21 @@
private ViewGroup mTitleLogoContainer;
private TabLayout mTabLayout;
private LinearLayout mMenuItemsContainer;
- private final MenuItem mOverflowButton;
+ private FrameLayout mSearchViewContainer;
+ private SearchView mSearchView;
+
+ // Cached values that we will send to views when they are inflated
+ private CharSequence mSearchHint;
+ private Drawable mSearchIcon;
+ private String mSearchQuery;
+ private final Set<OnSearchListener> mOnSearchListeners = new HashSet<>();
+ private final Set<OnSearchCompletedListener> mOnSearchCompletedListeners = new HashSet<>();
+
private final Set<OnBackListener> mOnBackListeners = new HashSet<>();
private final Set<OnTabSelectedListener> mOnTabSelectedListeners = new HashSet<>();
private final Set<OnHeightChangedListener> mOnHeightChangedListeners = new HashSet<>();
- private SearchView mSearchView;
+
+ private final MenuItem mOverflowButton;
private boolean mHasLogo = false;
private boolean mShowMenuItemsWhileSearching;
private State mState = State.HOME;
@@ -224,7 +235,7 @@
mTitle = requireViewById(R.id.car_ui_toolbar_title);
mTitleLogoContainer = requireViewById(R.id.car_ui_toolbar_title_logo_container);
mTitleLogo = requireViewById(R.id.car_ui_toolbar_title_logo);
- mSearchView = requireViewById(R.id.car_ui_toolbar_search_view);
+ mSearchViewContainer = requireViewById(R.id.car_ui_toolbar_search_view_container);
mProgressBar = requireViewById(R.id.car_ui_toolbar_progress_bar);
mTitle.setText(a.getString(R.styleable.CarUiToolbar_title));
@@ -511,18 +522,23 @@
}
/** Sets the hint for the search bar. */
- public void setSearchHint(int resId) {
- mSearchView.setHint(resId);
+ public void setSearchHint(@StringRes int resId) {
+ setSearchHint(getContext().getString(resId));
}
/** Sets the hint for the search bar. */
public void setSearchHint(CharSequence hint) {
- mSearchView.setHint(hint);
+ if (!Objects.equals(hint, mSearchHint)) {
+ mSearchHint = hint;
+ if (mSearchView != null) {
+ mSearchView.setHint(mSearchHint);
+ }
+ }
}
/** Gets the search hint */
public CharSequence getSearchHint() {
- return mSearchView.getHint();
+ return mSearchHint;
}
/**
@@ -531,8 +547,8 @@
* <p>The icon will be lost on configuration change, make sure to set it in onCreate() or
* a similar place.
*/
- public void setSearchIcon(int resId) {
- mSearchView.setIcon(resId);
+ public void setSearchIcon(@DrawableRes int resId) {
+ setSearchIcon(getContext().getDrawable(resId));
}
/**
@@ -542,7 +558,12 @@
* a similar place.
*/
public void setSearchIcon(Drawable d) {
- mSearchView.setIcon(d);
+ if (!Objects.equals(d, mSearchIcon)) {
+ mSearchIcon = d;
+ if (mSearchView != null) {
+ mSearchView.setIcon(mSearchIcon);
+ }
+ }
}
/**
@@ -748,7 +769,16 @@
* Sets the search query.
*/
public void setSearchQuery(String query) {
- mSearchView.setSearchQuery(query);
+ if (!Objects.equals(mSearchQuery, query)) {
+ mSearchQuery = query;
+ if (mSearchView != null) {
+ mSearchView.setSearchQuery(query);
+ } else {
+ for (OnSearchListener listener : mOnSearchListeners) {
+ listener.onSearch(query);
+ }
+ }
+ }
}
/**
@@ -758,6 +788,23 @@
public void setState(State state) {
mState = state;
+ if (mSearchView == null && (state == State.SEARCH || state == State.EDIT)) {
+ SearchView searchView = new SearchView(getContext());
+ searchView.setHint(mSearchHint);
+ searchView.setIcon(mSearchIcon);
+ searchView.setSearchListeners(mOnSearchListeners);
+ searchView.setSearchCompletedListeners(mOnSearchCompletedListeners);
+ searchView.setSearchQuery(mSearchQuery);
+ searchView.setVisibility(View.GONE);
+
+ FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT);
+ mSearchViewContainer.addView(searchView, layoutParams);
+
+ mSearchView = searchView;
+ }
+
for (MenuItemRenderer renderer : mMenuItemRenderers) {
renderer.setToolbarState(mState);
}
@@ -820,8 +867,14 @@
? VISIBLE : GONE);
mTabLayout.setVisibility(state == State.HOME && hasTabs ? VISIBLE : GONE);
- mSearchView.setVisibility(state == State.SEARCH || state == State.EDIT ? VISIBLE : GONE);
- mSearchView.setPlainText(state == State.EDIT);
+ if (mSearchView != null) {
+ if (state == State.SEARCH || state == State.EDIT) {
+ mSearchView.setPlainText(state == State.EDIT);
+ mSearchView.setVisibility(VISIBLE);
+ } else {
+ mSearchView.setVisibility(GONE);
+ }
+ }
boolean showButtons = (state != State.SEARCH && state != State.EDIT)
|| mShowMenuItemsWhileSearching;
@@ -918,22 +971,22 @@
/** Registers a new {@link OnSearchListener} to the list of listeners. */
public void registerOnSearchListener(OnSearchListener listener) {
- mSearchView.registerOnSearchListener(listener);
+ mOnSearchListeners.add(listener);
}
/** Unregisters an existing {@link OnSearchListener} from the list of listeners. */
public boolean unregisterOnSearchListener(OnSearchListener listener) {
- return mSearchView.unregisterOnSearchListener(listener);
+ return mOnSearchListeners.remove(listener);
}
/** Registers a new {@link OnSearchCompletedListener} to the list of listeners. */
public void registerOnSearchCompletedListener(OnSearchCompletedListener listener) {
- mSearchView.registerOnSearchCompletedListener(listener);
+ mOnSearchCompletedListeners.add(listener);
}
/** Unregisters an existing {@link OnSearchCompletedListener} from the list of listeners. */
public boolean unregisterOnSearchCompletedListener(OnSearchCompletedListener listener) {
- return mSearchView.unregisterOnSearchCompletedListener(listener);
+ return mOnSearchCompletedListeners.remove(listener);
}
/** Registers a new {@link OnBackListener} to the list of listeners. */