开发中,常常会用到动态在ScrollView、LinearLayout里addView的事,尤为是ItemView同样时,每次都要写一大堆代码 inflater 动态addView,很烦。javascript
还有就是在嵌套ListView、ScrollView时,想采用LinearLayout替代(这样性能更佳,不明白的看一个控件搞定嵌套ListView),但动态addView步骤神烦。java
这个时候就开始期待,能不能有一种快速为任意ViewGroup添加子View的东西。git
我以前为此事特地写过一篇LinearLayout封装博文,封装了一个控件使用。试图一个控件搞定嵌套ListView。可是后来发现,采用继承某个ViewGroup作这个事情不够优雅 ,对代码有侵入性,若是有其余ViewGroup须要动态addView,就会写重复的代码 。github
前几天有人在群里问,如何方便的给ScrollView动态添加不一样种类型的childView,相似RecyclerView那样。我以前的封装因为内部有一个简单的重用机制,只支持单一ItemType,也不支持多种类型的childView。编程
那么需求就来了:设计模式
除此以外,我还加入:缓存
OnItemClickListener
OnItemLongClickListener
本文就封装了这么一个东西。数据结构
核心:ide
PS:因此本文也算是填了以前的一个坑,在以前适配器模式博文文末,我就提到要写一篇为流式布局增长Adapter的文章,做为Adapter的实战演练。使用本文封装的Adapter天然能够达到这一点。函数
因为采用Adapter隔离ViewGroup和ItemView,在切换ViewGroup时,十分方便。
如:在需求让你把一个HorizontalScrollView包裹的水平标签转换成流式布局时,只须要在xml替换控件便可。Adapter将自动完成适配的工做。其余代码一句不用修改。
不BB了,先看看之后如何使用吧,够不够简单粗暴。
转载请标明出处:
gold.xitu.io/post/584d52…
本文出自:【张旭童的稀土掘金】(gold.xitu.io/user/56de21…)
代码传送门:喜欢的话,随手点个star。多谢
github.com/mcxtzhang/a…
Adapter泛型传入JavaBean,构造函数传入数据集和layout布局,一句代码搞定:
//单一ItemView
ViewGroupUtils.addViews(mLinearLayout, new SingleAdapter<TestBean>(this, mDatas, R.layout.item_test) {
@Override
public void onBindView(ViewGroup parent, View itemView, TestBean data, int pos) {
Glide.with(LinearLayoutActivity.this)
.load(data.getAvatar())
.into((ImageView) itemView.findViewById(R.id.ivAvatar));
((TextView) itemView.findViewById(R.id.tvName)).setText(data.getName());
}
});复制代码
效果:
多种Item类型分两种状况:
数据结构相同依然能够给Adapter传入泛型,避免强转:
//多种ItemViewType,可是数据结构相同,能够传入数据结构泛型,避免强转
ViewGroupUtils.addViews(linearLayout, new MulTypeAdapter<MulTypeBean>(this, initDatas()) {
@Override
public void onBindView(ViewGroup parent, View itemView, MulTypeBean data, int pos) {
((TextView) itemView.findViewById(R.id.tvWords)).setText(data.getName() + "");
Glide.with(MulTypeActivity.this)
.load(data.getAvatar())
.into((ImageView) itemView.findViewById(ivAvatar));
}
});复制代码
效果:
若是数据结构不一样,则不用传入泛型,可是使用时须要强转:
//多种Item类型:数据结构不一样 不传泛型了 使用时须要强转javaBean,判断ItemLayoutId
ViewGroupUtils.addViews((ViewGroup) findViewById(R.id.activity_mul_type_mul_bean), new MulTypeAdapter(this, datas) {
@Override
public void onBindView(ViewGroup parent, View itemView, IMulTypeHelper data, int pos) {
switch (data.getItemLayoutId()) {
case R.layout.item_mulbean_1:
MulBean1 mulBean1 = (MulBean1) data;
Glide.with(MulTypeMulBeanActivity.this)
.load(mulBean1.getUrl())
.into((ImageView) itemView);
break;
case R.layout.item_mulbean_2:
MulBean2 mulBean2 = (MulBean2) data;
TextView tv = (TextView) itemView;
tv.setText(mulBean2.getName());
}
}
});复制代码
数据结构:
public class MulBean1 implements IMulTypeHelper {
private String url;
@Override
public int getItemLayoutId() {
return R.layout.item_mulbean_1;
}
}复制代码
public class MulBean2 implements IMulTypeHelper {
private String name;
@Override
public int getItemLayoutId() {
return R.layout.item_mulbean_2;
}
}复制代码
Item1布局是一个ImageView,Item2布局是一个TextView
效果:
item的点击和长按等事件,有两种方法设置,这里以点击事件为例,长按事件同理:
在Adapter.onBindView()
方法里能拿到ItemView,天然就能够设置各类事件。相似RecyclerView。
在这里设置优先级更高。缘由后文会提到。
@Override
public void onBindView(ViewGroup parent, View itemView, final MulTypeBean data, int pos) {
....
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(mContext, "onBindView里设置:文字是:" + data.getName(), Toast.LENGTH_SHORT).show();
}
});
}复制代码
能够在ViewGroupUtils.addViews
直接做为参数传入.
也能够用ViewGroupUtils.setOnItemClickListener()
设置 。
优先级比Adapter.onBindView()
里设置低,缘由后文会提到。
//设置OnItemClickListener
OnItemClickListener onItemClickListener = new OnItemClickListener() {
@Override
public void onItemClick(ViewGroup parent, View itemView, int position) {
Toast.makeText(MulTypeActivity.this, "经过OnItemClickListener设置:" + position, Toast.LENGTH_SHORT).show();
}
};
//能够在`ViewGroupUtils.addViews`直接做为参数传入.\
ViewGroupUtils.addViews(linearLayout, adapter ,onItemClickListener);
//或者 也能够用`ViewGroupUtils.setOnItemClickListener()`设置
ViewGroupUtils.setOnItemClickListener(linearLayout,onItemClickListener);复制代码
看起来仍是挺好的,嗯~至少我本身这么以为,我我的比较喜欢这种0耦合,每个库都像可组装拆卸的机关枪同样,拿起来就用。而不是笨重功能繁多的重装坦克。
搭配个人得意之做,每次必安利的史上集成最简单侧滑菜单控件。
效果以下:
无特殊设置,仅仅替换ViewGroup为流式布局,替换Item根布局为我撸的侧滑菜单库,能感觉到这种0耦合的库的魅力了么。23333333 。
下面就让我手摸手带你们实现它。
先看类图。
先简要归纳
咱们的顶层接口IViewGroupAdapter
暴露出两个方法供ViewGroup使用。
ViewGroupUtils
是为任意ViewGroup 动态addView的工具类,只依赖于 IViewGroupAdapter
接口便可完成工做。
BaseAdapter
是第二层,在这一层引入了数据集,用List<T>
保存。实现IViewGroupAdapter
的方法,重载一个三参数的getView()
方法,供子类去实现。
SingleAdapter
是第三层,一个简化的Adapter,只支持单种Item,以LayoutId 构建View。实现getView()
方法,并暴露出onBindView()
供用户快速使用。
MulTypeAdapter
也同处第三层,一个支持多种Item的Adapter。依赖IMulTypeHelper
接口,利用其getItemLayoutId()
方法去实现getView()
方法,并暴露出onBindView()
供用户快速使用。
顶层接口,即IViewGroupAdapter
。
根据迪米特法则(最少知道原则),咱们应该抽象出一个顶层的接口,对ViewGroup暴露出最少的方法供使用。
咱们想一下,对于ViewGroup,它最少只须要哪些就能完成咱们的需求。
public interface IViewGroupAdapter {
/** * ViewGroup调用获取ItemView * * @param parent * @param pos * @return */
View getView(ViewGroup parent, int pos);
/** * ViewGroup调用,获得ItemCount * * @return */
int getCount();
}复制代码
ok,代码写到这里,后面的咱们暂且不提,咱们就能够写动态addView的工具类了。由于咱们的ViewGroup依赖的全部信息都由IViewGroupAdapter
这个接口提供了。
ViewGroupUtils
是为任意ViewGroup 动态addView的工具类,不考虑点击事件的状况下,只依赖于 IViewGroupAdapter
接口便可完成工做。
以下编写:
/** * 为任意ViewGroup 添加ItemViews. * * @param viewGroup 必传 * @param adapter 必传,至少提供要add的View和须要add的count * @param removeViews 是否须要remove掉以前的Views */
public static void addViews(final ViewGroup viewGroup, IViewGroupAdapter adapter
, boolean removeViews) {
if (viewGroup == null || adapter == null) {
return;
}
//若是须要remove掉以前的Views
if (removeViews && viewGroup.getChildCount() > 0) {
viewGroup.removeAllViews();
}
//开始添加子Views,经过Adapter得到须要添加的Count
int count = adapter.getCount();
for (int i = 0; i < count; i++) {
//经过Adapter得到ItemView
View itemView = adapter.getView(viewGroup, i);
viewGroup.addView(itemView);
}
}复制代码
如此便可完成 动态给任意ViewGroup addView 的工做。
不过咱们开头提过,我仍是想引入ItemView的点击和长按事件的。可是关于这两个ItemListener,仍是有一些东西要考虑的。
为ViewGroup提供OnItemClickListener
,有个问题须要考虑:
若是使用者调用了setOnItemClickListener
,且在Adapter
里本身又对ItemView
设置了OnClickListener
,那么究竟该触发哪一个Listener
,即它们的优先级。
咱们不该该本身靠脑子想答案,仍是参照系统原有的设计比较好。既然ListView提供了OnItemClickListener
,那么咱们参照它的设计来就行。
先说结论:经过参照ListView的源码,以及实验得知,对ItemView
的OnClickListener
优先级 > ViewGroup的OnItemClickListener
。
为何?答案在源码中,感兴趣看,不感兴趣直接跳过。
从AbsListView的onTouchEvent()
->onTouchUp()
->PerformClick
->performItemClick
-> AdapterView.performItemClick()
-> AdapterView.mOnItemClickListener
,便可找到答案。
这里不详细分析源码,不是本文重点,简单的说,从入口处是AbsListView的onTouchEvent()
,咱们能够知道,ListView自己并无干预ItemView
的点击事件(即没有为其设置OnClickListener
),是在ItemView
不消耗Touch事件时 才进行Item点击事件的触发。
所以若ItemView
设置了OnClickListener
,AbsListView的onTouchEvent()
将收不到MotionEvent.ACTION_UP
事件,于是也不会触发OnItemClickListener
。因此这决定了ItemView
的OnClickListener
优先级高。
还有一个问题,咱们能够经过View.hasOnClickListeners()
这个方法来判断View是否设置了OnClickListener
,可是这个方法在API15才加入,为了能兼容低版本,我采用了另外一种方法判断,itemView.isClickable()
,若是true,我当作有点击事件,若是false,我当作没有。
其实在ListView中这个问题也是同样的,itemView.isClickable()
为true的话,点击事件就被拦截住,不会分发至AbsListView的onTouchEvent()
里了。因此咱们这么写是没问题,而且是正确的。
既然如此,那么咱们在程序中,应该以下编写:
for (int i = 0; i < count; i++) {
View itemView = adapter.getView(viewGroup, i);
viewGroup.addView(itemView);
//添加点击事件
if (null != onItemClickListener && !itemView.isClickable()) {
final int finalI = i;
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
onItemClickListener.onItemClick(viewGroup, view, finalI);
}
});
}
//添加点击事件
if (null != onItemLongClickListener && !itemView.isLongClickable()) {
final int finalI = i;
itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
return onItemLongClickListener.onItemLongClick(viewGroup, view, finalI);
}
});
}
}复制代码
因此完整的addViews()
以下:
/** * 为任意ViewGroup 添加ItemViews. * * @param viewGroup 必传 * @param adapter 必传,至少提供要add的View和须要add的count * @param removeViews 是否须要remove掉以前的Views * @param onItemClickListener Item点击事件 * @param onItemLongClickListener Item长按事件 */
public static void addViews(final ViewGroup viewGroup, IViewGroupAdapter adapter
, boolean removeViews
, final OnItemClickListener onItemClickListener
, final OnItemLongClickListener onItemLongClickListener) {
if (viewGroup == null || adapter == null) {
return;
}
//若是须要remove掉以前的Views
if (removeViews && viewGroup.getChildCount() > 0) {
viewGroup.removeAllViews();
}
//开始添加子Views,经过Adapter得到须要添加的Count
int count = adapter.getCount();
for (int i = 0; i < count; i++) {
//经过Adapter得到ItemView
View itemView = adapter.getView(viewGroup, i);
viewGroup.addView(itemView);
//添加点击事件,itemView以前没有点击事件才会去设置
if (null != onItemClickListener && !itemView.isClickable()) {
final int finalI = i;
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
onItemClickListener.onItemClick(viewGroup, view, finalI);
}
});
}
//添加长按事件itemView以前没有长按事件才会去设置
if (null != onItemLongClickListener && !itemView.isLongClickable()) {
final int finalI = i;
itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
return onItemLongClickListener.onItemLongClick(viewGroup, view, finalI);
}
});
}
}
}复制代码
实际中,咱们可能不须要设置Listener,为了快速使用,我又提供了两个重载方法:
/** * 为任意ViewGroup 添加ItemViews. * 而且会清除掉以前全部add过的View * * @param viewGroup 必传 * @param adapter 必传,至少提供要add的View和须要add的count */
public static void addViews(final ViewGroup viewGroup, IViewGroupAdapter adapter) {
addViews(viewGroup, adapter, true, null, null);
}
/** * 为任意ViewGroup 添加ItemViews. * 而且会清除掉以前全部add过的View * * @param viewGroup 必传 * @param adapter 必传,至少提供要add的View和须要add的count * @param onItemClickListener Item点击事件 */
public static void addViews(final ViewGroup viewGroup, IViewGroupAdapter adapter
, final OnItemClickListener onItemClickListener) {
addViews(viewGroup, adapter, true, onItemClickListener, null);
}复制代码
若不在addViews()
里设置ItemListener,也能够经过setOnItemClickListener()
和setOnItemLongClickListener()
设置,不过这两个方法必须在addViews()方法以后调用:
/** * 为任意ViewGroup设置OnItemClickListener. * 该方法必须在addViews()方法以后调用,不然无效。 * 由于ItemView 必须被添加在ViewGroup里才能遍历到。 * 建议直接在addViews()方法里传入OnItemClickListener进行设置,性能更高 * * @param viewGroup * @param onItemClickListener */
public static void setOnItemClickListener(final ViewGroup viewGroup, final OnItemClickListener onItemClickListener) {
if (viewGroup == null || onItemClickListener == null) {
return;
}
int childCount = viewGroup.getChildCount();
for (int i = 0; i < childCount; i++) {
final View itemView = viewGroup.getChildAt(i);
//itemView以前没有点击事件才会去设置
if (null != itemView && !itemView.isClickable()) {
final int finalI = i;
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
onItemClickListener.onItemClick(viewGroup, itemView, finalI);
}
});
}
}
}
/** * 为任意ViewGroup设置OnItemLongClickListener. * 该方法必须在addViews()方法以后调用,不然无效。 * 由于ItemView 必须被添加在ViewGroup里才能遍历到。 * 建议直接在addViews()方法里传入OnItemLongClickListener进行设置,性能更高 * * @param viewGroup * @param onItemLongClickListener */
public static void setOnItemLongClickListener(final ViewGroup viewGroup, final OnItemLongClickListener onItemLongClickListener) {
if (viewGroup == null || onItemLongClickListener == null) {
return;
}
int childCount = viewGroup.getChildCount();
for (int i = 0; i < childCount; i++) {
final View itemView = viewGroup.getChildAt(i);
//itemView以前没有长按事件才会去设置
if (null != itemView && !itemView.isLongClickable()) {
final int finalI = i;
itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
return onItemLongClickListener.onItemLongClick(viewGroup, itemView, finalI);
}
});
}
}
}复制代码
终于到了咱们的重头戏,Adapter。
BaseAdapter
是第二层,在这一层引入了数据集,用List<T>
保存。实现IViewGroupAdapter
的方法,重载一个三参数的getView()
方法,供子类去实现。
它和咱们平时写的ListView、RecyclerView的Adapter就比较像了,我也是参照平时的写法。
核心就是实现IViewGroupAdapter
的getView(ViewGroup parent, int pos)
方法,增长一个数据,工做转交给三参数的getView(ViewGroup parent, int pos, T data)
方法。
子类应该 实现 getView(ViewGroup parent, int pos, T data)
方法,在其中inflate or new 出 ItemView,并绑定数据。
public abstract class BaseAdapter<T> implements IViewGroupAdapter {
protected List<T> mDatas;
protected Context mContext;
protected LayoutInflater mInflater;
/** * ViewGroup调用获取ItemView,create bind一块儿作 * * @param parent * @param pos * @return */
@Override
public View getView(ViewGroup parent, int pos) {
return getView(parent, pos, mDatas.get(pos));
}
/** * 实际的createItemView的地方 * * @param parent * @param pos * @param data * @return */
public abstract View getView(ViewGroup parent, int pos, T data);
/** * ViewGroup调用,获得ItemCount * * @return */
@Override
public int getCount() {
return mDatas != null ? mDatas.size() : 0;
}
}复制代码
SingleAdapter
是第三层,一个简化的Adapter,只支持单种Item,以LayoutId 构建View。实现getView()
方法,并暴露出onBindView()
供用户快速使用。
使用时,通常将数据结构的泛型传入,配合构造函数传入的ItemLayoutId使用。
它继承自BaseAdapter
,因此它了实现getView(ViewGroup parent, int pos, T data)
方法。在根据传入的itemLayoutId inflate出ItemView后,会回调onBindView(ViewGroup parent, View itemView, T data, int pos)
方法,并返回ItemView供ViewGroup使用。
在onBindView(ViewGroup parent, View itemView, T data, int pos)
方法里,咱们完成数据绑定的工做。例如加载图片,也能够设置点击事件。
public abstract class SingleAdapter<T> extends BaseAdapter<T> {
private int mItemLayoutId;
@Override
public View getView(ViewGroup parent, int pos, T data) {
//实现getView
View itemView = /*onCreateView(parent, pos)*/mInflater.inflate(mItemLayoutId, parent, false);
onBindView(parent, itemView, data, pos);
return itemView;
}
/** * 暴漏这个 让外部bind数据 * * @param parent * @param itemView * @param data * @param pos */
public abstract void onBindView(ViewGroup parent, View itemView, T data, int pos);
}复制代码
MulTypeAdapter
也同处第三层,一个支持多种Item的Adapter。依赖IMulTypeHelper
接口,利用其getItemLayoutId()
方法去实现getView()
方法,并暴露出onBindView()
供用户快速使用。
public abstract class MulTypeAdapter<T extends IMulTypeHelper> extends BaseAdapter<T> {
@Override
public View getView(ViewGroup parent, int pos, T data) {
View itemView = mInflater.inflate(data.getItemLayoutId(), parent, false);
onBindView(parent, itemView, data, pos);
return itemView;
}
/** * 暴漏这个 让外部bind数据 * * @param parent * @param itemView * @param data * @param pos */
public abstract void onBindView(ViewGroup parent, View itemView, T data, int pos);
}复制代码
IMulTypeHelper
接口,一样简单:
public interface IMulTypeHelper {
int getItemLayoutId();
}复制代码
代码传送门:喜欢的话,随手点个star。多谢
github.com/mcxtzhang/a…
由于想支持任意ViewGroup,对ViewGroup代码无侵入性,于是对部分功能进行了取舍,例如设置OnItemXXXListener
,若是采用继承ViewGroup,嵌入代码,能够作到不强制addViews()
和setOnItemClickListener()
顺序。
经过上文咱们能感觉到一些面向接口编程的奥义,例如咱们只须要设计好顶层的接口IViewGroupAdapter
,在没写剩下的xxxAdapter时,工具类均可以写好,编译经过。
这在之后扩展时,例如,我数据集不能用List来保存了,我多是多个List or Map 等等,只须要实现IViewGroupAdapter
接口便可定制一个Adapter。其余代码不需任何修改。
有人说过,设计模式怎么学,就是先学完一遍全部的设计模式。而后再所有忘掉他们,只要记住SOLID原则便可。
我以为颇有道理,就像我以前一直在纠结代理模式和装饰者模式的区别,后来我想,编码时,我管它是什么模式,只要写出来的是易维护的代码便可。
onBindView()
的ItemView
->通用的ViewHolder
,这样能够少写一些findViewById()
代码一个用ScrollView很容易实现,但RecyclerView、ListView就没法实现的动画效果,仿淘宝会员中心等级动画。凸显为全部ViewGroup增长Adapter模式的重要性。
感兴趣能够去阅读
gold.xitu.io/post/584fbd…
转载请标明出处:
gold.xitu.io/post/584d52…
本文出自:【张旭童的稀土掘金】(gold.xitu.io/user/56de21…)
代码传送门:喜欢的话,随手点个star。多谢
github.com/mcxtzhang/a…