草庐IT

设计模式实践-适配器模式,给LinearLayout插上Adapter的翅膀

h2coder 2023-03-28 原文

前言

最近app首页又改版了,不得不说现在项目越来越臃肿,什么模块都想整个入口在首页,导致首页的复杂度增加。为了适应多种类型的视图,一般我们会使用RecyclerView,但是在首页的条目的类型越来越多,甚至存在1种type就只有一个条目,这样导致RecyclerView的复用根本派不上用场,毕竟复用需要多个同type的条目才能产生复用。

甚至需要在RecyclerView的item中,再嵌入一个横向滑动的RecyclerView,或者是一个ViewPager,再或者一个九宫格的RecyclerView。每次滑动条目进出屏幕,都进行onBindView,再重新刷新item里面RecyclerView的adapter和数据等,造成卡顿,在低端机尤为明显。

于是决定把嵌套层级修改一下,最外层不再是一个RecyclerView,而是使用NestScrollView包裹几个不同模块RecyclerViewRecyclerView的item如果是垂直排列的,使用LinearLayout来垂直排列,而不是再嵌套一层RecyclerView,好处是LinearLayout离开屏幕不需要重新绘制,省去了不必要的开销。

思考

如果使用LinearLayout来排列,如果需要展示多个不同类型的item,代码写起来非常麻烦,可能需要for循环不断new View或者LayoutInflate.from(context).inflate(...),代码非常不优雅。而且拓展性非常差,如果突然UI设计改版,需要插入一个新的条目在中间位置,那代码就太难看了。

解决办法

RecyclerView可以显示那么多种类型的条目,使用的是Adapter适配器模式,把数据转换为ViewHolder,ViewHolder中存放着每个条目的Item,这样RecyclerView只需要面对需要的View即可,终于View长什么样,用数据数据渲染,都和RecyclerView没有关系,RecyclerView只需要把它add进自身就可以了。

那么LinearLayout可不可以也使用上Adapter适配器模式呢,经过翻看RecyclerView的源码,发现适配器模式实现起来并不难,几百行代码就可以搞定了。

  • 自定义一个ListLayout,继承于LinearLayout,在构造方法中,设置为垂直排列
  • 设置适配器Adapter,清除自身所有子View
  • 调用adapter.onCreateViewHolder(),获取每个条目的ViewHolder,创建条目View
  • 调用adapter.onBindViewHolder(),以条目数据,渲染条目内容
  • 把条目View添加到自身

优点

使用方法和RecyclerView基本是一模一样,所以可以轻松把一些RecyclerView的封装库进行移植,让LinearLayout使用起来更加方便。

推荐移植MultiType,这个库专注于RecyclerView的多类型条目绑定,支持一对一、一对多等,代码量少,非常优雅。

移植MultiType,修改的代码量非常少,基本是2个文件即可,所以让它支持上ListViewGridView都非常轻松,甚至是本篇文章的ListLayout

我移植的是3.5.0版本的MultiType,并非最新版。

项目地址

ListLayout

源码

/**
 * 垂直列表布局,支持类似RecyclerView的Adapter、ViewHolder的写法,但没有它的条目复用和滚动能力
 * 为了和其他滚动控件协调滚动的灵活性,所以需要滚动效果时,需要包一层NestScrollView来实现
 */
public class ListLayout extends LinearLayout {
    public static final long NO_ID = -1;
    /**
     * 适配器
     */
    private Adapter mAdapter;
    /**
     * 列表数据观察者,当数据notifyChange时,重新填充子View
     */
    private final ListLayoutDataObserver mObserver = new ListLayoutDataObserver();

    public ListLayout(Context context) {
        this(context, null);
    }

    public ListLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ListLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs, defStyleAttr);
    }

    private void init(Context context, AttributeSet attrs, int defStyleAttr) {
        //子View垂直排列
        setOrientation(LinearLayout.VERTICAL);
    }

    /**
     * 设置适配器
     */
    public void setAdapter(Adapter adapter) {
        //旧的Adapter取消监听
        if (mAdapter != null) {
            mAdapter.unregisterAdapterDataObserver(mObserver);
        }
        //新的Adapter注册监听
        mAdapter = adapter;
        if (adapter != null) {
            adapter.registerAdapterDataObserver(mObserver);
        }
        //重新填充子视图
        populate();
    }

    /**
     * 获取适配器
     */
    public Adapter getAdapter() {
        return mAdapter;
    }

    /**
     * 按数据填充视图
     */
    private void populate() {
        //清除掉之前的子View
        removeAllViews();
        //开始新建子View
        int itemCount = mAdapter.getItemCount();
        for (int position = 0; position < itemCount; position++) {
            long itemId = mAdapter.getItemId(position);
            int itemViewType = mAdapter.getItemViewType(position);
            //创建ViewHolder
            ViewHolder viewHolder = mAdapter.onCreateViewHolder(this, itemViewType);
            //设置Item的布局参数
            ViewGroup.LayoutParams itemLp = viewHolder.itemView.getLayoutParams();
            if (itemLp == null) {
                itemLp = new ListLayout.LayoutParams(
                        ListLayout.LayoutParams.MATCH_PARENT,
                        ListLayout.LayoutParams.WRAP_CONTENT
                );
            }
            //设置相关属性
            viewHolder.setAdapter(mAdapter);
            viewHolder.setItemViewType(itemViewType);
            viewHolder.setAdapterPosition(position);
            viewHolder.setItemId(itemId);
            //渲染ViewHolder,内部会渲染布局
            mAdapter.onBindViewHolder(viewHolder, position);
            //添加子View
            addView(viewHolder.itemView, itemLp);
        }
    }

    /**
     * 列表适配器
     */
    public abstract static class Adapter<VH extends ViewHolder> {
        private final AdapterDataObservable mObservable = new AdapterDataObservable();

        public abstract VH onCreateViewHolder(ViewGroup parent, int itemType);

        public abstract void onBindViewHolder(VH holder, int position);

        public int getItemViewType(int position) {
            return 0;
        }

        public long getItemId(int position) {
            return NO_ID;
        }

        public abstract int getItemCount();

        /**
         * 是否有观察者
         */
        public final boolean hasObservers() {
            return mObservable.hasObservers();
        }

        /**
         * 注册列表数据观察者
         */
        public void registerAdapterDataObserver(AdapterDataObserver observer) {
            mObservable.registerObserver(observer);
        }

        /**
         * 取消注册数据观察者
         */
        public void unregisterAdapterDataObserver(AdapterDataObserver observer) {
            mObservable.unregisterObserver(observer);
        }

        /**
         * 通知数据更新
         */
        public final void notifyDataSetChanged() {
            if (hasObservers()) {
                mObservable.notifyChanged();
            }
        }
    }

    public abstract static class ViewHolder {
        long mItemId = NO_ID;

        /**
         * 适配器
         */
        private Adapter adapter;
        /**
         * 条目View
         */
        public final View itemView;
        /**
         * 条目类型
         */
        private int mItemViewType;
        /**
         * 位置
         */
        private int adapterPosition;
        /**
         * 条目Id
         */
        private long itemId;

        public ViewHolder(View itemView) {
            this.itemView = itemView;
        }

        public void setItemViewType(int itemViewType) {
            mItemViewType = itemViewType;
        }

        public int getItemViewType() {
            return mItemViewType;
        }

        public void setAdapter(Adapter adapter) {
            this.adapter = adapter;
        }

        public Adapter getAdapter() {
            return adapter;
        }

        public void setAdapterPosition(int adapterPosition) {
            this.adapterPosition = adapterPosition;
        }

        public int getAdapterPosition() {
            return adapterPosition;
        }

        public void setItemId(long itemId) {
            this.mItemId = itemId;
        }

        public long getItemId() {
            return mItemId;
        }
    }

    public abstract static class AdapterDataObserver {
        public void onChanged() {
        }
    }

    /**
     * 列表数据观察者,当数据notifyChange时,重新填充子View
     */
    private class ListLayoutDataObserver extends AdapterDataObserver {
        ListLayoutDataObserver() {
        }

        @Override
        public void onChanged() {
            super.onChanged();
            //数据更新,重新填充子View
            populate();
        }
    }

    /**
     * 适配器被观察者
     */
    private static class AdapterDataObservable extends Observable<AdapterDataObserver> {
        /**
         * 是否有观察者
         */
        public boolean hasObservers() {
            return !mObservers.isEmpty();
        }

        /**
         * 通知观察者数据改变
         */
        public void notifyChanged() {
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onChanged();
            }
        }
    }

    /**
     * 被观察者
     */
    private static class Observable<T> {
        /**
         * 观察者列表
         */
        protected final List<T> mObservers = new ArrayList<T>();

        /**
         * 注册观察者
         */
        public void registerObserver(T observer) {
            if (observer == null) {
                throw new IllegalArgumentException("observer不能为空");
            }
            synchronized (mObservers) {
                if (!mObservers.contains(observer)) {
                    mObservers.add(observer);
                }
            }
        }

        /**
         * 取消注册观察者
         */
        public void unregisterObserver(T observer) {
            if (observer == null) {
                throw new IllegalArgumentException("observer不能为空");
            }
            synchronized (mObservers) {
                mObservers.remove(observer);
            }
        }

        /**
         * 取消订阅所有观察者
         */
        public void unregisterAll() {
            synchronized (mObservers) {
                mObservers.clear();
            }
        }
    }
}

有关设计模式实践-适配器模式,给LinearLayout插上Adapter的翅膀的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  3. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  4. ruby - capybara field.has_css?匹配器 - 2

    我在MiniTest::Spec和Capybara中使用以下规范:find_field('Email').must_have_css('[autofocus]')检查名为“电子邮件”的字段是否具有autofocus属性。doc说如下:has_css?(path,options={})ChecksifagivenCSSselectorisonthepageorcurrentnode.据我了解,字段“Email”是一个节点,因此调用must_have_css绝对有效!我做错了什么? 最佳答案 通过JonasNicklas得到了答案:No

  5. ruby - 如何在续集中重新加载表模式? - 2

    鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende

  6. ruby-on-rails - 使用 rails 4 设计而不更新用户 - 2

    我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它​​不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数

  7. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  8. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  9. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  10. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

随机推荐