Android:RecyclerView封装,打造列表极简加载

前言

mBinding.recycler.linear().divider().set<OrdinaryListBean> {addLayout(R.layout.layout_ordinary_item)}.setList(getList())

如果我要说,除了数据和布局之外,以上的几行代码,就实现了一个列表加载,有老铁会相信吗?

可以很负责人的告诉大家,经过Kotlin的DSL语言和扩展函数的使用,实现一个列表加载就是如此的简单,有的老铁可能会很懵,怎么没有看到适配器啊,也没有看到设置布局管理器啊,这就是此次封装的隐秘之处,弱化adapter的存在,当然了只是使用上弱化,其内部依然用到了adapter。

经过代码上不断的完善,功能上不断的拓展,目前的封装,已经满足绝大部分的常见需求,决定开源出来,希望可以给RecyclerView的使用上,带来一丝变化,当然了,此项目也会不定期更新,也欢迎大家在使用上提出自己的见解和问题,也希望点个小星星。

目前已开发功能:

本篇文章的概述如下

1、封装库快速使用

2、具体功能一一概述

3、开源地址

4、使用总结

5、后续规划

一、封装库快速使用

目前封装的库,已经上传到远程Maven,大家可以按照如下的步骤进行使用:

1、在你的根项目下的build.gradle文件下,引入maven。

allprojects {repositories {maven { url "https://gitee.com/AbnerAndroid/almighty/raw/master" }}
}

2、在你需要使用的Module中build.gradle文件下,引入依赖。

dependencies {implementation 'com.vip:relist:1.0.5'//一个包含了列表加载和下拉刷新、上拉加载的库,它包含了下面的两个库,使用它,下面的两个就不要引用了。implementation 'com.vip:list:1.0.4'//列表加载库,如果使用了relist,这个不要再引用implementation 'com.vip:refresh:1.0.0'//下拉刷新、上拉加载库,如果使用了relist,这个不要再引用
}

需要注意的是,目前拆分了三个依赖,大家一定看清楚后,进行选择使用。refresh依赖只是对SmartRefreshLayout包了一层,没有做过多的扩展,如果大家项目中已经有了刷新库,其实只用list这个依赖即可。list库是纯RecyclerView封装库,没有用到任何的第三方,大家可以放心使用。

依赖

概述

版本号

集成

relist

一个包含了列表加载和下拉刷新、上拉加载的库

1.0.5

implementation 'com.vip:relist:1.0.5'

list

只包含列表加载(添加头尾、缺省页、侧滑删除、吸顶效果、分割线、DataBinding等)

1.0.4

implementation 'com.vip:list:1.0.4'

refresh

只包含下拉刷新、上拉加载

1.0.0

implementation 'com.vip:refresh:1.0.0'

二、具体功能使用

关于功能上的使用,大家可以直接看源码,或者访问github地址后看使用说明也可以,当然了,在这里我也罗列一下。

1、普通的列表加载

普通的列表加载,就是RecyclerView原始的用法,创建适配器Adapter,给RecyclerView设置布局管理器,给RecyclerView设置适配器,虽然说,Adapter已经抽取封装了,但考虑到还有一部分小伙伴具有怀旧意识,也很喜欢使用这种方式加载列表,于是呢,这种方式就保留了。

具体使用,举例如下:

创建适配器

即便是普通列表加载,这里也是建议继承父类BaseAdapter,因为直接继承RecyclerView.Adapter的方式, 还得要重写一大堆方法,实在是冗余。

BaseAdapter对RecyclerView.Adapter做了一层封装,只需要传递要加载的layout和数据对象泛型即可,这两个就不贴源码了,一个是xml布局,一个自己定义的对象,没啥好说的。

数据绑定和逻辑处理,实现dataOperation方法即可,当然,这个也是强制要实现的方法。


class OrdinaryListAdapter : BaseAdapter<OrdinaryListBean>(R.layout.layout_ordinary_item) {override fun dataOperation(holder: BaseViewHolder, t: OrdinaryListBean?, position: Int) {t?.title?.let {holder.setText(R.id.tv_title, it)}t?.desc?.let {holder.setText(R.id.tv_desc, it)}val ivPic = holder.findView<ImageView>(R.id.iv_pic)t?.icon?.let {ivPic.setImageResource(it)}}
}

设置布局管理器和适配器

这些代码就比较熟悉了吧,都是RecyclerView的原始用法,点击事件调用adapter中的setOnItemClickListener方法即可。

因为使用了ViewDataBinding,这里的mBinding.recycler指的是RecyclerView控件。

       val manger = LinearLayoutManager(requireContext())manger.orientation = LinearLayoutManager.VERTICAL//设置布局管理器mBinding.recycler.layoutManager = mangerval adapter = OrdinaryListAdapter()//设置适配器mBinding.recycler.adapter = adapter//设置分割线mBinding.recycler.addItemDecoration(ItemDivider(Color.parseColor("#cccccc"),RecyclerView.VERTICAL, 0))//设置数据adapter.setList(getList())adapter.setOnItemClickListener {//条目点击事件Toast.makeText(requireContext(), "当前点击条目为:$it", Toast.LENGTH_SHORT).show()}

简化设置布局管理器和适配器

看到上面的代码,还是觉得有些冗余,普通列表加载,也是建议大家尽量使用简洁用法。

 val adapter = OrdinaryListAdapter()mBinding.recycler.linear()//设置布局管理器.divider()//设置分割线.adapter=adapter//设置适配器//设置数据adapter.setList(getList())adapter.setOnItemClickListener {//条目点击事件Toast.makeText(requireContext(), "当前点击条目为:$it", Toast.LENGTH_SHORT).show()}

布局管理器调用如下:

垂直列表:linear()
横向列表:linear(RecyclerView.HORIZONTAL)
网格列表:grid(2),一列展示几个,传递数值即可
瀑布流列表:staggered(2),一列展示几个,传递数值即可

至于其他的列表展示,如,网格和瀑布流形式,只需要更改布局管理器即可。

2、封装之后的列表加载

封装之后的列表加载,是推荐使用的,相对于普通的列表加载,完全弱化了适配器的存在,只需要考虑数据处理即可,非常的简单。

mBinding.recycler.linear().divider().set<OrdinaryListBean> {addLayout(R.layout.layout_ordinary_item)bindData {//获取DataBindingval binding = getDataBinding<LayoutOrdinaryItemBinding>()//获取Modelval model = getModel(adapterPosition)binding?.apply {tvTitle.text = model.titletvDesc.text = model.descivPic.setImageResource(model.icon!!)}setOnItemClickListener {//条目点击事件Toast.makeText(requireContext(), "当前点击条目为:$it", Toast.LENGTH_SHORT).show()}}}.setList(getList())

相关方法说明:

属性或方法

概述

mBinding.recycler

因为使用了ViewDataBinding,这里的mBinding.recycler指的是RecyclerView控件。

linear

布局管理器,可调用方法如下:

垂直列表:linear()

横向列表:linear(RecyclerView.HORIZONTAL)

网格列表:grid(2),一列展示几个,传递数值即可

瀑布流列表:staggered(2),一列展示几个,传递数值即可

divider

分割线

set

扩展函数,泛型为加载的对象

addLayout

添加列表Item展示的布局

bindData

绑定数据和处理逻辑

getDataBinding

获取ViewDataBinding

getModel

获取数据对象

setOnItemClickListener

条目点击事件

setList

设置列表数据

有的老铁可能会问了,封装之后简单是简单了,但是如果遇到复杂列表,都写到一个类里,代码量实在是太多了,哎!这确实是 个问题,但是呢,又不是问题,如果bindData里的逻辑比较多,你完成可以抽取到其他地方,比如ViewModel里,在ViewModel定义 方法后,调用即可,又或者呢,使用后边的DataBinding。

3、多条目列表加载

效果图可以忽略,重在功能哈~,多条目加载也封装了,有适配器和无适配方式,在实际的业务开发中,大家可以选择性进行使用。

有适配器模式

也就是和传统的多条目保持一致,都在适配器里进行数据的渲染和逻辑处理。

还是那句话,多条目的适配器也抽取了基类,既然都有基类了,为了代码上的简洁,建议还是继承基类比较好。

如何添加多条目?

直接在构造方法里,调用addLayout即可,有几个多条目就添加几个,是不是非常的方便,泛型为数据对象,参数为多条目xml布局。

如何数据渲染和逻辑处理?

实现bindOperation方法即可,通过holder.itemViewType来却分多条目的类型,当然了这个必须和数据对象里的类型保持一致。

区分多条目类型

数据对象实现BaseMultipleItem,重写itemViewType属性,itemViewType就是用来区分多条目类型的,可以随意设置,或者是接口的某个参数,或者是对应的layout。

class OrdinaryMultipleItemAdapter : BaseMultipleItemAdapter {constructor() {//添加多条目类型以及绑定的数据对象addLayout<MultipleItem01Bean>(R.layout.layout_ordinary_multiple_01)addLayout<MultipleItem02Bean>(R.layout.layout_ordinary_multiple_02)addLayout<MultipleItem03Bean>(R.layout.layout_ordinary_multiple_03)}override fun bindOperation(holder: BaseViewHolder, t: BaseMultipleItem?, position: Int) {when (holder.itemViewType) {1 -> {val bean = t as MultipleItem01Beanholder.setText(R.id.tv_title, bean.title!!)holder.setText(R.id.tv_desc, bean.desc!!)val ivPic = holder.findView<ImageView>(R.id.iv_pic)ivPic.setImageDrawable(bean.icon)}2 -> {val bean = t as MultipleItem02Beanval ivPic01 = holder.findView<ImageView>(R.id.iv_01)val ivPic02 = holder.findView<ImageView>(R.id.iv_02)val ivPic03 = holder.findView<ImageView>(R.id.iv_03)ivPic01.setImageDrawable(bean.icon01)ivPic02.setImageDrawable(bean.icon02)ivPic03.setImageDrawable(bean.icon03)}3 -> {val bean = t as MultipleItem03Beanholder.setText(R.id.tv_content, bean.content!!)}}}}

设置布局管理器和适配器

val adapter = OrdinaryMultipleItemAdapter()mBinding.recycler.linear().divider().adapter = adapteradapter.setList(getMoreList())

无适配器模式

无适配器模式,更加的简单,只需要调用setMore方法即可,有多少条目就调用addLayout几次,在bindData里进行数据渲染和逻辑处理,当然了,也可以使用DataBinding形式,需要追加BR。

mBinding.recycler.linear().divider().setMore {addLayout<MultipleItem01Bean>(R.layout.layout_multiple_01, BR.multiple1)addLayout<MultipleItem02Bean>(R.layout.layout_multiple_02, BR.multiple2)addLayout<MultipleItem03Bean>(R.layout.layout_multiple_03, BR.multiple3)bindData {}}.setList(getMoreList())

4、DataBinding形式列表加载

DataBinding的出现,使得数据绑定更加的简单化,大大减少了代码的书写,大家可以采用提供的两种方式进行使用,一种是针对Item的绑定,一种是对RecyclerView自身的绑定。

Item绑定

第一步,addLayout,增加和xml绑定的BR

 mBinding.recycler.linear().divider().set<OrdinaryListBean> {addLayout(R.layout.layout_ordinary_bind_item, BR.ordinary)setOnItemViewClickListener { view, position ->//条目点击事件Toast.makeText(requireContext(), "当前点击条目为:$position", Toast.LENGTH_SHORT).show()}}.setList(getList())

第二步,xml数据绑定

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"><data><variablename="ordinary"type="com.abner.list.ordinary.OrdinaryListBean" /></data><androidx.constraintlayout.widget.ConstraintLayoutandroid:layout_width="match_parent"android:layout_height="@dimen/vip_dp_80"android:paddingLeft="@dimen/vip_dp_20"android:paddingRight="@dimen/vip_dp_20"><ImageViewandroid:id="@+id/iv_pic"android:layout_width="@dimen/vip_dp_60"android:layout_height="@dimen/vip_dp_60"android:src="@mipmap/vip_list_logo"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintTop_toTopOf="parent" /><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginStart="@dimen/vip_dp_10"android:orientation="vertical"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toRightOf="@id/iv_pic"app:layout_constraintTop_toTopOf="parent"><TextViewandroid:id="@+id/tv_title"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{ordinary.title}"android:textColor="#222222"android:textSize="@dimen/vip_sp_16" /><TextViewandroid:id="@+id/tv_desc"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="@dimen/vip_dp_10"android:text="@{ordinary.desc}"android:textColor="#666666"android:textSize="@dimen/vip_sp_14" /></LinearLayout></androidx.constraintlayout.widget.ConstraintLayout>
</layout>

RecyclerView绑定

第一步,xml中RecyclerView数据绑定

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"><data><variablename="model"type="com.abner.list.bind.RecyclerViewBindViewModel" /></data><androidx.constraintlayout.widget.ConstraintLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"><androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/recycler"listData="@{model.list}"listLayout="@{model.layoutId}"listManager="@{0}"listVariableName="@{model.listVariableName}"android:layout_width="match_parent"android:layout_height="match_parent" /></androidx.constraintlayout.widget.ConstraintLayout>
</layout>

第二步、对应model中提供数据

class RecyclerViewBindViewModel : BaseViewModel() {val layoutId = R.layout.layout_ordinary_bind_item//item列表/*** AUTHOR:AbnerMing* INTRODUCE:获取视图绑定的name*/fun getListVariableName(): Int {return BR.ordinary}/*** AUTHOR:AbnerMing* INTRODUCE:模拟数据*/fun getList(): MutableList<OrdinaryListBean> {return mutableListOf<OrdinaryListBean>().apply {for (a in 0..20) {val bean = OrdinaryListBean()bean.title = "我是标题$a"bean.desc = "我是描述信息$a"bean.icon = R.mipmap.vip_list_logoadd(bean)}}}
}

属性一览

属性

类型

概述

listManager

Int

布局管理器:默认为纵向的普通列表。

0:普通列表

1:网格

2:瀑布流

listOrientation

Int

设置列表方向,默认纵向。

横向:RecyclerView.HORIZONTAL

listSpanCount

Int

展示几列,适用于网格和瀑布流

isDivider

Boolean

是否展示分割线

dividerDrawable

Int

分割线样式

listLayout

Int

Item布局

listData

MutableList<T>

数据

listVariableName

Int

绑定的BR

listAdapter

OnAdapterListener<T>

返回适配器,可以通过这里实现,适配器中的逻辑处理

isMultiple

Boolean

是否是多条目

multipleAdapter

OnAdapterMultipleListener

返回多条目适配器

multipleData

MutableList<BaseMultipleItem>

多条目数据

multipleLayout

MutableList<Int>

多条目布局

multipleLayoutBindData

MutableList<Class<*>>

layout绑定的数据对象

multipleVariableName

MutableList<Int>

xml绑定的对应的VariableName

两种绑定方式,各有优点,大家可以根据实际的业务进行选择使用。

5、设置分割线

调用divider方法即可。

 mBinding.recycler.linear().divider().set<OrdinaryListBean> {addLayout(R.layout.layout_ordinary_bind_item, BR.ordinary)}.setList(getList())

divider可选参数如下:

参数

类型

概述

color

Int

分割线颜色,默认是#cccccc

orientation

Int

分割线方向,默认和纵向保持一致,RecyclerView.VERTICAL

lineWidth

Int

分割线的高度

margin

Int

分割线距离左右或者距离上下的边距

hideLast

Boolean

是否隐藏最后一条分割线

itemType

Int

默认为0,不为0时,则绘制横向线条,适用于网格列表分割线

6、头和尾追加和删除

头和尾的添加支持layout和View两种添加方式,代码如下所示:

mAdapter?.addHead(R.layout.layout_head)//初始 添加头  不用刷新
mAdapter?.addFoot(R.layout.layout_foot)//初始 添加尾  不用刷新

后续如果动态添加头尾,需要更新适配器。

 mAdapter?.addHead(view, true)//追加头,需要刷新mAdapter?.addFoot(view, true)//追加尾,需要刷新

动态删除头尾操作,支持按照索引进行删除。

mAdapter?.removeHead()
mAdapter?.removeFooter()

7、数据追加和删除

初始添加数据

mAdapter?.setList(getList())

追加数据,支持对象和集合两种方式

 mAdapter?.addData()

删除数据

mAdapter?.removeData(0)

8、设置缺省页面

缺省页面没什么好说的,数据为空或者数据加载错误的时候,设置一张占位View。

空页面,调用addEmptyView即可,支持layout和View两种模式,错误页面,调用addErrorView,和空页面使用方式一致。

 mAdapter = mBinding.recycler.linear().divider().set {addEmptyView(R.layout.layout_empty)//初始化 空页面addErrorView(R.layout.layout_error)//初始化  错误页面addLayout(R.layout.layout_item, BR.str)}//初始添加数据mAdapter?.setList(getList())

功能

实现

显示空

mAdapter?.showEmptyView()

隐藏空

mAdapter?.hintEmptyView()

显示错误

mAdapter?.showErrorView()

隐藏错误

mAdapter?.hintErrorView()

9、拖拽排序功能

调用drag方法即可实现拖拽排序功能。

 mBinding.recycler.linear().drag()//支持拖拽.set<DragBean> {addLayout(R.layout.layout_drag_item, BR.drag)}.setList(getList())

注意事项

传递的数据对象必须实现BaseNoDragBean,需要重写isDrag属性,false为禁止拖拽,true为允许拖拽。

class DragBean : BaseNoDragBean {override var isDrag = falsevar content = ""}

10、侧滑删除条目

调用slideDelete方法即可,支持左右侧滑删除,传递不同的值即可。

 mBinding.recycler.linear().slideDelete()//支持侧滑删除 默认是左滑删除 0是右边  1是左右两边.set<String> {addLayout(R.layout.layout_main_item, BR.str)}.setList(getList())

11、侧滑显示按钮

由原来的set方法改为setSlide方法即可。

 mBinding.recycler.linear().divider().setSlide<String> {//如果要显示按钮 使用 setSlideaddLayout(R.layout.layout_item)bindData {val model = getModel(adapterPosition)setText(R.id.tv_content, model)}}.setList(getList())

有的老铁可能会问,我想展示多个按钮,或者展示自定义的View,如何实现呢?在setSlide调用addSlideLayout,传入自己的xml布局即可。

12、条目吸顶功能

实现吸顶就调用stick方法即可。

 mBinding.recycler.linear().stick().set<StickHeaderBean> {addLayout(R.layout.layout_stick_item, BR.stick)}.setList(getList())

需要注意,数据对象需要实现StickHeaderBean,重写分组标识。

class StickHeaderBean : BaseStickHeaderBean {override var stickGroup: String = ""//分组标识var name = ""//普通内容
}

13、单选、多选、全选、反选

当然了这都是业务层的逻辑,按理来说,没必要在封装,但是考虑到代码的简洁性,单选和多选的判断逻辑就封装了一下,大家如有用到此功能,可按照如下的方式进行操作即可。

单选

主要的就两部分:

1、开启单选刷新

mNotifySingleChoice = true

2、判断单选

 adapterPosition == mCheckPosition,可以利用mCheckPosition来判断,进而更新UI。

mBinding.recycler.linear().divider().set<SingleBean> {mNotifySingleChoice = true//开启单选刷新addLayout(R.layout.layout_single_choice_list_item, BR.single)bindData {val binding = this.getDataBinding<LayoutSingleChoiceListItemBinding>()//判断单选,直接判断  adapterPosition == mCheckPosition 即可binding?.checkbox?.isChecked = adapterPosition == mCheckPositionsetOnItemViewClickListener { view, position ->//条目点击val singleBean = getList()[position]//单选 选择的对象mViewModel.name.set(singleBean.name)}}}.setList(getList())

多选

和单选一样,也是两部分

1、设置多选更新

mNotifyMultipleChoice = true

2、多选回调监听

setOnMultipleChoiceListener{}

mBinding.recycler.linear().divider().set<MultipleBean> {mNotifyMultipleChoice = true//多选更新addLayout(R.layout.layout_multiple_choice_list_item, BR.multiple)bindData {//多选回调监听setOnMultipleChoiceListener {var allPrice = 0.0fit.forEach {allPrice += it.price}mViewModel.commodityNumber.set("选择商品数量为:" + it.size)mViewModel.allPrice.set("总价格为:$allPrice")}}}.setList(getList())

14、上拉刷新和下拉加载

刷新和加载使用的是SmartRefreshLayout这个开源框架,毕竟已经有很优秀的框架了,没必要再重新封装一个,具体的用法,大家可以按照SmartRefreshLayout的文档去操作使用就行。

支持全局设置下拉和上拉展示View,只需要在Application里初始化即可。

     //上拉加载和下拉刷新,初始化头和尾ListConfig.apply {addRefreshHeader {ClassicsHeader(it)}addRefreshFooter {ClassicsFooter(it)}}

这里也简单举个例子:

1、xml中引入

  <com.abner.refresh.kernel.SmartRefreshLayoutandroid:id="@+id/srl_layout"android:layout_width="match_parent"android:layout_height="match_parent"><androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/rv_view"android:layout_width="match_parent"android:layout_height="match_parent" /></com.abner.refresh.kernel.SmartRefreshLayout>

2、代码简单使用

mSmartRefreshLayout?.setOnRefreshLoadMoreListener(object : OnRefreshLoadMoreListener {override fun onRefresh(refreshLayout: RefreshLayout) {}override fun onLoadMore(refreshLayout: RefreshLayout) {}})

当然了,为了更好的拓展使用,针对SmartRefreshLayout,又简单做了一层包装,别无他意,就是为了让使用更加的简单。

1、xml里引入PageRefreshLayout

 <com.abner.relist.PageRefreshLayoutandroid:id="@+id/refresh"android:layout_width="match_parent"android:layout_height="match_parent" />

2、代码里使用

  mBinding.refresh.getRecycler().linear().divider().set<String> {addLayout(R.layout.layout_item, BR.str)}//刷新和加载mBinding.refresh.refresh { isRefresh, refreshLayout ->mViewModel.doHttp {addData(it)}}.autoRefresh()

直接调用refresh方法即可,isRefresh为true是下拉,否则就是上拉,非常的方便,而addData方法,则实现了分页加载数据,上拉和下拉直接调用addData即可。

PageRefreshLayout可调用方法如下

方法

参数

概述

autoRefresh

无参

自动刷新操作

refresh

无参

静默刷新(不带下拉刷新动画)

addData

Collection<T>

添加数据,分页会自动追加数据,下拉和上拉会自动关闭

setEnableRefresh

Boolean

是否禁止下拉

setEnableLoadMore

Boolean

是否禁止上拉

finishRefresh

无参

关闭下拉刷新

finishLoadMore

无参

关闭上拉刷新

getPager

无参

获取当前页码

refresh

回调函数

刷新和加载方法

getSmartRefresh

无参

获取SmartRefreshLayout

getRecycler

无参

获取RecyclerView

addEmptyView

Int/View

添加空的布局,支持layout和View

addErrorView

Int/View

添加错误的布局,支持layout和View

showEmptyView

无参

显示空布局

showErrorView

无参

显示错误布局

hintEmptyView

无参

隐藏空布局

hintErrorView

无参

隐藏错误布局

setHeightWrapContent

无参

设置整体的列表由充满改为包裹内容。

三、开源地址

关于大家在使用上的问题以及后续的优化,或者功能新增,都会时长更新,方便的话,给个小星星呗~

开源地址:https://github.com/AbnerMing888/VipList

四、使用总结

1、无适配器的模式,在逻辑相对复杂的页面,建议大家可以抽取到ViewModel中实现,当然,也可以采用DataBinding的形式。

2、很多使用方式,或者常见的业务开发场景,在文档中或者源码中,都会详细的备注,大家可以细心的查看即可。

3、条目点击事件,给出了两个,大家可以选择性使用。

只返回索引

setOnItemClickListener { }

返回当前点击的View和索引

 setOnItemViewClickListener { view, position ->}

 

五、后续规划

此库的封装,除了刷新加载库使用了SmartRefreshLayout,其他的都是自己从0到1的开发,目前,自己已经在项目中使用,暂时没有出现任何问题,当然了,后续,也会不断的对其进行优化,增加一些其他的功能,希望有需要的小伙伴,长期关注。

关于维护上,不断的优化和解决一些大家所提的问题。

关于源码上,后续会逐一剖析其实现方式,确确实实,里面用到了一些另类技术,当然了,这都是后话了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/10304.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在 Windows 中通过 WSL 2 高效使用 Docker

大家好&#xff0c;我是比特桃。平时开发中&#xff0c;不免会使用一些容器来跑中间件。而开发者使用的操作系统&#xff0c;大多是Mac OS 、Windows。Docker 为了兼顾这两个平台的用户&#xff0c;推出了 Docker Desktop 应用。Docker Desktop 中的内核还是采用了 Linux 的内核…

基于规则指导的知识图谱推理协作代理学习(2019)7.27

基于规则指导的知识图谱推理协作代理学习 摘要介绍问题和准备工作问题公式基于符号的方法基于游走的方法 RuleGuider模型架构实体代理策略网络 模型学习奖励设计训练过程 实验实验设置数据集实验结果消融研究人工评估 总结 摘要 基于 行走模型 是通过在提供可解释决策的同时实…

flutter android Webview 打开网页错误ERR_CLEARTEXT_NOT_PERMITTED 、 net:ERR_CACHE_MISS

当你在Flutter应用中尝试打开一个非安全连接的网页&#xff08;例如HTTP连接而不是HTTPS连接&#xff09;时&#xff0c;可能会遇到"ERR_CLEARTEXT_NOT_PERMITTED"错误。这是因为默认情况下&#xff0c;Android 9及更高版本禁止应用程序通过非安全的明文HTTP连接进行…

Linux学习笔记--如何在ubuntu中启用root用户和安装软件的方法(解决安装依赖)

一、ubuntu启用root用户 打开Terminal(终端)&#xff0c;右键点击桌面&#xff0c;选择终端&#xff0c;弹出终端窗口。&#xff08;使用快捷键ctrlaltt&#xff0c;也可以调出Terminal&#xff09;。 指令su&#xff0c;该指令可切换用户或者切换到超级管理员root。 su 在终端…

python与深度学习(八):CNN和fashion_mnist二

目录 1. 说明2. fashion_mnist的CNN模型测试2.1 导入相关库2.2 加载数据和模型2.3 设置保存图片的路径2.4 加载图片2.5 图片预处理2.6 对图片进行预测2.7 显示图片 3. 完整代码和显示结果4. 多张图片进行测试的完整代码以及结果 1. 说明 本篇文章是对上篇文章训练的模型进行测…

day43-Feedback Ui Design(反馈ui设计)

50 天学习 50 个项目 - HTMLCSS and JavaScript day43-Feedback Ui Design&#xff08;反馈ui设计&#xff09; 效果 index.html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport&q…

opencv-26 图像几何变换04- 重映射-函数 cv2.remap()

什么是重映射&#xff1f; 重映射&#xff08;Remapping&#xff09;是图像处理中的一种操作&#xff0c;用于将图像中的像素从一个位置映射到另一个位置。重映射可以实现图像的平移、旋转、缩放和透视变换等效果。它是一种基于像素级的图像变换技术&#xff0c;可以通过定义映…

【正规方程对波士顿房价数据集进行预测】

数据准备 我们首先需要加载波士顿房价数据集。该数据集包含房屋特征信息和对应的房价标签。 import pandas as pd import numpy as npdata_url "http://lib.stat.cmu.edu/datasets/boston" raw_df pd.read_csv(data_url, sep"\s", skiprows22, headerN…

安全DNS,状态码,编码笔记整理

一 DNS DNS&#xff08;Domain Name System&#xff09;是互联网中用于将域名转换为IP地址的系统。 DNS的主要功能包括以下几个方面&#xff1a; 域名解析&#xff1a;DNS最主要的功能是将用户输入的域名解析为对应的IP地址。当用户在浏览器中输入一个域名时&#xff0c;操作…

github token使用方法

git remote set-url origin https://<githubtoken>github.com/<username>/<repositoryname>.git 在私有仓库的HTTPS的url上加入<githubtoken>即为token url&#xff0c;可以免ssh key登录

NoSQL之redis配置与优化

NoSQL之redis配置与优化 高可用持久化功能Redis提供两种方式进行持久化1.触发条件手动触发自动触发 执行流程优缺点缺点&#xff1a;优势AOF出发规则&#xff1a; AOF流程AOF缺陷和优点 NoSQL之redis配置与优化 mysql优化 1线程池优化 2硬件优化 3索引优化 4慢查询优化 5内…

iptables与防火墙

目录 防火墙 安全技术 划分方式 iptables 构成 四表 优先级 五链 iptables的规则 匹配顺序 iptables的命令格式 管理选项 匹配条件 控制类型 隐藏扩展模块 注意事项 防火墙 隔离功能&#xff0c;一般部署在网络边缘或者主机边缘&#xff0c;在工作中防火墙的…

Java 悲观锁 乐观锁

锁可以从不同的角都分类。其中乐观锁和悲观锁是一种分类方式 一、悲观锁、乐观锁定义 悲观锁就是我们常说到的锁。对于悲观锁来说&#xff0c;他总是认为每次访问共享资源时会发生冲突&#xff0c;所以必须每次数据操作加上锁&#xff0c;以保证临界区的程序同一时间只能有一个…

SQLite Studio 连接 SQLite数据库

1、在SQLite中创建数据库和表 1.1、按WINR&#xff0c;打开控制台&#xff0c;然后把指引到我们的SQLite的安装路径&#xff0c;输入D:&#xff0c;切换到D盘&#xff0c;cd 地址&#xff0c;切换到具体文件夹&#xff0c;输入“sqlite3”&#xff0c;启动服务 1.2、创建数据库…

多租户分缓存处理

多租户redis缓存分租户处理 那么数据库方面已经做到了拦截&#xff0c;但是缓存还是没有分租户&#xff0c;还是通通一个文件夹里&#xff0c; 想实现上图效果&#xff0c;global文件夹里存的是公共缓存。 首先&#xff0c;那么就要规定一个俗称&#xff0c;缓存名字带有globa…

数据库应用:MySQL MHA高可用集群

目录 一、理论 1.MHA 2.MySQL MHA部署准备 3.MySQL MHA部署 二、实验 1.MHA部署 三、问题 1.中英文符兼容报错 2.MHA测试 ssh 无密码认证语法报错 3.MHA测试 ssh 无密码认证log-bin报错 4.MHA测试 mysql 主从连接情况报错slave replication 5.MHA测试 mysql 主从连…

Elasticsearch监控工具Cerebro安装

Elasticsearch监控工具Cerebro安装 1、在windwos下的安装 1.1 下载安装包 https://github.com/lmenezes/cerebro/releases/download/v0.9.4/cerebro-0.9.4.zip 1.2 解压 1.3 修改配置文件 如果需要修改相关信息&#xff0c;编辑C:\zsxsoftware\cerebro-0.9.4\conf\applica…

css3的filter图片滤镜使用

业务介绍 默认&#xff1a;第一个图标为选中状态&#xff0c;其他三个图标事未选中状态 样式&#xff1a;选中状态是深蓝&#xff0c;未选中状体是浅蓝 交互&#xff1a;鼠标放上去选中&#xff0c;其他未选中&#xff0c;鼠标离开时候保持当前选中状态 实现&#xff1a;目前…

Component template should contain exactly one root element

在vue中报错&#xff1a; Component template should contain exactly one root element. If you are using v-if on multiple elements, use v-else-if to chain them instead报错的大致意思是&#xff1a;组件的模板应该只能包含一个根元素&#xff0c;也就是是说作为元素的直…

【每日一题】—— C - (K+1)-th Largest Number (AtCoder Beginner Contest 273)

&#x1f30f;博客主页&#xff1a;PH_modest的博客主页 &#x1f6a9;当前专栏&#xff1a;每日一题 &#x1f48c;其他专栏&#xff1a; &#x1f534; 每日反刍 &#x1f7e1; C跬步积累 &#x1f7e2; C语言跬步积累 &#x1f308;座右铭&#xff1a;广积粮&#xff0c;缓称…