阿里P7Android高级架构进阶视频免费学习请点击:https://space.bilibili.com/474380680 本篇文章将继续从以下两个内容来介绍布局加载与资源系统:
[ LayoutManager]
[ Resources 和 AssetManager]
一、LayoutManager
流程
继承RecyclerView.LayoutManager 重写onLayoutChildren来添加子View 重写scrollVerticallyBy来实现竖向滚动 继承RecyclerView.LayoutManager 就一个抽象方法,重写就行了
onLayoutChildren
相当于ViewGroup的 onLayout.一开始的界面构建就是这个入口
1 在RecyclerView初始化时,会被调用两次。 2 在调用adapter.notifyDataSetChanged()时,会被调用。 3 在调用setAdapter替换Adapter时,会被调用。 4 在RecyclerView执行动画时,它也会被调用。
onLayoutChildren中的流程
报废当前的View 获取对应位置的子view 添加进RecyclerView 测量子View的宽高 根据测量的宽高,给他们排列好位置 注意,这一版本是没考虑缓存,全一股脑添加进RecyclerView的,目的是熟悉代码为后面铺垫 其中的函数
detachAndScrapAttachedViews : 是将当前Recycler中的view全部移除并放到报废缓存里,之后优先重用缓存里的view getDecoratedMeasuredWidth/getDecoratedMeasuredHeight 获取宽高,这个是加上了DecorateView 的,现在没有 RecyclerView.addItemDecoration();直接理解为宽高就行 layoutDecorated就是用来个子View排位置的 actualHeight记录了目前的高度,为了实现LinearLayoutManager的垂直排序来的 然后看看具体代码吧
测试的Activity
就可以看到布局排列好了.但是
onCreateViewHolder调用了20次,onBindViewHolder也调用了20.不能滑动滑动 canScrollVertically返回true就是可以垂直滑动 scrollVerticallyBy是滑动具体的逻辑 scrollVerticallyBy的参数要说明下
参数: dy : 是当前滑动的距离,界面向下滚动的时候,dy为正,向上滚动的时候dy为负 返回的值: 如果Math.abs(返回值)小于dy,说明到达边界了,这里简单的处理下,如果到达边界了直接返回0
逻辑 通过totalScrollY来记录已经滑动的总距离 向下滚动的时候,如果总距离超过了子view的总高度-屏幕高度,说明到达下边界了 向上滚动的时候,如果总距离小于等于0,就是到达了上边界 其他就是正常情况了,使用offsetChildrenVertical来滚动界面 具体如下
这样就完成了简陋版的LinearLayoutManager.完整代码
加入回收功能
其实就是基于上边的简陋版本进行扩展
onLayoutChildren的时候不添加全部view,只添加可视范围内的View 滑动的时候要更复杂一点 如果向下滚动,先往RecyclerView下面添加即将展示的View 如果往上滚动,就往RecyclerView上面添加即将展示的View 添加完View后就调用offsetChildrenVertical进行滚动 完了后检查是否有子View离开了可视界面,如果不可见了,就是用removeAndRecycleView来移除掉 onLayoutChildren 与之前不同的就是最后几句,如果超过RecyclerView的高度了,就不Add了
scrollVerticallyBy 之前说了,分为填充,滚动,回收
填充
例如向下滚动
通过getChildAt获取最后一个View 再通过getPosition获取这个View的Adapter中的位置,最后一个了,就不要继续填充了,因为没有了,如果有下一个,就继续 这里还没有滑动,但是即将滑动的距离dy传进来了,如果最后一个View滑动dy后小于RecyclerView的高度了说明最后一个View已经全部出现在界面上了,之后就是空白了,需要添加新的子View 那就做获取,测量,添加操作 向上滚动是一样的逻辑
滚动
滚动就是直接用offsetChildrenVertical(dy*-1);但是需要和简陋版一样,需要搞定边界问题 例如: 到达上边界后,滑动的距离不是dy,而是第一个View还剩下多少距离可以滑动,代码如下
回收
通过getChildCount() 获取当前所有的子View 例如向上滚动,name就回收最下面的,最下面的View的top滑动后超出了RecyclerView的高度,说明这个View全部在界面外了,可以回收了,使用removeAndRecycleView移除并回收 向下滚动就判断顶部的Bottom是否小于0
这样带回收的LayoutManager也完成了,全部代码如下
public class CustomLayoutManager2 extends RecyclerView .LayoutManager {
private final String TAG = CustomLayoutManager2.class.getSimpleName();
@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams () {
return new RecyclerView .LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT);
}
@Override
public void onLayoutChildren (RecyclerView.Recycler recycler, RecyclerView.State state) {
Log.d(TAG,"onLayoutChildren " );
if (getItemCount() == 0 ){
detachAndScrapAttachedViews(recycler);
return ;
}
if (getItemCount() == 0 && state.isPreLayout()){
return ;
}
detachAndScrapAttachedViews(recycler);
int actualHeight = 0 ;
for (int i = 0 ;i < getItemCount() ; i++){
View scrap = recycler.getViewForPosition(i);
addView(scrap);
measureChildWithMargins(scrap,0 ,0 );
int width = getDecoratedMeasuredWidth(scrap);
int height = getDecoratedMeasuredHeight(scrap);
layoutDecorated(scrap,0 ,actualHeight,width,actualHeight+height);
actualHeight+=height;
if (actualHeight > getHeight()){
break ;
}
}
}
@Override
public boolean canScrollVertically () {
return true ;
}
@Override
public int scrollVerticallyBy (int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
Log.d("feifeifei" ,"getChildCount() " + getChildCount() + " recycler.getScrapList().size() " + recycler.getScrapList().size());
int canScroll = dy;
if (dy>0 ){
View lastView = getChildAt(getChildCount() -1 );
int lastPos = getPosition(lastView);
if (lastPos >= getItemCount()-1 ){
if (lastView.getBottom() - dy < getHeight()){
canScroll = lastView.getBottom() - getHeight();
offsetChildrenVertical(canScroll*-1 );
return 0 ;
}
}
}else {
View firView = getChildAt(0 );
int firstPos = getPosition(firView);
if (firstPos <= 0 ){
if (firView.getTop() - dy >= 0 ){
canScroll = firView.getTop();
offsetChildrenVertical(canScroll*-1 );
return 0 ;
}
}
}
fill(dy,recycler,state);
offsetChildrenVertical(dy*-1 );
recycleOut(dy,recycler,state);
return dy;
}
private void fill (int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
if (dy > 0 ){
View lastView = getChildAt(getChildCount() -1 );
int lastPos = getPosition(lastView);
if (lastPos == getChildCount()-1 ){
return ;
}
Log.d("feifeifei" ,"lastView top" + lastView.getTop() + " bottom " + lastView.getBottom());
if (lastView.getBottom() - dy < getHeight()){
View scrap = recycler.getViewForPosition(lastPos+1 );
addView(scrap);
measureChildWithMargins(scrap,0 ,0 );
int width = getDecoratedMeasuredWidth(scrap);
int height = getDecoratedMeasuredHeight(scrap);
layoutDecorated(scrap,0 ,lastView.getBottom(),width,lastView.getBottom()+height);
}
}else {
View firstView = getChildAt(0 );
Log.d("feifeifei" ,"firstView top" + firstView.getTop() + " bottom " + firstView.getBottom());
int layoutPostion = getPosition(firstView);
if (layoutPostion == 0 ){
return ;
}
if (firstView.getTop() >= 0 ){
View scrap = recycler.getViewForPosition(layoutPostion -1 );
addView(scrap,0 );
measureChildWithMargins(scrap,0 ,0 );
int width = getDecoratedMeasuredWidth(scrap);
int height = getDecoratedMeasuredHeight(scrap);
layoutDecorated(scrap,0 ,firstView.getTop() - height,width,firstView.getTop());
}
}
}
private void recycleOut (int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
for (int i = 0 ; i <getChildCount() ;i++){
View view = getChildAt(i);
Log.d("feifeifei" ,"recycleOut position " + i + " top " + view.getTop() + " bottom " + view.getBottom());
if (dy >0 ){
if (view.getBottom()-dy <0 ){
Log.d("feifeifei" ,"recycleOut " + i);
removeAndRecycleView(view,recycler);
}
}else {
if (view.getTop()-dy > getHeight()){
Log.d("feifeifei" ,"recycleOut " + i);
removeAndRecycleView(view,recycler);
}
}
}
}
}
然后通过adapter打印日志,onCreateViewHolder只打印了7次(我的界面上显示的7个item),然后滚动界面的时候,onBindViewHolder依次打印.看来回收还是成功的.这样一个简单版的带回收的LinearLayoutManager就好了
二、Resources 和 AssetManager
在 Android 开发中我们使用 Resources 来获取 res 目录下的各种与设备相关的资源。而使用 AssetManager 来获取 assets 目录下的资源。
一般来说 Resources 对象通过 Context 获得。 appContext 中的 resources 是在创建之后通过如下代码设置。 context.setResources(packageInfo.getResources()); 而 LoadedApk 中则是通过如下代码创建:
其中 mResDir 对应 ApplicationInfo.sourceDir 字段
ResourcesManager.getInstance().getResources 的主要逻辑如下:
getOrCreateResources 主要逻辑如下:
其中 createAssetManager(key) 的主要逻辑如下:
最终 Resources 的创建逻辑如下:
以 Resouces.getString 来查看资源的读取流程
1)调用 getText
2)通过 resourcesImpl 调用 AssetsManager 的 getResourceText
3)getResourceText
4)getResourceValue
5)最后通过 loadResourceValue 这一原生方法读取。
阿里P7Android高级架构进阶视频免费学习请点击:https://space.bilibili.com/474380680 参考 https://www.jianshu.com/p/f775cfcae2f6 https://www.jianshu.com/p/1799e16c80f9
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从二进制到误差:逐行拆解C语言浮点运算中的4008175468544之谜
· .NET制作智能桌面机器人:结合BotSharp智能体框架开发语音交互
· 软件产品开发中常见的10个问题及处理方法
· .NET 原生驾驭 AI 新基建实战系列:向量数据库的应用与畅想
· 从问题排查到源码分析:ActiveMQ消费端频繁日志刷屏的秘密
· C# 13 中的新增功能实操
· Ollama本地部署大模型总结
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(4)
· 卧槽!C 语言宏定义原来可以玩出这些花样?高手必看!
· langchain0.3教程:从0到1打造一个智能聊天机器人