多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

vlayout 源码级实战指南:用 VirtualLayoutManager 在 RecyclerView 中搭建异构混合布局

vlayout 源码级实战指南:用 VirtualLayoutManager 在 RecyclerView 中搭建异构混合布局 移动开发UI组件前端【免费下载链接】vlayoutProject vlayout is a powerfull LayoutManager extension for RecyclerView, it provides a group of layouts for RecyclerView. Make it able to handle a complicate situation when grid, list and other layouts in the same recyclerview.项目地址https://gitcode.com/gh_mirrors/vl/vlayout点击查看免费下载vlayout 是阿里巴巴开源的一套 RecyclerView 布局扩展方案通过自定义VirtualLayoutManager接管布局流程让同一个 RecyclerView 页面中能够同时容纳线性列表、网格、瀑布流、吸顶、悬浮与固定视图等异构布局。本文以 vlayout 的官方中文文档为主体结合仓库内源码、布局属性文档与 FAQ 进行纵深讲解读完你可以完成从依赖引入、布局初始化、复用池调优到自定义 Adapter 与 LayoutHelper 属性的完整接入并掌握滚动定位、嵌套滚动、背景图等高频实战问题的解决方案。一、设计思路LayoutManager LayoutHelper 的两级架构vlayout 的核心设计是通过定制化的VirtualLayoutManager接管整个 RecyclerView 的布局逻辑。在 VirtualLayoutManager.java 中LayoutManager管理着一系列LayoutHelper而每一个LayoutHelper只负责页面某一段 position 范围内组件的具体布局。由此得到两个关键能力异构布局不同LayoutHelper可以执行完全不同的布局逻辑于是同一页面内可以混排 Grid、List、瀑布流、吸顶等结构这是系统自带LinearLayoutManager、GridLayoutManager难以直接做到的能力可扩展性LayoutHelper是抽象类见 LayoutHelper.java框架预留了beforeLayout、doLayout、afterLayout、computeAlignOffset、isFixLayout等抽象钩子开发者可以继承并实现自己的LayoutHelper以提供全新布局能力。从LayoutHelper源码还可以看到每个 helper 通过RangeIntegerRANGE_ALL/RANGE_EMPTY来声明自己负责的 item 区间isOutOfRange(position)判断某个 position 是否属于该 helpersetRange(start, end)在区间与itemCount不一致时会抛出MismatchChildCountException。这意味着每个LayoutHelper实质上是一个分而治之的布局单元最终由VirtualLayoutManager统一调度它们共同完成整页布局。二、内置布局家族非 fix 与 fix 两大类别vlayout 提供了一套默认通用布局实现解耦了 View 与布局之间的关系。默认实现全部位于 vlayout/src/main/java/com/alibaba/android/vlayout/layout/ 目录LayoutHelper布局效果关键配置LinearLayoutHelper线性布局行为类似ListView/LinearLayoutManagerdividerHeightGridLayoutHelperGrid 布局支持横向 colspan、spanCount、权重、间隙spanCount、spanSizeLookup、weights、vGap/hGap、autoExpandFixLayoutHelper固定布局始终在屏幕固定位置显示alignType、x、y、fixAreaAdjusterScrollFixLayoutHelper固定布局仅当页面滑动到该区域时才显示可用于返回顶部或书签showType、alignType、x、yFloatLayoutHelper浮动布局固定显示在屏幕上且可被用户拖拽alignType、x、yColumnLayoutHelper栏格布局所有子项排在一排可配置列宽比例weightsSingleLayoutHelper通栏布局只显示一个组件 View—OnePlusNLayoutHelper一拖 N 布局可配置 1-5 个子元素—StickyLayoutHelper吸顶/吸底布局stickyStart、offsetStaggeredGridLayoutHelper瀑布流布局lane、vGap/hGap上述默认实现大致可分为两类非 fix 类型如线性、Grid、栏格、瀑布流它们的子节点处于页面流中随页面滚动而滚动fix 类型如FixLayoutHelper、ScrollFixLayoutHelper、FloatLayoutHelper、StickyLayoutHelper子节点往往不随页面滚动或滚动到临界点后被固定。此外vlayout 扩展了 RecyclerView 的组件复用机制管理大的模块布局组合使同一 RecyclerView 内的 View 可以复用减少 View 的创建与销毁从而提升滚动性能。三、快速接入依赖引入与 LayoutManager 初始化3.1 引入依赖版本请参考 vlayout 官方 release 说明 中的最新版本最新的 aar 已发布到 jcenter 与 MavenCentral确保项目配置了这两个仓库源后再引入。Gradle 方式compile (com.alibaba.android:vlayout:1.2.8aar) { transitive true }Maven 方式pom.xmldependency groupIdcom.alibaba.android/groupId artifactIdvlayout/artifactId version1.2.8/version typeaar/type /dependency需要说明的是仓库首页明确标注该项目停止维护后续重大更新与维护主要基于 Tangram-Android 进行。官方建议虽然 vlayout 布局灵活但 API 相对原始手工维护数据与LayoutHelper较为繁琐若用于生产环境强烈建议通过 Tangram-Android 间接使用 vlayout——Tangram 具备 vlayout 的所有功能且隐藏了细节通过数据配置即可搭建页面能规避绝大多数 Issue 中提到的坑。3.2 初始化 VirtualLayoutManagerfinal RecyclerView recyclerView (RecyclerView) findViewById(R.id.recycler_view); final VirtualLayoutManager layoutManager new VirtualLayoutManager(this); recyclerView.setLayoutManager(layoutManager);3.3 设置回收复用池大小如果一屏内相同类型的 View 个数较多需要设置一个合适大小的RecycledViewPool防止来回滚动时频繁创建 ViewRecyclerView.RecycledViewPool viewPool new RecyclerView.RecycledViewPool(); recyclerView.setRecycledViewPool(viewPool); viewPool.setMaxRecycledViews(0, 10);注意上述示例只针对 type0 的 item 设置了复用池大小。如果你的页面有多种 type需要为每一种类型分别调用setMaxRecycledViews(type, size)。FAQ 中给出的完整写法是按类型逐个配置例如viewPool new RecyclerView.RecycledViewPool(); recyclerView.setRecycledViewPool(viewPool); viewPool.setMaxRecycledViews(0, 5); viewPool.setMaxRecycledViews(1, 5); viewPool.setMaxRecycledViews(2, 5); viewPool.setMaxRecycledViews(3, 10); viewPool.setMaxRecycledViews(4, 10); viewPool.setMaxRecycledViews(5, 10);3.4 混淆配置如果 release 包混淆过无法正常运行而 debug 包正常多半是混淆配置不完整。请为 vlayout 添加以下防混淆配置-keepattributes InnerClasses -keep class com.alibaba.android.vlayout.ExposeLinearLayoutManagerEx { *; } -keep class android.support.v7.widget.RecyclerView$LayoutParams { *; } -keep class android.support.v7.widget.RecyclerView$ViewHolder { *; } -keep class android.support.v7.widget.ChildHelper { *; } -keep class android.support.v7.widget.ChildHelper$Bucket { *; } -keep class android.support.v7.widget.RecyclerView$LayoutManager { *; }四、数据加载的两种方式4.1 方式一使用 DelegateAdapter推荐DelegateAdapter本身继承自VirtualLayoutAdapter见 DelegateAdapter.java使用方式与普通 Adapter 几乎一致唯一区别是需要继承DelegateAdapter.Adapter并重写onCreateLayoutHelper()方法该方法签名定义在 DelegateAdapter.javaDelegateAdapter delegateAdapter new DelegateAdapter(layoutManager, hasConsistItemType); recycler.setAdapter(delegateAdapter); // 之后可以通过 setAdapters 或 addAdapter 方法添加 DelegateAdapter.Adapter delegateAdapter.setAdapters(adapters); // or CustomAdapter adapter new CustomAdapter(data, new GridLayoutHelper()); delegateAdapter.addAdapter(adapter); // 如果数据有变化调用自定义 adapter 的 notifyDataSetChanged() adapter.notifyDataSetChanged();hasConsistItemType参数是复用行为的开关hasConsistItemTypetrue不论是否属于同一个子 adapter相同 itemType 的 item 都能复用即不同子 adapter 之间共享同一类型hasConsistItemTypefalse默认不同子 adapter 之间的类型不共享。从源码可以看到hasConsistItemTypefalse时DelegateAdapter.getItemViewType()会使用 Cantor 配对函数Cantor.getCantor(subItemType, index)将子 adapter 下标 子 itemType编码成全局唯一的 viewType参见 DelegateAdapter.java并在onCreateViewHolder中通过Cantor.reverseCantor反解回原始类型。这一点在 FAQ 中被专门强调如果没有处理好hasConsistItemType不同子 adapter 返回相同 itemType 时会导致ViewHolder类型转换异常等 bug。另外DelegateAdapter内部还维护了每个子 adapter 的AdapterDataObservermStartPosition记录其在总列表中的起始偏移当子 adapter 的 itemCount 变化时会自动更新后续 adapter 的起始位置并同步LayoutHelper的 itemCount见 DelegateAdapter.java。4.2 方式二继承 VirtualLayoutAdapter当业务有自定义复杂需求时可以继承VirtualLayoutAdapter实现自己的 Adapterpublic class MyAdapter extends VirtualLayoutAdapter { ...... } MyAdapter myAdapter new MyAdapter(layoutManager); // 构造 layoutHelper 列表 ListLayoutHelper helpers new LinkedList(); GridLayoutHelper gridLayoutHelper new GridLayoutHelper(4); gridLayoutHelper.setItemCount(25); helpers.add(gridLayoutHelper); GridLayoutHelper gridLayoutHelper2 new GridLayoutHelper(2); gridLayoutHelper2.setItemCount(25); helpers.add(gridLayoutHelper2); // 将 layoutHelper 列表传递给 adapter myAdapter.setLayoutHelpers(helpers); // 将 adapter 设置给 recyclerView recycler.setAdapter(myAdapter);注意这种方式下当LayoutHelpers的结构或数据数量等会影响布局的元素发生变化时必须主动调用setLayoutHelpers()更新布局模式而DelegateAdapter会明确拒绝直接调用setLayoutHelpers源码中直接抛出UnsupportedOperationException见 DelegateAdapter.javaLayoutHelper 的构建完全由onCreateLayoutHelper()驱动。五、LayoutHelper 布局属性全景解析每一种 LayoutHelper 都有自己的布局属性来控制布局样式完整说明见 docs/ATTRIBUTES-ch.md。以下属性是 vlayout 布局能力的关键均通过对应的 setter 接口配置。5.1 margin、paddingLayoutHelper支持设置外边距margin与内边距padding概念与 Android 系统的 margin/padding 相同但有明显差异它不是整个 RecyclerView 页面的 margin/padding而是每一块 LayoutHelper 所负责区域的margin/padding一个页面可以有多个 LayoutHelper不同 LayoutHelper 可设置不同的 margin/paddingLayoutHelper 的 margin/padding 与页面 RecyclerView 自身的 margin/padding 可以共存目前主要针对非 fix 类型的 LayoutHelper 实现fix 类型内部没有相对位置关系不处理边距例如FixLayoutHelper的setMargin在源码中就是空实现见 FixLayoutHelper.java。public void setPadding(int leftPadding, int topPadding, int rightPadding, int bottomPadding) public void setMargin(int leftMargin, int topMargin, int rightMargin, int bottomMargin)5.2 bgColor、bgImg区域背景背景色或背景图严格说不是布局属性但由于 vlayout 直接对视图进行布局、不同区域视图的父节点都是 RecyclerView要为某一块区域单独绘制背景很难做到因此框架做了特殊处理非 fix、非 float 类型的 LayoutHelper 支持配置背景色或背景图。设置背景色public void setBgColor(int bgColor)设置背景图需要两步。首先为 LayoutManager 提供一个ImageView简单工厂LayoutViewFactory定义见 LayoutViewFactory.javathis.mLayoutManager.setLayoutViewFactory(new LayoutViewFactory() { Override public opinion generateLayoutView(NonNull Context context) { return new XXImageView(context); } });再为 LayoutHelper 设置图片加载的 ListenerbaseHelper.setLayoutViewBindListener(new BindListener(imgUrl)); baseHelper.setLayoutViewUnBindListener(new UnbindListener(imgUrl)); private static class BindListener implements BaseLayoutHelper.LayoutViewBindListener { private String imgUrl; public BindListener(String imgUrl) { this.imgUrl imgUrl; } Override public void onBind(View layoutView, BaseLayoutHelper baseLayoutHelper) { //loading image } } private static class UnbindListener implements BaseLayoutHelper.LayoutViewUnBindListener { private String imgUrl; public UnbindListener(String imgUrl) { this.imgUrl imgUrl; } Override public void onUnbind(View layoutView, BaseLayoutHelper baseLayoutHelper) { //cancel loading image } }背景绘制在BaseLayoutHelper中有完整实现通过mLayoutRegionRect累加可见子项的区域滚动时按 scrolled 偏移命中可视区域后通过helper.generateLayoutView()生成并挂载背景 View越界或不需要背景时移除见 BaseLayoutHelper.java 与afterLayout相关逻辑。FAQ 提醒设置背景图可能触发循环布局。背景设置发生在布局 view 的阶段设置图片会触发一次新的 layout进而再次触发背景图设置最终死循环。必须在onBind()里判断图片是否已成功加载一次绑定过就不再设置在onUnbind()里清除绑定成功状态即可阻断死循环路径。仓库还提供了一个封装了该逻辑的DefaultLayoutViewHelper可以参考。此外背景图滑动变形的问题#275可以通过将背景 ImageView 的scaleType设为matrix、并按不同手机 dpi 提前调整好背景图片尺寸来解决。5.3 aspectRatio宽高比为了保证布局过程中视图高度一致vlayout 设计了aspectRatio属性它是宽与高的比例。LayoutHelper与 vlayout 添加视图的LayoutParams上都有aspectRatio后者的优先级更高但两者含义不同LayoutHelper的aspectRatio指一行视图整体宽度与高度之比宽度已扣除 RecyclerView 与该 LayoutHelper 的 margin、paddingLayoutParams的aspectRatio在 LayoutHelper 计算出视图宽度之后用于确定视图高度会覆盖 LayoutHelper 计算出的视图高度。// LayoutHelper 维度 public void setAspectRatio(float aspectRatio) // LayoutParams 维度 ((VirtualLayoutManager.LayoutParams) layoutParams).mAspectRatio在 FixLayoutHelper.java 的doMeasureAndLayout中可以印证测量高度时优先判断params.mAspectRatio非 NaN 且大于 0其次才回退到mAspectRatio最后才是WRAP_CONTENT/固定高度即视图级属性确实覆盖 helper 级属性。5.4 dividerHeight线性分隔高度LinearLayoutHelper的属性该 Helper 是类似 ListView 的线性布局dividerHeight即每个组件之间的间距public void setDividerHeight(int dividerHeight)源码中该值会被限制为非负小于 0 时置 0实际布局时在非首行的每一项前加入该间距见 LinearLayoutHelper.java。5.5 weights列宽权重ColumnLayoutHelper、GridLayoutHelper的属性。默认情况下每个网格中每一列的宽度相同通过weights可以按比例分配各列宽度效果类似LinearLayout的 weight 属性。weights是 float 数组每一项代表某一列占父容器宽度的百分比总和建议为 100否则布局会超出容器宽度布局有 4 列时weights 长度应为 4长度大于 4多出的部分不参与宽度计算小于 4不足的部分默认平分剩余空间。public void setWeights(float[] weights)建议优先使用功能更强大的GridLayoutHelper。5.6 vGap、hGap网格间隙GridLayoutHelper与StaggeredGridLayoutHelper都有这两个属性分别控制视图之间的垂直间距与水平间距public void setHGap(int hGap) public void setVGap(int vGap)源码会将负值修正为 0。GridLayoutHelper计算列宽时会扣除间隙mSizePerSpan (mTotalSize - (mSpanCount - 1) * mHGap) / mSpanCount见 GridLayoutHelper.java。5.7 spanCount、spanSizeLookup网格列数与跨列GridLayoutHelper的属性参考系统GridLayoutManager。spanCount表示网格列数默认每个视图占一个网格通过自定义SpanSizeLookup可指定某个位置的视图占用多个网格区域横向 colspanpublic void setSpanCount(int spanCount) public void setSpanSizeLookup(SpanSizeLookup spanSizeLookup)spanCount必须大于 0否则抛出IllegalArgumentException见 GridLayoutHelper.java。FAQ 特别提示在SpanSizeLookup.getSpanSize(int position)中position参数是整个页面的绝对位置需要减去getStartPosition()得到当前 LayoutHelper 内的相对位置再使用。5.8 autoExpand不足一行时是否自动撑满GridLayoutHelper的属性。当一行内视图个数少于spanCount时如果autoExpand为 true视图总宽度会填满可用区域否则会在屏幕右侧留白public void setAutoExpand(boolean isAutoExpand)5.9 lane瀑布流列数StaggeredGridLayoutHelper的属性与GridLayoutHelper的spanCount类似控制瀑布流列数public void setLane(int lane)5.10 fixAreaAdjusterfix 布局区域偏移fix 类型的 LayoutHelper 有时需要相对父容器四边设置偏移量。典型场景整个页面有一个固定的标题栏叠加在 vlayout 容器上vlayout 内部的 fix 类型视图不希望与外部标题栏重叠此时通过fixAreaAdjuster做整体偏移public void setAdjuster(FixAreaAdjuster adjuster)FixAreaAdjuster的实现见 FixAreaAdjuster.java最终在FixLayoutHelper.doMeasureAndLayout中会叠加到四个边的坐标计算里如left helper.getPaddingLeft() mX mAdjuster.left。5.11 alignType、x、y吸边基准与偏移FixLayoutHelper、ScrollFixLayoutHelper、FloatLayoutHelper的属性表示吸边时的基准位置四个取值分别为TOP_LEFT、TOP_RIGHT、BOTTOM_LEFT、BOTTOM_RIGHT常量定义见 FixLayoutHelper.java。x、y是相对该基准位置的偏移量最终偏移还会叠加fixAreaAdjuster的影响TOP_LEFT基准为左上角x是视图左边相对父容器左边距的偏移y是视图顶边相对父容器上边距的偏移TOP_RIGHT基准为右上角x是视图右边相对父容器右边距的偏移y是视图顶边相对父容器上边距的偏移BOTTOM_LEFT基准为左下角x是视图左边相对父容器左边距的偏移y是视图底边相对父容器下边距的偏移BOTTOM_RIGHT基准为右下角x是视图右边相对父容器右边距的偏移y是视图底边相对父容器下边距的偏移。public void setAlignType(int alignType) public void setX(int x) public void setY(int y)5.12 showTypeScrollFix 显示时机ScrollFixLayoutHelper的属性取值有SHOW_ALWAYS、SHOW_ON_ENTER、SHOW_ON_LEAVESHOW_ALWAYS与FixLayoutHelper行为一致固定在某个位置SHOW_ON_ENTER默认不显示当页面滚动到这个视图的位置时才显示SHOW_ON_LEAVE默认不显示当页面滚出这个视图的位置时显示。public void setShowType(int showType)5.13 stickyStart、offset吸顶/吸底与偏移StickyLayoutHelper的属性。当视图位置在屏幕范围内时随页面滚动当视图滑出屏幕时StickyLayoutHelper将其固定在顶部stickyStarttrue或底部stickyStartfalse固定位置支持偏移量offsetpublic void setStickyStart(boolean stickyStart) public void setOffset(int offset)FAQ 提示吸顶状态的 item 会被添加到 RecyclerView 的最顶层如果被其他 item 覆盖很可能是其他 item 设置了 z 参数Android 5.0或调整了 drawingOrder如使用CardView的典型场景解决方法是给吸顶 item 设置更大的 z 值或检查 drawingOrder。判断吸顶 item 是否到达顶部可通过virtualLayoutManager.findFirstVisibleItemPosition()当其大于 StickyLayoutHelper 内 item 的位置即视为已到顶。六、实战 FAQ高频问题的官方解法vlayout/src/.../VLayoutFAQ.md 汇总了社区高频问题以下是几个最具代表性的实战解法。6.1 下拉刷新与加载更多vlayout 只负责布局下拉刷新与加载更多需要业务方自行处理且可能存在与某些下拉刷新控件不兼容的 bug。很多下拉刷新框架通过判断 RecyclerView 第一个 view 的 top 是否为 0 来触发下拉动作而 vlayout 在处理背景、悬浮态时会加入一些对 LayoutManager 不可见、但又真实存在于视图树中的 View建议使用layoutManager.getChildAt(0)获取第一个 view。加载更多可通过注册OnScrollListener监听滚动状态触发RecyclerView.OnScrollListener onScrollListener new RecyclerView.OnScrollListener() { public void onScrollStateChanged(RecyclerView recyclerView, int newState) { } public void onScrolled(RecyclerView recyclerView, int dx, int dy) { //hasMore: status of current page, means if theres more data, you have to maintain this status if(hasMore) { VirtualLayoutManager lm (VirtualLayoutManager)recyclerView.getLayoutManager(); int first0, last0, total0; first ((LinearLayoutManager)lm).findFirstVisibleItemPosition(); last ((LinearLayoutManager)lm).findLastVisibleItemPosition(); total recyclerView.getAdapter().getItemCount(); if(last 0 last total - earlyCountForAutoLoad) { //earlyCountForAutoLoad: help to trigger load more listener earlier //TODO trigger loadmore listener } } } };6.2 横向滑动vlayout 没有实现横向滚动的 LayoutHelper因为LayoutHelper目前只能做静态布局。对于与数据绑定的动态横向滚动布局如ViewPager或横向 RecyclerView官方建议以组件的形式提供用一个LinearLayoutHelper包一个 Item这个 Item 是ViewPager或横向滚动的 RecyclerView且它们可以与整页 RecyclerView 共用回收复用池。6.3 可滚动区域中嵌套使用 vlayout 的 RecyclerView不建议嵌套滚动除非手势不冲突如果要完全展开 vlayout 内容、牺牲滚动复用可调用VirtualLayoutManager.setNoScrolling(true)见 VirtualLayoutManager.java。6.4 获取 DelegateAdapter 中数据的相对位置DelegateAdapter提供findOffsetPosition(int absolutePosition)方法传入整个页面的绝对位置即可返回相对位置实现见 DelegateAdapter.java。也可以在自定义 Adapter 中重写onBindViewHolderWithOffset()代替传统onBindViewHolder()其position参数同样是相对位置public static abstract class AdapterVH extends RecyclerView.ViewHolder extends RecyclerView.AdapterVH { public abstract LayoutHelper onCreateLayoutHelper(); protected void onBindViewHolderWithOffset(VH holder, int position, int offsetTotal) { } }6.5 滚动到某个 item 并带偏移自带scrollToPosition/smoothScrollToPosition不满足需求时可自行用动画驱动滚动。FAQ 给出的RecyclerViewFlinger参考实现核心思路先判断目标 position 是否在可视区域内不在则按步长默认屏幕高度的一半分步smoothScrollBy逼近在区域内则计算top - offset精确微调最后通过ScrollFinishedListener回调完成状态完整实现见 docs/VLayoutFAQ.md。七、Demo 与更多资源仓库内提供了完整可运行的示例工程examples 目录下包含MainActivity、VLayoutActivity、OnePlusNLayoutActivity、DebugActivity、TestActivity等覆盖了多种 LayoutHelper 的组合用法单元测试位于 vlayout/src/androidTest/java/com/alibaba/android/vlayout/VirtualLayoutManagerTest.java可作为验证布局行为与理解内部机制的参考。阅读完本文后建议继续深入以下仓库资源布局属性完整说明docs/ATTRIBUTES-ch.md官方 FAQ组件复用、混淆、横向滑动等docs/VLayoutFAQ.md框架设计文档vlayout/DESIGN.md核心实现VirtualLayoutManager.java、DelegateAdapter.java、LayoutHelper.java各布局实现vlayout/src/main/java/com/alibaba/android/vlayout/layout/vlayout 遵循 MIT 开源许可证具体的开源协议声明见 LICENSE。赞分享移动开发UI组件前端【免费下载链接】vlayoutProject vlayout is a powerfull LayoutManager extension for RecyclerView, it provides a group of layouts for RecyclerView. Make it able to handle a complicate situation when grid, list and other layouts in the same recyclerview.项目地址https://gitcode.com/gh_mirrors/vl/vlayout点击查看免费下载相关推荐如何实现Windows远程桌面多用户同时登录RDPWrap配置文件的完整指南如何实现Windows远程桌面多用户同时登录RDPWrap配置文件的完整指南 你是否曾经遇到过这样的困扰公司团队需要同时远程访问同一台服务器但WindowQQ空间数据备份5 分钟完整搬回本地电脑QQ空间数据备份5 分钟完整搬回本地电脑 账号一旦被封QQ空间里的历史说说、留言和互动记录说没就没了这笔账你算过吗 平台不会替你留档记录消失就是消失。网页爬虫数据分析终极vlayout布局管理器指南VirtualLayoutManager工作机制深度解析终极vlayout布局管理器指南VirtualLayoutManager工作机制深度解析 想要在Android应用中实现复杂多变的布局效果吗vlayout是移动开发UI组件前端创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表