[Android TV] Phần 6: Tạo một Card View

Ở phần trước chúng ta đã tạo được một catalog browser, thực hiện trên một browse fragment, hiển thị chúng thành 1 danh sách các media items. Trong phần này, chúng ta sẽ tạo và tuỳ biến dánh sách các media items đó theo dạng card view. Lớp BaseCardView và lớp con hiển thị các meta data liên kết với một media item. Lớp ImageCardView được tìm hiểu trong phần này sẽ đảm nhiệm việc tuỳ biến hiển thị một hình anh với nội dung chính kèm theo tiêu đề của từng item. Phần này sẽ mô tả code từ một ví dụ Android Leanback sample app, ví dụ này có trên Github, chúng ta có thể lấy phần code ví dụ về để trực quan trải nghiệm.

alt

Figure 1. Leanback sample app thẻ ảnh khi được chọn

Tạo một Card Presenter

Một Presenter tạo ra views và liên kết các objects đó theo yêu cầu đặt ra. Trên một browse framgent nơi mà ứng dụng của chúng ta hiển thị các nội dung chính đến người dùng, chúng ta sẽ tạo một presenter cho phần nội dung dạng cards và truyền nó thông qua một adapter và thêm nó phần nội dung chính trên màn hình. Phần code sau, CardPresenter được tạo ra trong hàm [onLoadFinished](https://developer.android.com/reference/android/support/v4/app/LoaderManager.LoaderCallbacks.html#onLoadFinished(android.support.v4.content.Loader<D>, D)) và được gọi lại ở LoaderManager.

@Override
public void onLoadFinished(Loader<HashMap<String, List<Movie>>> arg0,
                           HashMap<String, List<Movie>> data) {

    mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
    CardPresenter cardPresenter = new CardPresenter();

    int i = 0;

    for (Map.Entry<String, List<Movie>> entry : data.entrySet()) {
        ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(cardPresenter);
        List<Movie> list = entry.getValue();

        for (int j = 0; j < list.size(); j++) {
            listRowAdapter.add(list.get(j));
        }
        HeaderItem header = new HeaderItem(i, entry.getKey(), null);
        i++;
        mRowsAdapter.add(new ListRow(header, listRowAdapter));
    }

    HeaderItem gridHeader = new HeaderItem(i, getString(R.string.more_samples),
            null);

    GridItemPresenter gridPresenter = new GridItemPresenter();
    ArrayObjectAdapter gridRowAdapter = new ArrayObjectAdapter(gridPresenter);
    gridRowAdapter.add(getString(R.string.grid_view));
    gridRowAdapter.add(getString(R.string.error_fragment));
    gridRowAdapter.add(getString(R.string.personal_settings));
    mRowsAdapter.add(new ListRow(gridHeader, gridRowAdapter));

    setAdapter(mRowsAdapter);

    updateRecommendations();
}

Tạo một Card View

Ở bước này, chúng ta xây dựng một card presenter với một view holder cho card view để mô tả phần hiển thị nội dung của các items. Chúng ta cần lưu ý rằng mỗi một presenter chỉ tạo ra một kiểu view. Nếu chúng ta có hai kiểu card views khác nhau thì chúng ta cần phải tạo ra 2 card presenters khác nhau.

Ở trong Presenter, thực hiện gọi lại phương thức onCreateViewHolder() để tạo một view holder có thể sử dụng để hiển thị nội dung của một item.

@Override
public class CardPresenter extends Presenter {

    private Context mContext;
    private static int CARD_WIDTH = 313;
    private static int CARD_HEIGHT = 176;
    private Drawable mDefaultCardImage;

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent) {
        mContext = parent.getContext();
        mDefaultCardImage = mContext.getResources().getDrawable(R.drawable.movie);
...

Trong phương thức onCreateViewHolder(), tạo một card view cho nội dung của items. Ví dụ dưới đây sử dụng một ImageCardView. Khi một card được chọn, mặc định nó sẽ được mở ra với một kích thước lớn hơn. Nếu chúng ta muốn chỉ định một màu khác cho card được chọn, chúng ta sẽ gọi phương thức setSelected()

...
    ImageCardView cardView = new ImageCardView(mContext) {
        @Override
        public void setSelected(boolean selected) {
            int selected_background = mContext.getResources().getColor(R.color.detail_background);
            int default_background = mContext.getResources().getColor(R.color.default_background);
            int color = selected ? selected_background : default_background;
            findViewById(R.id.info_field).setBackgroundColor(color);
            super.setSelected(selected);
        }
    };
...

Khi người dùng chọn một ImageCardView, nó se mở đến một phần vùng text với màu nền do chúng ta chỉ định, giống như ảnh Figure 1.

Xây dụng một View chi tiết(Details View)

Các lớp media browsing interface được cung cấp bởi [v17 leanback support library] bao gồm các lớp hiển thị thêm phần thông tin của một media item, chẳng hạn như mô tả hoặc đánh giá, và các hành động trên mục đó, chẳng hạn như mua hay sử dụng nội dung của nó.

Phần này sẽ cho chúng ta hiểu làm thế nào để tạo một lớp presenter card cho media item details, và làm thế nào để sử dụng các lớp trong DetailsFramgent để thực hiện một detail view cho một media item khi nó được chọn bởi người dùng.

Chú ý : Ví dụ thực hiện ở đây sử dụng một activity bổ sung để chứa các DetailsFramgent. Tuy nhiên nó có thể để tránh tạo ra một activity thứ hai bằng cách thay thế BrowseFragment hiện tại với một DetailsFramgent trong cùng một activity khi chúng ta sử dụng fragment transaction. Để có thêm thông tin về việc sử dụng các fragment transaction. xem thêm ở Building a Dynamic UI with Framgnets

Xây dựng một Details Presenter

Trong media browsing framework cung cấp bởi thư viện leanback, chúng ta sử dụng presenter objects để điều khiển các dữ liệu hiển thị trên màn hình, bao gồm cả các media item details. Framework cung cấp lớp AbstractDetailsDescriptionPresenter cho mục đích này, nó là một thực hiện gần như hoàn chỉnh của một presenter cho media item details. Thực hiện phương thức onBindDescription() để bind dữ liệu lên các trường trên view, đoạn code dưới sẽ giống như một ví dụ:

public class DetailsDescriptionPresenter
        extends AbstractDetailsDescriptionPresenter {

    @Override
    protected void onBindDescription(ViewHolder viewHolder, Object itemData) {
        MyMediaItemDetails details = (MyMediaItemDetails) itemData;
        // In a production app, the itemData object contains the information
        // needed to display details for the media item:
        // viewHolder.getTitle().setText(details.getShortTitle());

        // Here we provide static data for testing purposes:
        viewHolder.getTitle().setText(itemData.toString());
        viewHolder.getSubtitle().setText("2014   Drama   TV-14");
        viewHolder.getBody().setText("Lorem ipsum dolor sit amet, consectetur "
                + "adipisicing elit, sed do eiusmod tempor incididunt ut labore "
                + " et dolore magna aliqua. Ut enim ad minim veniam, quis "
                + "nostrud exercitation ullamco laboris nisi ut aliquip ex ea "
                + "commodo consequat.");
    }
}

Extend the Details Fragment

Khi sử dụng lớp DetailsFragment để hiển thị các media item details của chúng ta, sử dụng các phương thức được cung cấp giống như việc xem trước ảnh và hoạt động của media item. Chúng ta cũng có thể cung cấp thêm nội dụng bổ sung chẳng hạn như một danh sách các media items có liên quan.

Đoạn code sau trình bày cách sử dụng lớp presenter trong phần trước, để thêm một hình ảnh hiển thị và hành động cho media item đang được xem. Ví dụ này cũng cho thấy việc bổ sung các media item có liên quan, được hiển thị ở bên dưới danh sách.

public class MediaItemDetailsFragment extends DetailsFragment {
    private static final String TAG = "MediaItemDetailsFragment";
    private ArrayObjectAdapter mRowsAdapter;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        Log.i(TAG, "onCreate");
        super.onCreate(savedInstanceState);

        buildDetails();
    }

    private void buildDetails() {
        ClassPresenterSelector selector = new ClassPresenterSelector();
        // Attach your media item details presenter to the row presenter:
        FullWidthDetailsOverviewRowPresenter rowPresenter =
            new FullWidthDetailsOverviewRowPresenter(
                new DetailsDescriptionPresenter());

        selector.addClassPresenter(DetailsOverviewRow.class, rowPresenter);
        selector.addClassPresenter(ListRow.class,
                new ListRowPresenter());
        mRowsAdapter = new ArrayObjectAdapter(selector);

        Resources res = getActivity().getResources();
        DetailsOverviewRow detailsOverview = new DetailsOverviewRow(
                "Media Item Details");

        // Add images and action buttons to the details view
        detailsOverview.setImageDrawable(res.getDrawable(R.drawable.jelly_beans));
        detailsOverview.addAction(new Action(1, "Buy $9.99"));
        detailsOverview.addAction(new Action(2, "Rent $2.99"));
        mRowsAdapter.add(detailsOverview);

        // Add a Related items row
        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(0, "Related Items", null);
        mRowsAdapter.add(new ListRow(header, listRowAdapter));

        setAdapter(mRowsAdapter);
    }
}

Tạo một Deatils Activity

Fragment chẳng hạn như DetailsFragment phải được chạy trên nền một activity để có thể hiển thị. Tạo một activity cho details view của chúng ta, riêng biệt cho các browse activity, cho phép chúng ta gọi details view sử dụng Intent. Phần này giải thích làm thế nào để xây dựng một activity cho phần details view. Bắt đầu tạo một details activity bằng cách xây dụng một layout tham chiếu đến DeatilsFragment:

<!-- file: res/layout/details.xml -->

<fragment xmlns:android="http://schemas.android.com/apk/res/android"
    android:name="com.example.android.mediabrowser.MediaItemDetailsFragment"
    android:id="@+id/details_fragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
/>

Tiếp theo, tạo một lớp activity để điều khiển layout chúng ta vừa tạo phía trên:

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

Cuối cùng, thêm phần khai báo activity vừa tạo vào manifest.

<application>
  ...

  <activity android:name=".DetailsActivity"
    android:exported="true"
    android:theme="@style/Theme.Leanback"/>

</application>

Khai báo lắng nghe sự kiện khi clicked vào items

Sau khi chúng ta thực hiện các DetailsFramgent, sửa đổi phần main media browsing view để di chuyển đến các details view khi ngưới dùng click . Để khai báo hành vi này, chúng ta dùng phương thức OnItemViewClickedListener đến BrowseFragment để khởi tạo activity của từng details item.

Ví dụ sau đây cho thấy làm thế nào để thực hiện một lắng nghe để bắt đầu xem chi tiết khi người dùng nhấp vào một mục media item trong main media browsing activity:

public class BrowseMediaActivity extends Activity {
    ...

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

        // create the media item rows
        buildRowsAdapter();

        // add a listener for selected items
        mBrowseFragment.OnItemViewClickedListener(
            new OnItemViewClickedListener() {
                @Override
                public void onItemClicked(Object item, Row row) {
                    System.out.println("Media Item clicked: " + item.toString());
                    Intent intent = new Intent(BrowseMediaActivity.this,
                            DetailsActivity.class);
                    // pass the item information
                    intent.getExtras().putLong("id", item.getId());
                    startActivity(intent);
                }
            });
    }
}

Phần tiếp theo chúng ta thảo luận về Displaying a Now Playing Card.

Thank in advande for your reading.