Android 实现菜单拖拽排序

news/2024/3/29 14:18:07/文章来源:https://blog.csdn.net/qq_39312146/article/details/129239813

效果图

简介

本文主角是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

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

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

相关文章

ARM的工作模式和37个寄存器

一、ARM的工作模式 ARM一共有7种工作模式 模式含义User非特权模式&#xff0c;大部分任务执行在这种模式FIQ当一个高优先级&#xff08;fast) 中断产生时将会进入这种模式IRQ当一个低优先级&#xff08;normal) 中断产生时将会进入这种模式Supervisor当复位或软中断指令执行时…

CISP注册信息安全专业人员证书

一、什么是“CISP”&#xff1f; 注册信息安全专业人员(Certified Information Security Professional&#xff0c;简称“CISP”)&#xff0c;是安全行业最为权威的安全资格认证&#xff0c;由中国信息安全测评中心统一授权组织&#xff0c;中国信息安全测评中心授权培训机构进…

GMP洁净净化车间布局建设|喜格净化设计建设

GMP洁净净化车间布局建设方案应该根据具体的生产流程、工艺要求和产品特点进行设计。以下喜格SICOLAB基本的设计原则和注意事项&#xff1a;&#xff08;1&#xff09;设计洁净度级别&#xff1a;根据产品特点和生产工艺要求&#xff0c;确定洁净度级别&#xff0c;一般分为100…

OpenCV 图像轮廓检测

本文是OpenCV图像视觉入门之路的第15篇文章&#xff0c;本文详细的介绍了图像轮廓检测的各种操作&#xff0c;例如&#xff1a;轮廓检索模式、轮廓逼近算子等操作。 图像轮廓是具有相同颜色或灰度的连续点的曲线&#xff0c;轮廓在形状分析和物体的检测和识别中很有用。图像轮廓…

2023年鞋服配饰行业如何玩转全域经营?

2023年&#xff0c;鞋服配饰行业私域已进入深水区&#xff0c;这就对私域运营提出了更高的挑战和目标&#xff0c;企业纷纷发力以私域为基石、以消费者为核心的全域经营。 不过&#xff0c;虽然鞋服配饰行业私域起步早&#xff0c;玩法多。但在迈向全域经营的过程中&#xff0…

IntelliJ插件开发教程之新建项目

JetBrains公司系列产品IDEA、WebStrom、PyCharm、CLion、GoLand等都是基于IntelliJ Platform开发而成&#xff0c;掌握IntelliJ插件开发技能便能拥有提升开发效率的终极武器。本教程Demo源码请微信公众号“开发效率”进行获取。阅读原文如果您是JetBrains产品的用户&#xff0c…

【打卡】图分析与节点嵌入

背景介绍 图&#xff08;Graphs&#xff09;是一种对物体&#xff08;objects&#xff09;和他们之间的关系&#xff08;relationships&#xff09;建模的数据结构&#xff0c;物体以结点&#xff08;nodes&#xff09;表示&#xff0c;关系以边&#xff08;edges&#xff09;…

【数电基础】——数制和码制

目录 1.概述 1.信号&#xff08;电路&#xff09;的功能 2.信号的分类&#xff1a; 3.数字信号的输入和输出的逻辑关系表示方法 2.数制 1.十进制&#xff08;D/d&#xff09; 2.二进制(B/b) 3.八进制&#xff08;O/o&#xff09; 4.十六进制&#xff08;H/h&#xff09;…

腾讯TIM实现即时通信 v3+ts实践

目录 初始化sdk 功能描述 初始化 准备 SDKAppID 调用初始化接口 监听事件 发送消息 创建消息 创建文本消息 登录登出 功能描述 登录 登出 销毁 登录设置 获取会话列表 功能描述 获取会话列表 获取全量的会话列表 历史消息 功能描述 拉取消息列表 分页拉取…

C++ Primer Plus 第6版 读书笔记(2)第2章 开始学习 C++

C是在 C 语言基础上开发的一种集面向对象编程、泛型编程和过程化编程于一体的编程语言&#xff0c;是C语言的超集。本书是根据2003年的ISO/ANSI C标准编写的&#xff0c;通过大量短小精悍的程序详细而全面地阐述了 C的基本概念和技术&#xff0c;并专辟一章介绍了C11新增的功能…

Telnet 基础实验2: SSH 实验

Telnet 基础实验2&#xff1a; SSH 实验 本实验只能使用 eNSP中 AR 系统的路由器做 拓扑图 SSH &#xff1a; Secure Shell 是一个网络安全协议&#xff0c;基本于 TCP 协议 22 端口传输数据&#xff0c;通过对网络数据的加密&#xff0c;使其能够在一个不安全的网络环境中&a…

浅析Tomcat架构上的Valve内存马(内存马系列篇十一)

写在前面 这篇也是在Tomcat容器上面构造的内存马(收回之前说的不搞Tomcat了)&#xff0c;这是建立在Tomcat的管道上面做文章的一个内存马的实现方式。这是内存马系列的第十一篇文章了。 前置 什么是Pipeline-Valve管道&#xff1f; 根据前面Tomcat架构的相关知识&#xff0…

Java中的this与super关键字深度解析

一、this关键字this 关键字是 Java 常用的关键字&#xff0c;可用于任何实例方法内指向当前对象&#xff0c;也可指向对其调用当前方法的对象&#xff0c;或者在需要当前类型对象引用时使用。&#xff08;1&#xff09;this.属性名this修饰的变量用于指代成员变量方法的形参如果…

3 决策树及Python实现

1 主要思想 1.1 数据 1.2 训练和使用模型 训练&#xff1a;建立模型&#xff08;树&#xff09; 测试&#xff1a;使用模型&#xff08;树&#xff09; Weka演示ID3&#xff08;终端用户模式&#xff09; 双击weka.jar选择Explorer载入weather.arff选择trees–>ID3构建树…

SVIP优先办理服务-课后程序(JAVA基础案例教程-黑马程序员编著-第八章-课后作业)

【案例8-2】 Svip优先办理服务 【案例介绍】 1.任务描述 在日常工作生活中&#xff0c;无论哪个行业都会设置一些Svip用户&#xff0c;Svip用户具有超级优先权&#xff0c;在办理业务时&#xff0c;Svip用户具有最大的优先级。 本案例要求编写一个模拟Svip优先办理业务的程…

newbing的注册使用

newbing是一款全新的智能搜索引擎&#xff0c;它可以帮助你快速、准确地找到你想要的信息&#xff0c;还可以与你进行友好、有趣的对话。newbing不仅拥有强大的搜索功能&#xff0c;还具备创造性和逻辑性&#xff0c;可以为你生成诗歌、故事、代码、歌词等各种内容。newbing还可…

【Spring从成神到升仙系列 一】2023年再不会动态代理,就要被淘汰了

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱敲代码的小黄&#xff0c;独角兽企业的Java开发工程师&#xff0c;CSDN博客专家&#xff0c;阿里云专家博主&#x1f4d5;系列专栏&#xff1a;Java设计模式、数据结构和算法、Kafka从入门到成神、Kafka从成神到升仙…

浅析 Redis 主从同步与故障转移原理

我们在生产中使用 Redis&#xff0c;如果只部署一个 Redis 实例&#xff0c;当该实例宕机&#xff0c;到恢复之前都不可用&#xff1b;虽说 Redis 一般都用来做缓存&#xff0c;但不可用给业务系统带来的影响也是不小的&#xff0c;流量大时甚至会导致整个服务宕机。所以 Redis…

芯驰(E3-gateway)开发板环境搭建

1-Windows下环境配置 可以在Windows上使用命令行或者IAR IDE编译SSDK项目。Windows编译依赖的工具已经包含在 prebuilts/windows 目录中&#xff0c;包括编译器、Python和命令行工具。 1.1.1 CMD SSDK集成 msys 工具&#xff0c;可以在Windows命令行中完成SDK的配置、编译和…

Binder系统-C程序示例_框架分析

IPC&#xff1a;进程间的通信&#xff0c;远程调用&#xff0c;比如我们的A进程需要打开LED灯&#xff0c;调用led_open/led_ctl方法&#xff0c;但是他是没有权限去操作的&#xff0c;所以进程A通过&#xff1a;1.首先构造一些数据&#xff0c;2.通过IPC发送数据到进程B&#…