Katalog tarayıcı oluşturma

Compose ile daha iyi hizmet verin
Android TV OS için Jetpack Compose'u kullanarak minimum kodla etkileyici kullanıcı arayüzleri oluşturun.

TV'de çalışan bir medya uygulamasının, kullanıcıların sunduğu içeriklere göz atmasına olanak tanıması ve seçip içeriği oynatmaya başlayabilirsiniz. İçeriğe göz atma deneyimi basit ve sezgisel, aynı zamanda görsel açıdan hoş ve ilgi çekici olmalıdır.

Bu kılavuzda, androidx.leanback kitaplığı tarafından sağlanan sınıfların nasıl kullanılacağı açıklanmaktadır. (uygulamanızın medya kataloğundaki müzik veya videolara göz atmak için kullanıcı arayüzü)

Not: Burada gösterilen uygulama örneğinde BrowseSupportFragment kullanımdan kaldırılan BrowseFragment yerine sınıfını kullanır. BrowseSupportFragment, AndroidX'i genişletir Fragment sınıf, cihazlar ve Android sürümleri arasında tutarlı davranış sağlanmasına yardımcı oluyor.

Uygulama ana ekranı

Şekil 1. Leanback örnek uygulamasının göz atma parçası, video kataloğu verilerini gösterir.

Medyaya göz atma düzeni oluştur

BrowseSupportFragment Leanback kullanıcı arayüzü araç setindeki sınıf , kategorilere ve medya öğesi satırlarına göz atmak için bir tercih edebilirsiniz. Aşağıdaki örnekte, BrowseSupportFragment nesne:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/main_frame"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        android:name="com.example.android.tvleanback.ui.MainFragment"
        android:id="@+id/main_browse_fragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</FrameLayout>

Uygulamanın ana etkinliği, aşağıdaki örnekte gösterildiği gibi bu görünümü ayarlar:

Kotlin

class MainActivity : Activity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main)
    }
...

Java

public class MainActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
...

BrowseSupportFragment yöntemleri görünümü video verilerini ve kullanıcı arayüzü öğelerini ayarlayabilir, simge, başlık ve ve kategori başlıklarının etkin olup olmadığını gösterir.

Kullanıcı arayüzü öğelerini ayarlama hakkında daha fazla bilgi için bkz. Kullanıcı arayüzünü ayarlama öğeler bölümüne bakın. Üstbilgileri gizleme hakkında daha fazla bilgi için Üstbilgileri gizleyin veya devre dışı bırakın bölümünü.

Uygulamanın, BrowseSupportFragment parametresini uygulayan alt sınıfı Yöntemleri, kullanıcı arayüzü öğelerinde kullanıcı işlemleri için etkinlik işleyiciler de ayarlar ve arka plan yöneticisini ekleyin:

Kotlin

class MainFragment : BrowseSupportFragment(),
        LoaderManager.LoaderCallbacks<HashMap<String, List<Movie>>> {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        loadVideoData()
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        prepareBackgroundManager()
        setupUIElements()
        setupEventListeners()
    }
    ...
    private fun prepareBackgroundManager() {
        backgroundManager = BackgroundManager.getInstance(activity).apply {
            attach(activity?.window)
        }
        defaultBackground = resources.getDrawable(R.drawable.default_background)
        metrics = DisplayMetrics()
        activity?.windowManager?.defaultDisplay?.getMetrics(metrics)
    }

    private fun setupUIElements() {
        badgeDrawable = resources.getDrawable(R.drawable.videos_by_google_banner)
        // Badge, when set, takes precedent over title
        title = getString(R.string.browse_title)
        headersState = BrowseSupportFragment.HEADERS_ENABLED
        isHeadersTransitionOnBackEnabled = true
        // Set header background color
        brandColor = ContextCompat.getColor(requireContext(), R.color.fastlane_background)

        // Set search icon color
        searchAffordanceColor = ContextCompat.getColor(requireContext(), R.color.search_opaque)
    }

    private fun loadVideoData() {
        VideoProvider.setContext(activity)
        videosUrl = getString(R.string.catalog_url)
        loaderManager.initLoader(0, null, this)
    }

    private fun setupEventListeners() {
        setOnSearchClickedListener {
            Intent(activity, SearchActivity::class.java).also { intent ->
                startActivity(intent)
            }
        }

        onItemViewClickedListener = ItemViewClickedListener()
        onItemViewSelectedListener = ItemViewSelectedListener()
    }
    ...

Java

public class MainFragment extends BrowseSupportFragment implements
        LoaderManager.LoaderCallbacks<HashMap<String, List<Movie>>> {
}
...
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        loadVideoData();
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        prepareBackgroundManager();
        setupUIElements();
        setupEventListeners();
    }
...
    private void prepareBackgroundManager() {
        backgroundManager = BackgroundManager.getInstance(getActivity());
        backgroundManager.attach(getActivity().getWindow());
        defaultBackground = getResources()
            .getDrawable(R.drawable.default_background);
        metrics = new DisplayMetrics();
        getActivity().getWindowManager().getDefaultDisplay().getMetrics(metrics);
    }

    private void setupUIElements() {
        setBadgeDrawable(getActivity().getResources()
            .getDrawable(R.drawable.videos_by_google_banner));
        // Badge, when set, takes precedent over title
        setTitle(getString(R.string.browse_title));
        setHeadersState(HEADERS_ENABLED);
        setHeadersTransitionOnBackEnabled(true);
        // Set header background color
        setBrandColor(ContextCompat.getColor(requireContext(), R.color.fastlane_background));
        // Set search icon color
        setSearchAffordanceColor(ContextCompat.getColor(requireContext(), R.color.search_opaque));
    }

    private void loadVideoData() {
        VideoProvider.setContext(getActivity());
        videosUrl = getString(R.string.catalog_url);
        getLoaderManager().initLoader(0, null, this);
    }

    private void setupEventListeners() {
        setOnSearchClickedListener(new View.OnClickListener() {

            @Override
            public void onClick(View view) {
                Intent intent = new Intent(getActivity(), SearchActivity.class);
                startActivity(intent);
            }
        });

        setOnItemViewClickedListener(new ItemViewClickedListener());
        setOnItemViewSelectedListener(new ItemViewSelectedListener());
    }
...

Kullanıcı arayüzü öğelerini ayarlama

Önceki örnekte, setupUIElements() gizli yönteminde birden fazla BrowseSupportFragment içerik kataloğu tarayıcısının stilini belirleme yöntemleri:

  • setBadgeDrawable(). belirtilen çekilebilir kaynağı göz atma parçasının sağ üst köşesine yerleştirir: resimler 1 ve 2'de gösterilmiştir. Bu yöntem, başlık dizesini setTitle() çağrılırsa çekilebilir kaynak. Çekilebilir kaynak 52 dp olmalıdır uzun.
  • setTitle(). başlık dizesini göz atma parçasının sağ üst köşesindeki ayarlar setBadgeDrawable() çağrıldı.
  • setHeadersState(). ve setHeadersTransitionOnBackEnabled() üstbilgileri gizler veya devre dışı bırakır. Daha fazla bilgi için Üstbilgileri gizleme veya devre dışı bırakma bölümüne bakın.
  • setBrandColor(). göz atma parçasındaki, özellikle de üstbilgideki kullanıcı arayüzü öğelerinin arka plan rengini ayarlar bölüm arka plan rengi (belirtilen renk değerine sahip).
  • setSearchAffordanceColor(). arama simgesinin rengini belirtilen renk değerine ayarlar. Arama simgesi simgesi, şekil 1 ve 2'de gösterildiği gibi göz atma parçasının sol üst köşesinde görünür.

Başlık görünümlerini özelleştirme

Şekil 1'de gösterilen göz atma parçası, video kategorisi adlarını, Bunlar, metin görünümlerinde video veritabanındaki satır başlıklarıdır. Ayrıca üstbilgisini, daha karmaşık bir düzende ek görünümler içerecek şekilde düzenleyebilirsiniz. Aşağıdaki bölümlerde, Şekil 2'de gösterildiği gibi, kategori adının yanında bir simge görüntüleyen bir resim görünümü ekleyin.

Uygulama ana ekranı

Şekil 2. Göz atma parçasında, hem simge hem de ve bir metin etiketi ekleyin.

Satır başlığının düzeni şu şekilde tanımlanır:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/header_icon"
        android:layout_width="32dp"
        android:layout_height="32dp" />
    <TextView
        android:id="@+id/header_label"
        android:layout_marginTop="6dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

Bir Presenter kullanın ve görünüm sahibini oluşturmak, bağlamak ve bağlantısını kaldırmak için soyut yöntemler kullanır. Aşağıdakiler örnek, iki görünümle izleyiciyi birbirine nasıl bağlayacağınızı gösteriyor. ImageView ve TextView.

Kotlin

class IconHeaderItemPresenter : Presenter() {

    override fun onCreateViewHolder(viewGroup: ViewGroup): Presenter.ViewHolder {
        val view = LayoutInflater.from(viewGroup.context).run {
            inflate(R.layout.icon_header_item, null)
        }

        return Presenter.ViewHolder(view)
    }


    override fun onBindViewHolder(viewHolder: Presenter.ViewHolder, o: Any) {
        val headerItem = (o as ListRow).headerItem
        val rootView = viewHolder.view

        rootView.findViewById<ImageView>(R.id.header_icon).apply {
            rootView.resources.getDrawable(R.drawable.ic_action_video, null).also { icon ->
                setImageDrawable(icon)
            }
        }

        rootView.findViewById<TextView>(R.id.header_label).apply {
            text = headerItem.name
        }
    }

    override fun onUnbindViewHolder(viewHolder: Presenter.ViewHolder) {
        // no-op
    }
}

Java

public class IconHeaderItemPresenter extends Presenter {
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup viewGroup) {
        LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext());

        View view = inflater.inflate(R.layout.icon_header_item, null);

        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(ViewHolder viewHolder, Object o) {
        HeaderItem headerItem = ((ListRow) o).getHeaderItem();
        View rootView = viewHolder.view;

        ImageView iconView = (ImageView) rootView.findViewById(R.id.header_icon);
        Drawable icon = rootView.getResources().getDrawable(R.drawable.ic_action_video, null);
        iconView.setImageDrawable(icon);

        TextView label = (TextView) rootView.findViewById(R.id.header_label);
        label.setText(headerItem.getName());
    }

    @Override
    public void onUnbindViewHolder(ViewHolder viewHolder) {
    // no-op
    }
}

D-pad'in kullanılabilmesi için başlıklarınıza odaklanılabilir olmalıdır. arasında gezinin. Bunu yönetmenin iki yolu vardır:

  • onBindViewHolder() ürününde görünümünüzü odaklanılabilir olacak şekilde ayarlayın:

    Kotlin

    override fun onBindViewHolder(viewHolder: Presenter.ViewHolder, o: Any) {
        val headerItem = (o as ListRow).headerItem
        val rootView = viewHolder.view
    
        rootView.focusable = View.FOCUSABLE
        // ...
    }
    

    Java

    @Override
    public void onBindViewHolder(ViewHolder viewHolder, Object o) {
        HeaderItem headerItem = ((ListRow) o).getHeaderItem();
        View rootView = viewHolder.view;
        rootView.setFocusable(View.FOCUSABLE) // Allows the D-Pad to navigate to this header item
        // ...
    }
    
  • Düzeninizi odaklanılabilir olacak şekilde ayarlayın:
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
       ...
       android:focusable="true">
    .

Son olarak, BrowseSupportFragment katalog tarayıcısı için setHeaderPresenterSelector() kullanın yöntemini kullanın.

Kotlin

setHeaderPresenterSelector(object : PresenterSelector() {
    override fun getPresenter(o: Any): Presenter {
        return IconHeaderItemPresenter()
    }
})

Java

setHeaderPresenterSelector(new PresenterSelector() {
    @Override
    public Presenter getPresenter(Object o) {
        return new IconHeaderItemPresenter();
    }
});

Tam bir örnek için bkz. Leanback örnek uygulaması ,

Üstbilgileri gizleme veya devre dışı bırakma

Bazı durumlarda, örneğin yeterli satır olmadığında satır başlıklarının görünmesini istemezsiniz. kaydırılabilir liste gerektirecek. BrowseSupportFragment.setHeadersState() hattını arayın parçanın onActivityCreated() işlemi sırasındaki yöntem yöntemini kullanabilirsiniz. setHeadersState() yöntemi, aşağıdakilerden biri kapsamında göz atma parçasındaki üstbilgilerin ilk durumunu belirler sabit değerleri kullanabilirsiniz:

  • HEADERS_ENABLED: göz atma parçası etkinliği oluşturulduğunda, üstbilgiler etkinleştirilir ve varsayılandır. Üstbilgiler, bu sayfadaki şekil 1 ve 2'de gösterildiği gibidir.
  • HEADERS_HIDDEN: göz atma parçası etkinliği oluşturulduğunda, üstbilgiler varsayılan olarak etkinleştirilir ve gizlenir. Ekranın başlık bölümü daraltılmış durumdadır (bkz. ) bir şekil Kart görünümü sağlayın. İlgili içeriği oluşturmak için kullanılan kullanıcı daraltılmış başlık bölümünü seçerek genişletebilir.
  • HEADERS_DISABLED: göz atma parçası etkinliği oluşturulduğunda başlıklar varsayılan olarak devre dışı bırakılır ve hiçbir zaman görüntülenmez.

HEADERS_ENABLED veya HEADERS_HIDDEN ayarlandıysa telefon edebilirsiniz setHeadersTransitionOnBackEnabled() satırdaki seçilen içerik öğesinden satır başlığına geri gitmeyi desteklemek için. Bu özellik, yöntemi çağrıştırmazsanız varsayılan olarak ayarlanır. Geri hareketi kendiniz yönetmek için setHeadersTransitionOnBackEnabled() adlı kişiye false geç kendi sırt yığın işlemenizi uygulayın.

Medya listelerini görüntüle

BrowseSupportFragment dersimiz sayesinde göz atılabilir medya içeriği kategorilerini ve medya öğelerini tanımlamak ve görüntülemek bağdaştırıcıları ve sunucuları kullanarak bir medya kataloğu oluşturun. Adaptörler bağlanmanızı sağlar verileri içeren yerel veya çevrimiçi veri kaynaklarıyla tutarlı şekilde güncelleyin. Adaptörler, görünüm oluşturmak ve verileri bu görünümlere bağlamak için sunucuları kullanır. bir öğe görüntülenir.

Aşağıdaki örnek kod, dize verilerini görüntülemek için bir Presenter uygulamasını göstermektedir:

Kotlin

private const val TAG = "StringPresenter"

class StringPresenter : Presenter() {

    override fun onCreateViewHolder(parent: ViewGroup): Presenter.ViewHolder {
        val textView = TextView(parent.context).apply {
            isFocusable = true
            isFocusableInTouchMode = true
            background = parent.resources.getDrawable(R.drawable.text_bg)
        }
        return Presenter.ViewHolder(textView)
    }

    override fun onBindViewHolder(viewHolder: Presenter.ViewHolder, item: Any) {
        (viewHolder.view as TextView).text = item.toString()
    }

    override fun onUnbindViewHolder(viewHolder: Presenter.ViewHolder) {
        // no op
    }
}

Java

public class StringPresenter extends Presenter {
    private static final String TAG = "StringPresenter";

    public ViewHolder onCreateViewHolder(ViewGroup parent) {
        TextView textView = new TextView(parent.getContext());
        textView.setFocusable(true);
        textView.setFocusableInTouchMode(true);
        textView.setBackground(
                parent.getResources().getDrawable(R.drawable.text_bg));
        return new ViewHolder(textView);
    }

    public void onBindViewHolder(ViewHolder viewHolder, Object item) {
        ((TextView) viewHolder.view).setText(item.toString());
    }

    public void onUnbindViewHolder(ViewHolder viewHolder) {
        // no op
    }
}

Medya öğeleriniz için sunucu sınıfı oluşturduktan sonra bir adaptöre takın ve BrowseSupportFragment cihazına bağlayın. kullanıcının göz atması için bu öğeleri ekranda görüntüleyin. Aşağıdaki örnek kod, kategorileri ve öğeleri görüntülemek için bir bağdaştırıcının nasıl oluşturulacağını gösterir bu kategorilerde gösterilen StringPresenter sınıfını kullanarak önceki kod örneği:

Kotlin

private const val NUM_ROWS = 4
...
private lateinit var rowsAdapter: ArrayObjectAdapter

override fun onCreate(savedInstanceState: Bundle?) {
    ...
    buildRowsAdapter()
}

private fun buildRowsAdapter() {
    rowsAdapter = ArrayObjectAdapter(ListRowPresenter())
    for (i in 0 until NUM_ROWS) {
        val listRowAdapter = ArrayObjectAdapter(StringPresenter()).apply {
            add("Media Item 1")
            add("Media Item 2")
            add("Media Item 3")
        }
        HeaderItem(i.toLong(), "Category $i").also { header ->
            rowsAdapter.add(ListRow(header, listRowAdapter))
        }
    }
    browseSupportFragment.adapter = rowsAdapter
}

Java

private ArrayObjectAdapter rowsAdapter;
private static final int NUM_ROWS = 4;

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    buildRowsAdapter();
}

private void buildRowsAdapter() {
    rowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());

    for (int i = 0; i < NUM_ROWS; ++i) {
        ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(
                new StringPresenter());
        listRowAdapter.add("Media Item 1");
        listRowAdapter.add("Media Item 2");
        listRowAdapter.add("Media Item 3");
        HeaderItem header = new HeaderItem(i, "Category " + i);
        rowsAdapter.add(new ListRow(header, listRowAdapter));
    }

    browseSupportFragment.setAdapter(rowsAdapter);
}

Bu örnekte bağdaştırıcıların statik uygulaması gösterilmektedir. Tipik bir medya tarama uygulaması çevrimiçi bir veritabanından veya web hizmetinden alınan verileri kullanır. Örneğin, Yeşil Ofis web sitesinde web'den alınan verileri kullanıyorsa, Leanback örnek uygulaması ,

Arka planı güncelle

TV'deki bir medya tarama uygulamasına görsel ilgi eklemek için arka planı güncelleyebilirsiniz görsel olarak da kullanabilirsiniz. Bu teknik, uygulamanızla etkileşimi artırabilir sinematik ve eğlenceli.

Leanback kullanıcı arayüzü araç seti, bir BackgroundManager sağlar. TV uygulaması etkinliğinizin arka planını değiştirme sınıfı. Aşağıdaki örnekte, TV uygulaması etkinliğinizde arka planı güncellemek için basit bir yöntem oluşturabilirsiniz:

Kotlin

protected fun updateBackground(drawable: Drawable) {
    BackgroundManager.getInstance(this).drawable = drawable
}

Java

protected void updateBackground(Drawable drawable) {
    BackgroundManager.getInstance(this).setDrawable(drawable);
}

Medya tarama uygulamalarının çoğu, kullanıcı gezinirken arka planı otomatik olarak günceller aracılığıyla iletişim kurabilirsiniz. Bunu yapmak için bir seçim işleyiciyi otomatik olarak kullanıcının mevcut seçimine göre arka planı güncelleyebilir. Aşağıdaki örnekte, OnItemViewSelectedListener sınıfını ayarlamak için seçim etkinliklerini yakalama ve arka planı güncelleme:

Kotlin

protected fun clearBackground() {
    BackgroundManager.getInstance(this).drawable = defaultBackground
}

protected fun getDefaultItemViewSelectedListener(): OnItemViewSelectedListener =
        OnItemViewSelectedListener { _, item, _, _ ->
            if (item is Movie) {
                item.getBackdropDrawable().also { background ->
                    updateBackground(background)
                }
            } else {
                clearBackground()
            }
        }

Java

protected void clearBackground() {
    BackgroundManager.getInstance(this).setDrawable(defaultBackground);
}

protected OnItemViewSelectedListener getDefaultItemViewSelectedListener() {
    return new OnItemViewSelectedListener() {
        @Override
        public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
                RowPresenter.ViewHolder rowViewHolder, Row row) {
            if (item instanceof Movie ) {
                Drawable background = ((Movie)item).getBackdropDrawable();
                updateBackground(background);
            } else {
                clearBackground();
            }
        }
    };
}

Not: Önceki uygulama, illüstrasyon. Bu işlevi kendi uygulamanızda oluştururken daha iyi performans için arka plan güncelleme işlemini ayrı bir ileti dizisinde toplayın. Ayrıca öğeler arasında gezinen kullanıcılara yanıt olarak arka planı güncellemeyi planlıyor, Arka plan resmi güncellemesini, kullanıcı bir öğe üzerinde karar verene kadar ertelemek için gereken süreyi ifade eder. Bu teknik, çok fazla arka plan resmi güncellemesi.