RecyclerView 调用 notifyItemInserted 自动滚动到底部的问题

项目中发现一个奇怪的现象

RecyclerView 加载完数据以后,调用 notifyItemInserted 方法,RecyclerView 会滑动到底部。

简化后的效果图:

在这里插入图片描述

因为这个 RecyclerView 的适配器有一个 FootViewHolder,所以怀疑是 FootViewHolder 的问题。通过源码分析,果然是 FootViewHolder 的问题。接下来就一步一步分析一下原因。

适配器代码

class TestAnimatorAdapter(private val context: Context
) : RecyclerView.Adapter<TestAnimatorAdapter.ViewHolder>() {companion object {val TYPE_FOOT = 1val FOOT_COUNT = 1}val dataList = mutableListOf<CheckBoxModel>()fun onDataSourceChanged(dataList: MutableList<CheckBoxModel>) {this.dataList.clear()this.dataList.addAll(dataList)}override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {if (viewType == TYPE_FOOT) {val view =LayoutInflater.from(context).inflate(R.layout.foot_view_load_more, parent, false)return FootViewHolder(view)}val view =LayoutInflater.from(context).inflate(R.layout.item_test_animation, parent, false)return ViewHolder(view)}override fun onBindViewHolder(holder: ViewHolder, position: Int) {if (position == itemCount - 1) {return}val model = dataList[position]holder.checkBox?.isSelected = model.isCheckedholder.textDescription?.text = model.description}override fun getItemCount(): Int {return dataList.size + FOOT_COUNT}override fun getItemViewType(position: Int): Int {if (position == itemCount - 1) {return TYPE_FOOT}return super.getItemViewType(position)}open class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {var checkBox: CheckBox? = nullvar textDescription: TextView? = nullinit {checkBox = itemView.findViewById(R.id.check_box)textDescription = itemView.findViewById(R.id.text_description)}}class FootViewHolder(itemView: View) : ViewHolder(itemView)}

适配器有一个 FooterViewHolder。

测试代码:添加4个数据,然后调用 notifyItemInserted 方法。

binding.btnNotifyItemChanged.setOnClickListener {val newArrayList = arrayListOf<CheckBoxModel>()for (i in 0 until 4) {newArrayList.add(CheckBoxModel("hi Hello$i", false))}testAnimatorAdapterAdapter.onDataSourceChanged(newArrayList)for (index in 0 until 4) {//总共添加了4条数据,调用4次 notifyItemInserted testAnimatorAdapterAdapter.notifyItemInserted(index)}
}

调用 Adapter#notifyItemInserted 方法以后,会调用 RecyclerView 的 dispatchLayout 方法。

void dispatchLayout() {//...mState.mIsMeasuring = false;if (mState.mLayoutStep == State.STEP_START) {//注释1处,调用dispatchLayoutStep1方法。dispatchLayoutStep1();mLayout.setExactMeasureSpecsFrom(this);//注释2处,调用dispatchLayoutStep2方法。dispatchLayoutStep2();} else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()|| mLayout.getHeight() != getHeight()) {// First 2 steps are done in onMeasure but looks like we have to run again due to// changed size.mLayout.setExactMeasureSpecsFrom(this);dispatchLayoutStep2();} else {mLayout.setExactMeasureSpecsFrom(this);}//注释3处,调用dispatchLayoutStep3方法。dispatchLayoutStep3();
}

dispatchLayoutStep1 预布局阶段

在预布局阶段,首先会调用 RecyclerView 的 offsetPositionRecordsForInsert 方法,将已有的 FootViewHolder 向后移动,为插入的ViewHolder 留出位置。在我们的例子中,添加了4条数据,调用4次 notifyItemInserted 。最后 FootViewHolder 的 position 从 0 变化到 4 。

void offsetPositionRecordsForInsert(int positionStart, int itemCount) {final int childCount = mChildHelper.getUnfilteredChildCount();for(int i = 0; i < childCount; i++) {final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));if(holder != null && !holder.shouldIgnore() && holder.mPosition >= positionStart) {if(sVerboseLoggingEnabled) {Log.d(TAG, "offsetPositionRecordsForInsert attached child " + i + " holder " + holder + " now at position " + (holder.mPosition + itemCount));}holder.offsetPosition(itemCount, false);mState.mStructureChanged = true;}}mRecycler.offsetPositionRecordsForInsert(positionStart, itemCount);requestLayout();
}

在debug的时候,评估一下FootViewHolder 。Evaluate FootViewHolder

 FootViewHolder{bd564b0 position=4 id=-1, oldPos=0, pLpos:0}

然后就没什么特殊的,dispatchLayoutStep1方法内部会调用一次 mLayout.onLayoutChildren(mRecycler, mState);,进行预布局。预布局结束的时候,只有一个FootViewHolderFootViewHolder还是被布局在了 position = 0 的位置。 预布局的时候,使用的是 pLpos = 0

这里要注意一下:预布局结束的时候,FootViewHolder 的 position=4 。在 dispatchLayoutStep2 阶段布局的时候,使用的是 position。也就是说会把 FootViewHolder 布局在 position=4 的位置。

dispatchLayoutStep2

内部会调用一次 mLayout.onLayoutChildren(mRecycler, mState);,进行布局。

这个时候先评估一下 LinearLayoutManager.mAnchorInfo 的值

AnchorInfo{mPosition=4, mCoordinate=0, mLayoutFromEnd=false, mValid=true}

注意:此时锚点位置 mAnchorInfo.mPosition = 4

onLayoutChildren 方法内部,

  1. detachAndScrapAttachedViews 回收 FootViewHolder。没啥可说的。

  2. 然后调用 updateLayoutStateToFillEnd(AnchorInfo anchorInfo) 方法。将 mLayoutState.mCurrentPosition 设置为 4。

  3. 然后调用 fill 方法进行填充。这时候,锚点位置是 4,对应的 ViewHolder是 FootViewHolder ,所以会先布局 FootViewHolder。FootViewHolder ,布局位置(layoutDecoratedWithMargins)是 top = 0,bottom = 144。(FootView 的高度就是144)

  4. 然后 FootViewHolder 后面没有数据了。此时 mLayoutState.mCurrentPosition = 5。(Adapter 只有 4条数据加一个Foot,position最大是4)。从锚点开始向下填充结束。

接下来要从锚点开始向上填充

LinearLayoutManager 的 onLayoutChildren 方法中部分代码

 // fill towards start
updateLayoutStateToFillStart(mAnchorInfo);
mLayoutState.mExtraFillSpace = extraForStart;
//注释1处,这里会将 mLayoutState.mCurrentPosition 改为3
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
fill(recycler, mLayoutState, state, false);

先调用 updateLayoutStateToFillStart(AnchorInfo anchorInfo) 方法。更新一些信息。将mLayoutState.mLayoutDirection 赋值为 LayoutState.LAYOUT_START(值是-1);

紧接着调用了一行代码 mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;。向上填充的时候,mLayoutState.mItemDirection = -1。计算出来,mLayoutState.mCurrentPosition = 4 - 1 = 3

然后调用 fill 方法向上填充:

layoutChunk 方法中 ViewHolder3 的 布局 layoutDecoratedWithMargins(view, left, top, right, bottom); 位置是在 FootViewHolder 上面 top = -900,bottom = 0。

ViewHolder2 的 布局位置是 top = -1800,bottom = -900。

ViewHolder1 的 布局位置是 top = -2700,bottom = -1800。

布局完 ViewHolder1,以后,remainingSpace < 0 ,结束向上填充。

为什么会结束呢,在我们的例子中,remainingSpace = 2255 ,布局完 144 + Math.abs(-2700),已经大于 2255 了。

这个时候,FootViewHolder 的位置是 top = 0,bottom = 144。距离 RecyclerView 的底部还有很大的一段距离(在我们的例子中是 2111像素)。然后会走到 fixLayoutEndGap 方法。

private int fixLayoutEndGap(int endOffset, RecyclerView.Recycler recycler,RecyclerView.State state, boolean canOffsetChildren) {//注释1处,这里大于0,2111px,表示end方向有空隙int gap = mOrientationHelper.getEndAfterPadding() - endOffset;int fixOffset = 0;if(gap > 0) {//注释2处,向下滚动fixOffset = -scrollBy(-gap, recycler, state);} else {return 0; // nothing to fix}// move offset according to scroll amountendOffset += fixOffset;if(canOffsetChildren) {// re-calculate gap, see if we could fix itgap = mOrientationHelper.getEndAfterPadding() - endOffset;if(gap > 0) {mOrientationHelper.offsetChildren(gap);return gap + fixOffset;}}return fixOffset;
}

注释1处,这里大于0,表示end方向有空隙。

注释2处,向下滚动。这个时候,最大滚动距离是 2111 像素。

int scrollBy(int delta, RecyclerView.Recycler recycler, RecyclerView.State state) {if(getChildCount() == 0 || delta == 0) {return 0;}ensureLayoutState();mLayoutState.mRecycle = true;final int layoutDirection = delta > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;final int absDelta = Math.abs(delta);updateLayoutState(layoutDirection, absDelta, true, state);final int consumed = mLayoutState.mScrollingOffset + fill(recycler, mLayoutState, state, false);if(consumed < 0) {if(DEBUG) {Log.d(TAG, "Don't have any more elements to scroll");}return 0;}final int scrolled = absDelta > consumed ? layoutDirection * consumed : delta;//注释1处,偏移子ViewmOrientationHelper.offsetChildren(-scrolled);if(DEBUG) {Log.d(TAG, "scroll req: " + delta + " scrolled: " + scrolled);}mLayoutState.mLastScrollDelta = scrolled;return scrolled;
}

注释1处,偏移所有的子View。也就是说所有的子View向下滚动了2111像素。

FootViewHolder会偏移到 RecyclerView 的底部。 FootViewHolder 的 top = 2111,bottom = 2255。

滚动了这么多的距离,需要填充新的ViewHolder吗?不需要,我们在上面分析中,ViewHolder1 的 top 是 -2700足够滚动到屏幕中,还有剩余589px。

ViewHolder3 的 top 是 1211 ,bottom 是 2111。

ViewHolder2 的 top 是 311,bottom 是 1211。

ViewHolder1 的 top 是 -589,bottom 是 311。

dispatchLayoutStep2 结束

dispatchLayoutStep3 阶段,执行动画

记录当前阶段的动画信息,对比 dispatchLayoutStep1 阶段记录的动画信息,执行合适的动画。

FootViewHolder 会执行 move 动画。此时 FootViewHolder 的 top 是 2111。

动画开始前,把 FootViewHolder 的 translationY 设置为 -2111。在动画过程中,变化到 translationY = 0 。 实现了从上滑动到底部的效果。

新增的 ViewHolder 会执行 alpha 透明度动画。动画开始前 alpha = 0,动画结束后 alpha = 1。

dispatchLayoutStep3 结束

先说下结论

  • 调用 notifyItemInserted 方法的时候,会把 FootViewHolder 的 position 向下偏移。在预布局 dispatchLayoutStep1 结束的时候, FootViewHolder 的 position = 4。
  • 在 dispatchLayoutStep2 阶段,会以 FootViewHolder 为锚点 position = 4 进行填充。先填充 FootViewHolder。此时FootViewHolder 布局在屏幕中的坐标是 top = 0,bottom = 144。(FootView 的高度就是144)
  • 从 position =5 向锚点下方填充,此时没有更多的数据。
  • position = 3 向锚点上方填充,直到没有更多空间。
  • 此时 FootViewHolder 距离 RecyclerView底部还有很大一段距离。RecyclerView 会向下偏移所有的子View,结束后,FootViewHolder的bottom 就是 RecyclerView的 最底部的坐标。
  • dispatchLayoutStep3 阶段,FootViewHolder 执行一个 move 动画,从上向下移动一段距离。
  • 新创建的 ViewHolder 执行 alpha 动画,从透明到不透明。

在搞明白了这个问题以后,又想到另一个问题。

如果给适配器加一个HeadViewHolder,那么 notifyItemInserted 以后,RecyclerView 就会以会以 HeadViewHolder 为锚点,从上到下进行布局,是不是就可以解决 因为 有FootViewHolder 而导致 RecyclerView自动滚动到底部的问题呢?,我们来验证一下。

改造过后的适配器代码 有一个 HeadViewHolder,并且新增了一个 myNotifyItemInserted 方法。。

class TestAnimatorAdapter(private val context: Context
) : RecyclerView.Adapter<TestAnimatorAdapter.ViewHolder>() {companion object {val TYPE_HEADER = -1val TYPE_FOOTER = 1val HEAD_COUNT = 1val FOOT_COUNT = 1private const val TAG = "TestAnimatorAdapterAdap"}val dataList = mutableListOf<CheckBoxModel>()fun onDataSourceChanged(dataList: MutableList<CheckBoxModel>) {this.dataList.clear()this.dataList.addAll(dataList)}/*** 这里一定要注意了,因为有head,所以要加上head的数量*/fun myNotifyItemInserted(position: Int) {notifyItemInserted(position + HEAD_COUNT)}override fun onCreateViewHolder(parent: ViewGroup,viewType: Int): ViewHolder {if (viewType == TYPE_HEADER) {val view = LayoutInflater.from(context).inflate(R.layout.head_view, parent, false)return HeadViewHolder(view)}if (viewType == TYPE_FOOTER) {val view =LayoutInflater.from(context).inflate(R.layout.foot_view_load_more, parent, false)return FootViewHolder(view)}val view =LayoutInflater.from(context).inflate(R.layout.item_test_animation, parent, false)return ViewHolder(view)}override fun onBindViewHolder(holder: ViewHolder, position: Int) {if (position == 0) {return}if (position == itemCount - 1) {return}val dataPosition = position - 1val model = dataList[dataPosition]holder.checkBox?.isSelected = model.isCheckedholder.textDescription?.text = model.descriptionLog.i(TAG,"onBindViewHolder: dataPosition = $dataPosition  holder = $holder model = $model")}override fun getItemCount(): Int {return dataList.size + HEAD_COUNT + FOOT_COUNT}override fun getItemViewType(position: Int): Int {if (position == 0) {return TYPE_HEADER}if (position == itemCount - 1) {return TYPE_FOOTER}return super.getItemViewType(position)}open class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {var checkBox: CheckBox? = nullvar textDescription: TextView? = nullinit {checkBox = itemView.findViewById(R.id.check_box)textDescription = itemView.findViewById(R.id.text_description)}}class HeadViewHolder(itemView: View) : ViewHolder(itemView) {}class FootViewHolder(itemView: View) : ViewHolder(itemView) {}

这里一定要注意了:


/**
* 这里一定要注意了,因为有head,所以要加上head的数量*/
fun myNotifyItemInserted(position: Int) {notifyItemInserted(position + HEAD_COUNT)
}

因为有 head,在 notifyItemInserted 的时候,position 要要加上 head 的数量。

测试代码

binding.btnNotifyItemChanged.setOnClickListener {val newArrayList = arrayListOf<CheckBoxModel>()for (i in 0 until 4) {newArrayList.add(CheckBoxModel("hi Hello$i", false))}testAnimatorAdapterAdapter.onDataSourceChanged(newArrayList)for (index in 0 until 4) {//总共添加了4条数据,调用4次 notifyItemInserted testAnimatorAdapterAdapter.myNotifyItemInserted(index)}
}

效果图:

在这里插入图片描述

可以看到,RecyclerView 不会自动滚动到底部。

如果这里不加上 Head 的数量,RecyclerView 会以 HeadViewHolder 为锚点,向下布局,然后再以 HeadViewHolder 为锚点向上布局。在我们的例子中,导致最后的结果是,RecyclerView 还是会自动滚动到底部。

测试代码 调用 testAnimatorAdapterAdapter.notifyItemInserted(index)

binding.btnNotifyItemChanged.setOnClickListener {val newArrayList = arrayListOf<CheckBoxModel>()for (i in 0 until 4) {newArrayList.add(CheckBoxModel("hi Hello$i", false))}testAnimatorAdapterAdapter.onDataSourceChanged(newArrayList)for (index in 0 until 4) {//总共添加了4条数据,调用4次 notifyItemInserted testAnimatorAdapterAdapter.notifyItemInserted(index)}
}

效果图:

在这里插入图片描述

为什么呢?因为 notifyItemInserted 从 0 开始布局,会将 HeadViewHolder 向下偏移。最后 HeadViewHolder 的 position = 4 。关键的方法是 RecyclerView 的 offsetPositionRecordsForInsert 方法:

void offsetPositionRecordsForInsert(int positionStart, int itemCount) {final int childCount = mChildHelper.getUnfilteredChildCount();for(int i = 0; i < childCount; i++) {final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));//注释1处,偏移 position >= positionStart 的 ViewHolderif(holder != null && !holder.shouldIgnore() && holder.mPosition >= positionStart) {holder.offsetPosition(itemCount, false);mState.mStructureChanged = true;}}mRecycler.offsetPositionRecordsForInsert(positionStart, itemCount);requestLayout();
}

注释1处,偏移 position >= positionStart 的 ViewHolder。在我们的例子中,HeadViewHolder 的 position = 0,从 0 开始 notifyItemInserted,会将 HeadViewHolder 向下偏移。最后 HeadViewHolder 的 position = 4 。然后开始布局的时候,position = 4 的位置 itemType 是正常的ViewHolder,所以 position = 4 的位置布局的是正常的ViewHolder。 position = 5 是 FootViewHolder。

还想到一个问题,只有 FootView 的时候,为什么调用 notifyDataChanged 以后,RecyclerView 不会自动滚动到底部呢?

在这里插入图片描述

原因是:

  1. 调用notifyDataSetChanged 不会偏移 FootViewHolder。FootViewHolder 的 position = 0。
  2. dispatchLayoutStep2 阶段,会以 FootViewHolder 为锚点,从 position = 0 开始布局。
  3. position = 0 的位置 ItemType 是正常的 ViewHolder。
  4. 然后一直向下布局,直到没有更多的空间 remainingSpace ,结束布局。

参考链接:

  • RecyclerView第一次设置LayoutManager和Adapter之后的源码分析
  • RecyclerView源码分析之二 滚动时候的ViewHolder的回收和复用
  • RecyclerView notifyDataSetChanged 之后的源码分析
  • RecyclerView notifyItemInserted 之后的源码分析
  • RecyclerView notifyItemRemoved 之后的源码分析

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

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

相关文章

Linux中常用命令(文件、目录和文件压缩)及功能示例

一、Linux关于文件与目录的常用命令及其功能示例 命令: ls 全名: List (列表) 常用选项: -l: 详细列表格式&#xff0c;显示详细信息。-a: 显示所有文件&#xff0c;包括隐藏文件。 功能: 列出目录内容。 示例: ls -la /home 此命令以详细格式列出/home目录中的所有文件&#x…

推荐一本牛逼的入门 Python书!,如何试出一个Python开发者真正的水平

本书详细解说了 Python 语言和编程的本质&#xff0c;无论你是否接触过编程语言&#xff0c;只要是 Python 编程的初学者&#xff0c;都可阅读本书。 本书讲解的内容虽然基础&#xff0c;但并不简单。本书提供了 165 幅图表&#xff0c;可以让大家能够轻松地理解并掌握复杂的概…

踏入网页抓取的旅程:使用 grequests 构建 Go 视频下载器

引言 在当今数字化的世界中&#xff0c;网页抓取技术变得越来越重要。无论是获取数据、分析信息&#xff0c;还是构建自定义应用程序&#xff0c;我们都需要从互联网上抓取数据。本文将介绍如何使用 Go 编程语言和 grequests 库来构建一个简单的 Bilibili 视频下载器&#xff…

UE4_碰撞_射线检测不到物体原因及跳不到圈内的问题

UseSimpleAsComplex 和 UseComplexAsSimple 标记的作用和使用时间。 虚幻引擎 4 中有简单和复杂碰撞形态。 简单碰撞 是基础&#xff0c;如盒体、 球体、胶囊体和凸包。 复杂碰撞 是给定对象的三角网格图。 虚幻引擎 4 会默认创建简单和复杂两种形态&#xff0c;然后基于用户需…

gan zoo: 最新GAN 相关paper/code收集

相关推荐&#xff1a; 简单实现 GAN 简单实现 DCGAN 简单实现 InfoGAN 简单实现 Pix2Pix 一文带你读懂概率生成模型 GPT-1/GPT-2/GPT-3简介 GPT从0到1构建(附视频代码链接) 一文带你读懂变分自编码器(VAEs) 文本引导图像生成模型的演变(DALLE/CLIP/GLIDE) 作者对迄今为止所有的…

Java类和对象练习题

练习一 下面代码的运行结果是&#xff08;&#xff09; public static void main(String[] args){String s;System.out.println("s"s);} 解析&#xff1a;本题中的代码不能编译通过&#xff0c;因为在Java当中局部变量必须先初始化&#xff0c;后使用。所以此处编译不…

JAVAEE之网络原理

1.IP地址 IP地址主要用于标识网络主机、其他网络设备&#xff08;如路由器&#xff09;的网络地址。简单说&#xff0c;IP地址用于定位主机的网络地址。 格式 IP地址是一个32位的二进制数&#xff0c;通常被分割为4个“8位二进制数”&#xff08;也就是4个字节&#xff09;&…

每日一题(leetcode331):验证二叉树的前序序列化——栈

类似消消乐&#xff0c;数字&#xff0b;“#”“#”就可以消成一个“#”&#xff0c;到最后如果栈中只剩一个“#”便说明序列正确。 当然也可以用槽位理解&#xff0c;一个数字出现会消耗一个槽位产生两个槽位&#xff08;即产生一个槽位&#xff09;&#xff0c;一个“#”出现…

企业防止数据泄露的措施有哪些?(企业机密文件防泄密解决方案)

企业防止数据泄露的措施是一个复杂且关键的问题&#xff0c;涉及到企业的核心竞争力和信息安全。 随着信息技术的快速发展&#xff0c;数据泄露事件频发&#xff0c;给企业带来了巨大的经济损失和声誉损害。 企业防止数据泄露的措施有哪些? 首先&#xff0c;企业需要建立健全…

雷卯有多种接口与电源保护方案

在当今的电子设备中&#xff0c;各种接口和电源保护至关重要。它们不仅关乎设备的正常运行&#xff0c;更直接影响到数据传输的稳定性和设备的安全。雷卯公司以其专业的技术和丰富的经验&#xff0c;为您提供全面的接口与电源保护方案&#xff0c;确保您的系统安全稳定运行。 …

Android vehicle车辆属性新增demo

目录 前言一、Vehicle模块1.1 简介1.2 Vehicle框架1.3 主要功能和特点1.4 重要服务CarService1.4.1 简介1.4.2 组成1.4.3 启动时序1.4.4 作用 二、车辆属性新增demo2.1 CarPropertyService2.1.1 简介2.1.2 架构2.1.3 车辆属性 API2.1.4 CarPropertyService 初始化流程 2.2 App …

学习鸿蒙基础(9)

目录 一、鸿蒙国际化配置 二、鸿蒙常用组件介绍 三、鸿蒙像素单位介绍 四、鸿蒙布局介绍 1、Row与Column线性布局 2、层叠布局-Stack 3、弹性布局 4、栅格布局 5、网格布局 一、鸿蒙国际化配置 base目录下为默认的string。en_US对应美国的。zh_CN对应中国的。新增一个s…

Backend - gitea 首次建库(远端本地)

目录 一、建立远端储存库 1. 进入新增画面 2. 填写储存库名称&#xff08;如book&#xff09;&#xff0c;点击“建立”即可 二、本地关联远端储存库 1. 本地初始化储存库代码 &#xff08;1&#xff09;新建文件夹 &#xff08;2&#xff09;获取远端储存库 2. 本地编写…

阿基米德分牛问题及其Python求解

文章目录 题目大意sympy求解结果 题目大意 问 太阳神有一牛群&#xff0c;由白、黑、花、棕四种颜色的公、母牛组成&#xff0c;其间关系如下&#xff0c;求每种牛的个数。 公牛中&#xff0c;白牛多于棕牛&#xff0c;二者之差为黑牛的 1 2 1 3 \frac{1}{2}\frac{1}{3} 21​…

SpringBoot 集成分布式任务调度 XXL-JOB【保姆级上手】

文章目录 XXL-JOB 介绍分布式任务调度XXL-JOB 概述 快速入门下载源码初始化调度数据库编译源码调度中心调度中心介绍配置调度中心部署调度中心集群部署调度中心&#xff08;可选&#xff09;Docker 镜像方式搭建调度中心&#xff08;可选&#xff09; 执行器执行器介绍添加依赖…

外包干了5天,技术退步明显.......

先说一下自己的情况&#xff0c;大专生&#xff0c;18年通过校招进入杭州某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落! 而我已经在一个企业干了四年的功能测…

meanshift论文学习

1. abstract 2. 理论解读 目标函数 然后对(11)求导&#xff0c;求解x&#xff0c;x实际就是求解当图像位置的值&#xff0c;求导之后表示为&#xff1a; 进一步整理得&#xff1a; 上式第二项即为meanshift 进一步整理为 上式表明了均值漂移与核函数之间的关系。 3. 缺点…

AI预测福彩3D第22弹【2024年3月31日预测--第4套算法重新开始计算第8次测试】

昨天周六单位事情比较多&#xff0c;忙了一天&#xff0c;回来比较晚了&#xff0c;实在没有闲暇时间去做预测了&#xff0c;先给各位道个歉。今天上午比较忙&#xff0c;下午有点空&#xff0c;趁这个时间赶紧把预测的结果发出来供大家参考。 今天继续对第4套算法进行测试&…

阿里云的服务码获取的申请按钮怎么是灰色的

您好&#xff0c;您目前已经进入阿里云备案工单服务渠道&#xff0c;很高兴为您服务。工单渠道的服务响应时效为0-90分钟&#xff0c; 如您所遇到的问题比较着急&#xff0c;您可以通过阿里云官网右上角联系我们中的“在线服务”进行咨询&#xff0c;我们会第一时间为您服务。感…

CentOS7 磁盘相关的命令及磁盘重新调整分配

umount 在CentOS 7中&#xff0c;umount是一个常用的命令&#xff0c;用于卸载文件系统。以下是一些常用的umount命令&#xff1a; 卸载指定的文件系统&#xff1a; umount /dev/sdXN 其中&#xff0c;/dev/sdXN是你想要卸载的分区。例如&#xff0c;/dev/sda1。 卸载并…