尾声
你不踏出去一步,永远不知道自己潜力有多大,千万别被这个社会套在我们身上的枷锁给捆住了,30岁我不怕,35岁我一样不怕,去做自己想做的事,为自己拼一把吧!不试试怎么知道你不行呢?
改变人生,没有什么捷径可言,这条路需要自己亲自去走一走,只有深入思考,不断反思总结,保持学习的热情,一步一步构建自己完整的知识体系,才是最终的制胜之道,也是程序员应该承担的使命。
附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
class HeaderSimpleAdapter : PagedListAdapter<Student, RecyclerView.ViewHolder>(diffCallback) {
// 1.根据position为item分配类型
// 如果position = 1,视为Header
// 如果position != 1,视为普通的Student
override fun getItemViewType(position: Int): Int {
return when (position == 0) {
true -> ITEM_TYPE_HEADER
false -> super.getItemViewType(position)
}
}
// 2.根据不同的viewType生成对应ViewHolder
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
ITEM_TYPE_HEADER -> HeaderViewHolder(parent)
else -> StudentViewHolder(parent)
}
}
// 3.根据holder类型,进行对应的渲染
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is HeaderViewHolder -> holder.renderHeader()
is StudentViewHolder -> holder.renderStudent(getStudentItem(position))
}
}
// 4.这里我们根据StudentItem的position,
// 获取position-1位置的学生
private fun getStudentItem(position: Int): Student? {
return getItem(position - 1)
}
// 5.因为有Header,item数量要多一个
override fun getItemCount(): Int {
return super.getItemCount() + 1
}
// 省略其他代码…
// 省略ViewHolder代码
}
代码和注释已经将我的个人思想展示的很清楚了,我们固定一个Header
在多类型列表的最上方,这也导致我们需要重写getItemCount()
方法,并且在对Item
进行渲染的onBindViewHolder()
方法中,对Sutdent
的获取进行额外的处理——因为多了一个Header,导致产生了数据源和列表的错位差—— 第n个数据被获取时,我们应该将其渲染在列表的第n+1个位置上。
我简单绘制了一张图来描述这个过程,也许更加直观易懂:
代码写完后,直觉告诉我似乎没有什么问题,让我们来看看实际的运行效果:
Gif也许展示并不那么清晰,简单总结下,问题有两个:
- 1.在我们进行下拉刷新时,因为
Header
更应该是一个静态独立的组件,但实际上它也是列表的一部分,因此白光一闪,除了Student
列表,Header
作为Item
也进行了刷新,这与我们的预期不符; - 2.下拉刷新之后,列表 并未展示在最顶部,而是滑动到了一个奇怪的位置。
导致这两个问题的根本原因仍然是Paging
计算列表的position
时出现的问题:
对于问题1,Paging
对于列表的刷新理解为 所有Item的刷新,因此同样作为Item
的Header
也无法避免被刷新;
问题2依然也是这个问题导致的,在Paging
获取到第一页数据时(假设第一页数据只有10条),Paging
会命令更新position in 0..9
的Item
,而实际上因为Header
的关系,我们是期望它能够更新第position in 1..10
的Item
,最终导致了刷新以及对新数据的展示出现了问题。
3.向Google和Github寻求答案
正如标题而言,我尝试求助于Google
和Github
,最终找到了这个链接:
PagingWithNetworkSample - PagedList RecyclerView scroll bug
如果您简单研究过PagedListAdapter
的源码的话,您应该了解,PagedListAdapter
内部定义了一个AsyncPagedListDiffer
,用于对列表数据的加载和展示,PagedListAdapter
更像是一个空壳,所有分页相关的逻辑实际都 委托 给了AsyncPagedListDiffer
:
public abstract class PagedListAdapter<T, VH extends RecyclerView.ViewHolder>
extends RecyclerView.Adapter {
final AsyncPagedListDiffer mDiffer;
public void submitList(@Nullable PagedList pagedList) {
mDiffer.submitList(pagedList);
}
protected T getItem(int position) {
return mDiffer.getItem(position);
}
public int getItemCount() {
return mDiffer.getItemCount();
}
public PagedList getCurrentList() {
return mDiffer.getCurrentList();
}
}
虽然Paging
中数据的获取和展示我们是无法控制的,但我们可以尝试 瞒过 PagedListAdapter
,即使Paging
得到了position in 0..9
的List<Data>
,但是我们让PagedListAdapter
去更新position in 1..10
的item不就可以了嘛?
因此在上方的Issue
链接中,onlymash 同学提出了一个解决方案:
重写PagedListAdapter
中被AsyncPagedListDiffer
代理的所有方法,然后实例化一个新的AsyncPagedListDiffer
,并让这个新的differ代理这些方法。
篇幅所限,我们只展示部分核心代码:
class PostAdapter: PagedListAdapter<Any, RecyclerView.ViewHolder>() {
private val adapterCallback = AdapterListUpdateCallback(this)
// 当第n个数据被获取,更新第n+1个position
private val listUpdateCallback = object : ListUpdateCallback {
override fun onChanged(position: Int, count: Int, payload: Any?) {
adapterCallback.onChanged(position + 1, count, payload)
}
override fun onMoved(fromPosition: Int, toPosition: Int) {
adapterCallback.onMoved(fromPosition + 1, toPosition + 1)
}
override fun onInserted(position: Int, count: Int) {
adapterCallback.onInserted(position + 1, count)
}
override fun onRemoved(position: Int, count: Int) {
adapterCallback.onRemoved(position + 1, count)
}
}
// 新建一个differ
private val differ = AsyncPagedListDiffer(listUpdateCallback,
AsyncDifferConfig.Builder(POST_COMPARATOR).build())
// 将所有方法重写,并委托给新的differ去处理
override fun getItem(position: Int): Any? {
return differ.getItem(position - 1)
}
// 将所有方法重写,并委托给新的differ去处理
override fun submitList(pagedList: PagedList?) {
differ.submitList(pagedList)
}
// 将所有方法重写,并委托给新的differ去处理
override fun getCurrentList(): PagedList? {
return differ.currentList
}
}
现在我们成功实现了上文中我们的思路,一图胜千言:
4.另外一种实现方式
上一小节的实现方案是完全可行的,但我个人认为美中不足的是,这种方案 对既有的Adapter
中代码改动过大。
我新建了一个AdapterListUpdateCallback
、一个ListUpdateCallback
以及一个新的AsyncPagedListDiffer
,并重写了太多的PagedListAdapter
的方法——我添加了数十行分页相关的代码,但这些代码和正常的列表展示并没有直接的关系。
当然,我可以将这些逻辑都抽出来放在一个新的类里面,但我还是感觉我 好像是模仿并重写了一个新的PagedListAdapter
类一样,那么是否还有其它的思路呢?
最终我找到了这篇文章:
Android RecyclerView + Paging Library 添加头部刷新会自动滚动的问题分析及解决
这篇文章中的作者通过细致分析Paging
的源码,得出了一个更简单实现Header
的方案,有兴趣的同学可以点进去查看,这里简单阐述其原理:
通过查看源码,以添加分页为例,Paging
对拿到最新的数据后,对列表的更新实际是调用了RecyclerView.Adapter
的notifyItemRangeInserted()
方法,而我们可以通过重写Adapter.registerAdapterDataObserver()
方法,对数据更新的逻辑进行调整:
// 1.新建一个 AdapterDataObserverProxy 类继承 RecyclerView.AdapterDataObserver
class AdapterDataObserverProxy extends RecyclerView.AdapterDataObserver {
RecyclerView.AdapterDataObserver adapterDataObserver;
int headerCount;
public ArticleDataObserver(RecyclerView.AdapterDataObserver adapterDataObserver, int headerCount) {
this.adapterDataObserver = adapterDataObserver;
this.headerCount = headerCount;
}
@Override
public void onChanged() {
adapterDataObserver.onChanged();
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount) {
adapterDataObserver.onItemRangeChanged(positionStart + headerCount, itemCount);
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) {
adapterDataObserver.onItemRangeChanged(positionStart + headerCount, itemCount, payload);
}
// 当第n个数据被获取,更新第n+1个position
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
adapterDataObserver.onItemRangeInserted(positionStart + headerCount, itemCount);
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
adapterDataObserver.onItemRangeRemoved(positionStart + headerCount, itemCount);
}
@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
super.onItemRangeMoved(fromPosition + headerCount, toPosition + headerCount, itemCount);
}
}
// 2.对于Adapter而言,仅需重写registerAdapterDataObserver()方法
// 然后用 AdapterDataObserverProxy 去做代理即可
class PostAdapter extends PagedListAdapter {
@Override
public void registerAdapterDataObserver(@NonNull RecyclerView.AdapterDataObserver observer) {
super.registerAdapterDataObserver(new AdapterDataObserverProxy(observer, getHeaderCount()));
}
}
我们将额外的逻辑抽了出来作为一个新的类,思路和上一小节的十分相似,同样我们也得到了预期的结果。
经过对源码的追踪,从性能上来讲,这两种实现方式并没有什么不同,唯一的区别就是,前者是针对PagedListAdapter
进行了重写,将Item
更新的代码交给了AsyncPagedListDiffer
;而这种方式中,AsyncPagedListDiffer
内部对Item
更新的逻辑,最终仍然是交给了RecyclerView.Adapter
的notifyItemRangeInserted()
方法去执行的——两者本质上并无区别。
5.最终的解决方案
虽然上文只阐述了Paging library
如何实现Header
,实际上对于Footer
而言也是一样,因为Footer
也可以被视为另外一种的Item
;同时,因为Footer
在列表底部,并不会影响position
的更新,因此它更简单。
下面是完整的Adapter
示例:
class HeaderProxyAdapter : PagedListAdapter<Student, RecyclerView.ViewHolder>(diffCallback) {
override fun getItemViewType(position: Int): Int {
return when (position) {
0 -> ITEM_TYPE_HEADER
itemCount - 1 -> ITEM_TYPE_FOOTER
else -> super.getItemViewType(position)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
ITEM_TYPE_HEADER -> HeaderViewHolder(parent)
ITEM_TYPE_FOOTER -> FooterViewHolder(parent)
else -> StudentViewHolder(parent)
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is HeaderViewHolder -> holder.bindsHeader()
is FooterViewHolder -> holder.bindsFooter()
is StudentViewHolder -> holder.bindTo(getStudentItem(position))
}
}
private fun getStudentItem(position: Int): Student? {
return getItem(position - 1)
}
override fun getItemCount(): Int {
return super.getItemCount() + 2
}
如何做好面试突击,规划学习方向?
面试题集可以帮助你查漏补缺,有方向有针对性的学习,为之后进大厂做准备。但是如果你仅仅是看一遍,而不去学习和深究。那么这份面试题对你的帮助会很有限。最终还是要靠资深技术水平说话。
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。建议先制定学习计划,根据学习计划把知识点关联起来,形成一个系统化的知识体系。
学习方向很容易规划,但是如果只通过碎片化的学习,对自己的提升是很慢的。
同时我还搜集整理2020年字节跳动,以及腾讯,阿里,华为,小米等公司的面试题,把面试的要求和技术点梳理成一份大而全的“ Android架构师”面试 Xmind(实际上比预期多花了不少精力),包含知识脉络 + 分支细节。
在搭建这些技术框架的时候,还整理了系统的高级进阶教程,会比自己碎片化学习效果强太多。
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!