Android 实现菜单拖拽排序
效果图

简介
本文主角是ItemTouchHelper。
它是RecyclerView对于item交互处理的一个「辅助类」,主要用于拖拽以及滑动处理。
以接口实现的方式,达到配置简单、逻辑解耦、职责分明的效果,并且支持所有的布局方式。
功能拆解

功能实现
4.1、实现接口
自定义一个类,实现ItemTouchHelper.Callback接口,然后在实现方法中根据需求简单配置即可。
class DragCallBack(adapter: DragAdapter, data: MutableList<String>) : ItemTouchHelper.Callback() {
}
ItemTouchHelper.Callback必须实现的3个方法:
getMovementFlags
onMove
onSwiped
其他方法还有onSelectedChanged、clearView等。
4.1.1、getMovementFlags
用于创建交互方式,交互方式分为两种:
1. 拖拽,网格布局支持上下左右,列表只支持上下(LEFT、UP、RIGHT、DOWN)。
2. 滑动,只支持前后(START、END)。
最后,通过makeMovementFlags把结果返回回去,makeMovementFlags接收两个参数,dragFlags和swipeFlags,即上面拖拽和滑动组合的标志位。
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {var dragFlags = 0var swipeFlags = 0when (recyclerView.layoutManager) {is GridLayoutManager -> {// 网格布局dragFlags = ItemTouchHelper.LEFT or ItemTouchHelper.UP or ItemTouchHelper.RIGHT or ItemTouchHelper.DOWNreturn makeMovementFlags(dragFlags, swipeFlags)}is LinearLayoutManager -> {// 线性布局dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWNswipeFlags = ItemTouchHelper.START or ItemTouchHelper.ENDreturn makeMovementFlags(dragFlags, swipeFlags)}else -> {// 其他情况可自行处理return 0}}
}
4.1.2、onMove
拖拽时回调,这里我们主要对起始位置和目标位置的item做一个数据交换,然后刷新视图显示。
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {// 起始位置val fromPosition = viewHolder.adapterPosition// 结束位置val toPosition = target.adapterPosition// 固定位置if (fromPosition == mAdapter.fixedPosition || toPosition == mAdapter.fixedPosition) {return false}// 根据滑动方向 交换数据if (fromPosition < toPosition) {// 含头不含尾for (index in fromPosition until toPosition) {Collections.swap(mData, index, index + 1)}} else {// 含头不含尾for (index in fromPosition downTo toPosition + 1) {Collections.swap(mData, index, index - 1)}}// 刷新布局mAdapter.notifyItemMoved(fromPosition, toPosition)return true
}
4.1.3、onSwiped
滑动时回调,这个回调方法里主要是做数据和视图的更新操作。
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {if (direction == ItemTouchHelper.START) {Log.i(TAG, "START--->向左滑")} else {Log.i(TAG, "END--->向右滑")}val position = viewHolder.adapterPositionmData.removeAt(position)mAdapter.notifyItemRemoved(position)}
4.2、绑定RecyclerView
上面接口实现部分我们已经简单写好了,逻辑也挺简单,总共不超过100行代码。
接下来就是把这个辅助类绑定到RecyclerView。
RecyclerView显示的实现就是基础的样式,就不展开了,可以查看源码。
val dragCallBack = DragCallBack(mAdapter, list)
val itemTouchHelper = ItemTouchHelper(dragCallBack)
itemTouchHelper.attachToRecyclerView(mBinding.recycleView)
绑定只需要调用attachToRecyclerView就好了。
至此,简单的效果就已经实现了。下面开始优化和进阶的部分。
4.3、设置分割线
RecyclerView网格布局实现等分,我们一般先是自定义ItemDecoration,然后调用addItemDecoration来实现的。
但是我在实现效果的时候遇到一个问题,因为我加了布局切换的功能,在每次切换的时候,针对不同的布局分别设置layoutManager和ItemDecoration,这就导致随着切换次数的增加,item的间隔就越大。
addItemDecoration,顾名思义是添加,通过查看源码发现RecyclerView内部是有一个ArrayList来维护的,所以当我们重复调用addItemDecoration方法时,分割线是以递增的方式在增加的,并且在绘制的时候会从集合中遍历所有的分割线绘制。
部分源码:
@Override
public void draw(Canvas c) {super.draw(c);final int count = mItemDecorations.size();for (int i = 0; i < count; i++) {mItemDecorations.get(i).onDrawOver(c, this, mState);}//...
}
既然知道了问题所在,也大概想到了3种解决办法:
1. 调用addItemDecoration前,先调用removeItemDecoration方法remove掉之前所有的分割线。
2. 调用addItemDecoration(@NonNull ItemDecoration decor, int index),通过index来维护。
3. add时通过一个标示来判断,添加过就不添加了。
好像可行,实际上并不太行...因为始终都有两个分割线实例。
我们再来梳理一下:
两种不同的布局
都有分割线
分割线只需设置一次
我想到另外一个办法,不对RecyclerView做处理了,既然两种布局都有分割线,是不是可以把分割线合二为一了,然后根据LayoutManager去绘制不同的分割线?
理论上是可行的,事实上也确实可以...
自定义分割线:
class GridSpaceItemDecoration(private val spanCount: Int, private val spacing: Int = 20, private var includeEdge: Boolean = false) :RecyclerView.ItemDecoration() {override fun getItemOffsets(outRect: Rect, view: View, recyclerView: RecyclerView, state: RecyclerView.State) {recyclerView.layoutManager?.let {when (recyclerView.layoutManager) {is GridLayoutManager -> {val position = recyclerView.getChildAdapterPosition(view) // 获取item在adapter中的位置val column = position % spanCount // item所在的列if (includeEdge) {outRect.left = spacing - column * spacing / spanCountoutRect.right = (column + 1) * spacing / spanCountif (position < spanCount) {outRect.top = spacing}outRect.bottom = spacing} else {outRect.left = column * spacing / spanCountoutRect.right = spacing - (column + 1) * spacing / spanCountif (position >= spanCount) {outRect.top = spanCount}outRect.bottom = spacing}}is LinearLayoutManager -> {outRect.top = spanCountoutRect.bottom = spacing}}}}}
4.4、选中放大/背景变色
为了提升用户体验,可以在拖拽的时候告诉用户当前拖拽的是哪个item,比如选中的item放大、背景高亮等。
网格布局,选中变大。
列表布局,背景变色。
这里用到ItemTouchHelper.Callback中的两个方法,onSelectedChanged和clearView,我们需要在选中时改变视图显示,结束时再恢复。
4.4.1、onSelectedChanged
拖拽或滑动 发生改变时回调,这时我们可以修改item的视图。
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {viewHolder?.let {// 因为拿不到recyclerView,无法通过recyclerView.layoutManager来判断是什么布局,所以用item的宽度来判断// itemView.width > 500 用这个来判断是否是线性布局,实际取值自己看情况if (it.itemView.width > 500) {// 线性布局 设置背景颜色val drawable = it.itemView.background as GradientDrawabledrawable.color = ContextCompat.getColorStateList(it.itemView.context, R.color.greenDark)} else {// 网格布局 设置选中放大ViewCompat.animate(it.itemView).setDuration(200).scaleX(1.3F).scaleY(1.3F).start()}}}super.onSelectedChanged(viewHolder, actionState)
}
actionState:
ACTION_STATE_IDLE 空闲状态。
ACTION_STATE_SWIPE 滑动状态。
ACTION_STATE_DRAG 拖拽状态。
4.4.2、clearView
拖拽或滑动 结束时回调,这时我们要把改变后的item视图恢复到初始状态。
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {// 恢复显示// 这里不能用if判断,因为GridLayoutManager是LinearLayoutManager的子类,改用when,类型推导有区别when (recyclerView.layoutManager) {is GridLayoutManager -> {// 网格布局 设置选中大小ViewCompat.animate(viewHolder.itemView).setDuration(200).scaleX(1F).scaleY(1F).start()}is LinearLayoutManager -> {// 线性布局 设置背景颜色val drawable = viewHolder.itemView.background as GradientDrawabledrawable.color = ContextCompat.getColorStateList(viewHolder.itemView.context, R.color.greenPrimary)}}super.clearView(recyclerView, viewHolder)
}
4.5、固定位置
在实际需求中,交互可能要求我们第一个菜单不可以变更顺序,只能固定,比如效果中的第一个菜单「推荐」固定在首位这种情况。
4.5.1、修改adapter
定义一个固定值,并设置不同的背景色和其他菜单区分开。
class DragAdapter(private val mContext: Context, private val mList: List<String>) : RecyclerView.Adapter<DragAdapter.ViewHolder>() {val fixedPosition = 0 // 固定菜单override fun onBindViewHolder(holder: ViewHolder, position: Int) {holder.mItemTextView.text = mList[position]// 第一个固定菜单val drawable = holder.mItemTextView.background as GradientDrawableif (holder.adapterPosition == 0) {drawable.color = ContextCompat.getColorStateList(mContext, R.color.greenAccent)}else{drawable.color = ContextCompat.getColorStateList(mContext, R.color.greenPrimary)}}//...
}
4.5.1、修改onMove回调
在onMove方法中判断,只要是固定位置就直接返回false。
class DragCallBack(adapter: DragAdapter, data: MutableList<String>) : ItemTouchHelper.Callback() {/*** 拖动时回调*/override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {// 起始位置val fromPosition = viewHolder.adapterPosition// 结束位置val toPosition = target.adapterPosition// 固定位置if (fromPosition == mAdapter.fixedPosition || toPosition == mAdapter.fixedPosition) {return false}// ...return true}
}
虽然第一个菜单无法交换位置了,但是它还是可以拖拽的。
效果实现了吗,好像也实现了,可是又好像哪里不对,就好像填写完表单点击提交时你告诉我格式不正确一样,你不能一开始就告诉我吗?
为了进一步提升用户体验,可以让固定位置不可以拖拽吗?
可以,ItemTouchHelper.Callback中有两个方法:
1. isLongPressDragEnabled 是否可以长按拖拽。
2. isItemViewSwipeEnabled 是否可以滑动。
这俩方法默认都是true,所以即使不能交换位置,但默认也是支持操作的。
4.5.3、重写isLongPressDragEnabled
以拖拽举例,我们需要重写isLongPressDragEnabled方法把它禁掉,然后在非固定位置的时候去手动开启。
override fun isLongPressDragEnabled(): Boolean {//return super.isLongPressDragEnabled()return false
}
禁掉之后什么时候再触发呢?
因为我们现在的交互是长按进入编辑,那就需要在长按事件中再调用startDrag手动开启。
mAdapter.setOnItemClickListener(object : DragAdapter.OnItemClickListener {//...override fun onItemLongClick(holder: DragAdapter.ViewHolder) {if (holder.adapterPosition != mAdapter.fixedPosition) {itemTouchHelper.startDrag(holder)}}
})
ok,这样就完美实现了。
4.6、其他
4.6.1、position
因为有拖拽操作,下标其实是变化的,在做相应的操作时,要取实时位置。
holder.adapterPosition
4.6.2、重置
不管是拖拽还是滑动,其实本质都是对Adapter内已填充的数据进行操作,实时数据通过Adapter获取即可。
如果想要实现重置功能,直接拿最开始的原始数据重新塞给Adapter即可。
源码探索
看源码时,找对一个切入点,往往能达到事半功倍的效果。
这里就从绑定RecyclerView开始吧。
val dragCallBack = DragCallBack(mAdapter, list)
val itemTouchHelper = ItemTouchHelper(dragCallBack)
itemTouchHelper.attachToRecyclerView(mBinding.recycleView)
实例化ItemTouchHelper,然后调用其attachToRecyclerView方法绑定到RecyclerView。
5.1、attachToRecyclerView
public void attachToRecyclerView(@Nullable RecyclerView recyclerView) {if (mRecyclerView == recyclerView) {return; // nothing to do}if (mRecyclerView != null) {destroyCallbacks();}mRecyclerView = recyclerView;if (recyclerView != null) {final Resources resources = recyclerView.getResources();mSwipeEscapeVelocity = resources.getDimension(R.dimen.item_touch_helper_swipe_escape_velocity);mMaxSwipeVelocity = resources.getDimension(R.dimen.item_touch_helper_swipe_escape_max_velocity);setupCallbacks();}
}
这段代码其实有点意思的,解读一下:
1. 第一个if判断,避免重复操作,直接return。
2. 第二个if判断,调用了destroyCallbacks,在destroyCallbacks里面做了一些移除和回收操作,说明只能绑定到一个RecyclerView;同时,注意这里判断的主体是mRecyclerView,不是我们传进来的recyclerView,而且我们传进来的recyclerView是支持Nullable的,所以我们可以传个空值走到destroyCallbacks里来做解绑操作。
3. 第三个if判断,当我们传的recyclerView不为空时,调用setupCallbacks。
5.2、setupCallbacks
private void setupCallbacks() {ViewConfiguration vc = ViewConfiguration.get(mRecyclerView.getContext());mSlop = vc.getScaledTouchSlop();mRecyclerView.addItemDecoration(this);mRecyclerView.addOnItemTouchListener(mOnItemTouchListener);mRecyclerView.addOnChildAttachStateChangeListener(this);startGestureDetection();
}
这个方法里已经大概可以看出内部实现原理了。
两个关键点:
addOnItemTouchListener
startGestureDetection
通过触摸和手势识别来处理交互显示。
5.3、mOnItemTouchListener
private final OnItemTouchListener mOnItemTouchListener = new OnItemTouchListener() {@Overridepublic boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent event) {mGestureDetector.onTouchEvent(event);if (action == MotionEvent.ACTION_DOWN) {//...if (mSelected == null) {if (animation != null) {//...select(animation.mViewHolder, animation.mActionState);}}} else if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {select(null, ACTION_STATE_IDLE);} else if (mActivePointerId != ACTIVE_POINTER_ID_NONE) {//...if (index >= 0) {checkSelectForSwipe(action, event, index);}}return mSelected != null;}@Overridepublic void onTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent event) {mGestureDetector.onTouchEvent(event);//...if (activePointerIndex >= 0) {checkSelectForSwipe(action, event, activePointerIndex);}switch (action) {case MotionEvent.ACTION_MOVE: {if (activePointerIndex >= 0) {moveIfNecessary(viewHolder);}break;}//...}}@Overridepublic void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {select(null, ACTION_STATE_IDLE);}
};
这段代码删减之后还是有点多,不过没关系,提炼一下,核心通过判断MotionEvent调用了几个方法:
select
checkSelectForSwipe
moveIfNecessary
5.3.1、select
void select(@Nullable ViewHolder selected, int actionState) {if (selected == mSelected && actionState == mActionState) {return;}//...if (mSelected != null) {if (prevSelected.itemView.getParent() != null) {final float targetTranslateX, targetTranslateY;switch (swipeDir) {case LEFT:case RIGHT:case START:case END:targetTranslateY = 0;targetTranslateX = Math.signum(mDx) * mRecyclerView.getWidth();break;//...}//...} else {removeChildDrawingOrderCallbackIfNecessary(prevSelected.itemView);mCallback.clearView(mRecyclerView, prevSelected);}}//...mCallback.onSelectedChanged(mSelected, mActionState);mRecyclerView.invalidate();
}
这里面主要是在拖拽或滑动时对translateX/Y的计算和处理,然后通过mCallback.clearView和mCallback.onSelectedChanged回调给我们,最后调用invalidate()实时刷新。
5.3.2、checkSelectForSwipe
void checkSelectForSwipe(int action, MotionEvent motionEvent, int pointerIndex) {//...if (absDx < mSlop && absDy < mSlop) {return;}if (absDx > absDy) {if (dx < 0 && (swipeFlags & LEFT) == 0) {return;}if (dx > 0 && (swipeFlags & RIGHT) == 0) {return;}} else {if (dy < 0 && (swipeFlags & UP) == 0) {return;}if (dy > 0 && (swipeFlags & DOWN) == 0) {return;}}select(vh, ACTION_STATE_SWIPE);
}
这里是滑动处理的check,最后也是收敛到select()方法统一处理。
5.3.3、moveIfNecessary
void moveIfNecessary(ViewHolder viewHolder) {if (mRecyclerView.isLayoutRequested()) {return;}if (mActionState != ACTION_STATE_DRAG) {return;}//...if (mCallback.onMove(mRecyclerView, viewHolder, target)) {// keep target visiblemCallback.onMoved(mRecyclerView, viewHolder, fromPosition,target, toPosition, x, y);}
}
这里检查拖拽时是否需要交换item,通过mCallback.onMoved回调给我们。
5.4、startGestureDetection
private void startGestureDetection() {mItemTouchHelperGestureListener = new ItemTouchHelperGestureListener();mGestureDetector = new GestureDetectorCompat(mRecyclerView.getContext(),mItemTouchHelperGestureListener);
}
5.4.1、ItemTouchHelperGestureListener
private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener {//...@Overridepublic void onLongPress(MotionEvent e) {//...View child = findChildView(e);if (child != null) {ViewHolder vh = mRecyclerView.getChildViewHolder(child);if (vh != null) {//...if (pointerId == mActivePointerId) {//...if (mCallback.isLongPressDragEnabled()) {select(vh, ACTION_STATE_DRAG);}}}}}
}
这里主要是对长按事件的处理,最后也是收敛到select()方法统一处理。
5.5、源码小结
1. 绑定RecyclerView。
2. 注册触摸手势监听。
3. 根据手势,先是内部处理各种校验、位置计算、动画处理、刷新等,然后回调给ItemTouchHelper.Callback。
事儿大概就是这么个事儿,主要工作都是源码帮我们做了,我们只需要在回调里根据结果处理业务逻辑即可。
源码地址
https://github.com/yechaoa/MaterialDesign
相关文章:

Android 实现菜单拖拽排序
效果图简介本文主角是ItemTouchHelper。它是RecyclerView对于item交互处理的一个「辅助类」,主要用于拖拽以及滑动处理。以接口实现的方式,达到配置简单、逻辑解耦、职责分明的效果,并且支持所有的布局方式。功能拆解功能实现4.1、实现接口自…...
通过window.open打开新的页面并修改样式添加内容
const img new Image(); img.src res; //res是图片的路径地址 const newWin window.open(, _blank); newWin.document.write(img.outerHTML); // newWin.document.body.style.background #000; newWin.document.body.style.textAlign center; newWin.document.body.oncl…...
Java中 Synchronized 的用法
《编程思想之多线程与多进程(1)——以操作系统的角度述说线程与进程》一文详细讲述了线程、进程的关系及在操作系统中的表现,这是多线程学习必须了解的基础。本文将接着讲一下Java线程同步中的一个重要的概念synchronized. synchronized是Java中的关键字,…...

Rust语言的基本介绍
rust缘起和目标 rust的英文是锈菌,是一种真菌,这种真菌的生命力非常顽强,其 在生命周期内可以产生多达5种孢子类型,这5种生命形态还可以相互转 化。“Rust”也有“铁锈”的意思,暗合“裸金属”之意,代表了R…...

新冠小阳人症状记录
原想挺过春节后再养,发现事与愿违。生理期期间抵抗力下降,所以在生理期第二天就有些症状了。可能是生理期前一天出去采购食物染上,也可能是合租夫妻染上。anyway,记录下自己的症状与相应有效的偏方: 第一天:…...

SQL零基础入门学习(十四)
上篇:SQL零基础入门学习(十三) SQL NULL 值 NULL 值代表遗漏的未知数据。 默认地,表的列可以存放 NULL 值。 如果表中的某个列是可选的,那么我们可以在不向该列添加值的情况下插入新记录或更新已有的记录。这意味着该…...

Excel工作表不能移动或复制?看看是不是这两个原因
Excel工作表不能移动或复制?今天来看看如何解决。 大家都知道,Excel表格分为工作簿和工作表,工作簿就是整个Excel文件;工作簿里面,也就是Excel表可以有多个工作表。 而各个工作表之间是可以相互移动或复制的…...

利用递归实现括号匹配
案例引入以下则是各个字符串经过括号处理之后的结果:12((21))(12-->12(21)1232((((2121)212(21)-->32(2121)212(21)ABDF((SA)SA)SA(SA)SA(((-->ABDF((SA)SA)SA(SA)SA算法思路:这个问题的解决方法就是将字符按顺序逐一加入到新的string容器store…...
14.线程数量怎么制定?
什么是CPU 密集型任务和耗时 IO 型任务 ? CPU 密集型任务 CPU 密集型任务,比如加密、解密、压缩、计算等一系列需要大量耗费 CPU 资源的任务。 耗时 IO 型任务 数据库、文件的读写,网络通信等任务,这种任务的特点是并不会特别消耗…...
C++中STL标准模板库学习记录
文章目录:1.vector1.1 遍历方式1.2 构造函数1.3 容量大小问题1.4 插入和删除1.5 存取值1.6 交换两个vectot的元素1.7 预定义存储空间2.string3. deque4. stack4.1 常用函数5. queue5.1 特点5.2 方法6. list6.1 优点6.2 缺点6.3 构造函数6.4 交换6.5 大小6.6 插入和删…...

《数据库系统概论》学习笔记——第六章 关系数据理论
教材为数据库系统概论第五版(王珊) 这一章重点在于各种范式的概念和将低级范式转为高级范式。一定要看多值依赖和4NF(因为这个概念很绕又烦,但是期中期末都考了)。最后计算题就是一定要会:算闭包࿰…...

Odoo | Webserivce | 5分钟学会【JSONRPC】接口开发
文章目录Odoo - JsonRPC1. Odoo内方法结构(接收端)2. POST接口请求结构(发送端)3. 实例测试Odoo - JsonRPC 1. Odoo内方法结构(接收端) # -*- coding: utf-8 -*- import odoo import logging import trac…...

搜广推 NeuralCF - 改进协同过滤+矩阵分解的思想
😄 NeuralCF:2017新加坡国立大学提出。【后文简称NCF】 😄 PNN:2016年上海交通大学提出。 文章目录 NeuralCF动机原理general NCFNCF终极版(GMF+MLP的结合)缺点优点ReferenceNeuralCF 动机 前面学了MF,可知MF在用户-物品评分矩阵的基础上做矩阵分解(用户矩阵Q和物品…...

dbever连接kerberos认证的hive
文章目录一、本地安装kerberos客户端二、本地kerberos客户端登录三、dbever连接hive一、本地安装kerberos客户端 下载地址:https://web.mit.edu/kerberos/dist/index.html 安装:下一步或者自定义安装即可 安装后会自动生成配置文件:C:\Pro…...

pom依赖产生的各种问题
文章目录问题一(org.apache.ibatis.session.Configuration)解决方法问题二(ERROR StatusLogger No log4j2)解决方法问题三(com.google.common.util.concurrent)解决方法问题四(start bean documentationPluginsBootstrapper)解决方法问题五(Unable to infer base url. )解决办法…...

RPC编程:RPC框架设计目标
一:前导知识 Http是超文本传输协议,跨平台性非常好。Http可以传输文本,更多的时候传输的是文本,我们也是可以传输二进制的,我们基于Http进行下载的时候,就是走的Http协议。 Tcp协议,处理的时候…...

RBAC 权限模型介绍
RBAC 权限: 一、关系: 这基于角色的访问控制的结构就叫RBAC结构。 二、RBAC 重要对象: 用户(Employee):角色施加的主体;用户通过拥有某个或多个角色以得到对应的权限。角色(Role&…...

西电面向对象程序设计核心考点汇总(期末真题)
文章目录前言一、往年真题与答案1.1 改错题1.2 读程题1.3 面向对象程序设计二、易错知识点2.1 构造函数2.2 静态成员变量和静态成员函数2.3 权限2.4 继承2.5 多态总结前言 主要针对西安电子科技大学《面向对象程序设计》的核心考点进行汇总,包含总共8章的核心简答。…...

判断一个用字符串表达的数字是否可以被整除
一.问题引出 当一个数字很大的时候,我们常用字符串进行表达,(超过了int和long等数据类型可以存储的最大范围),但是这个时候我们该如何判断他是否可以被另一个数整除呢? 这个时候我们不妨这样来考虑问题,每次将前边求模之后的数保存下来,然后乘以10和这一位的数字进行相加的操…...

这是一款值得开发人员认真研究的软件,数据库优化,应用服务器安全优化...
1.查询数据库死锁相关信息2.查看数据库的链接情况3.当前实例上的所有用户4.创建数据库独立密码5.查看数据库使用的端口号6.当前数据库设置的最大连接数7.当前数据库最大的理论可连接数8.当前数据库实例的连接数9.当前数据库连接数10.当前数据库连接超时设置11.当前sqlserver 超…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...

【OSG学习笔记】Day 18: 碰撞检测与物理交互
物理引擎(Physics Engine) 物理引擎 是一种通过计算机模拟物理规律(如力学、碰撞、重力、流体动力学等)的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互,广泛应用于 游戏开发、动画制作、虚…...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例
使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件,常用于在两个集合之间进行数据转移,如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model:绑定右侧列表的值&…...

基于当前项目通过npm包形式暴露公共组件
1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹,并新增内容 3.创建package文件夹...

Cinnamon修改面板小工具图标
Cinnamon开始菜单-CSDN博客 设置模块都是做好的,比GNOME简单得多! 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...

视频字幕质量评估的大规模细粒度基准
大家读完觉得有帮助记得关注和点赞!!! 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用,因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型(VLMs)在字幕生成方面…...
python如何将word的doc另存为docx
将 DOCX 文件另存为 DOCX 格式(Python 实现) 在 Python 中,你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是,.doc 是旧的 Word 格式,而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...

html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...

云原生玩法三问:构建自定义开发环境
云原生玩法三问:构建自定义开发环境 引言 临时运维一个古董项目,无文档,无环境,无交接人,俗称三无。 运行设备的环境老,本地环境版本高,ssh不过去。正好最近对 腾讯出品的云原生 cnb 感兴趣&…...

Kafka入门-生产者
生产者 生产者发送流程: 延迟时间为0ms时,也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于:异步发送不需要等待结果,同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…...