Creating a SearchView that looks like the material design guidelines
Solution 1
After a week of puzzling over this. I think I've figured it out.
I'm now using just an EditText inside of the Toolbar. This was suggested to me by oj88 on reddit.
I now have this:
First inside onCreate() of my activity I added the EditText with an image view on the right hand side to the Toolbar like this:
// Setup search container view
searchContainer = new LinearLayout(this);
Toolbar.LayoutParams containerParams = new Toolbar.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
containerParams.gravity = Gravity.CENTER_VERTICAL;
searchContainer.setLayoutParams(containerParams);
// Setup search view
toolbarSearchView = new EditText(this);
// Set width / height / gravity
int[] textSizeAttr = new int[]{android.R.attr.actionBarSize};
int indexOfAttrTextSize = 0;
TypedArray a = obtainStyledAttributes(new TypedValue().data, textSizeAttr);
int actionBarHeight = a.getDimensionPixelSize(indexOfAttrTextSize, -1);
a.recycle();
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, actionBarHeight);
params.gravity = Gravity.CENTER_VERTICAL;
params.weight = 1;
toolbarSearchView.setLayoutParams(params);
// Setup display
toolbarSearchView.setBackgroundColor(Color.TRANSPARENT);
toolbarSearchView.setPadding(2, 0, 0, 0);
toolbarSearchView.setTextColor(Color.WHITE);
toolbarSearchView.setGravity(Gravity.CENTER_VERTICAL);
toolbarSearchView.setSingleLine(true);
toolbarSearchView.setImeActionLabel("Search", EditorInfo.IME_ACTION_UNSPECIFIED);
toolbarSearchView.setHint("Search");
toolbarSearchView.setHintTextColor(Color.parseColor("#b3ffffff"));
try {
// Set cursor colour to white
// https://stackoverflow.com/a/26544231/1692770
// https://github.com/android/platform_frameworks_base/blob/kitkat-release/core/java/android/widget/TextView.java#L562-564
Field f = TextView.class.getDeclaredField("mCursorDrawableRes");
f.setAccessible(true);
f.set(toolbarSearchView, R.drawable.edittext_whitecursor);
} catch (Exception ignored) {
}
// Search text changed listener
toolbarSearchView.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
Fragment mainFragment = getFragmentManager().findFragmentById(R.id.container);
if (mainFragment != null && mainFragment instanceof MainListFragment) {
((MainListFragment) mainFragment).search(s.toString());
}
}
@Override
public void afterTextChanged(Editable s) {
// https://stackoverflow.com/a/6438918/1692770
if (s.toString().length() <= 0) {
toolbarSearchView.setHintTextColor(Color.parseColor("#b3ffffff"));
}
}
});
((LinearLayout) searchContainer).addView(toolbarSearchView);
// Setup the clear button
searchClearButton = new ImageView(this);
Resources r = getResources();
int px = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16, r.getDisplayMetrics());
LinearLayout.LayoutParams clearParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
clearParams.gravity = Gravity.CENTER;
searchClearButton.setLayoutParams(clearParams);
searchClearButton.setImageResource(R.drawable.ic_close_white_24dp); // TODO: Get this image from here: https://github.com/google/material-design-icons
searchClearButton.setPadding(px, 0, px, 0);
searchClearButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
toolbarSearchView.setText("");
}
});
((LinearLayout) searchContainer).addView(searchClearButton);
// Add search view to toolbar and hide it
searchContainer.setVisibility(View.GONE);
toolbar.addView(searchContainer);
This worked, but then I came across an issue where onOptionsItemSelected() wasn't being called when I tapped on the home button. So I wasn't able to cancel the search by pressing the home button. I tried a few different ways of registering the click listener on the home button but they didn't work.
Eventually I found out that the ActionBarDrawerToggle I had was interfering with things, so I removed it. This listener then started working:
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// toolbarHomeButtonAnimating is a boolean that is initialized as false. It's used to stop the user pressing the home button while it is animating and breaking things.
if (!toolbarHomeButtonAnimating) {
// Here you'll want to check if you have a search query set, if you don't then hide the search box.
// My main fragment handles this stuff, so I call its methods.
FragmentManager fragmentManager = getFragmentManager();
final Fragment fragment = fragmentManager.findFragmentById(R.id.container);
if (fragment != null && fragment instanceof MainListFragment) {
if (((MainListFragment) fragment).hasSearchQuery() || searchContainer.getVisibility() == View.VISIBLE) {
displaySearchView(false);
return;
}
}
}
if (mDrawerLayout.isDrawerOpen(findViewById(R.id.navigation_drawer)))
mDrawerLayout.closeDrawer(findViewById(R.id.navigation_drawer));
else
mDrawerLayout.openDrawer(findViewById(R.id.navigation_drawer));
}
});
So I can now cancel the search with the home button, but I can't press the back button to cancel it yet. So I added this to onBackPressed():
FragmentManager fragmentManager = getFragmentManager();
final Fragment mainFragment = fragmentManager.findFragmentById(R.id.container);
if (mainFragment != null && mainFragment instanceof MainListFragment) {
if (((MainListFragment) mainFragment).hasSearchQuery() || searchContainer.getVisibility() == View.VISIBLE) {
displaySearchView(false);
return;
}
}
I created this method to toggle visibility of the EditText and menu item:
public void displaySearchView(boolean visible) {
if (visible) {
// Stops user from being able to open drawer while searching
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
// Hide search button, display EditText
menu.findItem(R.id.action_search).setVisible(false);
searchContainer.setVisibility(View.VISIBLE);
// Animate the home icon to the back arrow
toggleActionBarIcon(ActionDrawableState.ARROW, mDrawerToggle, true);
// Shift focus to the search EditText
toolbarSearchView.requestFocus();
// Pop up the soft keyboard
new Handler().postDelayed(new Runnable() {
public void run() {
toolbarSearchView.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 0, 0, 0));
toolbarSearchView.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, 0, 0, 0));
}
}, 200);
} else {
// Allows user to open drawer again
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
// Hide the EditText and put the search button back on the Toolbar.
// This sometimes fails when it isn't postDelayed(), don't know why.
toolbarSearchView.postDelayed(new Runnable() {
@Override
public void run() {
toolbarSearchView.setText("");
searchContainer.setVisibility(View.GONE);
menu.findItem(R.id.action_search).setVisible(true);
}
}, 200);
// Turn the home button back into a drawer icon
toggleActionBarIcon(ActionDrawableState.BURGER, mDrawerToggle, true);
// Hide the keyboard because the search box has been hidden
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(toolbarSearchView.getWindowToken(), 0);
}
}
I needed a way to toggle the home button on the toolbar between the drawer icon and the back button. I eventually found the method below in this SO answer. Though I modified it slightly to made more sense to me:
private enum ActionDrawableState {
BURGER, ARROW
}
/**
* Modified version of this, https://stackoverflow.com/a/26836272/1692770<br>
* I flipped the start offset around for the animations because it seemed like it was the wrong way around to me.<br>
* I also added a listener to the animation so I can find out when the home button has finished rotating.
*/
private void toggleActionBarIcon(final ActionDrawableState state, final ActionBarDrawerToggle toggle, boolean animate) {
if (animate) {
float start = state == ActionDrawableState.BURGER ? 1.0f : 0f;
float end = Math.abs(start - 1);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
ValueAnimator offsetAnimator = ValueAnimator.ofFloat(start, end);
offsetAnimator.setDuration(300);
offsetAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
offsetAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float offset = (Float) animation.getAnimatedValue();
toggle.onDrawerSlide(null, offset);
}
});
offsetAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
toolbarHomeButtonAnimating = false;
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
toolbarHomeButtonAnimating = true;
offsetAnimator.start();
}
} else {
if (state == ActionDrawableState.BURGER) {
toggle.onDrawerClosed(null);
} else {
toggle.onDrawerOpened(null);
}
}
}
This works, I've managed to work out a few bugs that I found along the way. I don't think it's 100% but it works well enough for me.
EDIT: If you want to add the search view in XML instead of Java do this:
toolbar.xml:
<android.support.v7.widget.Toolbar
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/toolbar"
contentInsetLeft="72dp"
contentInsetStart="72dp"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:elevation="4dp"
android:minHeight="?attr/actionBarSize"
app:contentInsetLeft="72dp"
app:contentInsetStart="72dp"
app:popupTheme="@style/ActionBarPopupThemeOverlay"
app:theme="@style/ActionBarThemeOverlay">
<LinearLayout
android:id="@+id/search_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="horizontal">
<EditText
android:id="@+id/search_view"
android:layout_width="0dp"
android:layout_height="?attr/actionBarSize"
android:layout_weight="1"
android:background="@android:color/transparent"
android:gravity="center_vertical"
android:hint="Search"
android:imeOptions="actionSearch"
android:inputType="text"
android:maxLines="1"
android:paddingLeft="2dp"
android:singleLine="true"
android:textColor="#ffffff"
android:textColorHint="#b3ffffff" />
<ImageView
android:id="@+id/search_clear"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:src="@drawable/ic_close_white_24dp" />
</LinearLayout>
</android.support.v7.widget.Toolbar>
onCreate() of your Activity:
searchContainer = findViewById(R.id.search_container);
toolbarSearchView = (EditText) findViewById(R.id.search_view);
searchClearButton = (ImageView) findViewById(R.id.search_clear);
// Setup search container view
try {
// Set cursor colour to white
// https://stackoverflow.com/a/26544231/1692770
// https://github.com/android/platform_frameworks_base/blob/kitkat-release/core/java/android/widget/TextView.java#L562-564
Field f = TextView.class.getDeclaredField("mCursorDrawableRes");
f.setAccessible(true);
f.set(toolbarSearchView, R.drawable.edittext_whitecursor);
} catch (Exception ignored) {
}
// Search text changed listener
toolbarSearchView.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
Fragment mainFragment = getFragmentManager().findFragmentById(R.id.container);
if (mainFragment != null && mainFragment instanceof MainListFragment) {
((MainListFragment) mainFragment).search(s.toString());
}
}
@Override
public void afterTextChanged(Editable s) {
}
});
// Clear search text when clear button is tapped
searchClearButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
toolbarSearchView.setText("");
}
});
// Hide the search view
searchContainer.setVisibility(View.GONE);
Solution 2
It is actually quite easy to do this, if you are using android.support.v7
library.
Step - 1
Declare a menu item
<item android:id="@+id/action_search"
android:title="Search"
android:icon="@drawable/abc_ic_search_api_mtrl_alpha"
app:showAsAction="ifRoom|collapseActionView"
app:actionViewClass="android.support.v7.widget.SearchView" />
Step - 2
Extend AppCompatActivity
and in the onCreateOptionsMenu
setup the SearchView.
import android.support.v7.widget.SearchView;
...
public class YourActivity extends AppCompatActivity {
...
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_home, menu);
// Retrieve the SearchView and plug it into SearchManager
final SearchView searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.action_search));
SearchManager searchManager = (SearchManager) getSystemService(SEARCH_SERVICE);
searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
return true;
}
...
}
Result
Solution 3
I know its a old thread but still posting the library I just made. Hope this might help someone.
https://github.com/Shahroz16/material-searchview
Solution 4
The first screenshot in your question is not a public widget. The support SearchView (android.support.v7.widget.SearchView
) mimics Android 5.0 Lollipop's SearchView (android.widget.SearchView
). Your second screenshot is used by other material designed apps like Google Play.
The SearchView in your first screenshot is used in Drive, YouTube and other closed source Google Apps. Fortunately, it is also used in the Android 5.0 Dialer. You can try to backport the view, but it uses some 5.0 APIs.
The classes which you will want to look at are:
SearchEditTextLayout, AnimUtils, and DialtactsActivity to understand how to use the View. You will also need resources from ContactsCommon.
Best of luck.
Solution 5
Here's my attempt at doing this:
Step 1: Create a style named SearchViewStyle
<style name="SearchViewStyle" parent="Widget.AppCompat.SearchView">
<!-- Gets rid of the search icon -->
<item name="searchIcon">@drawable/search</item>
<!-- Gets rid of the "underline" in the text -->
<item name="queryBackground">@null</item>
<!-- Gets rid of the search icon when the SearchView is expanded -->
<item name="searchHintIcon">@null</item>
<!-- The hint text that appears when the user has not typed anything -->
<item name="queryHint">@string/search_hint</item>
</style>
Step 2: Create a layout named simple_search_view_item.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.SearchView
android:layout_gravity="end"
android:layout_height="wrap_content"
android:layout_width="match_parent"
style="@style/SearchViewStyle"
xmlns:android="http://schemas.android.com/apk/res/android" />
Step 3: Create a menu item for this search view
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
app:actionLayout="@layout/simple_search_view_item"
android:title="@string/search"
android:icon="@drawable/search"
app:showAsAction="always" />
</menu>
Step 4: Inflate the menu
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_searchable_activity, menu);
return true;
}
Result:
The only thing I wasn't able to do was to make it fill the entire width of the Toolbar
. If someone could help me do that then that'd be golden.
Mike
Updated on April 20, 2020Comments
-
Mike about 4 years
I'm currently in the process of learning how to convert my app to Material design and I'm a bit stuck right now. I've got the Toolbar added and I have made my navigation drawer overlay all the content.
I'm now trying to create an expandable search that looks like the one in the material guidelines:
This is what I've got right now and I can't figure out how to make it like the above:
This is my menu xml:
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/action_search" android:icon="@android:drawable/ic_menu_search" android:title="Search" app:showAsAction="always" app:actionViewClass="android.support.v7.widget.SearchView" /> </menu>
That works, I get a menu item that expands to the SearchView and I can filter my list fine. It doesn't look anything like the 1st picture though.
I tried to useMenuItemCompat.setOnActionExpandListener()
onR.id.action_search
so I could change the home icon to a back arrow, but that doesn't seem to work. Nothing gets fired in the listener. Even if that worked it still wouldn't be very close to the 1st image.
How do I create a SearchView in the new appcompat toolbar that looks like the material guidelines? -
Mike over 9 yearsThanks for looking into that, I was hoping there was something already out there that could do it. For now I've just used an EditText with a transparent background and it seems ok for what I need.
-
aluxian over 9 yearsWorks great, looks great, thank you! However, instead of creating the layouts in code, I created the LinearLayout with the EditText and the ImageView in xml and just inflated it in onCreate.
-
Mike over 9 yearsYeah, I tried to do it in XML but I thought I was doing something wrong as Android Studio wouldn't give me autocomplete in the layout XML.
-
Greg Ennis about 9 yearsThis answer is very disturbing to me. Why does google use a private widget that happens to match their own material design guideline and then publish a crappy widget for us that does not? And every developer is now out there struggling with this on his own? What possible reasoning for this?
-
Shreyash Mahajan about 9 yearsCan you please update this answer with respective XML layout design required for this? that can help other very well.
-
Mike about 9 yearsI've updated the answer with the XML layout for this, I've also added the code needed in onCreate for it to work. The rest should stay the same.
-
Solace almost 9 yearsHey did you add those
navigate-back
(back arrow) andcancel
(the x) icons yourself or they were automatically added? -
Artem almost 9 years@Solace they are added automatically
-
Solace almost 9 yearsDo they appear only when you start writing te search query in the SearchView or are they there even when you have not written anything and the search hint is visible? I am asking because mine do not appear until I start writing the query. So this information will be very useful for me
-
Artem almost 9 years@Solace
navigate-back
is always shown,clear
is shown only after you written some search query. -
Solace almost 9 yearsI had upvoted it, but which emulator are you using? I have tried it but I am not getting the
navigate-back
button, but rather a search icon, and its distance from the left edge of the screen is not the same as that between theX
icon and the right edge of the screen (I don't have action overflow). Posted the question here, can you look into it? -
C-- almost 9 yearsI use a real device running API-18. But works fine on Emulators API-15 and API-22 so far.
-
Gopal Singh Sirvi over 8 yearsits not a material search. it is only the usual old search in action bar
-
adelriosantiago over 8 yearsNice, but how do make the search bar always visible without needing to click the Search icon?
-
adelriosantiago over 8 yearsI answered my own sub-question, to make it always visible without the need to click on the search button add the line
searchView.setIconifiedByDefault(false);
to theonCreateOptionsMenu
function. -
Vinay W over 8 yearsanother useful tidbit - if you want the search view to be expanded initially make sure you have
app:showAsAction="always"
and NOTapp:showAsAction="ifRoom|collapseActionView"
-
Hamed Ghadirian over 8 years@Mike Can you please update your answer and put your complete github source code?
-
Nilabja over 8 yearsplease post the complete project in github
-
Zen about 8 yearsHow can I set the background to white when its searching?
-
C-- about 8 years@summers The toolbar is just a normal view. You may set the background color using
myToolbar.setBackgroundColor(Color.WHITE);
-
Zen about 8 yearsAs in; where should I be calling it? searchView.setOnClickListener(new OnClickListener() ; doesnt get called
-
Zen about 8 yearsAlso, using this method, even when the searchBar is expanded the OverflowIcon is still visible (ie, the 3-dots). But some apps, handle search, by expanding it completely and changing the background to white. Like GMail
-
Slim_user71169 about 8 yearsIt's not hard, but I like your work. It saves my time to create my own searchview. Thanks, bro!
-
AdamMc331 about 8 yearsExcellent library, and great idea to abstract the open/close method so that a MenuItem isn't required to open/close it, I plan on using this in my app with a FloatingActionButton with a search icon, so it will work great.
-
Aspiring Dev almost 8 yearsWhy aren't there parameters for strings like 'Search'? It seems the library limits people to either english or portuguese versions of the string :/
-
Mauker almost 8 yearsBut it's possible to change the hint String by using styles as it's stated on the README. As for the voice input hint, it'll be released later: github.com/Mauker1/MaterialSearchView/issues/23
-
Mangesh over 7 years
I wasn't able to do was to make it fill the entire width
, which support library version are you using? Try settingapp:contentInsetStartWithNavigation="0dp"
for your Toolbar. -
Mauker over 7 years@rpgmaker Check the latest update, it's now possible to change those strings.
-
Shreyash Mahajan about 7 years@LittleChild, try solution given by Magnesh. If still it is not get resolved then try to add below lines in toolbar. app:contentInsetStartWithNavigation="0dp" app:contentInsetLeft="0dp" app:contentInsetStart="0dp" app:paddingStart="0dp" android:layout_marginLeft="0dp" android:layout_marginStart="0dp"
-
Greg about 7 years@Mauker any idea how to set android:imeOptions="actionSearch" to the EditText of your component? (I want to display a "search" button in the keyboard)
-
Greg about 7 years@Mauker actually I've seen in your lib it is already there (android:imeOptions="actionSearch|flagNoExtractUi") but my keyboard doesn't display a loupe or a search specific button (only a enter button) Is that specific to the manufacturer or do I need to add something else?
-
Mauker about 7 yearsWhat's your phone? @estoke (You can also open an issue on the github project so we can discuss this further)
-
sud007 about 7 yearsDamn Google has a full documentation but nowhere did they mention the
app:actionViewClass
attribute to be mentioned in theMenu.xml
. So bad! Thanks for the Answer, that worked! -
Ekta Bhawsar over 6 years@SubinSebastian how I can add an addTextChangedListener on this searchview ?
-
Ekta Bhawsar over 6 yearsHey guys I got my solution, you can use
setOnQueryTextListener
and give definition to it's override methods i.e.onQueryTextChange
andonQueryTextSubmit
. -
Shinta S over 6 yearsThank you for putting explanation for each line in SearchViewStyle!
-
Ali_Ai_Dev over 5 yearsLittle Child For entire width, you can do sth like this: ("stackoverflow.com/questions/27946569/…)
-
Charles Woodson over 5 yearsGreat answer but MenuItemCompat.getActionView(...) is deprecated. Try using MenuItem search = menu.findItem(R.id.search); SearchView searchView = (SearchView) search.getActionView(); instead
-
GilbertS about 4 yearsMake sure to use the package br.com.mauker in
MsvAuthority
. I had changed it to my own project package name and the app crashed when I was adding suggestions -
Mauker almost 4 yearsYou have to add your own authority, follow the README, you have to change it in two places to make it work, and avoid conflicts with other apps. There's will be an update where that won't be needed anymore, but there's no release date yet.
-
Minh Nghĩa about 3 yearsI'm writing a Single Activity app, do this in the fragment, and the Hamburger button doesn't change to Back button. Anyone has a solution to that?