先聊会天儿,正文开始有标记,可直接跳过。或者你看效果满足了你的需求,直接去下载项目也行。
最近一直在做一个类TvLauncher,为什么说是“类”呢?因为在我实在Launcher3的基础上又添加了两类Launcher,一类是GameLauncer--类似XBox等大型游戏机的主界面;而另一类就是TvLauncher--同普通的智能电视主界面一样,但我们的设计更加酷炫。所以其实是一种带有三种不同类型的Launcher界面,这儿咱们就单说一下咱们的电视Launcher。
想知道我们的产品是什么吗?京东搜索“万核客厅一体机”,即可找到我们的产品,在产品介绍中就可以看到我做的三种Launcher界面。还可以详细了解一下我们的产品,对于普通用户来说,它叫客厅一体机,集普通Android用户,Android游戏发烧友用户以及电视盒子用户于一身的一台神器!对于我们Android程序猿来说,他也是一台很不错的开发机哦!(我这么说,经理会不会***我?)
简单看一下我们的产品的三类Launcher主界面吧!很炫吧?,那就快去仔细看看吧!Please Go--京东万核旗舰店
看到里面的三种不同的主界面了吧,从左到右依次是:TvLauncher PcLauncher GameLauncher;
看第一个 TVLauncher 这是头布局,有五种不同的FocusView!!!刚拿到时,还真是懵了一下子!
我们公司的产品及设计者决定要做一个与众不同的TvLauncher,看这篇文章的你应该懂得吧,他们稍稍的一个与众不同,我们就不知道要死多少脑细胞!不过,换个角度想想,还是挺喜欢他们的,因为这样可以让我的项目也与众不同,而且也会学习到更多的开发技巧。
下面的这个TvRecyclerView基本就满足了你的各种开发需求了吧!
我的项目是我参考别人的项目(参考文献见文末)修改完成的,但我的项目太复杂,展示起来麻烦,也增加了理解难度系数,这里就简单的把别人项目中的一个比较好的点摘出来,稍加修改,使之更能完美的符合我们的需求。
-------------------------正文开始-------------------------
首先看一下效果图:
主要功能:
1:任意锁定焦点位置,如本例是将开始的焦点框跟随Item种的FocusView,后面的是让焦点框锁定在屏幕中间;
2:自由定义焦点缩放比例;焦点框的视觉效果;
3:可以任意定义FocusView,不受Item影响,复杂布局处理起来就简单了;
功能实现:
在这里,我就只把关键代码附上,其它的可以去下载自由查看;
其中,我会我注意到的关键的地方做好注释,方便理解。
1:RecyclerView的自定义及初始化:
首先决定焦点框是否跟随Item移动和位置的关键方法如下:
@Override public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) { /** * Creator:David * 首先,我们需要在绑定Holder的时候,按需要给item设置不同的焦点状态标记; * 然后,通过标记决定我们焦点框的状态 */ Object tag = child.getTag(R.id.focus_item_view_type); if (tag != null && ((int) tag == 1)) { //不需要锁定焦点框,正常返回时,焦点框跟随item移动。 return super.requestChildRectangleOnScreen(child, rect, immediate); } //锁定焦点位置,让recyclerView自己控制滑动距离,使Item和焦点框重合 focusItemOffsetX = focusItemOffsetY = 0; View focusView = child.findFocus(); Rect rectFocus = new Rect(); focusView.getDrawingRect(rectFocus); offsetDescendantRectToMyCoords(focusView, rectFocus); //dx dy 决定了焦点框锁定位置,可以自己设计算法,按需求确定位置 int dx = 0, dy = 0; if (getLayoutManager().canScrollHorizontally()) { dx = rectFocus.left - (getWidth() - rectFocus.width()) / 2; } else if (getLayoutManager().canScrollVertically()) { dy = rectFocus.top - (getHeight() - rectFocus.height() / 2); } if (immediate) { scrollBy(dx, dy); } else { smoothScrollBy(dx, dy); } return true; }初始化RecyclerView:
private void initView() { recyclerView.setHasFixedSize(true);//当确定Item的改变不影响RecyclerView的宽高的时候可以设置setHasFixedSize(true); recyclerView.setLastLineItemHandKey(true, false, true, false);//设置四个边缘是否消耗按键,屏幕的上下左右 gridLayoutManager = new GridLayoutManager(this, 4, LinearLayoutManager.HORIZONTAL, false); recyclerView.setLayoutManager(gridLayoutManager); recyclerView.setOnLoadMoreListener(this);//自定义加载更多的回调接口 recyclerView.addItemDecoration(new DividerGridItemDecoration(Color.GRAY, 20, 20, Color.GREEN, 20)); /** * Creator:David * 设置需要占用多行或列的Item的占用列数 */ gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { switch (rvAdapter.getItemViewType(position)) { case 2://当 ItemViewType为2时,让该Item占用两行或两列 return 2; default: return 1; } } }); //初始化完成开始创建数据源并加载数据 pageNumber = 0; createData(pageNumber); }
2:Adapter和Holder的处理:
这里在Library_recyclerView中,定义了他们两个的抽象类,我们在书写的时候可以直接继承,减少了相同代码的重复书写。只简单介绍用法,Livrary_recyclerView可以下载带全部效果的项目,然后在项目中查看。
先看用法:
private void createData(int pageNumber) { RecommendBean model; for (int i = pageNumber * pageCount; i < (pageNumber + 1) * pageCount; i++) { model = new RecommendBean(); model.type = i == 0 ? 2 : 0; model.title = "" + i; model.imgResColor = getResources().getColor(R.color.color_d7292b); data.add(model); } if (rvAdapter == null) { rvAdapter = new GridMiddleAdapter(this, data); recyclerView.setAdapter(rvAdapter); } else { recyclerView.setLoadMoreComplete();//加载完成时调用 rvAdapter.notifyDataSetChanged(); /** * Creator:David * 此处可能会需要另外的两种处理方法,方法中重写了 notifyDataSetChanged(); */ // rvAdapter.setDataList(data); // //or // rvAdapter.addDataList(data); } }再看Adapter: RvAdapter是可以自己按需求修改的,方便开发。
public class GridMiddleAdapter extends RVAdapter<RecommendBean, RVHolder> { public GridMiddleAdapter(Context context, List<RecommendBean> mDataList) { super(context, mDataList); } @Override public RVHolder newViewHolder(ViewGroup parent, int viewType) { View view = null; RVHolder holder = null; switch (viewType) { case 1: view = mInflater.inflate(R.layout.item_grid_page_diff_span, parent, false); holder = new Holder(view, AbsFocusEffectView.FocusType.FOCUS_POSTER, true, true); break; case 2: view = mInflater.inflate(R.layout.item_grid_page_group, parent, false); holder = new HolderGroup(view, AbsFocusEffectView.FocusType.FOCUS_POSTER, true, true); break; default: view = mInflater.inflate(R.layout.item_grid_page_h, parent, false); holder = new Holder(view, AbsFocusEffectView.FocusType.FOCUS_POSTER, true, true); break; } return holder; } @Override public int getItemViewType(int position) { return getItem(position).type; } }然后是Holder们:在RVHolder中可以自己按需求定义方法,方便开发。
public class Holder extends RVHolder<RecommendBean> { @BindView(R.id.recommend_img) ImageView bg; @BindView(R.id.recommend_title) TextView title; public Holder(View itemView, String focusViewType, boolean isFocusScaleAnim, boolean isTranslateAnim) { super(itemView, focusViewType, isFocusScaleAnim, isTranslateAnim); ButterKnife.bind(this, itemView); } @Override public void bindData(RecommendBean model, int position, int state, int dx, int dy, int firstVisPosition, int lastVisPosition) { /** * Creator:David * 给Item做标记,区分不需要焦点居中的Item; */ if (position < 3) { this.itemView.setTag(R.id.focus_item_view_type, 1); } if (model != null) { bg.setBackgroundColor(model.imgResColor); title.setText(model.title); } } @Override public void onItemClick(View view) { if (getAdapterPosition() == 30) { recyclerView.smoothScrollToPosition(0); } Toast.makeText(view.getContext(), "点击中间焦点:" + getAdapterPosition(), Toast.LENGTH_SHORT).show(); } @Override public void onFocusChanged(View view, boolean b) { } @Override public void destory() { } }
public class HolderGroup extends RVHolder<RecommendBean> { @BindView(R.id.recommend_img) ImageView bg; @BindView(R.id.recommend_title) TextView title; @BindView(R.id.item1) FrameLayout item1; @BindView(R.id.recommend_img2) ImageView bg2; @BindView(R.id.recommend_title2) TextView title2; @BindView(R.id.item2) FrameLayout item2; @BindView(R.id.recommend_img3) ImageView bg3; @BindView(R.id.recommend_title3) TextView title3; @BindView(R.id.item3) FrameLayout item3; @BindView(R.id.recommend_img4) ImageView bg4; @BindView(R.id.recommend_title4) TextView title4; @BindView(R.id.item4) FrameLayout item4; @BindView(R.id.recommend_img5) ImageView bg5; @BindView(R.id.recommend_title5) TextView title5; @BindView(R.id.item5) FrameLayout item5; private List<ImageView> images; private List<TextView> tvs; public HolderGroup(View itemView, String focusViewType, boolean isFocusScaleAnim, boolean isTranslateAnim) { super(itemView, focusViewType, isFocusScaleAnim, isTranslateAnim); ButterKnife.bind(this, itemView); /** * Creator:David * 初始化Group中的焦点View */ initView(item1, focusViewType, isFocusScaleAnim, isTranslateAnim); initView(item2, focusViewType, isFocusScaleAnim, isTranslateAnim); initView(item3, focusViewType, isFocusScaleAnim, isTranslateAnim); initView(item4, focusViewType, isFocusScaleAnim, isTranslateAnim); initView(item5, focusViewType, isFocusScaleAnim, isTranslateAnim); images = new ArrayList<>(); images.add(bg); images.add(bg2); images.add(bg3); images.add(bg4); images.add(bg5); tvs = new ArrayList<>(); tvs.add(title); tvs.add(title2); tvs.add(title3); tvs.add(title4); tvs.add(title5); } @Override public void bindData(RecommendBean model, int position, int state, int dx, int dy, int firstVisPosition, int lastVisPosition) { /** * Creator:David * 给Item做标记,区分不需要焦点居中的Item; */ if (position < 3) { this.itemView.setTag(R.id.focus_item_view_type, 1); } if (model != null) { for (ImageView iv : images) { showImageViewColor(iv, model.imgResColor); } for (TextView tv : tvs) { showTextview(tv, model.title); } } } @Override public void onItemClick(View view) { String clickViewwID; switch (view.getId()) { case R.id.item1: clickViewwID = "R.id.item1"; break; case R.id.item2: clickViewwID = "R.id.item2"; break; case R.id.item3: clickViewwID = "R.id.item3"; break; case R.id.item4: clickViewwID = "R.id.item4"; break; case R.id.item5: clickViewwID = "R.id.item5"; break; default: clickViewwID = null; break; } Toast.makeText(view.getContext(), "点击:" + getAdapterPosition() + ";子ViewID:" + clickViewwID, Toast.LENGTH_SHORT).show(); } @Override public void onFocusChanged(View view, boolean b) { } @Override public void destory() { } }
OK 需要自己写的东西基本上就结束了,其实很简单,不过,还是需要稍微理解一下library_recyclerview中的内容;
首先:目录结构:
其实,这原先是个Module,但是个性使然,我不喜欢它,就把它移动到自己项目中,并且删掉了自己不需要的东西,你也可以去下载完整的依赖包和查看我参考的Demo,Demo中有所有可以实现的效果。
参考文献:https://github.com/ShuKeW/TVRecyclerViewAndFocus
本项目参考该项目,如有侵权,立即删除... ...