Данная статься познакомит вас с разработкой простого приложения для Android TV. В первую очередь она ориентированна на тех, кто уже имел какой-либо опыт в разработке приложений для Android, поэтому я не буду здесь объяснять что такое Activity, Fragments и т.д.

В интернете есть много русскоязычной информации по разработке приложений для Android и не мало статей по написанию HelloWord для него же. Заинтересовавшись разработкой приложений для Android TV, я сразу же начал изучать эту тему на англоязычных сайтах. К моему удивлению материала оказалось не так уж и много, и я решил посмотреть что же есть на русском. На русском ничего найти не удалось (возможно плохо искал). В общем я намерен исправить эту ситуацию.

Так как интерфейс приложений для телефонов и Android TV имеет существенные различия, то мы должны создать интерфейс приложения, подходящий для взаимодействия на TV. Например, нам следует создавать приложения с которыми можно взаимодействовать, используя только клавиши — ↑ ↓ → ←. В реализации такого интерфейса нам может помочь библиотека LeanbackSupport, позволяющая вполне легко создавать UI, который будет удобен при работе с приложениями на Android TV.

Создание проекта в Android Studio


Запустив Android Studio, необходимо создать новый проект. При создании выбрать платформу TV и указать минимальную версию SDK. Android Studio предложит нам создать «Android TV Activity», однако на данный момент следует выбрать «Add No Activity», т.к. если вы выберите создание Activity, то AS создаст ��ного классов и файлов, в которых изначально сложнее разобраться.

Создание Activity


Для начала необходимо создать новый XML файл под именем activity_main.xml, который будет содержать разметку для нашей Activity. Код разметки мы изменим позже.

Теперь следует создать класс унаследованный от Activity. Для этого нужно создать новый класс с именем MainActivity и унаследовать его от класса Activty. Предопределить метод onCreate(Bundle SIS) и установить в нем содержимое для Activity из созданного файла разметки.

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

Объявление Activity в файле манифеста приложения


Если вы попытаетесь запустить приложение на данном этапе, то оно естественно не запустится, так как в файле AndroidManifest.xml у нас не объявлено ни одной Activity.

В файл манифеста нужно добавить следующий код.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.simpletvapp">

    <uses-feature
        android:name="android.hardware.touchscreen"
        android:required="false" />
    <uses-feature
        android:name="android.software.leanback"
        android:required="true" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/Theme.Leanback">
        <activity
            android:name=".MainActivity"
            android:icon="@drawable/app_icon_your_company"
            android:label="@string/app_name"
            android:logo="@drawable/app_icon_your_company" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Теперь разберем некоторые моменты. Часть кода приведенная ниже отключает тач.

<uses-feature
    android:name="android.hardware.touchscreen"
    android:required="false" />

В этой части вы указываете, что приложение должно запускаться только на Android TV. Если вы разрабатываете приложение не только для TV, то вам следует установить значение false.

<uses-feature
    android:name="android.software.leanback"
    android:required="true" />

При объявлении Activity мы указываем в intent-filter, что Activity должна запускаться на Android TV.

<category android:name=”android.intent.category.LEANBACK_LAUNCHER/>

Создание фрагмента


Сейчас нужно создать класс Java с именем MainFragment и унаследовать от класса BrowseFragment из библиотеки LeanbackSupport. BrowseFragment позволяет создавать стандартный интерфейс приложений для Android TV.

Теперь мы можем привязать созданный фрагмент к нашей Activity, для этого в файл разметки Activity (в моем случае это activity_main.xml) следует поместить следующий код разметки.

<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/main_browse_fragment"
    android:name="com.simpletvapp.MainFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

Запуск приложения


Чтобы запустить приложение, нужно создать эмулятор Android TV. Его можно создать в Android Virtual Device Manager.

После создания эмулятора можно запустить на нем наше приложение. На данный момент оно имеет следующий вид.



Здесь вы видите пустой BrowseFragment. Вы можете увидеть RowsFragment в левой части приложения (фрагмент отвечает за отображение списка заголовков) и HeaderFragment в правой части экрана (отвечает за отображение контента заголовков).

Далее мы заполним HeaderFragment, RowsFragment и рассмотрим их подробнее. Перед этим установим основные цвета UI и заголовок для приложения.

Настройка стиля приложения


Здесь я добавил метод setupUI() в MainFragment.java и вызвал его в предопределенном методе onActivityCreated.

   @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        setupUI();
    }


    private void setupUI() {
        setTitle("Hello Android TV!");
        int color = ContextCompat.getColor(getActivity(), R.color.fastlane_background);
        setBrandColor(color);
    }

Если вы запустите приложение на данном этапе, то вы должны увидеть вот такой UI.



Заполнение BrowseFragment


Давайте сначала разберемся на примере готового приложения Android TV содержание BrowseFragment. Каждый заголовок в левой части фрагмента (HeaderItem) имеет ряд с контентом в правой части. Комбинацию «заголовок (HeaderItem) + контент в правой части приложения», содержит класс ListRow. Содержание BrowseFragment представляет собой список из ListRow. Заголовок и список с контентом в правой части имеет отношение один к одному.



Рассмотрим ListRow детальнее. За список с контентом отвечает ArrayObjectAdapter. В данном случае CardInfo это элемент контента. CardInfo может быть любым объектом. Как создать и отобразить CardInfo мы рассмотри позже.



Сейчас мы можем сделать следующие выводы:

ArrayObjectAdapter — отвечает за список из ListRow
ListRow = HeaderItem (заголовок) + ArrayObjectAdapter (список контента в правой части)

Класс Presenter


Заполнение элементов контента определяется при помощи класса Presenter из библотеки LeanbackSupport. Он определяет отображение элемента контента. Presenter является абстрактным классом, поэтому мы должны создать новый класс и унаследовать его от Presenter. Когда вы создадите новый класс, вы должны предопределить как минимум 3 метода:

onCreateViewHolder(ViewGroup parent);
onBindViewHolder(ViewGolder, viewHolder, Object item);
onUnbindViewHolder(ViewHolder viewHolder);

Presenter содержит внутренний класс ViewHolder, который позволяет ссылаться к View (элемент контента). Мы можем получить доступ к View через ViewHolder при конкретных событиях, например в методах класса Presenter onBind() или onUnbind()

Заполнение HeadersFragment и RowsFragment


Давайте перейдем к делу. Здесь мы создаем класс GridItemPresenter унаследованный от класса Presenter. В этом приложении Object (элемент контента) отображает строку, а ViewHolder содержит в себе TextView для отображения этой строки. View создается в методе onCreateViewHolder(), а ее заполнение производится в методе onBindViewHolder().


public class GridItemPresenter extends Presenter {

    private static final int WIDTH_ITEM = 300;
    private static final int HEIGHT_ITEM = 200;

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent) {
        TextView view = new TextView(parent.getContext());
        view.setLayoutParams(new ViewGroup.LayoutParams(WIDTH_ITEM, HEIGHT_ITEM));
        view.setFocusable(true);
        view.setFocusableInTouchMode(true);
        view.setGravity(Gravity.CENTER);
        view.setBackgroundColor(ContextCompat.getColor(parent.getContext(), R.color.default_background));
        view.setTextColor(Color.WHITE);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(ViewHolder viewHolder, Object item) {
        TextView textView = (TextView) viewHolder.view;
        String str = (String) item;
        textView.setText(str);
    }

    @Override
    public void onUnbindViewHolder(ViewHolder viewHolder) {

    }
}

В класс MainFragment добавляем метод loadRows() и вызываем его в предопределенном методе onActivityCreated()

 private void loadRows() {
        // адаптер, отвечающий за ListRow (ListRow = заголовок + контент)
        ArrayObjectAdapter rowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
        // класс отвечает за заголовок, в конструкторе указываем что это первый заголовок в списке,
        // и сам заголовок содержит текст "Заголовок 1"
        HeaderItem headerItem = new HeaderItem(0, "Заголовок 1");
        // наш класс, отвечающий за заполнение элементов контента
        GridItemPresenter itemPresenter = new GridItemPresenter();
        // адаптер, отвечает за отображение контента в правой части
        ArrayObjectAdapter gridAdapter = new ArrayObjectAdapter(itemPresenter);
        // добавление трех элементов контента
        gridAdapter.add(0, "Элемент 1");
        gridAdapter.add(1, "Элемент 2");
        gridAdapter.add(2, "Элемент 3");
        // в адаптер, отвечающий за ListRows, добавляем ListRow.
        // в конструктор передаем класс, отвечающий за заголовок и адаптер, отвечающий за
        // отображение списка контента
        rowsAdapter.add(new ListRow(headerItem, gridAdapter));
        setAdapter(rowsAdapter);

    }

Теперь вы можете запустить приложение и увидеть экран представленный ниже.



На этом пока что все. В этой статье я попытался объяснить некоторые из основных принципов создания приложения для Android TV.

За основу данной статьи были взяты этот и этот мануалы.