看起来还是挺好的,嗯~至少我自己这么觉得,我个人比较喜欢这种0耦合,每一个库都像可组装拆卸的机关枪一样,拿起来就用。而不是笨重功能繁多的重装坦克。
搭配我的得意之作,每次必安利的史上集成最简单侧滑菜单控件。
效果如下:
无特殊设置,仅仅替换ViewGroup为流式布局,替换Item根布局为我撸的侧滑菜单库,能感受到这种0耦合的库的魅力了么。23333333 。
设计思路
下面就让我手摸手带大家实现它。
先看类图。
UML类图:
先简要概括
* 我们的顶层接口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,它最少只需要哪些就能完成我们的需求。
* ChildView是什么—> View
* 有多少ChildView 需要 添加—>count
所以,我们的最顶层接口如下编写:
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,还是有一些东西要考虑的。
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()
里了。所以我们这么写是没问题,并且是正确的。
ItemListener的完整实现:
既然如此,那么我们在程序中,应该如下编写:
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
终于到了我们的重头戏,Adapter。
BaseAdapter
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 implements IViewGroupAdapter {
protected List mDatas;
protected Context mContext;
protected LayoutInflater mInflater;
/**
-
ViewGroup调用获取ItemView,create bind一起做
-
@param parent
-
@param pos
-
@return
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
尾声
面试成功其实都是必然发生的事情,因为在此之前我做足了充分的准备工作,不单单是纯粹的刷题,更多的还会去刷一些Android核心架构进阶知识点,比如:JVM、高并发、多线程、缓存、热修复设计、插件化框架解读、组件化框架设计、图片加载框架、网络、设计模式、设计思想与代码质量优化、程序性能优化、开发效率优化、设计模式、负载均衡、算法、数据结构、高级UI晋升、Framework内核解析、Android组件内核等。
不仅有学习文档,视频+笔记提高学习效率,还能稳固你的知识,形成良好的系统的知识体系。这里,笔者分享一份从架构哲学的层面来剖析的视频及资料分享给大家梳理了多年的架构经验,筹备近6个月最新录制的,相信这份视频能给你带来不一样的启发、收获。
Android进阶学习资料库
一共十个专题,包括了Android进阶所有学习资料,Android进阶视频,Flutter,java基础,kotlin,NDK模块,计算机网络,数据结构与算法,微信小程序,面试题解析,framework源码!
大厂面试真题
PS:之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
《2017-2020字节跳动Android面试历年真题解析》
如果需要PDF版本可以自行领取!
提高学习效率,还能稳固你的知识,形成良好的系统的知识体系。这里,笔者分享一份从架构哲学的层面来剖析的视频及资料分享给大家梳理了多年的架构经验,筹备近6个月最新录制的,相信这份视频能给你带来不一样的启发、收获。
[外链图片转存中…(img-2FLTGGWV-1710853300491)]
Android进阶学习资料库
一共十个专题,包括了Android进阶所有学习资料,Android进阶视频,Flutter,java基础,kotlin,NDK模块,计算机网络,数据结构与算法,微信小程序,面试题解析,framework源码!
[外链图片转存中…(img-06V3o7jU-1710853300491)]
大厂面试真题
PS:之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
[外链图片转存中…(img-ObfjBY6O-1710853300491)]
《2017-2020字节跳动Android面试历年真题解析》
[外链图片转存中…(img-nZsVhpTP-1710853300492)]
如果需要PDF版本可以自行领取!