首页 文章 精选 留言 我的

精选列表

搜索[android jetpack],共10000篇文章
优秀的个人博客,低调大师

Android选择列表CheckListView

我之前做过一个封装,也发过一篇文章,https://www.jianshu.com/p/7e81f4f02a2c 但是在我之后的开发时我又不断的去为这个封装组件添加新东西, 关键是添加多了就变得臃肿,再加上我当时犯了一个很大的错误,我对这个组件进行扩展时我没有写注释也没有写文档,导致我现在每次看这个组件内部的代码都要研究一段时间,所以我决定防止之前的那个组件加上对面向对象进一步的了解进行重新的封装。 不多说,先上gayhub地址,节约时间就随便先写了两个demohttps://github.com/994866755/handsomeYe.KylinCheckListView 一.展示页面 1.设计布局 首先做的是先设计我们要展示怎么样子的页面,按我的做法会先画个简单的图。 还是分左右两种情况,这样我可以设计成Item使用LinearLayout来通过来按顺序addView,这个顺序可以定义一个showLacation参数来决定。 2.展示的思路 展示的思路我想按我之前封装的那个一样,我觉得那个思路很好。就是内部封装写好adapter和viewholder,图中内部的方块用个viewmodel来代替,这个viewmodel由外界传入,就相当于普通RecyclerView的viewholder 3.开发基本页面 先不考虑单选/多选的逻辑操作,先不考虑与外部的关联,先不考虑组件的扩展,而是现在组件内部把页面给跑起来。 public class KylinCheckListView extends FrameLayout{ // 单选与多选 public static final int RADIO = 0; public static final int MULTISELECT = 1; // 显示左/显示右 public static final int CHECKLEFT = 0; public static final int CHECKRIGHT = 1; // 列表控件 protected RecyclerView mRecyclerView; // 显示位置 protected int showLacation = CHECKLEFT; // 子布局 protected Class<?> itemClass; // 数据 protected List<String> datalist = new ArrayList<>(); // 适配器 protected KylinCheckListAdapter mAdapter; public KylinCheckListView(Context context) { super(context); create(); } public KylinCheckListView(Context context, AttributeSet attrs) { super(context, attrs); // todo 在xml中添加自定义参数 create(); } public KylinCheckListView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public KylinCheckListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } /** * 初始化操作 */ protected void create(){ initView(); initAdapter(); } /** * 初始化View */ protected void initView(){ mRecyclerView = initList(); this.addView(mRecyclerView); } /** * 初始化RecyclerView */ protected RecyclerView initList(){ RecyclerView recyclerView = new RecyclerView(getContext()); FrameLayout.LayoutParams lp = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); recyclerView.setLayoutParams(lp); recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));// todo 可设置传入参数 布局 recyclerView.addItemDecoration(new BerItemDecoration(1)); // todo 可设置传入参数 间距 return recyclerView; } /** * 初始化Adapter */ protected void initAdapter(){ mAdapter = new KylinCheckListAdapter(); } /** * 设置数据 */ public void setDataToView(List<String> datalist){ this.datalist = datalist; mRecyclerView.setAdapter(mAdapter); } // todo 添加删除列表数据等操作 /** * get or set */ //返回列表 public RecyclerView getRecyclerView() { return mRecyclerView; } // 设置Item的布局类 public void setItemClass(Class<?> itemClass) { this.itemClass = itemClass; } /** * RecycerView 的 Adapter */ public class KylinCheckListAdapter extends RecyclerView.Adapter<KylinCheckListViewHolder>{ @Override public int getItemViewType(int position) { return super.getItemViewType(position); } @Override public KylinCheckListViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { LinearLayout itemLayout = initChildLayout(); KylinCheckListViewHolder viewHolder = new KylinCheckListViewHolder(itemLayout); return viewHolder; } protected LinearLayout initChildLayout(){ LinearLayout itemLayout = new LinearLayout(getContext()); ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); // todo 可以在此设置padding等操作 itemLayout.setLayoutParams(lp); itemLayout.setOrientation(LinearLayout.HORIZONTAL); itemLayout.setBackgroundResource(R.color.app_blue); return itemLayout; } @Override public void onBindViewHolder(KylinCheckListViewHolder holder, int position) { holder.setPosition(position); holder.setData(datalist.get(position)); } @Override public int getItemCount() { return datalist.size(); } } /** * RecycerView 的 ViewHolder */ public class KylinCheckListViewHolder extends RecyclerView.ViewHolder{ protected CheckBox mCheckBox; protected String data; protected int position; protected CheckViewModel mViewModel; public KylinCheckListViewHolder(View itemView) { super(itemView); initView(); } private void initView(){ mCheckBox = initCheckBox(); mViewModel = initViewModel(); if (itemView instanceof LinearLayout){ LinearLayout llContent = (LinearLayout) itemView; // 没有核心布局的话没必要线束布局 if (mViewModel != null){ View contentChild = mViewModel.getContentView(); ViewGroup.LayoutParams lp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); contentChild.setLayoutParams(lp); if (showLacation == CHECKLEFT){ llContent.addView(mCheckBox); llContent.addView(contentChild); }else if (showLacation == CHECKRIGHT){ llContent.addView(contentChild); llContent.addView(mCheckBox); } } } } private CheckBox initCheckBox(){ CheckBox checkBox = new CheckBox(getContext()); LinearLayout.LayoutParams cbLp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); checkBox.setLayoutParams(cbLp); return checkBox; } private CheckViewModel initViewModel(){ try { // todo 添加Bundle情况的反射 Class[] paramTypes = new Class[]{Context.class}; Object[] params = new Object[]{getContext()}; Constructor con = itemClass.getConstructor(paramTypes); mViewModel = (CheckViewModel) con.newInstance(params); return mViewModel; } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } return null; } public void setData(String data){ this.data = data; if (mViewModel != null){ mViewModel.setData(data); } } public void setPosition(int position) { this.position = position; } } } 按照这样写法(当然写代码的中间调整了很多细节),可以得到结果 这样确实能得到一个我想要的结果,默认情况下选框在左,核心布局由外部传入通过反射来实现。 看看代码中的一些细节 (1)内部类没有使用静态内部类是因为我想让内部类直接能拿到外部类的变量,如果用静态内部类的话要定义然后传进去,参数有点多,我不想这样做。 (2)核心布局是由外部传入一个Class类让进行反射得到对象,我以前是写通过类名得到,但是传类名的话要把包名也写上,太啰嗦了,这里就改成传class对象。 其它也没什么,到这步代码也不难看懂。 二.添加数据 1.设计数据结构 页面能正常展示之后可以开始设计数据结构了。我打算把数据结构设计成像之前封装的那样,就是先写个基类结构来保存选框的选择状态。 public class CheckListEntity { public boolean isCheck = false; } 然后在之前的代码中用泛型来代替写死的String类型 public class KylinCheckListView<T extends CheckListEntity> extends FrameLayout{ 传进这个组件的列表的数据类型强制要求继承CheckListEntity类 2.isCheck 关联选框 更改viewholder中的seyData()方法 public void setData(T data){ this.data = data; // 设置Item数据 if (mViewModel != null){ mViewModel.setData(data); } // 设置选框状态 if (mCheckBox != null){ mCheckBox.setChecked(data.isCheck); } } 更改viewholder中的initCheckBox()方法,添加点击选框的事件 private CheckBox initCheckBox(){ CheckBox checkBox = new CheckBox(getContext()); LinearLayout.LayoutParams cbLp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); checkBox.setLayoutParams(cbLp); checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { data.isCheck = isChecked; // todo 单选情况下的逻辑 } }); return checkBox; } 这样就能正常的关联view和data。但是我想再多说个我开发过程中的心得。怎么知道数据已经关联?我个人是使用Debug来观察数据的走向,所以我想说的是,像我开发这个组件,功能很多,代码最后算起来也有几百上千行,这种情况下首先你要明确你一个开发的流程,想好再动手,然后这种情况肯定大量的用到Debug,所以不熟悉的肯定要弄懂,最后就是todo注释,可以看到我代码中有些地方先写了todo 三.选框的逻辑操作 数据已经不用关心了,我们把数据丢给相对的CheckViewModel来做,之后怎么处理展示页面中Item的数据,是CheckViewModel的事,而CheckListView内部也已经通过isChecked拿到它只关心的选框展示,所以已经拿到数据后开始做选框的逻辑操作。 1.单选情况 单选情况需要点击另一个选框,之前的选框选项就会被取消。 我之前有个加了todo的地方,可以看到我现在就能很快的找到在哪块地方写这个单选的逻辑。 选着优秀的算法来开发逻辑 要做这个逻辑,可能很多人会想就是,点击之后,用个for循环,把选中的position的data的isChecked变成true,然后其它变成false,再刷新列表。是不是,肯定很多人会有这种想法。虽然这种做法也行,而且我觉得对性能基本没影响,但是我有强迫症,我看到if啊,看到for啊,我就浑身不舒服。所以我打算用空间换取时间的做法: 定义一个变量来保存正在选中的选框的position,之后的做法就不用我说了吧。 在事件中设置逻辑 checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (showType == RADIO){ // 单选情况 Observable.just(position).subscribe(radioCheckAct); }else { //多选情况 data.isCheck = isChecked; //todo 多选情况下的其它操作 } } }); 用观察者 private Action1<Integer> radioCheckAct = new Action1<Integer>() { @Override public void call(Integer integer) { // 判断边界 if (integer >= datalist.size()){ return; } // 重复选的话别浪费时间去刷新 if (integer == radioIndex){ return; } if (radioIndex != -1){ datalist.get(radioIndex).isCheck = false; } datalist.get(integer).isCheck = true; radioIndex = integer; updataAdapter(); } }; private void updataAdapter(){ mAdapter.notifyDataSetChanged(); } 运行后发现报错Cannot call this method while RecyclerView is computing a layout or scrolling。 读不懂是什么意思,然后上网查,发现很多解决方法。这里再说说我的一个开发心得,防虫和抓虫。比如说我在网上找这个BUG,很多文章都是告诉你怎么怎么解决啊,直接贴代码给你啊,其实我很不喜欢这种文章,我就真的是很坦白说了,我不喜欢这种文章。 当我还是一个萌新时,我会直接抄解决的代码下来,正常就不管,还是不正常就找其他方法。这其实是一个防虫的过程,就算成功运行,你也只是防止了这个BUG发生,但是它可以通过其它环境或者方式再次使你的程序出BUG,这是一种不安全的方式,所以我很不建议就是直接抄别人的代码。你必须要找到这个错误在哪,这就是抓虫,就算你花很多时间你也要去抓。 其实道理很简单,你发现一只蚊子,你给自己喷花露水(防虫),还是拍死它(抓虫)。 回来讲讲这个BUG,我找了很多文章都没有详细说这个BUG是什么情况,我只能慢慢Debug去找,发现了最终的问题。如果我调用 notifyDataSetChanged()的话,会更新列表,但是列表设置数据时执行了mCheckBox.setChecked(data.isCheck); 而状态一变就会去调用那个监听的方法,就会notifyDataSetChanged(),也就是说在notifyDataSetChanged()的过程中执行notifyDataSetChanged(),所以就会报这个错误。 那我就不应该用setOnCheckedChangeListener监听,改用setOnClickListener来监听 checkBox.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (showType == RADIO){ // 单选情况 Observable.just(position).subscribe(radioCheckAct); }else { //todo 多选情况下的其它操作 } } }); 这样就能正常实现效果 2.多选情况 和单选一样,也是使用Rx来做 // 多选情况下的观察者 private Action1<Integer> multCheckAct = new Action1<Integer>() { @Override public void call(Integer integer) { // 判断边界 if (integer >= datalist.size()){ return; } datalist.get(integer).isCheck = !(datalist.get(integer).isCheck); updataAdapter(); // todo 优化成局部刷新 } }; 3.返回选择结果 既然是封装,必然需要返回所选取的选项的结果。 这里我打算先定义一个接口来定义这个View的和外界对接的行为。 public interface KylinCheckListImpl { /** * 单选的结果 */ int radioResult(); /** * 多选的结果 */ List<Integer> multResults(); /** * 多选时已选择的数量 (可以用这个数量来判断是否全选) */ int multCount(); /** * 全选/全不选 */ void multAllCheck(boolean state); /** * 单选 */ void radioCheck(int position); /** * 设置选择 */ void check(int position); /** * 懒更新 */ void lazyUpdate(); } 暂时先就想到这么多View和外界交互的行为,让KylinCheckListView实现这个就接口,然后重写接口中的方法。上面定义的radioCheck(int position)和check(int position)区别在于一个马上更新,一个不会马上更新 还有写这个接口有什么用,如果你有新的交互方法要扩展,直接在接口里面定义。 4.写接口中的方法 (1)radioResult public int radioResult() { return radioIndex; } 之前我说过radioIndex是记录单选的坐标,所以这里直接返回这个就是单选的结果,-1代码没选。 (2)multResults 返回多选的结果比较麻烦,我需要返回一个数组 public List<Integer> multResults() { List<Integer> resultList = new ArrayList<>(); for (int i = 0; i < datalist.size(); i++) { if (datalist.get(i).isCheck) { resultList.add(i); } } return resultList; } (3)multCount public int multCount() { int count = 0; for (int i = 0; i < datalist.size(); i++) { if (datalist.get(i).isCheck) { count++; } } return count; } 一般我们可以拿到这个多选时的数量和总数量对比,判断是否全选,但是想想,如果我要做一个每选择一次都要判断是否全选的话,就会没选一次都进行一次循环,这虽然对体验没什么影响,但我有强迫症,我肯定不容许每次都浪费时间去重复循环,所以我想了一个新的办法来判断是否全选。 (4)isMultAll 新添加的方法,用于判断是否全选 public int isMultAll(int count,int position){ if (count < 1){ return 0; } if (position >= datalist.size()){ return count; } if (datalist.get(position).isCheck){ return count++; }else { return count--; } } 可以看到我传入两个参数,第一个表示目前总选择的数量,第二个表示当前点击的选项的下标。我是这样想的,如果做每点一次都判断多选的话,我可以在一开始先用multCount获取到总选中的数量,然后再对每次选中的进行判断,如果当次选中的选项的isCheck是true,那就加1,相反减1,然后返回,这样就不用每次都做循环。至于行不行,等我全部写完试试就知道了。 (5)multAllCheck 根据传入的boolean值来设置全选或者全不选 public void multAllCheck(boolean state) { for (int i = 0; i < datalist.size(); i++) { datalist.get(i).isCheck = state; } mAdapter.notifyDataSetChanged(); } (6)radioCheck public void radioCheck(int position) { if (position < datalist.size()){ datalist.get(position).isCheck = true; mAdapter.notifyDataSetChanged(); } } (7)check 和 lazyUpdate 这两个就是把radioCheck的设置和更新给分开 好了,这样就把一些和外界常用的交互给做完了,现在试试效果。是我直接用debug来试 单选radioCheck,出了点问题,改一下 public void radioCheck(int position) { Observable.just(position).subscribe(radioCheckAct); } 然后发现多选的isMultAll还是出问题,后来想想这种做法也不对,违反了迪米特原则,改一下。 我把count定义在内部,由我来做操作,而不是将它暴露给用户去使用。 public boolean isMultAll(){ return multCount == datalist.size(); } 到这里就做到了一些常用的与外界的关联逻辑 四.设置事件 我这暂时只用到了选中时的事件,之后扩展也不难,所以这里就先写选中的事件。 public interface KylinOnCheckListener { void kylinCheckChange(int position,boolean isChecked); } 两个观察者内部加监听的操作 // 单选情况下的观察者 private Action1<Integer> radioCheckAct = new Action1<Integer>() { @Override public void call(Integer integer) { // 判断边界 if (integer >= datalist.size()){ return; } // 重复选的话别浪费时间去刷新 if (integer == radioIndex){ return; } if (radioIndex != -1){ datalist.get(radioIndex).isCheck = false; } datalist.get(integer).isCheck = true; radioIndex = integer; updataAdapter(); if (kylinOnCheckListener != null){ kylinOnCheckListener.kylinCheckChange(radioIndex,true); } } }; // 多选情况下的观察者 private Action1<Integer> multCheckAct = new Action1<Integer>() { @Override public void call(Integer integer) { // 判断边界 if (integer >= datalist.size()){ return; } datalist.get(integer).isCheck = !(datalist.get(integer).isCheck); if (datalist.get(integer).isCheck){ multCount++; }else { multCount--; } updataAdapter(); // todo 优化成局部刷新 if (kylinOnCheckListener != null){ kylinOnCheckListener.kylinCheckChange(radioIndex,datalist.get(integer).isCheck); } } }; 五.View属性扩展 之前做了那个搜索框组件发现用attrs的话那个尺寸有问题,我这里打算测试一下自定义view中的getDimension得到的结果是什么 首先结果肯定是一个float类型的没错 (1)直接写默认值不加单位 type.getDimension(R.styleable.KylinCheckListViewStyle_cb_margin_left,16); 得到结果: 16.0 (2)传dp type.getDimension(R.styleable.KylinCheckListViewStyle_cb_margin_left, DimensionUtils.dip2px(getContext(),16)); 得到结果: 48.0 DimensionUtils.dip2px(getContext(),16)的结果: 48.0 (3)在xml中传px app:cb_margin_left = "16px" 得到结果: 16.0 (4)在xml中传dp app:cb_margin_left = "16dp" 得到结果:48.0 看了下结果,没毛病啊,但是为什么我之前那个搜索的就这么怪,先不管了,这里没毛病的话先直接弄。 添加常用的属性 <!-- 选框样式 --> <declare-styleable name="KylinCheckListViewStyle"> <!-- checkbox的margin --> <attr name="cb_margin_left" format="dimension"/> <attr name="cb_margin_right" format="dimension"/> <attr name="cb_margin_top" format="dimension"/> <attr name="cb_margin_bottom" format="dimension"/> <!-- checkbox的背景--> <attr name="cb_backgroup" format="reference"/> <!-- item的背景--> <attr name="item_backgroup" format="reference"/> <!-- item的gravity--> <attr name="item_gravity" format="integer"/> <!-- recyclerview的分割线--> <attr name="item_decoration" format="dimension"/> </declare-styleable> 我目前就写这么多,实在没时间加太多。 六.添加数据的操作 发现还没有做完啊,还要添加数据的增删。 /** * 添加数据 */ public void addData(List<T> datas){ datalist.addAll(datas); updataAdapter(); } /** * 添加数据 */ public void addData(T data){ datalist.add(data); updataAdapter(); } /** * 删除 */ public void removeData(int position){ datalist.remove(position); updataAdapter(); } 把datalist设置个get方法,增强扩展。 七.总结 全部代码在gayhub,这里就不重复贴了,抓紧时间直接做个总结。 1.不要重复造轮子 我这里重写封装已经封装过的组件是一个错误的做法,主要以前做扩展的时候没有整理,导致多次扩展后原来的组件变得有点乱,所以要讲第二点 2.扩展时要进行整理和更新文档 3.开发前先想清楚要做成什么样子,想清楚开发的步骤 4.碰到BUG时,尽量抓虫而不是防虫 框架扩展 在实际项目中碰到问题对框架进行扩展,但是这里写得太多内容,就分出去写一篇新的文章来讲所扩展的内容。 https://www.jianshu.com/p/98d6c3bae5ef

优秀的个人博客,低调大师

Android 懒加载优化

目录介绍 1.什么是懒加载 1.1 什么是预加载 1.2 懒加载介绍 1.3 懒加载概括 2.实际应用中有哪些懒加载案例 2.1 ViewPager+Fragment组合 2.2 分析源码 3.ViewPager中setOffscreenPageLimit(int limit)相关源码 4.关于ViewPager与Fragment懒加载 4.1 遇到的问题 4.2 主要的思路做法 4.3 代码案例展示 4.4 onLazyLoad()加载数据条件 4.5 Fragment的方法setUserVisibleHint 5.懒加载Fragment为什么要继承BaseFragment 0.前言介绍 关于如何使用懒加载,可以直接看项目,地址:https://github.com/yangchong211/YCVideoPlayer 关于博客大汇总,可以直接看该文章,链接地址:https://www.jianshu.com/p/53017c3fc75d 关于我的GitHub开源项目:https://github.com/yangchong211 如果觉得可以,可以给个star,也可以指教…… 1.什么是懒加载 1.1 什么是预加载 ViewPager+Fragment的搭配在日常开发中也比较常见,可用于切换展示不同类别的页面,我们日常所见的咨询、购物、金融、社交等类型的APP都有机会用到这种控件组合 ViewPager控件有个特有的预加载机制,即默认情况下当前页面左右两侧的1个页面会被加载,以方便用户滑动切换到相邻的界面时,可以更加顺畅的显示出来。 通过ViewPager的setOffscreenPageLimit(int limit)可以设置预加载页面数量,当前页面相邻的limit个页面会被预加载进内存 1.2 懒加载介绍 懒加载,其实也就是延迟加载,就是等到该页面的UI展示给用户时,再加载该页面的数据(从网络、数据库等),而不是依靠ViewPager预加载机制提前加载两三个,甚至更多页面的数据。这样可以提高所属Activity的初始化速度,也可以为用户节省流量.而这种懒加载的方式也已经/正在被诸多APP所采用。 但是通过ViewPager方法setOffscreenPageLimit(int limit)的源码可以发现,ViewPager通过一定的逻辑判断来确保至少会预加载左右两侧相邻的1个页面,也就是说无法通过简单的配置做到懒加载的效果。 1.3 懒加载概括 当页面可见的时候,才加载当前页面。 没有打开的页面,就不会预加载。 说白了,懒加载就是可见的时候才去请求数据。 2.实际应用中有哪些懒加载案例 1.1 ViewPager+Fragment的搭配使用懒加载 1.2 H5网页使用懒加载 3.ViewPager中setOffscreenPageLimit(int limit)相关源码 3.1 源码如下所示 //默认的缓存页面数量(常量) private static final int DEFAULT_OFFSCREEN_PAGES = 1; //缓存页面数量(变量) private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES; public void setOffscreenPageLimit(int limit) { //当我们手动设置的limit数小于默认值1时,limit值会自动被赋值为默认值1(即DEFAULT_OFFSCREEN_PAGES) if (limit < DEFAULT_OFFSCREEN_PAGES) { Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to "+ DEFAULT_OFFSCREEN_PAGES); limit = DEFAULT_OFFSCREEN_PAGES; } if (limit != mOffscreenPageLimit) { //经过前面的拦截判断后,将limit的值设置给mOffscreenPageLimit,用于 mOffscreenPageLimit = limit; populate(); } } 3.2 分析源码可知 ViewPager通过一定的逻辑判断来确保至少会预加载左右两侧相邻的1个页面,也就是说无法通过简单的配置做到懒加载的效果。 4.关于ViewPager与Fragment懒加载 4.1 遇到的问题 在使用viewpager(或其他容器)与多个Fragment来组合使用,ViewPager 会默认一次加载当前页面前后隔一个页面,即使设置setofflimit(0)也无效果,也会预加载。这样把我们看不到的页面的数据也加载了,大大降低了性能,浪费初始化资源。然而我们就采用懒加载技术,只让用户看到的页面才会加载他的数据,大大提高效率。 4.2 主要的思路做法 主要的方法是Fragment中的setUserVisibleHint(),此方法会在onCreateView()之前执行,当viewPager中fragment改变可见状态时也会调用,当fragment 从可见到不见,或者从不可见切换到可见,都会调用此方法,使用getUserVisibleHint() 可以返回fragment是否可见状态。 在BaseMVPLazyFragment中需要在onActivityCreated()及setUserVisibleHint()方法中都调了一次lazyLoad() 方法。如果仅仅在setUserVisibleHint()调用lazyLoad(),当默认首页首先加载时会导致viewPager的首页第一次展示时没有数据显示,切换一下才会有数据。因为首页fragment的setUserVisible()在onActivityCreated() 之前调用,此时isPrepared为false 导致首页fragment 没能调用onLazyLoad()方法加载数据。 4.3 代码案例展示 public abstract class BaseMVPLazyFragment<T extends IBasePresenter> extends BaseMVPFragment<T> { /** * Fragment的View加载完毕的标记 */ protected boolean isViewInitiated; /** * Fragment对用户可见的标记 */ protected boolean isVisibleToUser; /** * 是否懒加载 */ protected boolean isDataInitiated; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } /** * 第一步,改变isViewInitiated标记 * 当onViewCreated()方法执行时,表明View已经加载完毕,此时改变isViewInitiated标记为true,并调用lazyLoad()方法 */ @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); isViewInitiated = true; //只有Fragment onCreateView好了, //另外这里调用一次lazyLoad() prepareFetchData(); //lazyLoad(); } /** * 第二步 * 此方法会在onCreateView()之前执行 * 当viewPager中fragment改变可见状态时也会调用 * 当fragment 从可见到不见,或者从不可见切换到可见,都会调用此方法 */ @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); this.isVisibleToUser = isVisibleToUser; prepareFetchData(); } /** * 第四步:定义抽象方法fetchData(),具体加载数据的工作,交给子类去完成 */ public abstract void fetchData(); /** * 第三步:在lazyLoad()方法中进行双重标记判断,通过后即可进行数据加载 * 第一种方法 * 调用懒加载,getUserVisibleHint()会返回是否可见状态 * 这是fragment实现懒加载的关键,只有fragment 可见才会调用onLazyLoad() 加载数据 */ private void lazyLoad() { if (getUserVisibleHint() && isViewInitiated && !isDataInitiated) { fetchData(); isDataInitiated = true; } } /** * 第二种方法 * 调用懒加载 */ public void prepareFetchData() { prepareFetchData(false); } /** * 第三步:在lazyLoad()方法中进行双重标记判断,通过后即可进行数据加载 */ public void prepareFetchData(boolean forceUpdate) { if (isVisibleToUser && isViewInitiated && (!isDataInitiated || forceUpdate)) { fetchData(); isDataInitiated = true; } } } 4.4 onLazyLoad()加载数据条件 getUserVisibleHint()会返回是否可见状态,这是fragment实现懒加载的关键,只有fragment 可见才会调用onLazyLoad() 加载数据。 isPrepared参数在系统调用onActivityCreated时设置为true,这时onCreateView方法已调用完毕(一般我们在这方法里执行findviewbyid等方法),确保 onLazyLoad()方法不会报空指针异常。 isLazyLoaded确保ViewPager来回切换时BaseFragment的initData方法不会被重复调用,onLazyLoad在该Fragment的整个生命周期只调用一次,第一次调用onLazyLoad()方法后马上执行 isLazyLoaded = true。 然后再继承这个BaseMVPLazyFragment实现onLazyLoad() 方法就行。他会自动控制当fragment 展现出来时,才会加载数据 4.5 Fragment的方法setUserVisibleHint 源码如下所示 通过此方法来设置Fragment的UI对用户是否可见,当该页面对用户可见/不可见时,系统都会回调此方法。 我们可以重写此方法,然后根据回调的isVisibleToUser参数来进行相关的逻辑判断,以达到懒加载的效果,比如如果isVisibleToUser==true的话表示当前Fragment对用户可见,此时再去加载页面数据。 5.懒加载Fragment为什么要继承BaseFragment 5.1 继承BaseFragment好处 这个BaseFragment就是最基础的基类了,里面进行一些最底层的设置,定义抽象函数:比如布局文件的绑定,初始化视图控件,初始化数据,初始化Toolbar 所以这个懒加载LazyLoadFragment基本上是隔离开了。耦合度相当低,基本都可以这样使用吧。 还有一点,有些Fragment不需要懒加载,那么可以直接继承BaseFragment类;需要懒加载的直接继承BaseMVPLazyFragment类 6.关于我的个人博客和站点 github: https://github.com/yangchong211 知乎: https://www.zhihu.com/people/yang-chong-69-24/pins/posts 简书: http://www.jianshu.com/u/b7b2c6ed9284 csdn: http://my.csdn.net/m0_37700275 喜马拉雅听书: http://www.ximalaya.com/zhubo/71989305/ 泡在网上的日子:http://www.jcodecraeer.com/member/content_list.php?channelid=1 邮箱:yangchong211@163.com 阿里云博客:https://yq.aliyun.com/users/article?spm=5176.100239.headeruserinfo.3.dT4bcV

优秀的个人博客,低调大师

Android SparseArray与ArrayMap

公共类数据Constant.java /** * 常量类 * @author mazaiting * @date 2018/2/7 */ public class Constant { public static final int MAX = 1000000; public static final String TAG = "tag"; } 1. SparseArray与HashMap正序插入测试 HashMapTest.java /** * HashMap测试类 * @author mazaiting * @date 2018/2/7 */ public class HashMapTest { /** * 正序插入 */ public void insertAdesc() { // 创建HashMap HashMap<Integer, String> hashMap = new HashMap<>(); // 获取当前时间 long start = System.currentTimeMillis(); // 获取当前内存 long startTotalMemory = Runtime.getRuntime().totalMemory(); // 插入数据 for (int i = 0; i < Constant.MAX; i++) { // 插入单条数据 hashMap.put(i, String.valueOf(i)); } // 获取结束时间 long end = System.currentTimeMillis(); // 获取结束时的内存 long endTotalMemory = Runtime.getRuntime().totalMemory(); // 获取插入所用时间 long time = end - start; // 获取插入所用内存 long memory = endTotalMemory - startTotalMemory; // 打印信息 Log.e(Constant.TAG,"<---HashMap的插入时间--->" + time +"<---HashMap占用的内存--->" + memory); } } 程序执行结果: <---HashMap的插入时间--->6623<---HashMap占用的内存--->136007680 SparseArrayTest.java /** * SparseArray测试类 * @author mazaiting * @date 2018/2/7 */ public class SparseArrayTest { /** * 正序插入 */ public void insertAdesc() { // 创建SparseArray SparseArray<String> sparseArray = new SparseArray<>(); // 获取当前时间 long start = System.currentTimeMillis(); // 获取当前内存 long startTotalMemory = Runtime.getRuntime().totalMemory(); // 插入数据 for (int i = 0; i < Constant.MAX; i++) { // 插入单条数据 sparseArray.put(i, String.valueOf(i)); } // 获取结束时间 long end = System.currentTimeMillis(); // 获取结束时的内存 long endTotalMemory = Runtime.getRuntime().totalMemory(); // 获取插入所用时间 long time = end - start; // 获取插入所用内存 long memory = endTotalMemory - startTotalMemory; // 打印信息 Log.e(Constant.TAG,"<---SparseArray的插入时间--->" + time +"<---SparseArray占用的内存--->" + memory); } 程序执行结果: <---SparseArray的插入时间--->2806<---SparseArray占用的内存--->23846912 2. SparseArray与HashMap倒序插入测试 HashMapTest.java /** * HashMap测试类 * @author mazaiting * @date 2018/2/7 */ public class HashMapTest { /** * 倒序插入 */ public void insertDesc() { // 创建HashMap HashMap<Integer, String> hashMap = new HashMap<>(); // 获取当前时间 long start = System.currentTimeMillis(); // 获取当前内存 long startTotalMemory = Runtime.getRuntime().totalMemory(); // 插入数据 for (int i = Constant.MAX; i > 0; i--) { // 插入单条数据 hashMap.put(i, String.valueOf(i)); } // 获取结束时间 long end = System.currentTimeMillis(); // 获取结束时的内存 long endTotalMemory = Runtime.getRuntime().totalMemory(); // 获取插入所用时间 long time = end - start; // 获取插入所用内存 long memory = endTotalMemory - startTotalMemory; // 打印信息 Log.e(Constant.TAG,"<---HashMap的插入时间--->" + time +"<---HashMap占用的内存--->" + memory); } } 程序执行结果: <---HashMap的插入时间--->5942<---HashMap占用的内存--->-20185088 SparseArrayTest.java /** * SparseArray测试类 * @author mazaiting * @date 2018/2/7 */ public class SparseArrayTest { /** * 倒序插入 */ public void insertDesc() { // 创建SparseArray SparseArray<String> sparseArray = new SparseArray<>(); // 获取当前时间 long start = System.currentTimeMillis(); // 获取当前内存 long startTotalMemory = Runtime.getRuntime().totalMemory(); // 插入数据 for (int i = Constant.MAX; i > 0; i++) { // 插入单条数据 sparseArray.put(i, String.valueOf(i)); } // 获取结束时间 long end = System.currentTimeMillis(); // 获取结束时的内存 long endTotalMemory = Runtime.getRuntime().totalMemory(); // 获取插入所用时间 long time = end - start; // 获取插入所用内存 long memory = endTotalMemory - startTotalMemory; // 打印信息 Log.e(Constant.TAG,"<---SparseArray的插入时间--->" + time +"<---SparseArray占用的内存--->" + memory); } } 程序执行结果: java.lang.OutOfMemoryError--内存溢出 3. SparseArray与HashMap倒序插入测试 HashMapTest.java /** * HashMap测试类 * @author mazaiting * @date 2018/2/7 */ public class HashMapTest { /** * 读取数据 */ public void get() { // 创建HashMap HashMap<Integer, String> hashMap = new HashMap<>(); // 插入数据 for (int i = 0; i < Constant.MAX; i++) { // 插入单条数据 hashMap.put(i, String.valueOf(i)); } // 获取当前时间 long start = System.currentTimeMillis(); // 获取当前内存 long startTotalMemory = Runtime.getRuntime().totalMemory(); // 循环获取数据 for (int i = 0; i < Constant.MAX; i++) { // 获取数据 hashMap.get(i); } // 获取结束时间 long end = System.currentTimeMillis(); // 获取结束时的内存 long endTotalMemory = Runtime.getRuntime().totalMemory(); // 获取插入所用时间 long time = end - start; // 获取插入所用内存 long memory = endTotalMemory - startTotalMemory; // 打印信息 Log.e(Constant.TAG,"<---HashMap的读取时间--->" + time +"<---HashMap占用的内存--->" + memory); } } ** 程序执行结果:** <---HashMap的读取时间--->817<---HashMap占用的内存--->0 SparseArrayTest.java /** * SparseArray测试类 * @author mazaiting * @date 2018/2/7 */ public class SparseArrayTest { /** * 读取数据 */ public void get() { // 创建SparseArray SparseArray<String> sparseArray = new SparseArray<>(); // 插入数据 for (int i = 0; i < Constant.MAX; i++) { // 插入单条数据 sparseArray.put(i, String.valueOf(i)); } // 获取当前时间 long start = System.currentTimeMillis(); // 获取当前内存 long startTotalMemory = Runtime.getRuntime().totalMemory(); // 循环获取数据 for (int i = 0; i < Constant.MAX; i++) { // 获取数据 sparseArray.get(i); } // 获取结束时间 long end = System.currentTimeMillis(); // 获取结束时的内存 long endTotalMemory = Runtime.getRuntime().totalMemory(); // 获取插入所用时间 long time = end - start; // 获取插入所用内存 long memory = endTotalMemory - startTotalMemory; // 打印信息 Log.e(Constant.TAG,"<---SparseArray的读取时间--->" + time +"<---SparseArray占用的内存--->" + memory); } } ** 程序执行结果:** <---SparseArray的读取时间--->412<---SparseArray占用的内存--->0 SparseArray、ArrayMap与HashMap结论 假设数据量都在千级以内的情况下: 如果key的类型已经确定为int类型,那么使用SparseArray,因为它避免了自动装箱的过程,如果key为long类型,它还提供了一个LongSparseArray来确保key为long类型时的使用 如果key类型为其它的类型,则使用ArrayMap

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册