Search code examples
androidandroid-toolbarsearchview

Correct implementation of SearchView in android toolbar


I have a problem with searchview implementation in android toolbar.

  1. The empty space padding is too big.
  2. I don't want to hide other actions, but these actions are overlapped by SearchView.
  3. SearchView's underline is not visible

How do i fix issues mentioned above ?

enter image description here

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:title="@string/car_num"
        android:icon="@drawable/ic_search_white_24dp"
        app:actionViewClass="android.support.v7.widget.SearchView"
        app:showAsAction="always" />

    <item
        android:id="@+id/action_add_client"
        android:icon="@drawable/ic_account_multiple_plus"
        android:title="@string/action_add_client"
        app:showAsAction="always" />
</menu>

fragment

@Override
public void onCreateOptionsMenu(final Menu menu, MenuInflater inflater) {
    inflater.inflate(R.menu.menu_fragment_reg_vehicles, menu);

    final MenuItem item = menu.findItem(R.id.action_search);
    searchView = (SearchView) MenuItemCompat.getActionView(item);
    searchView.setQueryHint("Search");
    searchView.setMaxWidth(Integer.MAX_VALUE);
    searchView.setIconifiedByDefault(false);
    searchView.setOnQueryTextListener(this);
    searchView.setOnCloseListener(new SearchView.OnCloseListener() {
        @Override
        public boolean onClose() {
            setItemsVisibility(menu, item, true);
            return false;
        }
    });
    searchView.setOnSearchClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            setItemsVisibility(menu, item, false);
            searchView.requestFocus();
        }
    });
}

Solution

  • Regarding your posted code, this is the output:

    Toolbar with SearchView AppCompat by default

    As you can see, there is two left margins: the widget's container and the magnify icon. This is why you have an empty space bigger than an another window with a title. And the menu items are pushed outside the toolbar which, I think, it's the default SearchView ActionView when it's not a CollapseActionView so it fills the parent.

    From the source of SearchView widget and its layout abc_search_view.xml, I tried to remove the extra margins and avoid pushing the other items outside the toolbar.
    But after many manipulations, my guess is you have to use a custom widget and/or a custom layout. Or to play with setIconifiedByDefault(true) which removes the magnify icon and its extra margin and to use setMaxWidth(MAX_SIZE) where MAX_SIZE is calculated dynamically by Integer.MAX_VALUE - (SIZE_OF_A_MENU_ITEM * NB_OF_MENU_ITEMS)... But it requires a lot of work for nothing. So using a custom layout could be the solution.

    However, there is a possible way to keep the appcompat widget, some little workarounds. First, to avoid puhsing out the other items, you can use the CollapseActionView.

    <item
        ...
        app:actionViewClass="android.support.v7.widget.SearchView"
        app:showAsAction="always|collapseActionView"/>
    

    And to maintain your requirements, you have to expand it when you initialize it:

    final SearchView searchView =
                (SearchView) MenuItemCompat.getActionView(item);
    MenuItemCompat.expandActionView(item);
    

    Be aware that you have to use setOnActionExpandListener() in order to close the window if you don't want to collapse the item. This suggestion will give you this result:

    Toolbar with SearchView AppCompat expanded

    Still the extra margins, right? Therefore, you have to retrieve the container and the magnify icon by their ids (which you can find in abc_search_view.xml... but let's save some time: they are R.id.search_edit_frame and R.id.search_mag_icon). You can remove their margins by using this method:

    private void changeSearchViewElements(View view) {
        if (view == null) 
            return;
    
        if (view.getId() == R.id.search_edit_frame
                || view.getId() == R.id.search_mag_icon) {
            LinearLayout.LayoutParams p = 
                    (LinearLayout.LayoutParams) view.getLayoutParams();
            p.leftMargin = 0; // set no left margin
            view.setLayoutParams(p);
        }
    
        if (view instanceof ViewGroup) {
            ViewGroup viewGroup = (ViewGroup) view;
            for (int i = 0; i < viewGroup.getChildCount(); i++) {
                changeSearchViewElements(viewGroup.getChildAt(i));
            }
        }
    }
    

    By calling it in a thread:

    final SearchView searchView =
                (SearchView) MenuItemCompat.getActionView(item);
    ...
    searchView.post(new Runnable() {
            @Override
            public void run() {
                changeSearchViewElements(searchView);
            }
        });
    

    Here's the output:

    Toolbar with SearchView AppCompat without left margin

    Finally, to get the line under the field, there is a possible workaround as using a 9-patch drawable and set it as a background. You can easily find how-to on Google. So the condition will be:

    private void changeSearchViewElements(View view) {
        ...
        if (view.getId() == R.id.search_edit_frame
                || view.getId() == R.id.search_mag_icon) {
            LinearLayout.LayoutParams p = 
                    (LinearLayout.LayoutParams) view.getLayoutParams();
            p.leftMargin = 0; // set no left margin
            view.setLayoutParams(p);
    
        } else if (view.getId() == R.id.search_src_text) {
            AutoCompleteTextView searchEdit = (AutoCompleteTextView) view;
            searchEdit.setBackgroundResource(R.drawable.rect_underline_white);
        }
        ...
    }
    

    From the OP's comment below, the underline's field can also be done with the following statement:

    searchView.findViewById(android.support.v7.appcompat.R.id.se‌​arch_src_text)
            .setBa‌​ckgroundResource(R.d‌​rawable.abc_textfiel‌​d_search_default_mtr‌​l_alpha);
    

    After these workarounds, as I said, it might be easier to use a custom layout. But if you want to keep the default SearchView widget, this might help.