前言
记录一下自己在项目中实现的比较好的方案, 免得以后忘了.
如果有具体实现则使用伪代码说明.
多种不同类型新闻的列表显示(策略模式)
设计目的
数据类型太多(单图, 图集, 纯文本, 视频等等), 如果在数据展示列表 adapter 的 getView 中 进行类型判断、各种类型的 convertView 构建的话, 大量的 if/else 或者大块的 switch 会导致 adapter 十分臃肿, 难以维护, 所以使用策略模式处理不同类型 convertView 的构建. 实现的多类型列表场景:
具体实现
新建新闻model的基类 BaseNews
public abstract class BaseNews { protected String publicParams; // 其他公共参数 // 获取adapter中ConvertView的抽象方法 abstract View getConvertView(NewsAdapter.ViewHolder viewHolder); protected void otherPublicFunc() { ... } }
创建请求接口返回的数据model
public class NewsBean { public int newsType; // 新闻类型 public String otherParams; }
为各个类型的新闻创建子类
public class SingleImageNews extends BaseNews { private String privateParams; public SingleImageNews(NewsBean newsBean) { initData(); // 初始化数据等 } @Override public View getContentView(NewsAdapter.ViewHolder viewHolder) { ... return singleImageView; } } public class MuiltImageNews extends BaseNews { private String privateParams; public MuiltImageNews (NewsBean newsBean) { initData(); // 初始化数据等 } @Override public View getContentView(NewsAdapter.ViewHolder viewHolder) { ... return muiltImageView; } } ...
新闻工厂
public class NewsFactory { public static BaseNews getNews(NewsBean newsBean) { switch (newsBean.newsType) { case 0: return new SingleImageNews(newsBean); case 1: return new MuiltImageNews(newsBean); ... default: return null; } } }
新闻列表adapter
public class NewsAdapter extends BaseAdapter { @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder; ... return getItem(position).getContentView(viewholder); // TODO 所有的地方都是为了简化此处 } public class ViewHolder{ ... } }
接口回调中
private List<NewsBean> newsBeanList; private NewsAdapter newsAdapter; @Override public void onSuccess(List<NewsBean> list) { for(NewsBean newsBean : list) { newsBeanList.add(NewsFactory.getNews(newsBean)); // 使用NewsFactory构建不同类型的News } newsAdapter.notifyDataSetChanged(); }
自定义控件: 动态加载的带有指示器可循环滚动的菜单页
设计目的
项目中很多地方都用到了动态加载的菜单页, 主要思路是使用 ViewPager+RecyclerView实现, 但是再加上小白点指示器的话, 代码稍显复杂, 所以封装了一个控件, 在布局中引入, 在类中设置一个数据绑定方法即可实现此效果, 且一条属性实现循环滚动, 每页的UI均可不同. 效果图:
1.
2.
3.
4.
具体实现
自定义的控件, 内部维护一个ViewPager+RadioGroup, ViewPager作为主界面, RadioGroup实现小白点切换指示器. 根据布局中定义的属性处理数据集合, 实现循环滚动.
public class PagerGridView extends FrameLayout { private boolean cycleScroll; // 用于控制是否循环滚动的参数 public PagerGridView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { LayoutInflater.from(getContext()).inflate(R.layout.layout_pager_grid, this); viewPager = (ViewPager) findViewById(R.id.view_pager); // 主体界面的viewpager radioGroup = (RadioGroup) findViewById(R.id.radio_group); // 小白点指示器的RadioGroup // 处理TypedArray获取是否是可以循环滑动的viewpager final TypedArray typedArray = context.obtainStyledAttributes(...); cycleScroll = typedArray.getBoolean(R.styleable..., false); typedArray.recycle(); // 处理小白点指示器RadioGroup的联动, 以及循环滚动相关 viewPager.addOnPageChangeListener(...) } public void setPagerAdapter(Context context, CopyOnWriteArrayList<PagerGridBean> list) { this.dataList = list; refreshStrip(); // 如果是循环滚动的话处理数据首尾 if (cycleScroll && dataList.size() > 1) { dataList.add(0, dataList.get(dataList.size() - 1)); dataList.add(dataList.get(1)); } pagerGridAdapter = new PagerGridAdapter(context, dataList); viewPager.setAdapter(pagerGridAdapter); if (cycleScroll && dataList.size() > 1) { viewPager.setCurrentItem(1); } } // 动态添加RadioButton private void refreshStrip() { radioGroup.removeAllViews(); int pageNum = dataList.size(); if (pageNum > 1) { while (pageNum-- > 0) { LayoutInflater.from(getContext()).inflate(R.layout.., radioGroup, true); } ((RadioButton) radioGroup.getChildAt(0)).setChecked(true); } } }
ViewPager的adapter, 主要是在构造方法中处理数据集合, 构建RecyclerView集合.
扫描二维码关注公众号,回复: 1680958 查看本文章public class PagerGridAdapter extends PagerAdapter { PagerGridAdapter(Context context, CopyOnWriteArrayList<PagerGridBean> list) { ... initData(); } private void initData() { gridViewList.clear(); for (PagerGridBean pagerGridBean : dataList) { RecyclerView recyclerView = new RecyclerView(context); // 每行的个数 recyclerView.setLayoutManager(new GridLayoutManager(context, pagerGridBean.lineSize)); recyclerView.setAdapter(new SwitchAdapter(context, pagerGridBean)); gridViewList.add(recyclerView); } } ... }
用于传递数据的PagerGridBean, 存储真正的数据以及各个RecyclerView的布局属性.
public class PagerGridBean { /** * 真正的adapter数据 */ public CopyOnWriteArrayList list; /** * 当前页的布局 */ @LayoutRes int itemView; /** * 每行的item个数 */ int lineSize; /** * 行数 */ int lineNum; /** * onBindViewHolder 处理时需要的操作 */ BindViewHolderRunnable bindViewHolderRunnable; }
ViewPager内的RecyclerView的adapter
public class SwitchAdapter extends RecyclerView.Adapter<SwitchAdapter.ViewHolder> { SwitchAdapter(Context context, PagerGridBean pagerGridBean) { this.context = context; this.list = pagerGridBean.list; this.itemView = pagerGridBean.itemView; this.lineSize = pagerGridBean.lineSize; this.lineNum = pagerGridBean.lineNum; this.bindViewHolderRunnable = pagerGridBean.bindViewHolderRunnable; } @Override public void onBindViewHolder(SwitchAdapter.ViewHolder holder, int position) { // 因为内部并不知道使用集合数据的那个字段构建界面, 所以需要在**集合数据**中传入构建接口 bindViewHolderRunnable.onBindViewHolder(holder, list.get(position)); } @Override public int getItemCount() { // 如果规定显示的个数比list中少的情况下, 按限制行数显示 return list.size() > lineNum * lineSize ? lineNum * lineSize : list.size(); } ... }
activity中使用此布局
override fun onCreate(savedInstanceState: Bundle?) { ... var i = 0 while (i < 10) { i++ drawableList.add(R.mipmap.ic_launcher) } dataList.add(PagerGridBean(drawableList, R.layout..., 5, 2, bindHolderRunnable)) dataList.add(PagerGridBean(drawableList, R.layout..., 5, 2, bindHolderRunnable)) pagerGridView.setPagerAdapter(this, dataList) } private val bindHolderRunnable = BindViewHolderRunnable { viewHolder, drawableResource -> viewHolder.imageView.setBackgroundResource(drawableResource as Int) }
布局文件
<com.jdw.widget.PagerGridView android:id="@+id/pagerGridView" android:layout_width="match_parent" android:layout_height="120dp" app:cycleScroll="true" />