[Android+JetPack] (Java实现) Retrofit2+RxJava3+Paging3+RecyclerView 实现加载网络数据例子 记录
文章目录
- 前言
- 参考链接
- 依赖库及版本
- Demo效果
- 接口及数据展示
- 各项模块
- `Retrofit2`
- Bean,对应上面的接口返回.
- Service API部分
- `Paging3`
- `PagingSource`以及 `RxPagingSource`
- `PagingDataAdapter` 适配器
- `ViewModel`
- `PublicInfoPage /Activity`
- 最后
前言
继续安卓学习之旅,本章的主要目标是:
1.完成一个无限上拉加载的列表(Paging3 + RecyclerView)
2.加载的是网络数据, 要采用主流的 Retrofit+okhttp方式
3.在了解了RxJava之后,也希望用上Rxjava
4.用到ViewModel来配合,以及一些jetpack的东西都用上
(为什么不用Paging2
? 这里主要是看说3比2还要方便些,所以就偷懒没去用Paging2
)
参考链接
这些是在学习和搜索中看到的比较好的文章,不过他们要么是kotlin 要么是RxJava2,都不是能直接套上去就用的,但是从文章里面总结归纳,也是有借鉴效果的.
- SmartRefreshLayout-github 这个后期再结合Paging3,完成一个有酷炫下拉及淘宝二楼效果的的demo
- Java实现)使用官方Paging3分页库实现RecyclerView加载更多(loadmore)的功能 这个较为简洁,没那么多原理的描述,方便更实战的理解借鉴
- Android paging3 使用和踩坑经验分享 这个虽然是kotlin,不过里面一些名词的解释不错, 适合快速扫盲
- Jetpack新成员,Paging3从吐槽到真香
依赖库及版本
为什么要说这个, 因为在实际百度各方面资料的时候,没仔细区分好版本,导致在练习过程中走了不少弯路,踩了坑.为避免这个情况,这里列出本Demo中的各个依赖库及版本
Retrofix2
// 引入 retrofix 网络框架(自带okhttp)// github :https://github.com/square/retrofit// 视频教学// https://www.bilibili.com/video/BV1vV411W75V?p=4&vd_source=3dc64571e08f84008d5c43796c009480implementation "com.squareup.retrofit2:retrofit:2.9.0"implementation 'com.squareup.retrofit2:converter-gson:2.9.0'implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'
Rxjava3
// 支持RxJava/RxAndroidimplementation 'io.reactivex.rxjava3:rxandroid:3.0.2'implementation 'io.reactivex.rxjava3:rxjava:3.1.5'
Paging3
// 引入 paging 3// 注意, 由于上面我们用的是 retrofit2 + rxjava3// 所以,在使用 paging3的时候, 要选 支持rxjava3的 paging-rxjava3// 切记版本对应好def paging_version = "3.1.1"implementation "androidx.paging:paging-runtime:$paging_version"
// // optional - RxJava2 support
// implementation "androidx.paging:paging-rxjava2:$paging_version"// optional - RxJava3 supportimplementation "androidx.paging:paging-rxjava3:$paging_version"
这里稍微提一下, 如果用的是
RxJava3
, 就使用RxJava3 support的可选项, 不然不匹配,但同时也造成另一个问题, 这里插入说一下哈
就是
包括目前官网(点击进入)那边的, 关于对RxPagingSource
的示例里面, 也应该用的还是RxJava2
,如果你和我一样用RxJava3
,那大概率在做map
的时候,会报错说, 类型转换失败, 不能用 this::toLoadResult
这个稍后再说…
Demo效果
一个简单的demo
接口及数据展示
各项模块
Retrofit2
Bean,对应上面的接口返回.
Response_public_info_bean
package retrofit.bean;import java.util.List;/*** @author: tiannan* @time: 2023/4/12.* @email: tianNanYiHao@163.com* @descripetion: 此处添加描述*/
public class Response_public_info_bean {private String msg;private String code;private Datas data;public String getMsg() {return msg;}public void setMsg(String msg) {this.msg = msg;}public String getCode() {return code;}public void setCode(String code) {this.code = code;}public Datas getData() {return data;}public void setData(Datas data) {this.data = data;}public class Datas {private int pageSize;private List<Cell> list;public int getPageSize() {return pageSize;}public void setPageSize(int pageSize) {this.pageSize = pageSize;}public List<Cell> getList() {return list;}public void setList(List<Cell> list) {this.list = list;}@Overridepublic String toString() {return "Datas{" +"list=" + list +'}';}public class Cell {private String productName;private String productTypeName;private String riskRateName;private int id;private int pageNum; // 增加两个下标 page页下标private int indexNum;// 增加两个下标 newsInfo(cell)页下标@Overridepublic String toString() {return "News{" +"productName='" + productName + '\'' +", productTypeName='" + productTypeName + '\'' +", riskRateName='" + riskRateName + '\'' +", id=" + id +", pageNum=" + pageNum +", indexNum=" + indexNum +'}';}public String getProductName() {return productName;}public void setProductName(String productName) {this.productName = productName;}public String getProductTypeName() {return productTypeName;}public void setProductTypeName(String productTypeName) {this.productTypeName = productTypeName;}public String getRiskRateName() {return riskRateName;}public void setRiskRateName(String riskRateName) {this.riskRateName = riskRateName;}public int getId() {return id;}public void setId(int id) {this.id = id;}public int getPageNum() {return pageNum;}public void setPageNum(int pageNum) {this.pageNum = pageNum;}public int getIndexNum() {return indexNum;}public void setIndexNum(int indexNum) {this.indexNum = indexNum;}}}
}
Service API部分
这里不展开说太多
Retrofit2
部分的东西,这里只贴一下和本章有关的部分代码
/*** 请求公募数据列表* @param map* @return*/@GET(App_Url.admin_getPublicProductInfoPageList)Single<Response_public_info_bean> admin_getPublicProductInfoPageList(@QueryMap HashMap<String,String> map);
以上部分完成后 ,能够通过RxJava3
+ Retrofit2
配合完成一次网络请求, 基本就算完成Demo一半功能了
Paging3
PagingSource
以及 RxPagingSource
这里一开始我用错了
RxPagingSource
的导入版本, 用成了rxjava2的,踩了些坑
当时导入了paging.rxjava2
这个版本,
主要原因还是在配置依赖的时候, 把paging-rxjava2:3.1.1
版本也同步了
版本问题注意好
回到 RxPagingSource
按照网上的文章的示例, 先处理 loadSingle()函数的实现,这里有坑就是上面说的, RxJava3 + Paging3的情况下,
在 return
网络请求的时候,会报错
我这里没有在详细探究是否由于RxJava2的原因导致不能用 ::
这种双冒号的写法
这里仅仅贴一下RxJava2
下的map 和 RxJava3
下的map的源码区别
RxJava2版本:
RxJava3版本:
确实有一点区别, 这个先放一放, 等后期有空再看怎么处理…
先直接看怎么去写这个 this::toLoadResult
首先,既然通过Retrofit2
,我们已经定义了网络请求的返回值
那么我们在RxPagingSource
的 loadSingle()
中, 会去调用网络请求,得到一个 Single<Response_public_info_bean>
我们再看官网例子的这部分代码
注意看返回值其实是LoadResult<Integer, User>
,或者说,在本文章 我们要的返回值其实是 LoadResult<Integer, Response_public_info_bean.Datas.Cell>
所以.对于map操作符,在Rxjava3的下, 我们是可以自己去提供一个Function<T,R>
,这里面T
就是我们上面的Response_public_info_bean
R
就是Response_public_info_bean.Datas.Cell>
所以代码就是
(这里要注意下prevkey, 和 nextKey的入参 , 要做好逻辑判断, 一开始我参考别人的代码, 在prevKey填的是null, 在nextKey填入的是nextPageKey+1,结果导致加载页码瞬间冲到了几百页, 其实总page数量才不过十几页)
为了更加清晰明了的展示页码和条数下标, 我又添加了一个map操作符, 是给cell这个Bean数据再添加一下当前所属的页码 和 当前的下标
Function的入参依然是 Response_public_info_bean
返回也还是 Response_public_info_bean
, 相当于我们就对Response_public_info_bean
数据做了个数据加工
所以,基于RxJava3的map操作符这边就可以这样返回
PagingDataAdapter
适配器
public class PublicInfoAdapter extends PagingDataAdapter<Response_public_info_bean.Datas.Cell, PublicInfoAdapter.Holder> {public PublicInfoAdapter(@NotNull DiffUtil.ItemCallback<Response_public_info_bean.Datas.Cell> diffCallback) {super(diffCallback);}@NonNull@NotNull@Overridepublic Holder onCreateViewHolder(@NonNull @NotNull ViewGroup parent, int viewType) {PublicInfoCellBinding publicInfoCellBinding = PublicInfoCellBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);return new Holder(publicInfoCellBinding);}@Overridepublic void onBindViewHolder(@NonNull @NotNull Holder holder, int position) {Response_public_info_bean.Datas.Cell cell = getItem(position);Log.d("dfaddfsa", "onBindViewHolder: " + cell.getPageNum() +"."+ cell.getIndexNum() + "---" + cell.getProductName());holder.publicInfoCellBinding.textTitle.setText(cell.getProductName());holder.publicInfoCellBinding.underflag.setText("第"+cell.getPageNum() + "页.第" + cell.getIndexNum()+"条");}public class Holder extends RecyclerView.ViewHolder {/*** 给每个 Holder 实例 一个 viewBinding*/private PublicInfoCellBinding publicInfoCellBinding;public Holder(@NonNull @NotNull View itemView) {super(itemView);}public Holder(@NonNull PublicInfoCellBinding publicInfoCellBinding){super(publicInfoCellBinding.getRoot());this.publicInfoCellBinding = publicInfoCellBinding;}}
}
这里没太多可以说的,网上基本讲明白了, 我仅仅分享我遇到的一个问题
页面列表在加载完成后, 出现了一屏数据的重复渲染, 而且随着页面的滚动,该组数据的最后一条一直在渲染不同内容(但随着日志打印,数据都是正常输出的)
最后通过UI观察, 感觉最后一条数据随滚动而渲染,有点想是for循环没拦住的那种意思
就猜想,是不是 PagingDataAdapter
里面没处理好,
后来果然发现, PublicInfoCellBinding publicInfoCellBinding;
一不小心写成了全局的,而不是给每个Holder一个PublicInfoCellBinding publicInfoCellBinding;
, 最后修复下即可
这里还是由于对PagingDataAdapter
的不够熟悉, 刚写着玩意儿,才出现的低级错误
ViewModel
vm部分,网上也大同小异 ,写demo过程中未出现过多的坎儿
public class PublicInfoViewModel extends ViewModel {// paging3 page对象Pager<Integer, Response_public_info_bean.Datas.Cell> pager;// paging3 数据源对象PublicInfoSource publicInfoSource;// rxjava3 的 obserable 可观察对象Flowable<PagingData<Response_public_info_bean.Datas.Cell>> pagingDataFlowable;public PublicInfoViewModel(Context context) {CoroutineScope viewModelScope = ViewModelKt.getViewModelScope(this);publicInfoSource = new PublicInfoSource();// Maximum size must be at least pageSize + 2*prefetchDist, pageSize=20, prefetchDist=20, maxSize=20/*** pageSize 每页多少个条目* prefetchDistance 预加载下一页的距离,滑动到倒数第几个条目就加载下一页,无缝加载(可选)默认值是pageSize* enablePlaceholders 是否启用条目占位,当条目总数量确定的时候;列表一次性展示所有条目,* 但是没有数据;在adapter的onBindViewHolder里面绑定数据时候,是空数据,判断是空数据展示对应的占位item;可选,默认开启* initialLoadSize 第一页加载条目数量 ,可选,默认值是 3*pageSize (有时候需要第一页多点数据可用)* maxSize : 定义列表最大数量;可选,默认值是:Int.MAX_VALUE* jumpThreshold : 暂时还不知道用法,从文档注释上看,是滚动大距离导致加载失效的阈值;可选,默认值是:Int.MIN_VALUE (表示禁用此功能)**/PagingConfig pagingConfig = new PagingConfig(20,1,false,20*3);pager = new Pager<Integer, Response_public_info_bean.Datas.Cell>(pagingConfig, () -> publicInfoSource);pagingDataFlowable = PagingRx.getFlowable(pager);PagingRx.cachedIn(pagingDataFlowable, viewModelScope);}public Flowable<PagingData<Response_public_info_bean.Datas.Cell>> getPagingDataFlowable() {return pagingDataFlowable;}
}
PublicInfoPage /Activity
这里要注意的是, setLayoutManager
要设置 否则啥也不展示
public class PublicInfoPage extends AppCompatActivity {ActivityNewsPageBinding newsPageBinding;PublicInfoViewModel newsViewModel;PublicInfoAdapter newsAdapter;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);newsPageBinding = ActivityNewsPageBinding.inflate(getLayoutInflater());newsAdapter = new PublicInfoAdapter(new DiffUtil.ItemCallback<Response_public_info_bean.Datas.Cell>() {@Overridepublic boolean areItemsTheSame(@NonNull @NotNull Response_public_info_bean.Datas.Cell oldItem, @NonNull @NotNull Response_public_info_bean.Datas.Cell newItem) {return oldItem.getId() == newItem.getId();}@Overridepublic boolean areContentsTheSame(@NonNull @NotNull Response_public_info_bean.Datas.Cell oldItem, @NonNull @NotNull Response_public_info_bean.Datas.Cell newItem) {return oldItem.getProductName().equals(newItem.getProductName()) && oldItem.getProductTypeName().equals(newItem.getProductTypeName());}});newsPageBinding.recicleView.setAdapter(newsAdapter);newsPageBinding.recicleView.setLayoutManager(new LinearLayoutManager(this));setContentView(newsPageBinding.getRoot());}@Overrideprotected void onResume() {super.onResume();newsViewModel = new PublicInfoViewModel(this);newsViewModel.getPagingDataFlowable().subscribe(new Consumer<PagingData<Response_public_info_bean.Datas.Cell>>() {@Overridepublic void accept(PagingData<Response_public_info_bean.Datas.Cell> newsPagingData) throws Throwable {newsAdapter.submitData(getLifecycle(), newsPagingData);}});}
}
最后
以上就是这样了.
SmartRefreshLayout 也可以结合Paging3
这个有空也看一下,
安卓的玩法确实和iOS不一样, 也和RN不一样, 但互相又看得到对方的影子
相关文章:

[Android+JetPack] (Java实现) Retrofit2+RxJava3+Paging3+RecyclerView 实现加载网络数据例子 记录
文章目录 前言参考链接依赖库及版本Demo效果接口及数据展示各项模块Retrofit2Bean,对应上面的接口返回.Service API部分 Paging3PagingSource以及 RxPagingSourcePagingDataAdapter 适配器ViewModelPublicInfoPage /Activity 最后 前言 继续安卓学习之旅,本章的主要目标是: 1.完…...
Java 解析配置文件注入到配置类属性中供全局使用【开发记录】
1、背景:假设目前有两个接口,一个是查询快递订单状态的JSF接口,一个是查询快运订单状态的JSF接口,现有一个需求,要将这两个接口统一为一个入口,发布到物流开放平台供外界调用。 注意:以下代码均…...

【Python开发手册】深入剖析Google Python开发规范:规范Python注释写作
💖 作者简介:大家好,我是Zeeland,全栈领域优质创作者。📝 CSDN主页:Zeeland🔥📣 我的博客:Zeeland📚 Github主页: Undertone0809 (Zeeland) (github.com)&…...

Python入门教程+项目实战-9.3节: 字符串的操作方法
目录 9.3.1 字符串常用操作方法 9.3.2 获取字符串长度 9.3.3 字符串的大小写操作 9.3.4 删除字符串中的空白字符 9.3.5 字符串的子串查找 9.3.6 字符串的子串统计 9.3.7 字符串的子串替换 9.3.8 字符串的拆分函数 9.3.9 字符串的前缀与后缀9.3.10 知识要点 9.3.11 系…...

ENVI 5.6软件安装教程
软件下载 [软件名称]:ENVI 5.6 [软件大小]:3.25G [安装环境]:Win7~Win11或更高 软件介绍 ENVI 5.6是一款实现遥感图像处理的工具,已经广泛应用于科研、环境保护、气象、石油矿产勘探、农业、林业、医学、地球科学、公用设施管…...

在Windbg中设置断点追踪打开C++程序远程调试开关的模块
目录 1、Windbg动态调试 2、在Windbg中设置断点 2.1、在函数入口处设置断点 2.2、在函数内部某一行上设置断点 3、设置断点跟踪对打开远程调试开关接口的调用 3.1、编写演示代码 3.2、在Windbg中设置调用SetRemoteDebugOn接口的断点进行跟踪 4、最后 VC常用功能开发汇总…...

CRM客户管理软件开发功能有哪些?
互联网技术的不断提高使得企业管理方式也发生了变化,企业CRM系统应用市场逐渐扩大,相关软件开发也引起越来越多商家企业的关注。因为企业CRM系统软件开发能够根据企业需求制作,帮助企业更好的追踪管理客户信息,实时更新并进行相关…...
C++函数式魔法之旅(Journey of Functional Magic)
C函数式魔法之旅(Journey of Functional Magic) 一、引言(Introduction)C Functional模板库简介(Overview of C Functional Template Library)Functional模板库的重要性和作用(The Importance a…...

Vue基础入门(上)
<script src"https://unpkg.com/vuenext"></script> 从面向dom编程到面向数据编程 输入显示列表 const appVue.createApp({data(){return{inputValue:,list:[]}},methods:{handleAddItem(){this.list.push(this.inputValue);this.inputValue;}},templ…...
字符串匹配—KMP算法
字符串匹配的应用非常广泛,例如在搜索引擎中,我们通过键入一些关键字就可以得到相关的搜索结果,搜索引擎在这个过程中就使用字符串匹配算法,它通过在资源中匹配关键字,最后给出符合条件的搜索结果。并且我们在使用计算…...

【微信小程序】 权限接口梳理以及代码实现
1、权限接口说明 官方权限说明 部分接口需要经过用户授权统一才能调用。我们把这些接口按使用范围分成多个scope,用户选择对scope进行授权,当授权给一个scope之后,其对应的所有接口都可以直接使用。 此类接口调用时: 如…...
【每日一词】leit-motif
1、释义 leit-motif: n. 主乐调;主题;主旨。 复数:leit-motifs 2、例句 Hence the ‘ancient’ rhyme that appears as the leit-motif of The Lord of the Rings, Three Rings for the Elven-Kings under the sky, Seven for the Dwarf-lor…...
windows 环境修改 Docker 存储目录
windows 环境修改存储目录 docker 安装时不提供指定安装路径和数据存储路径的选项,且默认是安装在C盘的。C盘比较小的,等docker运行久了,一大堆的东西放在上面容易导致磁盘爆掉。所以安装前可以做些准备,让安装的实际路径不在C盘&…...
上海市青少年算法月赛丙组—目录汇总
上海市青少年算法2023年3月月赛(丙组) T1 神奇的字母序列 T2 约数的分类 T3 循环播放 T4 数对的个数 T5 选取子段 上海市青少年算法2023年2月月赛(丙组) T1 格式改写 T2 倍数统计 T3 区间的并 T4 平分数字(一…...
手动实现promise.all
手动实现promise.all function promiseAll(promises) {return new Promise((resolve, reject) > {const results [];let count 0;promises.forEach((promise, index) > {Promise.resolve(promise).then(result > {results[index] result;count;if (count promise…...

如何搭建关键字驱动自动化测试框架?这绝对是全网天花板的教程
目录 1. 关键字驱动自动化测试介绍 2. 搭建关键字驱动自动化测试框架 步骤1:选择测试工具 步骤2:定义测试用例 步骤3:编写测试驱动引擎 步骤4:实现测试关键字库 步骤5:执行测试 3. 实现关键字驱动自动化测试的关…...
字符串反转操作
1:将字符串反转 给定一句英语,要求你编写程序,将句中所有单词的顺序颠倒输出。 输入格式: 测试输入包含一个测试用例,在一行内给出总长度不超过 80 的字符串。字符串由若干单词和若干空格组成,其中单词是由英文字母…...
TensorFlow 智能移动项目:1~5
原文:Intelligent mobile projects with TensorFlow 协议:CC BY-NC-SA 4.0 译者:飞龙 本文来自【ApacheCN 深度学习 译文集】,采用译后编辑(MTPE)流程来尽可能提升效率。 不要担心自己的形象,只…...

[MAUI 项目实战] 手势控制音乐播放器(四):圆形进度条
文章目录 关于图形绘制创建自定义控件使用控件创建专辑封面项目地址 我们将绘制一个圆形的音乐播放控件,它包含一个圆形的进度条、专辑页面和播放按钮。 关于图形绘制 使用MAUI的绘制功能,需要Microsoft.Maui.Graphics库。 Microsoft.Maui.Graphics 是…...

web路径专题+会话技术
目录 自定义快捷键 1. 工程路径问题及解决方案1.1 相对路径1.2 相对路径缺点1.3 base标签1.4 作业11.5 作业21.6注意细节1.7 重定向作业1.8 web工程路径优化 2. Cookie技术2.1 Cookie简单示意图2.2 Cookie常用方法2.2 Cookie创建2.3 Cookie读取2.3.1 JSESSIONID2.3.2 读取指定C…...
Ubuntu系统下交叉编译openssl
一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机:Ubuntu 20.04.6 LTSHost:ARM32位交叉编译器:arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...
基于服务器使用 apt 安装、配置 Nginx
🧾 一、查看可安装的 Nginx 版本 首先,你可以运行以下命令查看可用版本: apt-cache madison nginx-core输出示例: nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...

从零实现STL哈希容器:unordered_map/unordered_set封装详解
本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说,直接开始吧! 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...

【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)
🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...
Rapidio门铃消息FIFO溢出机制
关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系,以下是深入解析: 门铃FIFO溢出的本质 在RapidIO系统中,门铃消息FIFO是硬件控制器内部的缓冲区,用于临时存储接收到的门铃消息(Doorbell Message)。…...

html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码
目录 一、👨🎓网站题目 二、✍️网站描述 三、📚网站介绍 四、🌐网站效果 五、🪓 代码实现 🧱HTML 六、🥇 如何让学习不再盲目 七、🎁更多干货 一、👨…...

安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲
文章目录 前言第一部分:体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分:体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...

基于IDIG-GAN的小样本电机轴承故障诊断
目录 🔍 核心问题 一、IDIG-GAN模型原理 1. 整体架构 2. 核心创新点 (1) 梯度归一化(Gradient Normalization) (2) 判别器梯度间隙正则化(Discriminator Gradient Gap Regularization) (3) 自注意力机制(Self-Attention) 3. 完整损失函数 二…...

在Mathematica中实现Newton-Raphson迭代的收敛时间算法(一般三次多项式)
考察一般的三次多项式,以r为参数: p[z_, r_] : z^3 (r - 1) z - r; roots[r_] : z /. Solve[p[z, r] 0, z]; 此多项式的根为: 尽管看起来这个多项式是特殊的,其实一般的三次多项式都是可以通过线性变换化为这个形式…...
NPOI Excel用OLE对象的形式插入文件附件以及插入图片
static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...