TV 앱 내 검색

사용자는 TV에서 미디어 앱을 사용할 때 특정 콘텐츠를 마음에 두고 있는 경우가 많습니다. 앱이 대규모 콘텐츠 카탈로그를 포함하고 있다면 탐색을 통해 특정 타이틀을 검색하는 방식은 사용자 입장에서 원하는 항목을 찾는 효율성이 떨어질 수 있습니다. 검색 인터페이스는 탐색과 비교해 더 빨리 원하는 콘텐츠에 도달할 수 있게 합니다.

Leanback androidx 라이브러리는 앱 내에서 TV의 다른 검색 기능과 일관된 표준 검색 인터페이스를 사용할 수 있게 해 주는 일련의 클래스 및 음성 입력과 같은 기능을 제공합니다.

이 과정에서는 Leanback 지원 라이브러리 클래스를 사용하여 앱에 검색 인터페이스를 제공하는 방법을 설명합니다.

검색 작업 추가

미디어 탐색 인터페이스에 BrowseFragment 클래스를 사용하면 검색 인터페이스를 사용자 인터페이스의 표준 요소로 사용 설정할 수 있습니다. 검색 인터페이스는 BrowseFragment 객체에서 View.OnClickListener를 설정할 때 레이아웃에 나타나는 아이콘입니다. 다음 샘플 코드는 이 기법을 보여줍니다.

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())
    }
    

자바

    @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());
    }
    

참고: setSearchAffordanceColor(int)를 사용하여 검색 아이콘의 색상을 설정할 수 있습니다.

검색어 입력 및 결과 추가

사용자가 검색 아이콘을 선택하면 시스템은 정의된 인텐트를 통해 검색 활동을 호출합니다. 검색 활동은 SearchFragment가 포함된 선형 레이아웃을 사용해야 합니다. 또한 이 프래그먼트는 검색 결과를 표시하기 위해 SearchFragment.SearchResultProvider 인터페이스를 구현해야 합니다.

다음 코드 샘플은 SearchFragment 클래스를 확장하여 검색 인터페이스 및 결과를 제공하는 방법을 보여줍니다.

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
        }
    }
    

자바

    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;
        }
    }
    

위의 코드 예는 별도의 스레드에서 검색어를 실행하는 별도의 SearchRunnable 클래스와 함께 사용하기 위한 것입니다. 이 기법을 사용하면 쿼리 실행 속도가 느린 경우에도 기본 사용자 인터페이스 스레드를 차단하지 않습니다.