先聊会天儿,正文开始有标记,可直接跳过。或者你看效果满足了你的需求,直接去下载项目也行。
最近一直在做一个类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
本项目参考该项目,如有侵权,立即删除... ...
==============================================
@ Name : David
@ email :david.forever.god@gmail.com
Learn from yesterday, live for today, hope for tomorrow.