前言:剧集类控件,在TV app中非常常见,今天将介绍构建一个TV app中的剧集列表控件,此控件上传到我的Github:github.com/hejunlin201…,点击【阅读原文】,可看对应的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<T>{
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<Integer> positions) {
mChildrenAdapter.setSelectedPositions(positions);
}
public abstract List<String> getChildrenList();
public abstract List<String> getParentList();
public abstract int getChildrenPosition(int childPosition);
public abstract int getParentPosition(int parentPosition);
}
ParentAdapter作用:
-
每10集为一组,进行控制
public class ParentAdapter extends RecyclerView.Adapter<ParentAdapter.MyViewHolder> {
private static final int GROUPS_COLUMN_COUNT = 10;
private OnItemClickListener mItemClickListener;
private OnItemFocusListener mItemFocusListener;
private List<String> mDatas;
private int parentWidth;
private int itemWidth;
private int mCurrentPosition;
public ParentAdapter(List<String> 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<String> 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,源码分析,最新开源项目推荐,更多有价值的思考,欢迎关注我的微信公众号,扫一扫下方二维码或者长按识别二维码,即可关注。