Android Tab吸顶 嵌套滚动通用实现方案✅

2023-12-16

很多应用的首页都会有一些嵌套滚动、Tab吸顶的布局,尤其是一些生鲜类应用,例如 朴朴超市、大润发优鲜、盒马等等。

在 Android 里面,滚动吸顶方式通常可以通过 CoordinatorLayout + AppBarLayout + CollapsingToolbarLayout + NestedScrollView 来实现,但是 AppBarLayoutBehavior fling 无法传递到 NestedScrollView,快速来回滑动偶尔也会有些抖动,导致滚动不流畅。

另外对于头部是一些动态列表的,还是更希望通过 RecyclerView 来实现,那么嵌套的方式变为: RecyclerView + ViewPager + RecyclerView ,那么就需要处理好 RecyclerView 的滑动冲突问题。

如果 ViewPager 的 RecyclerView 内部还嵌套一层 ViewPager,例如一些广告Banner图,那么事件处理也会更加复杂。本文将介绍一种通用的嵌套滚动方案,既可以实现Tab的吸顶,又可以单纯实现的两个垂直 RecyclerView 嵌套(主要场景是:尾部的recyclerview可以实现容器级别的复用,例如往多个列表页的尾部嵌套一个相同样式的推荐商品列表,如下图所示)。

nested2.jpg

代码库地址: github.com/smuyyh/Nest…

目前已应用到线上,如有一些好的建议欢迎交流交流呀~~

核心思路:

  • 父容器滑动到底部之后,触摸事件继续交给子容器滑动
  • 子容器滚动到顶部之后,触摸事件继续交给父容器滑动
  • fling 在父容器和子容器之间传递
  • Tab 在屏幕中间,切换 ViewPager 之后,如果子容器不在顶部,需要优先处理滑动

代码实现:

ParentRecyclerView

因为触摸事件首先分发到父容器,所以核心的协调逻辑主要由父容器实现,子容器只需要处理 fling 传递即可。


  

java

复制代码

public class ParentRecyclerView extends RecyclerView { private final int mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); /** * fling时的加速度 */ private int mVelocity = 0; private float mLastTouchY = 0f; private int mLastInterceptX; private int mLastInterceptY; /** * 用于向子容器传递 fling 速度 */ private final VelocityTracker mVelocityTracker = VelocityTracker.obtain(); private int mMaximumFlingVelocity; private int mMinimumFlingVelocity; /** * 子容器是否消耗了滑动事件 */ private boolean childConsumeTouch = false; /** * 子容器消耗的滑动距离 */ private int childConsumeDistance = 0; public ParentRecyclerView(@NonNull Context context) { this(context, null); } public ParentRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public ParentRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } private void init() { ViewConfiguration configuration = ViewConfiguration.get(getContext()); mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity(); mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity(); addOnScrollListener(new OnScrollListener() { @Override public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); if (newState == SCROLL_STATE_IDLE) { dispatchChildFling(); } } }); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mVelocity = 0; mLastTouchY = ev.getRawY(); childConsumeTouch = false; childConsumeDistance = 0; ChildRecyclerView childRecyclerView = findNestedScrollingChildRecyclerView(); if (isScrollToBottom() && (childRecyclerView != null && !childRecyclerView.isScrollToTop())) { stopScroll(); } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: childConsumeTouch = false; childConsumeDistance = 0; break; default: break; } try { return super.dispatchTouchEvent(ev); } catch (Exception e) { e.printStackTrace(); return false; } } @Override public boolean onInterceptTouchEvent(MotionEvent event) { if (isChildConsumeTouch(event)) { // 子容器如果消费了触摸事件,后续父容器就无法再拦截事件 // 在必要的时候,子容器需调用 requestDisallowInterceptTouchEvent(false) 来允许父容器继续拦截事件 return false; } // 子容器不消费触摸事件,父容器按正常流程处理 return super.onInterceptTouchEvent(event); } /** * 子容器是否消费触摸事件 */ private boolean isChildConsumeTouch(MotionEvent event) { int x = (int) event.getRawX(); int y = (int) event.getRawY(); if (event.getAction() != MotionEvent.ACTION_MOVE) { mLastInterceptX = x; mLastInterceptY = y; return false; } int deltaX = x - mLastInterceptX; int deltaY = y - mLastInterceptY; if (Math.abs(deltaX) > Math.abs(deltaY) || Math.abs(deltaY) <= mTouchSlop) { return false; } return shouldChildScroll(deltaY); } /** * 子容器是否需要消费滚动事件 */ private boolean shouldChildScroll(int deltaY) { ChildRecyclerView childRecyclerView = findNestedScrollingChildRecyclerView(); if (childRecyclerView == null) { return false; } if (isScrollToBottom()) { // 父容器已经滚动到底部 且 向下滑动 且 子容器还没滚动到底部 return deltaY < 0 && !childRecyclerView.isScrollToBottom(); } else { // 父容器还没滚动到底部 且 向上滑动 且 子容器已经滚动到顶部 return deltaY > 0 && !childRecyclerView.isScrollToTop(); } } @Override public boolean onTouchEvent(MotionEvent e) { if (isScrollToBottom()) { // 如果父容器已经滚动到底部,且向上滑动,且子容器还没滚动到顶部,事件传递给子容器 ChildRecyclerView childRecyclerView = findNestedScrollingChildRecyclerView(); if (childRecyclerView != null) { int deltaY = (int) (mLastTouchY - e.getRawY()); if (deltaY >= 0 || !childRecyclerView.isScrollToTop()) { mVelocityTracker.addMovement(e); if (e.getAction() == MotionEvent.ACTION_UP) { // 传递剩余 fling 速度 mVelocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity); float velocityY = mVelocityTracker.getYVelocity(); if (Math.abs(velocityY) > mMinimumFlingVelocity) { childRecyclerView.fling(0, -(int) velocityY); } mVelocityTracker.clear(); } else { // 传递滑动事件 childRecyclerView.scrollBy(0, deltaY); } childConsumeDistance += deltaY; mLastTouchY = e.getRawY(); childConsumeTouch = true; return true; } } } mLastTouchY = e.getRawY(); if (childConsumeTouch) { // 在同一个事件序列中,子容器消耗了部分滑动距离,需要扣除掉 MotionEvent adjustedEvent = MotionEvent.obtain( e.getDownTime(), e.getEventTime(), e.getAction(), e.getX(), e.getY() + childConsumeDistance, // 更新Y坐标 e.getMetaState() ); boolean handled = super.onTouchEvent(adjustedEvent); adjustedEvent.recycle(); return handled; } if (e.getAction() == MotionEvent.ACTION_UP || e.getAction() == MotionEvent.ACTION_CANCEL) { mVelocityTracker.clear(); } try { return super.onTouchEvent(e); } catch (Exception ex) { ex.printStackTrace(); return false; } } @Override public boolean fling(int velX, int velY) { boolean fling = super.fling(velX, velY); if (!fling || velY <= 0) { mVelocity = 0; } else { mVelocity = velY; } return fling; } private void dispatchChildFling() { // 父容器滚动到底部后,如果还有剩余加速度,传递给子容器 if (isScrollToBottom() && mVelocity != 0) { // 尽量让速度传递更加平滑 float mVelocity = NestedOverScroller.invokeCurrentVelocity(this); if (Math.abs(mVelocity) <= 2.0E-5F) { mVelocity = (float) this.mVelocity * 0.5F; } else { mVelocity *= 0.46F; } ChildRecyclerView childRecyclerView = findNestedScrollingChildRecyclerView(); if (childRecyclerView != null) { childRecyclerView.fling(0, (int) mVelocity); } } mVelocity = 0; } public ChildRecyclerView findNestedScrollingChildRecyclerView() { if (getAdapter() instanceof INestedParentAdapter) { return ((INestedParentAdapter) getAdapter()).getCurrentChildRecyclerView(); } return null; } public boolean isScrollToBottom() { return !canScrollVertically(1); } public boolean isScrollToTop() { return !canScrollVertically(-1); } @Override public void scrollToPosition(final int position) { if (position == 0) { // 父容器滚动到顶部,从交互上来说子容器也需要滚动到顶部 ChildRecyclerView childRecyclerView = findNestedScrollingChildRecyclerView(); if (childRecyclerView != null) { childRecyclerView.scrollToPosition(0); } } super.scrollToPosition(position); } @Override public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { return target instanceof ChildRecyclerView; } @Override public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) { ChildRecyclerView childRecyclerView = findNestedScrollingChildRecyclerView(); //1、当前ParentRecyclerView没有滑动底,且dy> 0,即下滑 boolean isParentCanScroll = dy > 0 && !isScrollToBottom(); //2、当前ChildRecyclerView滑到顶部了,且dy < 0,即上滑 boolean isChildCanNotScroll = dy < 0 && (childRecyclerView == null || childRecyclerView.isScrollToTop()); if (isParentCanScroll || isChildCanNotScroll) { smoothScrollBy(0, dy); consumed[1] = dy; } } @Override public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) { return true; } @Override public boolean onNestedPreFling(View target, float velocityX, float velocityY) { ChildRecyclerView childRecyclerView = findNestedScrollingChildRecyclerView(); // 1、当前ParentRecyclerView没有滑动底,且向下滑动,即下滑 boolean isParentCanFling = velocityY > 0f && !isScrollToBottom(); // 2、当前ChildRecyclerView滑到顶部了,且向上滑动,即上滑 boolean isChildCanNotFling = velocityY < 0 && (childRecyclerView == null || childRecyclerView.isScrollToTop()); if (!isParentCanFling && !isChildCanNotFling) { return false; } fling(0, (int) velocityY); return true; } }

ChildRecyclerView

子容器主要处理 fling 传递,以及滑动到顶部时,允许父容器继续拦截事件。


  

java

复制代码

public class ChildRecyclerView extends RecyclerView { private ParentRecyclerView mParentRecyclerView = null; /** * fling时的加速度 */ private int mVelocity = 0; private int mLastInterceptX; private int mLastInterceptY; public ChildRecyclerView(@NonNull Context context) { this(context, null); } public ChildRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public ChildRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } private void init() { setOverScrollMode(OVER_SCROLL_NEVER); addOnScrollListener(new OnScrollListener() { @Override public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); if (newState == SCROLL_STATE_IDLE) { dispatchParentFling(); } } }); } private void dispatchParentFling() { ensureParentRecyclerView(); // 子容器滚动到顶部,如果还有剩余加速度,就交给父容器处理 if (mParentRecyclerView != null && isScrollToTop() && mVelocity != 0) { // 尽量让速度传递更加平滑 float velocityY = NestedOverScroller.invokeCurrentVelocity(this); if (Math.abs(velocityY) <= 2.0E-5F) { velocityY = (float) this.mVelocity * 0.5F; } else { velocityY *= 0.65F; } mParentRecyclerView.fling(0, (int) velocityY); mVelocity = 0; } } @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { mVelocity = 0; } int x = (int) ev.getRawX(); int y = (int) ev.getRawY(); if (ev.getAction() != MotionEvent.ACTION_MOVE) { mLastInterceptX = x; mLastInterceptY = y; } int deltaX = x - mLastInterceptX; int deltaY = y - mLastInterceptY; if (isScrollToTop() && Math.abs(deltaX) <= Math.abs(deltaY) && getParent() != null) { // 子容器滚动到顶部,继续向上滑动,此时父容器需要继续拦截事件。与父容器 onInterceptTouchEvent 对应 getParent().requestDisallowInterceptTouchEvent(false); } return super.dispatchTouchEvent(ev); } @Override public boolean fling(int velocityX, int velocityY) { if (!isAttachedToWindow()) return false; boolean fling = super.fling(velocityX, velocityY); if (!fling || velocityY >= 0) { mVelocity = 0; } else { mVelocity = velocityY; } return fling; } public boolean isScrollToTop() { return !canScrollVertically(-1); } public boolean isScrollToBottom() { return !canScrollVertically(1); } private void ensureParentRecyclerView() { if (mParentRecyclerView == null) { ViewParent parentView = getParent(); while (!(parentView instanceof ParentRecyclerView)) { parentView = parentView.getParent(); } mParentRecyclerView = (ParentRecyclerView) parentView; } } }

效果

有 Tab

无 Tab,两个 RecyclerView 嵌套

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android Tab吸顶 嵌套滚动通用实现方案✅ 的相关文章

  • Delphi XE5 REST/Android 客户端“会话已过期”

    我有一个REST Server与Android Client 都在Deplhi Xe5 Android客户端成功连接Rest服务器 在我的服务器中我有一个TDSHttpWebDispatcher with SessionTimeout 12
  • Kotlin 中的单例类

    我想知道如何在 Kotlin 中创建一个单例类 以便我的 Util 类在每次应用程序执行时仅实例化一次 但是 当我将 Java 类转换为 kotlin 时 生成了以下代码 它是否正确 companion object private var
  • Android x86_64 和 x86 模拟器在 AlertDialog.onClick 事件上崩溃

    我正在 x86 64 和 x86 模拟器中测试 Android 应用程序 在从显示列表的 AlertDialog 中选择一个项目后 它崩溃了 Android 模拟器意外关闭 AlertDialog 是从 AppCompatActivity
  • Android 已弃用屏幕尺寸?

    嘿 我需要在我的应用程序中获取屏幕的宽度 该应用程序将在 2 1 及更高版本上运行 我已经将其设置为如下所示 该方法已被弃用 我可能应该使用 getSize 或其他方式 但问题是 这是否适用于 3 0 和 4 0 等 Android 版本
  • InflateException:二进制 XML 文件行 #8:膨胀类 ImageView 时出错

    我知道还有其他问题也问同样的事情 我已经看过其中的大部分了 大多数答案都涉及内存错误 我看不出如何ImageView只加载一个小图标 24x24 像素 可以做出类似这样的东西 但我想在这里发布堆栈跟踪 也许其他人可以识别我无法识别的东西并帮
  • MaterialComponents AlertDialog 文本颜色

    Reading MaterialComponents 主题警报对话框按钮 https stackoverflow com questions 52829954 materialcomponents theme alert dialog bu
  • Orientation改变时如何处理Activity?

    我正在编写一个活动 它从服务器加载数据并使用 ArrayAdapter 将其显示为列表 为此 我显示了一个进度对话框 即加载 同时它从服务器加载所有数据 然后我在处理程序中关闭该对话框 我的问题是 当我更改方向时 会再次显示进度对话框 这是
  • 在 Android 中绘制一条带有弯曲边缘的线

    I am using canvas drawLine to draw some line in android but the lines are too sharp but i need a curved edges 这里的 1 是我所拥
  • 无法在 Android 模拟器上使用 ART

    我只是想在我的模拟器上尝试 android 4 4 的 ART 我所做的是创建一个模拟器 选择设备为 Nexus 7 目标为 Android 4 4 RAM 512 然后我启动模拟器并加载它 然后我进入开发者选项并选择运行时作为 ART 设
  • 我应该将 onClickListener 放在自定义 ListView 的哪里?

    我正在定制ListView包含 a 的行数CheckBox and a TextView 在我使用自定义之前ListViews使用 SimpleCursorAdapter 我的onListItemClick 工作得很好 我读过我必须添加一个
  • Android 6.0 缺乏访问相机服务的权限

    我在 Android 6 0 中使用 Camera2API 我在Android 5 0中没有报错 然而 当我在 Android 6 0 中使用我的代码时 我遇到了问题 问题是有时我可以成功打开相机并拍照 但有时相机打不开 出现错误 java
  • Galaxy Nexus 4.1.1 和 ISO14443 读卡器

    是否可以在 Galaxy Nexus Jelly Bean 4 1 1 移动设备和任何常规桌面非接触式读卡器 ISO 14443 A B 之间建立连接 据我所知 android不支持卡模拟模式 所以只能通过p2p模式来完成 p2p 是否基于
  • 使用 firebase 按最新消息对聊天列表进行排序

    我不知道为什么我陷入了一个问题chatList不按最后一条消息时间或最新消息排序 我尝试过存储timestamp在数据库中和订单子依据时间戳 但它仍然不起作用 不起作用意味着列表不会在每条消息后排序 并继续将列表显示为在第一条消息后排序 看
  • Android React-Native 中 MainActivity 不存在错误

    所以我收到 MainActivity 不存在错误 这可能是因为我将包和应用程序重命名为bomber to Bomber并将 appId 更改为com bomber to cool bomber android 我检查了 AndroidMan
  • Android 等距游戏引擎 [关闭]

    很难说出这里问的是什么 这个问题是含糊的 模糊的 不完整的 过于宽泛的或修辞性的 无法以目前的形式得到合理的回答 如需帮助澄清此问题以便重新打开 访问帮助中心 help reopen questions 是否有任何现有的开源或商业解决方案
  • Android 键盘清单未显示在设置中

    我正在制作我的第一个 Android 应用程序 我需要它作为键盘服务 据我所知 清单看起来不错 并且我有一个文件 WifiJoy java 在 com zwad3 wifijoy 包中 以及所有其他文件
  • Android:毫米或英寸尺寸的视图尺寸不正确

    我有一个布局高度为 10mm 的按钮 在大多数设备上看起来都是正确的 但在 Lg Optimus 2X 和 Motorola Defy 上 按钮的尺寸大约只有一半 我检索了这些设备上的 DisplayMetrics 信息 这是一个简短的概述
  • Android 方向传感器的替代品是什么?

    大家好 我正在为 Android 构建 3D 游戏 我目前正在尝试在我的游戏中添加一个传感器 允许玩家倾斜机器人作为其控制 理想情况下 我想使用方向传感器 但我注意到它已被弃用 有谁知道如何检测 Android 中的倾斜并且不使用这个传感器
  • 如何为 Android Q 打开具有特定专辑或文件夹的默认图库应用程序?

    我尝试打开图库中的特定文件夹 如下代码所示 但它对我不起作用 并且出现错误无法找到物品 fun openDirectoryInGallery context Context directory String val intent Inten
  • RecyclerView 不调用 onCreateViewHolder 或 onBindView

    没有收到任何错误 所有数据似乎都有效 由于某种原因 没有调用与视图相关的方法 我已确定以下事项 getItemCount 是唯一被调用的适配器方法 并且返回一个正整数值 我知道这将是你们将要查看的区域 构造函数正在被调用 成员变量有效 Pa

随机推荐

  • 一呼百应!腾讯、阿里等全都支持鸿蒙了,安卓该担心了

    前言 众所周知 目前华为鸿蒙系统 已经是全球第三大智能手机系统 仅次于安卓 iOS 不过大家也都清楚 这个第三 实际上还是有水份的 因为鸿蒙其实并没有自己的生态 靠的是兼容安卓生态 真正的纯血鸿蒙APP 仅几十个 如果靠着这几十个APP 完
  • 短视频制作:从构思到发布的全方位指南

    在当今数字化时代 短视频已成为备受欢迎的媒体形式 凭借其简洁有趣的内容 短视频成功吸引了大量观众的关注 然而 制作一部引人入胜的短视频并非易事 本文将为你提供从目标设定到平台发布的全面指导 帮助你制作出令人难以忘怀的短视频 第一步 明确目标
  • 有哪些PDF转图片工具好用?PDF转图片免费软件推荐

    在一个阳光明媚的下午 你正在翻阅着一份重要的PDF文件 想要快速将其中的内容以图片形式分享给朋友 然而 复制粘贴不仅繁琐 还会失去原本的排版和格式 那么 如何将PDF文件转换成图片呢 今天就来介绍两款可以实现这一功能的免费软件 如果你也想知
  • 你知道ai写作工具哪个好吗?教你用AI写年终总结

    又是一年的十二月到了 每年到这个时候 朋友圈都总会出现一首常驻歌曲 十二月的奇迹 身为打工人的大家应该都希望 在忙碌了一年的最后一个月被奇迹眷顾吧 不过俗话说得好 靠人不如靠己 与其把自己交给命运的奇迹 那不如自己也努力争取一下 在老板面前
  • 鸿蒙开发入门:快速修复命令行调试开发指导

    快速修复命令行调试开发指导 当前阶段 HarmonyOS为开发者提供了命令行的调试开发工具可供使用 比如 包名为com ohos quickfix的示例应用 版本号为1000000 该应用的当前版本运行中有某问题需要修复 此时 开发者可参考
  • 主动学习与弱监督学习

    人工智能数据的获取没有想象中的那么简单 虽然我们早已身处大数据的浪潮下 很多公司在获取数据的大浪中翻滚却始终没有找到一个合适的获取数据的渠道 很多情况下 获取高质量的人工智能数据需要消耗大量的人力 时间 金钱 但是对于未来世界 以 人机协同
  • Java处理SSH-免密登录

    前提 需要测试主机之间能够免密 配置ssh请自行百度 jar包 旧版 com jcraft jsch 仅支持老版的密钥格式 旧版本 RSA
  • go-zero开发入门-API网关开发示例

    开发一个 API 网关 代理 https blog csdn net Aquester article details 134856271 中的 RPC 服务 网关完整源代码 file main go package main import
  • 设计之妙,理解Android动画流程

    本文基于Android 12进行学习研究 参考 深入理解Android内核源码 思路学习总结 如有理解不对 望各位朋友指正 另外可能留存一些疑问 留后续再探索 输出只是为了分享和提升自己 动画初始化 按照窗口管理策略类中的定义 动画应该被分
  • 创建个人网站(一)从零开始配置环境,搭建项目

    目录 前言 配置环境 前端 后端 遇到的问题 1 安装了nvm和node vscode没反应 2 安装完脚手架之后vue指令不存在
  • docker配置连接harbor私有仓库

    一 前言 以下分为两种情况说明docker对harbor私有仓库的访问配置 一种是harbor使用自建证书配置https 一种是使用公有证书配置https 二 docker配置 harbor使用自建证书的情况 使用自建证书对harbor进行
  • 不看后悔系列!Android面试经验分享,附经典题库+答案解析

    前言 近期 许多同学向我咨询关于Android技术岗位的招聘事宜 希望能够在求职过程中更好地准备 以冲击大厂 拿到高薪 作为首批Android开发者 我十余年来一直深耕Android及移动互联网开发领域 拥有丰富的面试和实战经验 在此 我想
  • 活动报名|马普脑研究所主任Moritz Helmstaedter:Connectomics连接组学

    报告主题 Connectomics连接组学 报告日期 12月08日 周五 15 30 16 30 主题简介 大脑是由数百万至数十亿神经元组成的高度互联的网络 一个世纪以来 我们一直无法在突触分辨率上绘制这些连通性网络的图谱 只是最近 利用新
  • 协程与互斥锁: Kotlin Mutex的终极指南

    引言 今天我们将深入研究Kotlin中的Mutex 互斥锁 原理以及在实际开发中的使用技巧 Mutex是多线程编程中的关键工具 它可以有效地解决多线程访问共享资源时可能发生的竞态条件问题 Mutex的基本原理 Mutex是互斥锁的缩写 它是
  • USB-C口快充数据线背后的技术奥秘

    自从苹果iPhone 15也用上了USB C口后 市场上销售的快充数据线也日益增多 近日 有小伙伴向我反馈 使用苹果iPhone 15的USB C口原装数据线 无法给其他手机提供PD 120W快充 他们尝试更换其他数据线 有些可以激发120
  • mybatis plus 常见问题Invalid bound statement (not found)解决方法汇总

    我用的若依框架 将mybatis改为mybatis plus 在重启项目时报错Invalid bound statement not found 百思不得其解 百度回答各种mapper xml配置路径啥的 但是springboot项目需要配
  • 30天精通Nodejs--第十四天:MongoDB

    这里写目录标题 前言 什么是 MongoDB 安装 MongoDB 驱动 连接到 MongoDB 数据库 执行基本操作 插入文档 查询文档 更新文档 删除文档
  • Java的ThreadLocal

    ThreadLocal ThreadLocal 是 Java 中一个非常有用的类 它允许你创建线程局部变量 线程局部变量是指每个线程都有自己独立的变量副本 互不干扰 ThreadLocal 主要用于解决多线程环境下共享数据的线程安全性问题
  • 天猫交易rpa机器人轻松上架商品,助力提高电商效率!

    作为网店店主 你可能被困在电商运营各种繁琐的环节中已多时 当你每次上新都手忙脚乱 错漏百出 想节约人力成本而不知如何下手的时候 你可能需要了解一下RPA电商自动化解决方案中的商品自动上架机器人 只需一键点击 RPA机器人就能节约你80 的时
  • Android Tab吸顶 嵌套滚动通用实现方案✅

    很多应用的首页都会有一些嵌套滚动 Tab吸顶的布局 尤其是一些生鲜类应用 例如 朴朴超市 大润发优鲜 盒马等等 在 Android 里面 滚动吸顶方式通常可以通过 CoordinatorLayout AppBarLayout Collaps