Cómo realizar búsquedas en apps para TV

Los usuarios suelen tener contenido específico en la mente cuando usan una app de música en la TV. Si tu app contiene un gran catálogo de contenido, es posible que buscar un título específico no sea la manera más eficaz de que encuentren lo que buscan. Una interfaz de búsqueda puede ayudar a tus usuarios a obtener el contenido que desean más rápido que navegando.

La biblioteca de Leanback de AndroidX proporciona un conjunto de clases para habilitar una interfaz de búsqueda estándar dentro de tu app que es coherente con otras funciones de búsqueda en TV y ofrece funciones como la entrada de voz.

En esta lección, se analiza cómo proporcionar una interfaz de búsqueda en tu app con clases de biblioteca de compatibilidad de Leanback.

Cómo agregar una acción de búsqueda

Cuando usas la clase BrowseFragment para una interfaz de navegación multimedia, puedes habilitar una interfaz de búsqueda como parte estándar de la interfaz de usuario. La interfaz de búsqueda es un ícono que aparece en el diseño cuando configuras View.OnClickListener en el objeto BrowseFragment. En el siguiente código de muestra, se ilustra esta técnica.

Kotlin

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.browse_activity)
        browseFragment = fragmentManager.findFragmentById(R.id.browse_fragment) as BrowseFragment
        browseFragment.setOnSearchClickedListener { view ->
            val intent = Intent(this@BrowseActivity, SearchActivity::class.java)
            startActivity(intent)
        }

        browseFragment.setAdapter(buildAdapter())
    }
    

Java

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.browse_activity);

        browseFragment = (BrowseFragment)
                getFragmentManager().findFragmentById(R.id.browse_fragment);

        ...

        browseFragment.setOnSearchClickedListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(BrowseActivity.this, SearchActivity.class);
                startActivity(intent);
            }
        });

        browseFragment.setAdapter(buildAdapter());
    }
    

Nota: Puedes configurar el color del ícono de búsqueda con setSearchAffordanceColor(int).

Cómo agregar una entrada para búsqueda y resultados

Cuando un usuario selecciona el ícono de búsqueda, el sistema invoca una actividad de búsqueda mediante un intent definido. Tu actividad de búsqueda debería usar un diseño lineal que contenga un elemento SearchFragment. Este fragmento también debe implementar la interfaz SearchFragment.SearchResultProvider para mostrar los resultados de una búsqueda.

En la siguiente muestra de código, se indica cómo extender la clase SearchFragment para proporcionar una interfaz de búsqueda y resultados:

Kotlin

    class MySearchFragment : SearchFragment(), SearchFragment.SearchResultProvider {
        private val rowsAdapter = ArrayObjectAdapter(ListRowPresenter())
        private val handler = Handler()
        private val delayedLoad = SearchRunnable()

        val resultsAdapter: ObjectAdapter
        get() {
            return rowsAdapter
        }

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setSearchResultProvider(this)
            setOnItemClickedListener(getDefaultItemClickedListener())
        }

        fun onQueryTextChange(newQuery: String): Boolean {
            rowsAdapter.clear()
            if (!TextUtils.isEmpty(newQuery)) {
                delayedLoad.setSearchQuery(newQuery)
                handler.removeCallbacks(delayedLoad)
                handler.postDelayed(delayedLoad, SEARCH_DELAY_MS)
            }
            return true
        }

        fun onQueryTextSubmit(query: String): Boolean {
            rowsAdapter.clear()
            if (!TextUtils.isEmpty(query)) {
                delayedLoad.setSearchQuery(query)
                handler.removeCallbacks(delayedLoad)
                handler.postDelayed(delayedLoad, SEARCH_DELAY_MS)
            }
            return true
        }

        companion object {
            private val SEARCH_DELAY_MS = 300
        }
    }
    

Java

    public class MySearchFragment extends SearchFragment
            implements SearchFragment.SearchResultProvider {

        private static final int SEARCH_DELAY_MS = 300;
        private ArrayObjectAdapter rowsAdapter;
        private Handler handler = new Handler();
        private SearchRunnable delayedLoad;

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);

            rowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
            setSearchResultProvider(this);
            setOnItemClickedListener(getDefaultItemClickedListener());
            delayedLoad = new SearchRunnable();
        }

        @Override
        public ObjectAdapter getResultsAdapter() {
            return rowsAdapter;
        }

        @Override
        public boolean onQueryTextChange(String newQuery) {
            rowsAdapter.clear();
            if (!TextUtils.isEmpty(newQuery)) {
                delayedLoad.setSearchQuery(newQuery);
                handler.removeCallbacks(delayedLoad);
                handler.postDelayed(delayedLoad, SEARCH_DELAY_MS);
            }
            return true;
        }

        @Override
        public boolean onQueryTextSubmit(String query) {
            rowsAdapter.clear();
            if (!TextUtils.isEmpty(query)) {
                delayedLoad.setSearchQuery(query);
                handler.removeCallbacks(delayedLoad);
                handler.postDelayed(delayedLoad, SEARCH_DELAY_MS);
            }
            return true;
        }
    }
    

El ejemplo de código que se muestra más arriba debe usarse con una clase SearchRunnable separada que ejecute la consulta de búsqueda en un subproceso independiente. Esta técnica evita que las consultas que podrían ejecutarse lentamente bloqueen el subproceso de la interfaz de usuario principal.