[Android TV] Phần 5: Tạo một danh sách(mục lục) các trình duyệt.

Hôm nay, chúng ta tiếp tục tìm hiểu phần 5 trong series bài dịch về Android TV. Phần này chúng ta sẽ đề cập đến vấn đề Catalog Browser

Một ứng dụng phục vụ phương tiện truyền thông được chạy trên nền TV devices cần được yêu cầu cấp quyền cho phép người dùng được duyệt các nội dung, dịch vụ của thiết bị đó, đưa ra các lựa chọn, và bắt đầu sử dụng theo các nội dung đó. Theo trực quan của đa số các phản hồi từ phía người dùng thì các ứng dụng kiểu này nên được thiết kế một cách trực quan và đơn giản, để hướng tới việc làm hài lòng và hấp dẫn thị giác của người sử dụng.

Phần này chúng ta sẽ thảo luận về việc làm thế nào để sử dụng các class được cung cấp sẵn bởi các thư viện hỗ trợ có trong v17 leanback support library để thiết kế một giao diện người dùng cho các trình duyệt chạy âm nhạc, video.. từ các danh mục phương tiện truyền thông trên ứng dụng của chúng ta.

alt

Figure 1: Các ứng dụng mẫu sử dụng thư viện v17 leanback để hiển thị phần dữ liệu chứa các danh mục video

Tạo một Media Browse Layout

Class BrowseFragment trong thư viện leanback đồng ý cho bạn tạo một trang chứa các bố cục chính để duyệt catalogries và các hàng mục phương tiện truyền thông với số lượng code là nhỏ nhất. Ví dụ dưới đây sẽ cho chúng ta thấy làm thế nào để tạo ra một bố cục có chưa một đối tượng BrowseFragment

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

Chúng ta sẽ sets view này tại phần controller điều khiển như sau:


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

Các phương thức của BrowseFragment đang được đặt trong views này với cấc dữ liệu và các phần tử UI và đặt các thông số layout như icon, tiêu đề... được khởi tạo.

Các lớp con được kế thừa từ lớp BrowseFragment cũng sẽ kế thừa và cài đặt các sự kiện lắng nghe các hành động của người dùng, tham khảo đoạn code sau:

public class MainFragment extends BrowseFragment implements
        LoaderManager.LoaderCallbacks<HashMap<String, List<Movie>>> {

...

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

        loadVideoData();

        prepareBackgroundManager();
        setupUIElements();
        setupEventListeners();
    }
...

    private void prepareBackgroundManager() {
        mBackgroundManager = BackgroundManager.getInstance(getActivity());
        mBackgroundManager.attach(getActivity().getWindow());
        mDefaultBackground = getResources()
            .getDrawable(R.drawable.default_background);
        mMetrics = new DisplayMetrics();
        getActivity().getWindowManager().getDefaultDisplay().getMetrics(mMetrics);
    }

    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 headers background color
        setBrandColor(getResources().getColor(R.color.fastlane_background));
        // set search icon color
        setSearchAffordanceColor(getResources().getColor(R.color.search_opaque));
    }

    private void loadVideoData() {
        VideoProvider.setContext(getActivity());
        mVideosUrl = getActivity().getResources().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());
    }
...

Cài đặt thành phần cho UI

Trong phần ví dụ phía trên, phương thức cục bộ setupUIElements() gọi tới một số phương thức trong BrowserFragment để tạo các style cho các danh mục của browser:

  • setBadgeDrawable() đặt các resource ở góc trên bên phải của phần layout, được thể hiện như ở Figure 1 và 2. Các resource nên được đặt với kích thước 52dps
  • setTitle() cài đặt phần hiển thị tiêu đề ở góc trên bên phải của browse fragment, ngoại trừ setBadgeDrawable() được gọi.
  • setHeadersState()setHeadersTrasitionOnBackEnabled ẩn và vô hiệu hoá các tiêu đề. Đọc phần Hide or Disable Headers để hiểu rõ hơn.
  • setBrandColor() cài đặt màu nền cho các thành phần của UI, đặc biệt là phần màu nền cho tiêu đề với các mã màu đặc trưng.
  • setSearchAffordanceColor() cài đặt màu cho icon tìm kiếm với mã màu. Phần này được hiển thị ở Figure 1 và 2 ở phần phía trên cùng góc bên trái.

Tuỳ chỉnh Header Views

Phần Browse thể hiện trong hình 1 danh sách các video category (theo hàng) trong khung bên trái. Phần văn bản được hiển thị theo dữ liệu trong database. Chúng ta có thể thay đổi tuỳ chỉnh chúng với một bố cục phức tạp hơn. Các phần sau đây cho ta thấy làm thế nào để có thể thêm một biểu tượng trước các title như ở Figure 2:

alt

Figure 2: Các tiêu đề bao gồm cả biểu tượng đi kèm

Việc bố trí cho các tiêu đề theo dòng được xác định như sau:

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

Sử dụng Presenter và thực hiện các abtract methods để create, bind, và unbind the view holder. Ví dụ sau đây cho thấy làm thế nào để bind các viewholder với hai views, một ImageView và một TextView.

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

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

Đây là ví dụ cho chúng ta thấy làm thế nào để xác định được các presenter cho một layout bố trí phức tạp với nhiều views, và chúng ta nên sử dụng mô hình này để làm thêm nhiều view phức tạp hơn. Tuy nhiên, một cách đơn giản hơn để kết hợp các đối tượng giữa views và controller để có thể sử dụng các thuộc tính TextView.drawableLeft. Làm theo cách này, chúng ta không cần hiển thị ImageView ở đây:

Trong việc tích hợp BrowseFragment nơi mà hiển thị các catalog browser, sử dụng setHeaderPresenterSelector() phương thức cài đặt cho presenter cho dòng tiêu đề:

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

Hiển thị danh sách các media

Lớp BrowseFragment cho phép xác định và hiển thị các loại phương tiện truyền thông có thể xem nội dung và mục phương tiện truyền thông từ một danh mục thông qua adapter và presenters. Adapter cho phép kết nội với các local or online data sources. Adapters sử dụng presenters để tạo views và bind data với những phần views, items hiển thị trên màn hình.

Đoạn mã ví dụ sau đây cho thấy cách sử dụng một Presenter:

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

Một khi chúng ta đã xây dựng một lớp presenter cho các items media, chúng ta có thể xây dựng một adapter và gắn chúng đến BrowseFragment để hiển thị các mục trên màn hình của người dùng. Đoạn mã sau cho chúng ta thấy làm thế nào để xây dựng một adapter để hiển thị các loại và các catagories sử dụng lớp StringPresenter thế hiện trong ví dụ trước đó:

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

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...

    buildRowsAdapter();
}

private void buildRowsAdapter() {
    mRowsAdapter = 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);
        mRowsAdapter.add(new ListRow(header, listRowAdapter));
    }

    mBrowseFragment.setAdapter(mRowsAdapter);
}

Cập nhật background

Để thêm phần hiển thị sinh động hơn trên TV app, chúng ta có thể cập nhật các hình nên khi người dùng duỵệt qua những phần nội dung. Kỹ thuật này có thể tạo cho người dùng có thêm phần thú vị khi sử dụng ứng dụng của chúng ta.

Thư viện Leanback cung cấp một lớp BackgroundManager cho việc thay đổi hình nên của TV app activity. Đoạn code dưới đây cho chúng ta thấy việc cập nhật hình nền cho TV app Activity:

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

Việc sử dụng danh sách các item và cập nhật background tự động khi người dùng sử dụng điều hướng thông qua nó, chúng ta thiết lập một biến listener để tự động lắng nghe các hành động từ phía người dùng và cập nhật nền dựa trên lựa chọn của người dùng. Đoạn code sau cho chúng ta thấy làm thế nào để thiết lập một lớp OnItemSelectedListener để bắt sự kiện chọn và cập nhập hình nền:

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

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

Lưu ý: Phần hướng dẫn trên đây là một ví dụ đơn giản về việc làm thế nào để hiển thị những minh hoạ nhằm giải thích cho mục đích bài hướng dẫn. Khi tạo chức năng này trong ứng dụng nào đó, chúng ta nên xem xét việc chạy các background update để nhằm đạt hiệu suất tốt nhất. Ngoài ra nếu chúng ta muốn hình ảnh được cập nhật sau khi người dùng lựa chọn thì chúng ta nên cài đặt một khoảng thời gian chờ cho đến khi hình nền được updated. Việc này tránh được việc app bị lag do quả tải khi load ảnh.

Phần tiếp theo chúng ta sẽ tìm hiểu việc tạo một Card View..

Thanks for your reading.