[译]如何使用Presenter和ViewHolder--Android TV 应用开发教程三

版权声明:本文为博主原创翻译文章,转载请注明出处。

推荐:
欢迎关注我创建的Android TV 简书专题,会定期给大家分享一些AndroidTv相关的内容:
https://www.jianshu.com/c/3f0ab61a1322

《[译]如何使用Presenter和ViewHolder--Android TV 应用开发教程三》

一.本章学习目标

在上一章中,我们查看了GridItemPresenter。 其关系如下。

  • Presenter:GridItemPresenter
  • ViewHolder的视图:TextView
  • CardInfo / Item:String

这是很简单的例子。 在本章中,我们继续介绍另一种类型的Presenter,

  • Presenter:CardPresenter
  • ViewHolderr的视图:ImageCardView
  • CardInfo / Item:Movie class

二.ImageCardView

ImageCardView类是Android SDK提供的,它提供了具有主图像,标题文本和内容文本的卡片设计布局。

ImageCardView是BaseCardView的子类,所以开源看BaseCardView这个类。这是对BaseCardView的解释:

android.support.v17.leanback.widget
public class BaseCardView extends android.widget.FrameLayout
响应某些状态更改的卡片样式布局。它的孩子们排列在垂直的列中,不同的地区在不同的时间可见。BaseCardView将根据其类型,子类型的区域可见性以及小部件的状态来绘制其子项。一个孩子可能被标记为属于三个地区之一:主,信息或额外。主区域始终可见,而信息和额外区域可以根据View的激活或选择状态设置为显示。卡状态通过调用setActivated和setSelected来设置。

BaseCardView本身不提供特定的设计布局。所以当你想利用这个,你可以制作具有特定设计的BaseCardView子类。 ImageCardView是其中之一,目前我只能找到ImageCardView类作为SDK提供的BaseCardView子类。

在本章中,我们将把ImageCardView添加到我们的代码中。

三.实现CardPresenter,Movie class

我将首先放置必要的文件。 右键单击包装,
1.New → class → CardPresenter
2.New → class → Movie
3.对于主image,我们使用movie.png。
从Android TV示例应用程序res / drawable / movie.png中复制。
4.我们将使用Android TV示例应用程序提供的实用程序功能。
将[package name] / Utils类从Android TV示例应用程序复制到你的源代码。

首先,Utils.java只是从AOSP复制,这将在下面。

/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package com.corochann.androidtvapptutorial;

import android.content.Context;
import android.graphics.Point;
import android.view.Display;
import android.view.WindowManager;
import android.widget.Toast;

/**
 * A collection of utility methods, all static.
 */
public class Utils {

    /*
     * Making sure public utility methods remain static
     */
    private Utils() {
    }

    /**
     * Returns the screen/display size
     */
    public static Point getDisplaySize(Context context) {
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Display display = wm.getDefaultDisplay();
        Point size = new Point();
        display.getSize(size);
        return size;
    }

    /**
     * Shows a (long) toast
     */
    public static void showToast(Context context, String msg) {
        Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
    }

    /**
     * Shows a (long) toast.
     */
    public static void showToast(Context context, int resourceId) {
        Toast.makeText(context, context.getString(resourceId), Toast.LENGTH_LONG).show();
    }

    public static int convertDpToPixel(Context ctx, int dp) {
        float density = ctx.getResources().getDisplayMetrics().density;
        return Math.round((float) dp * density);
    }

    /**
     * Formats time in milliseconds to hh:mm:ss string format.
     */
    public static String formatMillis(int millis) {
        String result = "";
        int hr = millis / 3600000;
        millis %= 3600000;
        int min = millis / 60000;
        millis %= 60000;
        int sec = millis / 1000;
        if (hr > 0) {
            result += hr + ":";
        }
        if (min >= 0) {
            if (min > 9) {
                result += min + ":";
            } else {
                result += "0" + min + ":";
            }
        }
        if (sec > 9) {
            result += sec;
        } else {
            result += "0" + sec;
        }
        return result;
    }
}

接着,Movie类定义CardPresenter将使用ImageCardView显示的CardInfo / Item。 它应该有信息

  • main image
  • title text
  • content text (studio)
    但是在第一阶段,我只用“标题”和“内容”信息。
/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package com.corochann.androidtvapptutorial;

import android.util.Log;

import java.net.URI;
import java.net.URISyntaxException;

/**
 *  Modified from AOSP sample source code, by corochann on 2/7/2015.
 *  Movie class represents video entity with title, description, image thumbs and video url.
 */
public class Movie {

    private static final String TAG = Movie.class.getSimpleName();

    static final long serialVersionUID = 727566175075960653L;
    private long id;
    private String title;
    private String studio;

    public Movie() {
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getStudio() {
        return studio;
    }

    public void setStudio(String studio) {
        this.studio = studio;
    }

    @Override
    public String toString() {
        return "Movie{" +
                "id=" + id +
                ", title='" + title + '\'' +
                '}';
    }
}

最后的实现是CardPresenter,它是Presenter的一个子类。 CardPresenter拥有从Parent的Presenter.ViewHolder扩展的ViewHolder。 该ViewHolder保存ImageCardView,用于显示Movie项目的UI。

/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package com.corochann.androidtvapptutorial;


import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.support.v17.leanback.widget.ImageCardView;
import android.support.v17.leanback.widget.Presenter;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;

/**
 * Modified from AOSP sample source code, by corochann on 2/7/2015.
 */
public class CardPresenter extends Presenter {

    private static final String TAG = CardPresenter.class.getSimpleName();

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

    static class ViewHolder extends Presenter.ViewHolder {
        private Movie mMovie;
        private ImageCardView mCardView;
        private Drawable mDefaultCardImage;

        public ViewHolder(View view) {
            super(view);
            mCardView = (ImageCardView) view;
            mDefaultCardImage = mContext.getResources().getDrawable(R.drawable.movie);
        }

        public void setMovie(Movie m) {
            mMovie = m;
        }

        public Movie getMovie() {
            return mMovie;
        }

        public ImageCardView getCardView() {
            return mCardView;
        }

        public Drawable getDefaultCardImage() {
            return mDefaultCardImage;
        }

    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent) {
        Log.d(TAG, "onCreateViewHolder");
        mContext = parent.getContext();

        ImageCardView cardView = new ImageCardView(mContext);
        cardView.setFocusable(true);
        cardView.setFocusableInTouchMode(true);
        cardView.setBackgroundColor(mContext.getResources().getColor(R.color.fastlane_background));
        return new ViewHolder(cardView);
    }

    @Override
    public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
        Movie movie = (Movie) item;
        ((ViewHolder) viewHolder).setMovie(movie);

        Log.d(TAG, "onBindViewHolder");
        ((ViewHolder) viewHolder).mCardView.setTitleText(movie.getTitle());
        ((ViewHolder) viewHolder).mCardView.setContentText(movie.getStudio());
        ((ViewHolder) viewHolder).mCardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT);
        ((ViewHolder) viewHolder).mCardView.setMainImage(((ViewHolder) viewHolder).getDefaultCardImage());
    }

    @Override
    public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
        Log.d(TAG, "onUnbindViewHolder");
    }

    @Override
    public void onViewAttachedToWindow(Presenter.ViewHolder viewHolder) {
        // TO DO
    }

}

数据模型=Movie和presente= CardPresenter已经完成。 我们可以通过将项目放到适配器上来显示Movie项目。

private void loadRows() {
        mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
        ...

        /* CardPresenter */
        HeaderItem cardPresenterHeader = new HeaderItem(1, "CardPresenter");
        CardPresenter cardPresenter = new CardPresenter();
        ArrayObjectAdapter cardRowAdapter = new ArrayObjectAdapter(cardPresenter);

        for(int i=0; i<10; i++) {
            Movie movie = new Movie();
            movie.setTitle("title" + i);
            movie.setStudio("studio" + i);
            cardRowAdapter.add(movie);
        }
        mRowsAdapter.add(new ListRow(cardPresenterHeader, cardRowAdapter));

         ...
    }

四.第一次运行

《[译]如何使用Presenter和ViewHolder--Android TV 应用开发教程三》
《[译]如何使用Presenter和ViewHolder--Android TV 应用开发教程三》

CardPresenter标题将显示在第二行,ImageCardView显示默认的卡片图像。 当您从标题移动到内容(项目为“onActivated”)时,标题和内容文本将会出现。

源代码在github上。

使用Picasso从网上下载图片后更新主图

示例显示ImageCardView中必须包含在应用程序中的默认图像(图像是静态的)。 但是,有时希望使用从网页下载的图像,以便你的应用程序可以显示更新的信息。

Picasso图像加载库将帮助我们轻松实现。 以下是参考。

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:recyclerview-v7:22.2.0'
    compile 'com.android.support:leanback-v17:22.2.0'
    compile 'com.squareup.picasso:picasso:2.3.2'
}

我们将添加cardImageUrl成员到Movie类,它指向主图像的URL。

    private String cardImageUrl;

    public String getCardImageUrl() {
        return cardImageUrl;
    }

    public void setCardImageUrl(String cardImageUrl) {
        this.cardImageUrl = cardImageUrl;
    }

getter和setter可以由Android studio自动生成。 在上述修改中,您只需要声明cardImageUrl成员,然后再按[Alt] + [Insert]并生成getter和setter。 我们还实现了一个getImageURI函数,将URL字符串转换为URI格式。
Movie.java

   public URI getCardImageURI() {
        try {
            return new URI(getCardImageUrl());
        } catch (URISyntaxException e) {
            return null;
        }
    }

CardPresenter负责使用Picasso更新图像。 这通过实现updateCardViewImage函数来完成。 Picasso使源代码看起来更能直观了解加载和转换图像。

        public ViewHolder(View view) {
            super(view);
            mCardView = (ImageCardView) view;
            mImageCardViewTarget = new PicassoImageCardViewTarget(mCardView);
            mDefaultCardImage = mContext.getResources().getDrawable(R.drawable.movie);
        }

           ...

        protected void updateCardViewImage(URI uri) {
            Picasso.with(mContext)
                    .load(uri.toString())
                    .resize(Utils.convertDpToPixel(mContext, CARD_WIDTH),
                            Utils.convertDpToPixel(mContext, CARD_HEIGHT))
                    .error(mDefaultCardImage)
                    .into(mImageCardViewTarget);
        }
    }


    ...

    @Override
    public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
        Movie movie = (Movie) item;
        ((ViewHolder) viewHolder).setMovie(movie);

        Log.d(TAG, "onBindViewHolder");
        if (movie.getCardImageUrl() != null) {
            ((ViewHolder) viewHolder).mCardView.setTitleText(movie.getTitle());
            ((ViewHolder) viewHolder).mCardView.setContentText(movie.getStudio());
            ((ViewHolder) viewHolder).mCardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT);
            ((ViewHolder) viewHolder).updateCardViewImage(movie.getCardImageURI());
            //((ViewHolder) viewHolder).mCardView.setMainImage(((ViewHolder) viewHolder).getDefaultCardImage());
        }
    }

在updateCardViewImage的最后一行,它调用(mImageCardViewTarget)方法将图像加载到imageview。 该目标的实现如下。

    public static class PicassoImageCardViewTarget implements Target {
        private ImageCardView mImageCardView;

        public PicassoImageCardViewTarget(ImageCardView imageCardView) {
            mImageCardView = imageCardView;
        }

        @Override
        public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom loadedFrom) {
            Drawable bitmapDrawable = new BitmapDrawable(mContext.getResources(), bitmap);
            mImageCardView.setMainImage(bitmapDrawable);
        }

        @Override
        public void onBitmapFailed(Drawable drawable) {
            mImageCardView.setMainImage(drawable);
        }

        @Override
        public void onPrepareLoad(Drawable drawable) {
            // Do nothing, default_background manager has its own transitions
        }
    }

界面目标是在picasso库中定义的

represents an arbitrary listener for image loading.

目标界面允许我们实现3个监听器功能。

  • onBitmapLoade – 图像成功加载时回调。
  • onBitmapFailed – 图像成功加载时回调。 链接错误()
  • onPrepareLoad – 在您的请求提交之前调用回调。 与占位符()连接

剩下的任务是从MainFragment指定cardImageUrl,这是在

    private void loadRows() {

        ...

        for(int i=0; i<10; i++) {
            Movie movie = new Movie();
            movie.setCardImageUrl("http://heimkehrend.raindrop.jp/kl-hacker/wp-content/uploads/2014/08/DSC02580.jpg");
            movie.setTitle("title" + i);
            movie.setStudio("studio" + i);
            cardRowAdapter.add(movie);
        }
         ...
    }

最后,您需要在构建之前添加在AndroidManifest.xml中使用Internet的权限。



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

    <uses-permission android:name="android.permission.INTERNET" />

    ...


再次运行

《[译]如何使用Presenter和ViewHolder--Android TV 应用开发教程三》

现在主图像是从互联网上下载的。
Source code is on github.

自定义ImageCardView,BaseCardView

我们可以改变设计风格和卡的动画行为。 首先,我建议在Android SDK提供的源代码中引用BaseCardView说明,

BaseCardView将根据其类型,子类型的区域可见性以及小部件的状态来绘制其子项。 一个孩子可能被标记为属于三个地区之一: main, info, extra。 main区域始终可见,而info和extra可以根据View的激活或选择状态设置为显示。 Card状态通过调用setActivated和setSelected来设置。

在BaseCardView类中,您可以检查可用于更改设计的选项。

  • public void setCardType(int type)
  • public void setInfoVisibility(int visibility)
  • public void setExtraVisibility(int visibility)

setCardType(int type)

你可以为card设置下面的参数:

  • CARD_TYPE_MAIN_ONLY
  • CARD_TYPE_INFO_OVER
  • CARD_TYPE_INFO_UNDER
  • CARD_TYPE_INFO_UNDER_WITH_EXTRA
    ImageCardView的例子:

    《[译]如何使用Presenter和ViewHolder--Android TV 应用开发教程三》 CARD_TYPE_MAIN_ONLY
    《[译]如何使用Presenter和ViewHolder--Android TV 应用开发教程三》 CARD_TYPE_INFO_OVER

《[译]如何使用Presenter和ViewHolder--Android TV 应用开发教程三》 CARD_TYPE_INFO_UNDER,CARD_TYPE_INFO_UNDER_WITH_EXTRA

您可以在SDK文件夹\ sdk \ extras \ android \ support \ v17 \ leanback \ res \ layout \ lb_image_card_view.xml中查看ImageCardView的布局。

ImageCardView具有imageView作为主要区域,标题和内容文本位于信息区域。 没有设置额外的区域,因此CARD_TYPE_INFO_UNDER和CARD_TYPE_INFO_UNDER_WITH_EXTRA之间的行为是相同的。

setInfoVisibility(int visibility), setExtraVisibility(int visibility)

你可以设置以下的参数:

  • CARD_REGION_VISIBLE_ALWAYS- 区域(标题和内容文本区域)将始终显示.
  • CARD_REGION_VISIBLE_ACTIVATED – 当用户选择页眉时,该区域不会出现。当用户移动到RowsFragment时,该区域将显示。
  • CARD_REGION_VISIBLE_SELECTED – 未选择此卡/项目时,该区域不会出现。仅当选择卡/项目时,该区域才会出现。

这些选项的更详细的解释可以参考SDK源代码。

这里我通过修改CardPresenter类中的onCreateViewHolder来改变设置,

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent) {
        Log.d(TAG, "onCreateViewHolder");
        mContext = parent.getContext();

        ImageCardView cardView = new ImageCardView(mContext);
        cardView.setCardType(BaseCardView.CARD_TYPE_INFO_UNDER);
        cardView.setInfoVisibility(BaseCardView.CARD_REGION_VISIBLE_ALWAYS);
        cardView.setFocusable(true);
        cardView.setFocusableInTouchMode(true);
        cardView.setBackgroundColor(mContext.getResources().getColor(R.color.fastlane_background));
        return new ViewHolder(cardView);
    }

源码在 github.

下一章,Picasso背景管理 – Android TV应用手册教程四,实现背景图像更新功能。
关注微信公众号,定期为你推荐移动开发相关文章。

《[译]如何使用Presenter和ViewHolder--Android TV 应用开发教程三》 songwenju

    原文作者:wenju_song
    原文地址: https://www.jianshu.com/p/051fb11a15a9
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞