前言:剧集类控件,在TV app中非常常见,今天将介绍构建一个TV app中的剧集列表控件,此控件上传到我的Github:https://github.com/hejunlin2013/EpisodeListView,点击【阅读原文】,可看对应的github, 喜欢可以star。Agenda如下:效果图

效果图gif

实现思路

代码分析

效果图

效果图gif:

实现思路:1、用两个RecycleView作为控件横向布局

2、PopupWindow作为该集剧情简介

3、当焦点到达Parent时,对Child进行监听,并发生变化,同理,如果Child超过10个时,通知Parent

代码分析:

EpisodeListView.java 作用:负责组配两个RecycleView填充对应的数据

焦点监听及获焦情况

public class EpisodeListView extends RelativeLayout implements View.OnFocusChangeListener {    public static final String TAG = EpisodeListView.class.getSimpleName();    private Context mContext;    private RelativeLayout mContentPanel;    private RecyclerView mChildrenView;    private RecyclerView mParentView;    private LinearLayoutManager mEpisodesLayoutManager;    private LinearLayoutManager mGroupLayoutManager;    private EpisodeListViewAdapter mEpisodeListAdapter;    private ChildrenAdapter mChildrenAdapter;    private ParentAdapter mParentAdapter;    private Handler mHandler = new Handler(Looper.getMainLooper());    public EpisodeListView(Context context) {        this(context, null);    }    public EpisodeListView(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public EpisodeListView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        if (!isInEditMode()) {            mContext = context;            init();        }    }    private void init() {        LayoutInflater inflater = LayoutInflater.from(mContext);        inflater.inflate(R.layout.episodelist_layout, this, true);        mChildrenView = (RecyclerView) findViewById(R.id.episodes);        mParentView = (RecyclerView) findViewById(R.id.groups);        mEpisodesLayoutManager = new LinearLayoutManager(mContext, LinearLayout.HORIZONTAL, false);        mGroupLayoutManager = new LinearLayoutManager(mContext, LinearLayout.HORIZONTAL, false);        mChildrenView.setLayoutManager(mEpisodesLayoutManager);        mParentView.setLayoutManager(mGroupLayoutManager);        mChildrenView.setItemAnimator(new DefaultItemAnimator());        mParentView.setItemAnimator(new DefaultItemAnimator());        mChildrenView.setOnFocusChangeListener(this);        mParentView.setOnFocusChangeListener(this);        this.setOnFocusChangeListener(this);    }    public void setAdapter(final EpisodeListViewAdapter adapter) {        mEpisodeListAdapter = adapter;        mChildrenAdapter = adapter.getEpisodesAdapter();        mParentAdapter = adapter.getGroupAdapter();        mChildrenView.setAdapter(mChildrenAdapter);        mParentView.setAdapter(mParentAdapter);        mParentAdapter.setOnItemClickListener(new ParentAdapter.OnItemClickListener() {            @Override            public void onGroupItemClick(View view, int position) {                mEpisodesLayoutManager.scrollToPositionWithOffset(adapter.getChildrenPosition(position), 0);            }        });        mParentAdapter.setOnItemFocusListener(new ParentAdapter.OnItemFocusListener() {            @Override            public void onGroupItemFocus(View view, int position, boolean hasFocus) {                int episodePosition = adapter.getChildrenPosition(position);                mChildrenAdapter.setCurrentPosition(episodePosition);                mEpisodesLayoutManager.scrollToPositionWithOffset(adapter.getChildrenPosition(position), 0);            }        });        mChildrenAdapter.setOnItemFocusListener(new ChildrenAdapter.OnItemFocusListener() {            @Override            public void onEpisodesItemFocus(View view, int position, boolean hasFocus) {                if (hasFocus) {                    int groupPosition = adapter.getParentPosition(position);                    mGroupLayoutManager.scrollToPositionWithOffset(groupPosition, 0);                    mParentAdapter.setCurrentPosition(adapter.getParentPosition(groupPosition));                }            }        });        mChildrenAdapter.setOnItemClickListener(new ChildrenAdapter.OnItemClickListener() {            @Override            public void onEpisodesItemClick(View view, int position) {            }        });    }    public void setLongFocusListener(ChildrenAdapter.OnItemLongFocusListener listener) {        mChildrenAdapter.setOnItemLongFocusListener(listener);    }    @Override    public boolean dispatchKeyEvent(KeyEvent event) {        if (event.getAction() == KeyEvent.ACTION_DOWN) {            switch (event.getKeyCode()) {                case KeyEvent.KEYCODE_DPAD_UP:                    if (mParentView.hasFocus()) {                        mChildrenView.requestFocus();                        return true;                    }                    break;                case KeyEvent.KEYCODE_DPAD_DOWN:                    if (mChildrenView.hasFocus()) {                        mParentView.requestFocus();                        return true;                    }                    break;                case KeyEvent.KEYCODE_DPAD_RIGHT:                    if (mChildrenView.hasFocus()                            && mChildrenAdapter.getCurrentPosition() >= mChildrenAdapter.getData().size() - 1) {                        return true;                    }                    if (mParentView.hasFocus()                            && mParentAdapter.getCurrentPosition() >= mParentAdapter.getDatas().size() - 1) {                        return true;                    }            }        }        return super.dispatchKeyEvent(event);    }    @Override    public void onFocusChange(View v, boolean hasFocus) {        if (v == this && hasFocus) {            mChildrenView.requestFocus();        } else if (v == mChildrenView && hasFocus) {            View child = mChildrenView.getLayoutManager().findViewByPosition(mChildrenAdapter.getCurrentPosition());            if (child != null) {                child.requestFocus();            } else {                int lastPosition = mEpisodesLayoutManager.findLastVisibleItemPosition();                child = mEpisodesLayoutManager.findViewByPosition(lastPosition);                if (child != null)                    child.requestFocus();            }        } else if (v == mParentView && hasFocus) {            View child = mParentView.getLayoutManager().findViewByPosition(mParentAdapter.getCurrentPosition());            if (child != null) {                child.requestFocus();            }        }    }}

EpisodeListViewAdapter 作用:抽象类,在实例化时负责将外部数据转成list传入

实例化ParentAdapter及ChildrenAdapter

public abstract class EpisodeListViewAdapter{    private ChildrenAdapter mChildrenAdapter;    private ParentAdapter mParentAdapter;    public EpisodeListViewAdapter() {        mChildrenAdapter = new ChildrenAdapter(getChildrenList());        mParentAdapter = new ParentAdapter(getParentList());    }    public ChildrenAdapter getEpisodesAdapter() {        return mChildrenAdapter;    }    public ParentAdapter getGroupAdapter() {        return mParentAdapter;    }    public void setSelectedPositions(List positions) {        mChildrenAdapter.setSelectedPositions(positions);    }    public abstract List getChildrenList();    public abstract List getParentList();    public abstract int getChildrenPosition(int childPosition);    public abstract int getParentPosition(int parentPosition);}

ParentAdapter 作用:每10集为一组,进行控制

public class ParentAdapter extends RecyclerView.Adapter {    private static final int GROUPS_COLUMN_COUNT = 10;    private OnItemClickListener mItemClickListener;    private OnItemFocusListener mItemFocusListener;    private List mDatas;    private int parentWidth;    private int itemWidth;    private int mCurrentPosition;    public ParentAdapter(List datas) {        mDatas = datas;    }    @Override    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {        View view = LayoutInflater.from(parent.getContext())                .inflate(R.layout.parent_item, parent, false);        MyViewHolder holder = new MyViewHolder(view);        parentWidth = parent.getMeasuredWidth();        itemWidth = (parentWidth -                (holder.textView.getPaddingLeft() + holder.textView.getPaddingRight()) * (GROUPS_COLUMN_COUNT))                / GROUPS_COLUMN_COUNT + 1;        return holder;    }    @Override    public void onBindViewHolder(MyViewHolder holder, final int position) {        holder.textView.setText(mDatas.get(position));        holder.textView.setWidth(itemWidth);        holder.textView.setFocusable(true);        holder.textView.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                mItemClickListener.onGroupItemClick(v, position);            }        });        holder.textView.setOnFocusChangeListener(new View.OnFocusChangeListener() {            @Override            public void onFocusChange(View v, boolean hasFocus) {                if (hasFocus) {                    mItemFocusListener.onGroupItemFocus(v, position, hasFocus);                    mCurrentPosition = position;                }            }        });    }    @Override    public int getItemCount() {        return mDatas.size();    }    public List getDatas() {        return mDatas;    }    public int getCurrentPosition() {        return mCurrentPosition;    }    public void setCurrentPosition(int position) {        mCurrentPosition = position;    }    public void setOnItemFocusListener(OnItemFocusListener listener) {        mItemFocusListener = listener;    }    public void setOnItemClickListener(OnItemClickListener listener) {        mItemClickListener = listener;    }    class MyViewHolder extends RecyclerView.ViewHolder {        TextView textView;        public MyViewHolder(View view) {            super(view);            textView = (TextView) view.findViewById(R.id.item);        }    }    public interface OnItemClickListener {        void onGroupItemClick(View view, int position);    }    public interface OnItemFocusListener {        void onGroupItemFocus(View view, int position, boolean hasFocus);    }}

ChildrenAdapter 作用:每行最多显示10个,大于10可以左右变换

parent之间焦点变换时,children可立即响应。字数限制,不贴代码,可直接对应github看。

android tv 开发布局,Android TV开发总结(七)构建一个TV app中的剧集列表控件相关推荐

  1. Android TV开发总结(四)通过RecycleView构建一个TV app列表页(仿腾讯视频TV版)

    转载请把头部出处链接和尾部二维码一起转载,本文出自逆流的鱼yuiop:http://blog.csdn.net/hejjunlin/article/details/52854131 前言:昨晚看锤子手 ...

  2. Android TV开发总结(三)构建一个TV app的焦点控制及遇到的坑

    原文:Android TV开发总结(三)构建一个TV app的焦点控制及遇到的坑 版权声明:我已委托"维权骑士"(rightknights.com)为我的文章进行维权行动.转载务必 ...

  3. android tv 菜单键,Android TV开发总结(三)构建一个TV app的焦点控制及遇到的坑

    前言:关于<TV Metro界面(仿泰捷视频TV版)源码解析>由于都是相关代码,就不发公众号了,有兴趣的可以看链接:http://blog.csdn.net/hejjunlin/artic ...

  4. android tv 云播放器,Android TV开发总结(六)构建一个TV app的直播节目实例

    近年来,Android TV的迅速发展,传统的有线电视受到较大的冲击,在TV上用户同样也可以看到各个有线电视的直播频道,相对于手机,这种直播节目,体验效果更佳,尤其是一样赛事节目,大屏幕看得才够痛快, ...

  5. Android开发-列表控件

    列表控件是Android中最常见的控件之一 由于手机屏幕空间都比较有限,能够一次性在屏幕上显示的内容并不多,当我们的程序中有大量的数据需要展示的时候,就可以借助各种列表控件来实现. <ListV ...

  6. android 固定底部 布局_Android系统列表控件

    在android系统控件中,有多个控件可以展示列表数据. 一.ListView 该组件是android中最常用的一个UI组件,用于实现在屏幕上显示多个内容,以便于我们用手指进行滑动. ListView ...

  7. Android怎么自定义布局,Android 创建自定义的布局

    为可穿戴设备创建布局是和手持设备是一样的,除了我们需要为屏幕的尺寸和glanceability进行设计.但是不要期望通过搬迁手持应用的功能与UI到可穿戴上会有一个好的用户体验.仅仅在有需要的时候,我们 ...

  8. android滚动悬停布局,android布局滑动到顶端悬浮,录音

    ok 首先我们来看看是不是咱们想要的效果!免得浪费大家时间 这篇没劲,我另外一篇详细一点可以看一下 怎么样要这个效果么 其实这个实现方法,很简单只需使用原生的一些控件就能实现,记得刚开始接触这个的时候 ...

  9. 【操作系统】HarmonyOS App开发工具HUAWEI DevEco Studio下载安装及第一个HarmonyOS App实战教程

    路标 DevEco Studio简介 DevEco Studio的下载安装 安装过程演示 开发第一个基于Java的TV App 微信公众号原文链接:点击查看 2020年9月10日,华为开发者大会发布了 ...

最新文章

  1. MVC,MVP 和 MVVM 的图示
  2. Java编程之前的复习和练习
  3. 【错误记录】jar 执行错误 ( java.lang.UnsupportedClassVersionError: Unsupported major.minor version 52.0 )
  4. boost::type_erasure::add_assignable相关的测试程序
  5. linux拷贝文件夹怎么删除,linux文件及文件夹拷贝移动删除
  6. c语言鼠标移动响应,CSS鼠标响应事件经过、移动、点击示例介绍
  7. servlet下载文件(注意文件名字必须是英文)
  8. html中css二级联动,html二级联动学习笔记
  9. thinkphp跨库操作代码实例
  10. linux改狗命令,linux安装安全狗
  11. 【DTOJ】2701:问候
  12. 一步步完成FastDFS + Spring MVC上传下载整合示例
  13. 编译编译时,用不到的库,一定不要链接
  14. 22_多易教育之《yiee数据运营系统》用户画像-消费行为性别预测篇
  15. vs哪个版本比较好用_哪个跨境电商erp比较好用,跨境电商erp哪个好一点
  16. 如何向公众号添加的H5页面的链接(已解决)
  17. bypassing waf's in sql injection
  18. Aspnet Mvc 前后端分离项目手记(二)关于token认证
  19. 怎么卸载Safari浏览器?
  20. [0]SWM181-从零开发华芯微特MCU

热门文章

  1. 哈罗单车融资几十亿元,蚂蚁金服与春华资本加持
  2. BZOJ 1003 物流运输 最短路+dp
  3. winform自定义控件
  4. 基于Angular创建后台数据模拟(译)
  5. VS环境下的makefile编译
  6. Windows操作系统启动介绍(二)
  7. 甜、酸、苦、辣、咸与健康
  8. springMVC 前台向后台传数组
  9. SpringBoot集成EasyPoi实现Excel导入导出
  10. Django - 网页加载报错:A server error occurred. Please contact the administrator(亲测)