Search code examples
androidandroid-actionbarcontextual-action-bar

How do I create a "Search" field in a contextual action bar?


I have an activity with an ActionBar. One of the options is to search a document (not necessarily local). I want to use a contextual action bar (CAB) with the following items:

  • edit field - place for user to enter text they want to search for
  • Previous Button - moves to the previous item that matches the search
  • Next Button - moves to the next item that matches the search

I want to use a CAB for several reasons. Primarily, I want the user to pick an option which will bring up the CAB defined above. When the user is done with the search, they select the done button and the ActionBar returns to previous state.

Here's the problem. I can't get the search item to appear in my CAB in an expanded state. NOTE: The activity I'm attempting to modify is NOT the main activity but is an activity launched by the user based on certain events. Once this activity is loaded, the user may or may not decide to use the 'search' functionality I'm describing. I also do not want Android to do the searching. I have an API that will search for the results I want and allow me to navigate to the prev/next search result.

My code for the CAB (which is being called correctly) is:

     protected ActionMode mActionModeSearch;
private ActionMode.Callback mActionModeSearchCallback = new ActionMode.Callback()
{
    @Override
    public boolean onCreateActionMode(ActionMode actionMode, Menu menu)
    {
        actionMode.getMenuInflater().inflate(R.menu.pdf_menu_search, menu);
        // Associate searchable configuration with the SearchView
        SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
        SearchView searchView = (SearchView) menu.findItem(R.id.pdf_menu_search_item).getActionView();
        searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
        searchView.setIconifiedByDefault(false);
        searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener()
        {
            @Override
            public boolean onQueryTextSubmit(String s)
            {
                return false;
            }

            @Override
            public boolean onQueryTextChange(String s)
            {
                return false;
            }
        });
        return true;
    }

    @Override
    public boolean onPrepareActionMode(ActionMode actionMode, Menu menu)
    {
        return false;
    }

    @Override
    public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem)
    {
        switch(menuItem.getItemId())
        {
            case R.id.pdf_menu_search_prev:
                findPrevSearchResult();
                return true;
            case R.id.pdf_menu_search_next:
                findNextSearchResult();
                return true;
            default:
                return false;
        }
    }

    @Override
    public void onDestroyActionMode(ActionMode actionMode)
    {
        //resetHideToolbarsTimer();
    }
};

The CAB menu code is:

 <?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">
<group android:id="@+id/group_search_mode">
    <item
        android:id="@+id/pdf_menu_search_item"
        android:title="@string/search"
        android:icon="@drawable/ic_pdf_action_search"
        app:showAsAction="collapseActionView|ifRoom"
        app:actionViewClass="android.support.v7.widget.SearchView"/>
    <item
        android:id="@+id/pdf_menu_search_prev"
        android:title="@string/search_prev"
        android:icon="@drawable/ic_pdf_action_search_prev"
        app:showAsAction="ifRoom" />
    <item
        android:id="@+id/pdf_menu_search_next"
        android:title="@string/search_next"
        android:icon="@drawable/ic_pdf_action_search_next"
        app:showAsAction="ifRoom" />
</group>

I also changed my activity definition in AndroidManifest.xml although I'm not sure this is required:

         <activity
        android:name="com.myapp.myActivity"
        android:label="@string/app_name">
        <intent-filter>
            <action android:name="android.intent.action.SEARCH" />
        </intent-filter>
        <meta-data
            android:name="android.app.default_searchable"
            android:value="com.myapp.myActivity" />
        <meta-data
            android:name="android.app.searchable"
            android:resource="@xml/searchable" />
    </activity>

When I run the app, the activity load correctly. When the user selects the search option, I load the CAB and it seems to be running fine. I've stepped through the code to make sure the onCreateActionMode is functioning as expected. But, all I see is the icon for search, next and previous, with the 'Done' button. When I touch the 'Search' icon, it doesn't do anything. No text field is created in the CAB for me to enter text. What am I doing wrong????

Screenshot showing the CAB coming up. As you can see, the search icon shows but doesn't do anything when I press it. What I really need is for the search edit box to appear by default.

enter image description here


Solution

  • First, we should set always value for app:showAsAction of all menu items:

    <?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">
        <group android:id="@+id/group_search_mode">
            <item
                android:id="@+id/pdf_menu_search_item"
                android:icon="@drawable/ic_pdf_action_search"
                android:title="@string/search"
                app:actionViewClass="android.support.v7.widget.SearchView"
                app:showAsAction="always"/>
            <item
                android:id="@+id/pdf_menu_search_prev"
                android:icon="@drawable/ic_pdf_action_search_prev"
                android:title="@string/search_prev"
                app:showAsAction="always"/>
            <item
                android:id="@+id/pdf_menu_search_next"
                android:icon="@drawable/ic_pdf_action_search_next"
                android:title="@string/search_next"
                app:showAsAction="always"/>
        </group>
    </menu>
    

    Secondary, in this case we don't need to set intent filter for our Activity and searchable info for our SearchView.

    Definition of this Activity in AndroidManifest.xml:

    <activity
        android:name="com.myapp.myActivity"
        android:label="@string/app_name" />
    

    ActionMode.Callback implementation:

    private ActionMode.Callback mActionModeSearchCallback = new ActionMode.Callback() {
    
        private SearchView mSearchView;
    
        @Override
        public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
            actionMode.getMenuInflater().inflate(R.menu.home, menu);
            mSearchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.pdf_menu_search_item));
            mSearchView.setIconifiedByDefault(false);
            mSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
                @Override
                public boolean onQueryTextSubmit(String s) {
                    return false;
                }
    
                @Override
                public boolean onQueryTextChange(String s) {
                    return false;
                }
            });
            return true;
        }
    
        @Override
        public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
            mSearchView.requestFocus();
            return true;
        }
    
        @Override
        public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
            switch (menuItem.getItemId()) {
                case R.id.pdf_menu_search_prev:
                    findPrevSearchResult();
                    return true;
                case R.id.pdf_menu_search_next:
                    findNextSearchResult();
                    return true;
                default:
                    return false;
            }
        }
    
        @Override
        public void onDestroyActionMode(ActionMode actionMode) {
    
        }
    };
    

    I just tried this code on three 4.0+ devices and it was fully working. But I didn't test on devises with lower OS versions.

    Hope it will be helpful for you.