美文网首页AndroidAndroid TVAndroidTV开发
[译]如何使用Presenter和ViewHolder--And

[译]如何使用Presenter和ViewHolder--And

作者: wenju_song | 来源:发表于2017-04-17 09:48 被阅读1886次

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

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


    一.本章学习目标

    在上一章中,我们查看了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));
    
             ...
        }
    

    四.第一次运行


    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" />
    
        ...
    
    
    

    再次运行


    现在主图像是从互联网上下载的。
    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的例子:


      CARD_TYPE_MAIN_ONLY
      CARD_TYPE_INFO_OVER
    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应用手册教程四,实现背景图像更新功能。
    关注微信公众号,定期为你推荐移动开发相关文章。

    songwenju

    相关文章

      网友评论

      • 丶萤火虫:谢谢博主
      • 刻舟求剑KJ:楼主你这翻译的也太生硬了吧:joy:
        wenju_song:@刻舟求剑KJ 嗯嗯,我抽空再通读一遍,争取做到信达雅
        刻舟求剑KJ:@wenju_song 比如 『右键单击包装』,还有『首先,Utils.java只是从AOSP复制,这将在下面。』
        wenju_song:精力有限,哪里觉得生硬了,我再修改修改:smile:

      本文标题:[译]如何使用Presenter和ViewHolder--And

      本文链接:https://www.haomeiwen.com/subject/jjojottx.html