个人项目中比较好的设计, 整理记录一下

前言

记录一下自己在项目中实现的比较好的方案, 免得以后忘了.
如果有具体实现则使用伪代码说明.


多种不同类型新闻的列表显示(策略模式)

设计目的

数据类型太多(单图, 图集, 纯文本, 视频等等), 如果在数据展示列表 adapter 的 getView 中 进行类型判断、各种类型的 convertView 构建的话, 大量的 if/else 或者大块的 switch 会导致 adapter 十分臃肿, 难以维护, 所以使用策略模式处理不同类型 convertView 的构建. 实现的多类型列表场景:
实现的多类型列表场景

具体实现

  1. 新建新闻model的基类 BaseNews

    public abstract class BaseNews {
        protected String publicParams; // 其他公共参数
    
        // 获取adapter中ConvertView的抽象方法
        abstract View getConvertView(NewsAdapter.ViewHolder viewHolder); 
    
        protected void otherPublicFunc() {
            ...
        }
    }
  2. 创建请求接口返回的数据model

    public class NewsBean {
        public int newsType; // 新闻类型
        public String otherParams;
    }
  3. 为各个类型的新闻创建子类

    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;
        }
    }
    
    ...
  4. 新闻工厂

    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;
            }
        }
    }
  5. 新闻列表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{
            ...
        }
    }
  6. 接口回调中

    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. 这里写图片描述

具体实现

  1. 自定义的控件, 内部维护一个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);
            }
        }
    }
  2. 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);
            }
        }
        ...
    }
  3. 用于传递数据的PagerGridBean, 存储真正的数据以及各个RecyclerView的布局属性.

    public class PagerGridBean {
        /**
         * 真正的adapter数据
         */
        public CopyOnWriteArrayList list;
        /**
         * 当前页的布局
         */
        @LayoutRes
        int itemView;
        /**
         * 每行的item个数
         */
        int lineSize;
        /**
         * 行数
         */
        int lineNum;
        /**
         * onBindViewHolder 处理时需要的操作
         */
        BindViewHolderRunnable bindViewHolderRunnable;
    }
  4. 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();
        }
    
        ...
    }
  5. 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" />

github连接, 有完整代码.

猜你喜欢

转载自blog.csdn.net/j550341130/article/details/79192184