简体   繁体   中英

Looking for ideas to implement SearchView in Android in a Single Activity App approach

Looking for ideas to implement SearchView in Android. I am using Single Activity Approach in my app. I was following this doc:https://developer.android.com/guide/topics/search/search-dialog

But this adds the overhead of adding new activity.

Currently in my app I have bunch of screens and only on one screen I need to show SearchView.

No need for a new activity.

Create a menu.xml with the SearchView :


<?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="@drawable/ic_search"
            android:title="@string/search"
            android:menuCategory="secondary"
            app:showAsAction="ifRoom|collapseActionView"
            app:actionViewClass="androidx.appcompat.widget.SearchView"/>
</menu>

Then in the fragment, you want to display it:

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setHasOptionsMenu(true)
    }

    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        inflater.inflate(R.menu.your_menu_with_search, menu)
        setupSearchView(menu.findItem(R.id.action_search).actionView as SearchView)
    }


    private fun setupSearchView(searchView: SearchView) {
        searchView.queryHint = "Your option query hint"
        searchView.setOnQueryTextListener(object: SearchView.OnQueryTextListener {
            override fun onQueryTextChange(newText: String?): Boolean {
                TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
            }

            override fun onQueryTextSubmit(query: String?): Boolean {
                TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
            }

        })
        }
    }

I'm assuming that you have setup your navController with the toolbar, if not, I'll link the documentation to do it, as it is out of the scope of your question, good luck.

N/B: A sample app implementation can be found here

You could have a setup/configuration similar to this:

Step 1: Create a searchable config file in xml/searchable.xml

<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
    android:hint="@string/search_hint"
    android:label="@string/app_name"/>

Step 2: In AndroidManifest.xml , configure your single-activity(MainActivity) to be the searchable activity with launchMode set to singleTop

<?xml version="1.0" encoding="utf-8"?>
<manifest ...>
    <application ...>
        <!-- launchMode=singleTop is IMPORTANT to avoid relaunching MainActivity every time search is initiated -->
        <activity
            android:name=".MainActivity"
            android:launchMode="singleTop">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <action android:name="android.intent.action.SEARCH" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <meta-data
                android:name="android.app.searchable"
                android:resource="@xml/searchable" />
        </activity>
    </application>
</manifest>

Step 3: In your options menu, add a search menu item

<menu ...>
  <item
        android:id="@+id/menu_list_search"
        android:icon="@drawable/ic_action_search"
        android:orderInCategory="1"
        android:title="Search"
        app:showAsAction="ifRoom" />
</menu>

Step 4: In your MainActivity (also searchable activity)

/**
* Single activity serving as searchable-activity too
*/
class MainActivity : AppCompatActivity() {

    private lateinit var binding: MainActivityBinding

    private val navController: NavController by lazy {
        findNavController(R.id.navigation_host)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = MainActivityBinding.inflate(layoutInflater)
        setContentView(binding.root)

        handleIntent(intent)
    }

    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        setIntent(intent)
        handleIntent(intent)
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean =
        item.onNavDestinationSelected(navController) || super.onOptionsItemSelected(item)

    override fun onSearchRequested(): Boolean {
      val customAppData = Bundle().apply{
      putString("key1", "value1")
      }
        startSearch(null, false, customAppData, false)
        return true
//        return super.onSearchRequested() // Use when there is no need to send custom app data
    }

    private fun handleIntent(intent: Intent) {
        // Verify the action and get the query
        if (Intent.ACTION_SEARCH == intent.action) {
            val query = intent.getStringExtra(SearchManager.QUERY) ?: return
                        // Pass the [query] received to a fragment in which actual data-searching process will be done
                        val navOptions = NavOptions.Builder().setLaunchSingleTop(true).build()
                        navController.navigate(R.id.dest_search_results, SearchResultsFragmentArgs(query=query).toBundle(), navOptions)
        }
    }
}

Step 5: From your list-displaying fragment(the screen where search action will be initiated)

/**
* List displaying fragment
*/
class SearchInitiatorFragment: Fragment(){
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

                // Needed to help show options menu from fragments
        setHasOptionsMenu(true)
    }

    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        inflater.inflate(R.menu.list_menu, menu)
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
        /* 
                Launch SearchDialog when "search" menu item is clicked.

                N/B: I chose to go with a SearchDialog instead of SearchView since it was easier for me 
                to send/pass custom app-data to my searchable activity (MainActivity in this case) after 
                submiting a search query
                */
        R.id.menu_list_search -> activity?.onSearchRequested() ?: false
        else -> super.onOptionsItemSelected(item)
    }
}

Step 6: Create a search-results displaying fragment(the screen where search query will be sent(from the MainActivity ) and search results shown)

/**
* Search results displaying fragment
*/
class SearchResultsFragment: Fragment(){
  private val args: SearchResultsFragmentArgs by navArgs()

  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        Log.i("SearchResultsFragment", "Received Query: ${args.query}")
    }
}

Search Query Flow: list-displaying fragment -> MainActivity(searchable activity) -> result-displaying fragment

N/B: Some code snippets contains AndroidX's navigation component codes that might not be available in your project

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM