Jetpack Compose(8)——嵌套滚动

news/2024/7/4 16:53:44/文章来源:https://www.cnblogs.com/joy99/p/18272173

目录
  • 前言
  • 一、Jetpack Compose 中处理嵌套滚动的思想
  • 二、Modifier.nestedScroll
    • 2.1 NestedScrollConnection
    • 2.2 NestedScrollDispatcher
  • 三、实操讲解
    • 3.1 父组件消费子组件给过来的事件——NestedScrollConnection
    • 3.2 子组件对事件进行分发——NestedScrollDispatcher
    • 3.2 按照分发顺序依次消费
  • 小结

前言

所谓嵌套滚动,就是两个组件之间出现滚动事件冲突了,要给与特定的处理逻辑。在传统 View 系统中称之为滑动冲突,一般有两种解决方案,外部拦截法和内部拦截法。在 Jetpack Compose 中,提供了 Modifier.nestedScroll 修饰符用来处理嵌套滚动的场景。

一、Jetpack Compose 中处理嵌套滚动的思想

在介绍 Modifier.nestedScroll 之前,需要先了解 Compose 中嵌套滚动的处理思想。当组件获得滚动事件后,先交给它的父组件消费,父组件消费之后,将剩余可用的滚动事件在给到子组件,子组件再消费,子组件消费之后,再将剩余的滚动事件再给到父组件。

第一趟 ... -> 孙 ——> 子 ——> 父 -> ...
第二趟 ... <- 孙 <—— 子 <—— 父 <- ...
第三趟 ... -> 孙 ——> 子 ——> 父 -> ...

二、Modifier.nestedScroll

有了整体思路之后,再来看 Modifier.nestedScroll 这个修饰符。

fun Modifier.nestedScroll(connection: NestedScrollConnection,dispatcher: NestedScrollDispatcher? = null
): Modifier

使用 nestedScroll 参数列表中有一个必选参数 connection 和一个可选参数 dispatcher

  • connection: 嵌套滑动手势处理的核心逻辑,内部回调可以在子布局获得滑动事件前预先消费掉部分或全部手势偏移量,也可以获取子布局消费后剩下的手势偏移量。

  • dispatcher:调度器,内部包含用于父布局的 NestedScrollConnection , 可以调用 dispatch* 方法来通知父布局发生滑动

2.1 NestedScrollConnection

NestedScrollConnection 提供了四个回调方法。

interface NestedScrollConnection {/*** 预先劫持滑动事件,消费后再交由子布局。* available:当前可用的滑动事件偏移量* source:滑动事件的类型* 返回值:当前组件消费的滑动事件偏移量,如果不想消费可返回Offset.Zero*/fun onPreScroll(available: Offset, source: NestedScrollSource): Offset = Offset.Zero/*** 获取子布局处理后的滑动事件* consumed:之前消费的所有滑动事件偏移量* available:当前剩下还可用的滑动事件偏移量* source:滑动事件的类型* 返回值:当前组件消费的滑动事件偏移量,如果不想消费可返回 Offset.Zero ,则剩下偏移量会继续交由当前布局的父布局进行处理*/fun onPostScroll(consumed: Offset,available: Offset,source: NestedScrollSource): Offset = Offset.Zero/*** 获取 Fling 开始时的速度* available:Fling 开始时的速度* 返回值:当前组件消费的速度,如果不想消费可返回 Velocity.Zero*/suspend fun onPreFling(available: Velocity): Velocity = Velocity.Zero/*** 获取 Fling 结束时的速度信息* consumed:之前消费的所有速度* available:当前剩下还可用的速度* 返回值:当前组件消费的速度,如果不想消费可返回Velocity.Zero,剩下速度会继续交由当前布局的父布局进行处理*/suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {return Velocity.Zero}
}

关于各个方法的含义已经在方法注释中标注了。

注意 Fling 的含义: 当我们手指在滑动列表时,如果是快速滑动并抬起,则列表会根据惯性继续飘一段距离后停下,这个行为就是 Fling ,onPreFling 在你手指刚抬起时便会回调,而 onPostFling 会在飘一段距离停下后回调。

2.2 NestedScrollDispatcher

NestedScrollDispatcher 的主要方法:

fun dispatchPreScroll(available: Offset, source: NestedScrollSource): Offset {return parent?.onPreScroll(available, source) ?: Offset.Zero
}fun dispatchPostScroll(consumed: Offset,available: Offset,source: NestedScrollSource): Offset {return parent?.onPostScroll(consumed, available, source) ?: Offset.Zero}suspend fun dispatchPreFling(available: Velocity): Velocity {return parent?.onPreFling(available) ?: Velocity.Zero
}suspend fun dispatchPostFling(consumed: Velocity, available: Velocity): Velocity {return parent?.onPostFling(consumed, available) ?: Velocity.Zero
}

其实方法实现就能清楚,实际上让其父组件调用 NestedScrollConnection 中的预消费与后消费方法。

三、实操讲解

3.1 父组件消费子组件给过来的事件——NestedScrollConnection

先上效果图:

简单分析一下效果:

  1. 布局分为两部分,上面是一张图片,下面是一个滑动列表
  2. 滑动过程中,上滑时,首先头部响应滑动,收缩到最小高度之后,列表再开始向上滑动。下滑时,也是头部先影响滑动,头部图片展开到最大高度之后,列表再开始向下滑动。即:不论上上滑还是下滑,都是头部图片先响应。
  3. 我们希望是按住列表能滑动,按住头部图片是不能滑动的,也就是说头部图片不会检测滑动事件,只有下面列表会检测滑动事件。

下面开始编码:

  1. 手写整体布局应该是 Column 实现,头部使用一个 Image ,下面使用 LazyColumn
@Composable
fun NestedScrollDemo() {Column(modifier = Modifier.fillMaxSize()) {Image(painter = painterResource(id = R.mipmap.rc_1),contentDescription = null,contentScale = ContentScale.FillBounds,modifier = Modifier.fillMaxWidth().height(200.dp))LazyColumn {repeat(50) {item {Text(text = "item --> $it", modifier = Modifier.fillMaxWidth())}}}}
}
  1. 给 Column 组件使用 Modifier.nestedScroll。
    这里简单做一些定义:头部图片最小高度为 80.dp, 最大高度为 200.dp。注意 dp 和 px 之间的转换。
@Composable
fun NestedScrollDemo() {val minHeight = 80.dpval maxHeight = 200.dpval density = LocalDensity.currentval minHeightPx = with(density) {minHeight.toPx()}val maxHeightPx = with(density) {maxHeight.toPx()}var topHeightPx by remember {mutableStateOf(maxHeightPx)}val connection = remember {object : NestedScrollConnection {override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {return super.onPreScroll(available, source)}override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset {return super.onPostScroll(consumed, available, source)}override suspend fun onPreFling(available: Velocity): Velocity {return super.onPreFling(available)}override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {return super.onPostFling(consumed, available)}}}Column(modifier = Modifier.fillMaxSize().nestedScroll(connection = connection)) {Image(painter = painterResource(id = R.mipmap.rc_1),contentDescription = null,contentScale = ContentScale.FillBounds,modifier = Modifier.fillMaxWidth().height(with(density) {topHeightPx.toDp()}))LazyColumn {repeat(50) {item {Text(text = "item --> $it", modifier = Modifier.fillMaxWidth())}}}}
}
  1. 最后就是编写滑动处理逻辑了。LazyColumn 列表检测到滑动事件,把这个滑动距离先给到父组件Column 消费,Column 消费之后,把剩余的再给到 LazyColumn 消费,LazyColumn 消费之后,还有剩余,再给回 Column 消费。其中 LazyColumn 消费事件,不用我们处理,我们的 Modifier.nestedScroll 作用在 Column 上,我们需要预先消费 LazyColumn 给过来的滑动距离——在 onPreScroll 中实现,然后把剩余的给到 LazyColumn,最后 LazyColumn 消费后还有剩余的滑动距离,Column 处理 —— 在 onPostScroll 中处理。
val connection = remember {object : NestedScrollConnection {/*** 预先劫持滑动事件,消费后再交由子布局。* available:当前可用的滑动事件偏移量* source:滑动事件的类型* 返回值:当前组件消费的滑动事件偏移量,如果不想消费可返回Offset.Zero*/override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {if (source == NestedScrollSource.Drag) {  // 判断是滑动事件if (available.y < 0) { // 向上滑动val dH = minHeightPx - topHeightPx  // 向上滑动过程中,还差多少达到最小高度if (available.y > dH) {  // 如果当前可用的滑动距离全部消费都不足以达到最小高度,就将当前可用距离全部消费掉topHeightPx += available.yreturn Offset(x = 0f, y = available.y)} else {  // 如果当前可用的滑动距离足够达到最小高度,就只消费掉需要的距离。剩余的给到子组件。topHeightPx += dHreturn Offset(x = 0f, y = dH)}} else { // 下滑val dH = maxHeightPx - topHeightPx  // 向下滑动过程中,还差多少达到最大高度if (available.y < dH) {  // 如果当前可用的滑动距离全部消费都不足以达到最大高度,就将当前可用距离全部消费掉topHeightPx += available.yreturn Offset(x = 0f, y = available.y)} else {  // 如果当前可用的滑动距离足够达到最大高度,就只消费掉需要的距离。剩余的给到子组件。topHeightPx += dHreturn Offset(x = 0f, y = dH)}}} else {  // 如果不是滑动事件,就不消费。return Offset.Zero}}/*** 获取子布局处理后的滑动事件* consumed:之前消费的所有滑动事件偏移量* available:当前剩下还可用的滑动事件偏移量* source:滑动事件的类型* 返回值:当前组件消费的滑动事件偏移量,如果不想消费可返回 Offset.Zero ,则剩下偏移量会继续交由当前布局的父布局进行处理*/override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset {// 子组件处理后的剩余的滑动距离,此处不需要消费了,直接不消费。return Offset.Zero}override suspend fun onPreFling(available: Velocity): Velocity {return super.onPreFling(available)}override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {return super.onPostFling(consumed, available)}}
}

所有的代码通过注释已经写的很详细了。这样就实现了上图的效果。

3.2 子组件对事件进行分发——NestedScrollDispatcher

滑动距离消费不一定要体现在位置、大小之类的变化上。当使用 Modifier.nestedScroll 修饰符处理嵌套滚动时,绝大多数场景使用外部拦截法就能轻松实现,给父容器修饰,实现 NestedScollConnection 方法。

使用内部拦截法,一般用于父组件也可以消费事件,需要子容器使用 Modifier.nestedScroll ,并合理使用 NestedScrollDispatcher 的方法。

看下面这个示例

同样简单分析一下效果:

  1. 整个父组件是一个 LazyColumn, 自身可以滚动
  2. LazyColumn 中的一个元素是一张图片,使用 Image 组件,当按住图片滚动时,优先处理图片的收缩与展开。

实现如下:

@Composable
fun NestedScrollDemo4() {val minHeight = 80.dpval maxHeight = 200.dpval density = LocalDensity.currentval minHeightPx = with(density) {minHeight.toPx()}val maxHeightPx = with(density) {maxHeight.toPx()}var topHeightPx by remember {mutableStateOf(maxHeightPx)}val connection = remember {object : NestedScrollConnection {}}val dispatcher = NestedScrollDispatcher()LazyColumn(modifier = Modifier.background(Color.LightGray).fillMaxSize()) {for (i in 0..10) {item {Text(text = "item --> $i", modifier = Modifier.fillMaxWidth())}}item {Image(painter = painterResource(id = R.mipmap.rc_1),contentDescription = null,contentScale = ContentScale.FillBounds,modifier = Modifier.fillMaxWidth().height(with(density) {topHeightPx.toDp()}).draggable(state = rememberDraggableState { onDelta ->// 1. 滑动距离,给到父组件先消费// 调用父组件劫持滑动事件,让父组件先消费,返回值是父组件消费掉的滑动距离// 这里并不想让父组件先消费,就给父组件传了 Offset.Zero。 返回值也就是 Offset.Zero。val consumed = dispatcher.dispatchPreScroll(available = Offset(x = 0f, y = 0f), source = NestedScrollSource.Drag)// 2. 父组件消费完之后,剩余的滑动距离,自己按需消费// 计算父组件消费后剩余的可使用的滑动距离val availableY = onDelta - consumed.y// canConsumeY 是当前需要消费掉的距离val canConsumeY = if (availableY < 0) { // 向上滑动val dH = minHeightPx - topHeightPx  // 向上滑动过程中,还差多少达到最小高度if (availableY > dH) {  // 如果当前可用的滑动距离全部消费都不足以达到最小高度,就将当前可用距离全部消费掉availableY} else {  // 如果当前可用的滑动距离足够达到最小高度,就只消费掉需要的距离dH}} else { // 下滑val dH = maxHeightPx - topHeightPx  // 向下滑动过程中,还差多少达到最大高度if (availableY < dH) {  // 如果当前可用的滑动距离全部消费都不足以达到最大高度,就将当前可用距离全部消费掉availableY} else {  // 如果当前可用的滑动距离足够达到最大高度,就只消费掉需要的距离dH}}// 把当前消费掉的距离给到图片高度topHeightPx += canConsumeY// 父组件消费后,以及本次消费后,最后剩余的滑动距离val remain = onDelta - consumed.y - canConsumeY// 3. 自己消费完之后,还有剩余的滑动距离,再给到父组件dispatcher.dispatchPostScroll(consumed = Offset(x = 0f, y = consumed.y + canConsumeY), // 这里是总共消费的滑动距离,包括父组件消费的和本次自己消费的available = Offset(0f, remain),  // 剩余可用的滑动距离source = NestedScrollSource.Drag)}, orientation = Orientation.Vertical).nestedScroll(connection, dispatcher))}for (j in 11..40) {item {Text(text = "item --> $j", modifier = Modifier.fillMaxWidth())}}}
}

关键代码都已经加上了注释。看起来应该是非常清晰的。

这里,主要是内部使用 dispatcher 进行事件拦截。

3.2 按照分发顺序依次消费

在 3.1 的例子中,头部图片是不检测滑动事件的,手指按住图片滑动是不会响应的,现在需要修改为按住上面图片也是可以滑动,将头部收缩和展开。

下面开始改造:

  1. 给 Column 加上 Modifier.draggable 修饰
Column(modifier = Modifier.fillMaxSize().draggable(state = rememberDraggableState { onDelta ->},orientation = Orientation.Vertical).nestedScroll(connection = connection)
) {...
}
  1. 声明 dispatcher,使用 dispatcher 处理嵌套滑动事件
val dispatcher = remember { NestedScrollDispatcher() }Column(modifier = Modifier.fillMaxSize().draggable(state = rememberDraggableState { onDelta ->// 1. 滑动距离,给到父组件先消费// 调用父组件劫持滑动事件,让父组件先消费,返回值是父组件消费掉的滑动距离val consumed = dispatcher.dispatchPreScroll(available = Offset(x = 0f, y = onDelta), source = NestedScrollSource.Drag)// 2. 父组件消费完之后,剩余的滑动距离,自己按需消费// 计算父组件消费后剩余的可使用的滑动距离val availableY = (onDelta - consumed.y)// consume 是当前需要消费掉的距离val consumeY = if (availableY < 0) { // 向上滑动val dH = minHeightPx - topHeightPx  // 向上滑动过程中,还差多少达到最小高度if (availableY > dH) {  // 如果当前可用的滑动距离全部消费都不足以达到最小高度,就将当前可用距离全部消费掉availableY} else {  // 如果当前可用的滑动距离足够达到最小高度,就只消费掉需要的距离dH}} else { // 下滑val dH = maxHeightPx - topHeightPx  // 向下滑动过程中,还差多少达到最大高度if (availableY < dH) {  // 如果当前可用的滑动距离全部消费都不足以达到最大高度,就将当前可用距离全部消费掉availableY} else {  // 如果当前可用的滑动距离足够达到最大高度,就只消费掉需要的距离dH}}// 把当前消费掉的距离给到图片高度topHeightPx += consumeY// 父组件消费后,以及本次消费后,最后剩余的滑动距离val remain = onDelta - consumed.y - consumeY// 3. 自己消费完之后,还有剩余的滑动距离,再给到父组件dispatcher.dispatchPostScroll(consumed = Offset(x = 0f, y = consumed.y + consumeY), // 这里是总共消费的滑动距离,包括父组件消费的和本次自己消费的available = Offset(0f, remain),  // 剩余可用的滑动距离source = NestedScrollSource.Drag)},orientation = Orientation.Vertical).nestedScroll(connection = connection, dispatcher = dispatcher)
) {...
}

同样代码注释已经写得非常清晰了。

完整代码如下:

@Composable
fun NestedScrollDemo2() {val minHeight = 80.dpval maxHeight = 200.dpval density = LocalDensity.currentval minHeightPx = with(density) {minHeight.toPx()}val maxHeightPx = with(density) {maxHeight.toPx()}var topHeightPx by remember {mutableStateOf(maxHeightPx)}val dispatcher = remember { NestedScrollDispatcher() }val connection = remember {object : NestedScrollConnection {/*** 预先劫持滑动事件,消费后再交由子布局。* available:当前可用的滑动事件偏移量* source:滑动事件的类型* 返回值:当前组件消费的滑动事件偏移量,如果不想消费可返回Offset.Zero*/override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {if (source == NestedScrollSource.Drag) {  // 判断是滑动事件if (available.y < 0) { // 向上滑动val dH = minHeightPx - topHeightPx  // 向上滑动过程中,还差多少达到最小高度if (available.y > dH) {  // 如果当前可用的滑动距离全部消费都不足以达到最小高度,就将当前可用距离全部消费掉topHeightPx += available.yreturn Offset(x = 0f, y = available.y)} else {  // 如果当前可用的滑动距离足够达到最小高度,就只消费掉需要的距离。剩余的给到子组件。topHeightPx += dHreturn Offset(x = 0f, y = dH)}} else { // 下滑val dH = maxHeightPx - topHeightPx  // 向下滑动过程中,还差多少达到最大高度if (available.y < dH) {  // 如果当前可用的滑动距离全部消费都不足以达到最大高度,就将当前可用距离全部消费掉topHeightPx += available.yreturn Offset(x = 0f, y = available.y)} else {  // 如果当前可用的滑动距离足够达到最大高度,就只消费掉需要的距离。剩余的给到子组件。topHeightPx += dHreturn Offset(x = 0f, y = dH)}}} else {  // 如果不是滑动事件,就不消费。return Offset.Zero}}/*** 获取子布局处理后的滑动事件* consumed:之前消费的所有滑动事件偏移量* available:当前剩下还可用的滑动事件偏移量* source:滑动事件的类型* 返回值:当前组件消费的滑动事件偏移量,如果不想消费可返回 Offset.Zero ,则剩下偏移量会继续交由当前布局的父布局进行处理*/override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset {// 子组件处理后的剩余的滑动距离,此处不需要消费了,直接不消费。return Offset.Zero}override suspend fun onPreFling(available: Velocity): Velocity {return super.onPreFling(available)}override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {return super.onPostFling(consumed, available)}}}Column(modifier = Modifier.fillMaxSize().draggable(state = rememberDraggableState { onDelta ->// 1. 滑动距离,给到父组件先消费// 调用父组件劫持滑动事件,让父组件先消费,返回值是父组件消费掉的滑动距离val consumed = dispatcher.dispatchPreScroll(available = Offset(x = 0f, y = onDelta), source = NestedScrollSource.Drag)// 2. 父组件消费完之后,剩余的滑动距离,自己按需消费// 计算父组件消费后剩余的可使用的滑动距离val availableY = (onDelta - consumed.y)// consume 是当前需要消费掉的距离val consumeY = if (availableY < 0) { // 向上滑动val dH = minHeightPx - topHeightPx  // 向上滑动过程中,还差多少达到最小高度if (availableY > dH) {  // 如果当前可用的滑动距离全部消费都不足以达到最小高度,就将当前可用距离全部消费掉availableY} else {  // 如果当前可用的滑动距离足够达到最小高度,就只消费掉需要的距离dH}} else { // 下滑val dH = maxHeightPx - topHeightPx  // 向下滑动过程中,还差多少达到最大高度if (availableY < dH) {  // 如果当前可用的滑动距离全部消费都不足以达到最大高度,就将当前可用距离全部消费掉availableY} else {  // 如果当前可用的滑动距离足够达到最大高度,就只消费掉需要的距离dH}}// 把当前消费掉的距离给到图片高度topHeightPx += consumeY// 父组件消费后,以及本次消费后,最后剩余的滑动距离val remain = onDelta - consumed.y - consumeY// 3. 自己消费完之后,还有剩余的滑动距离,再给到父组件dispatcher.dispatchPostScroll(consumed = Offset(x = 0f, y = consumed.y + consumeY), // 这里是总共消费的滑动距离,包括父组件消费的和本次自己消费的available = Offset(0f, remain),  // 剩余可用的滑动距离source = NestedScrollSource.Drag)}, orientation = Orientation.Vertical).nestedScroll(connection = connection, dispatcher = dispatcher)) {Image(painter = painterResource(id = R.mipmap.rc_1),contentDescription = null,contentScale = ContentScale.FillBounds,modifier = Modifier.fillMaxWidth().height(with(density) {topHeightPx.toDp()}))LazyColumn {repeat(50) {item {Text(text = "item --> $it", modifier = Modifier.fillMaxWidth())}}}}
}

运行效果:

看效果图不明显,实际上就是按住图片位置拖动是可以收缩和展开顶部图片的。
当然,其实要实现这个效果,也不用整这么复杂,完全可以给 Image 设置 draggable 修饰符来实现:

Image(painter = painterResource(id = R.mipmap.rc_1),contentDescription = null,contentScale = ContentScale.FillBounds,modifier = Modifier.fillMaxWidth().height(with(density) {topHeightPx.toDp()}).draggable(state = rememberDraggableState { onDelta ->val consumeY = if (onDelta < 0) { // 向上滑动val dH = minHeightPx - topHeightPx  // 向上滑动过程中,还差多少达到最小高度if (onDelta > dH) {  // 如果当前可用的滑动距离全部消费都不足以达到最小高度,就将当前可用距离全部消费掉onDelta} else {  // 如果当前可用的滑动距离足够达到最小高度,就只消费掉需要的距离dH}} else { // 下滑val dH = maxHeightPx - topHeightPx  // 向下滑动过程中,还差多少达到最大高度if (onDelta < dH) {  // 如果当前可用的滑动距离全部消费都不足以达到最大高度,就将当前可用距离全部消费掉onDelta} else {  // 如果当前可用的滑动距离足够达到最大高度,就只消费掉需要的距离dH}}topHeightPx += consumeY},orientation = Orientation.Vertical)
)

这样就可以了。

小结

  1. 本文介绍了 Jetpack Compose 中嵌套滚动的相关知识。
    Compose 中嵌套滚动事件的分发思想是,滚动事件会预先交给父组件预先处理,父组件处理消费之后,自己处理剩余滚动距离,自己处理消费完之后,还有剩余,会再交给父组件处理。
  2. 一般来说,当子组件检测滚动事件,则需要实现 NestedScrollConnection 中的 onPreScrollonPostScroll 方法。当自己检测滚动事件,则需要使用 NestedScrollDispatcher 的相关方法对滚动事件进行分发。
  3. 另外还有 Fling 事件,惯性滚动,其分发思想与滚动一致,不同的它的值表示速度。另外惯性滚动过程实现比较复杂,Compose 提供了默认实现,ScrollableDefaults.flingBehavior(),感兴趣的朋友可以继续研究。

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

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

相关文章

Unity Address Asset System:Assembly-CSharp - 可用Assembly-CSharp.Player - 不可用

在使用Unity的Addressables插件进行游戏资源分包管理的时候,报了这个错误: 反编译查看发现是unity与.net版本不匹配导致的问题 解决方案: 在Unity中打开Edit->Project Settings->Player,更改.Net版本 微软官方文档: 在 Unity 中使用 .NET 4 和更高版本 | Microsoft …

CC2分析与利用

CC2分析与利用 环境配置 一、 CC2 需要使用commons-collections-4.0版本,因为3.1-3.2.1版本中TransformingComparator没有实现Serializable接口,不能被序列化,所以CC2不用3.x版本。还需要 javassist 依赖,利用链2需要。 pom.xml 添加:<dependency><groupId>or…

【计算机网络】TCP连接三次握手和四次挥手

三次握手建立连接 TCP(传输控制协议)的三次握手机制是一种用于在两个 TCP 主机之间建立一个可靠的连接的过程。这个机制确保了两端的通信是同步的,并且在数据传输开始前,双方都准备好了进行通信。①、第一次握手:SYN(最开始都是 CLOSE,之后服务器进入 LISTEN)发起连接:…

原型设计

原型设计的重要性 网页原型显示了网页的骨架结构,因此可以更好地了解用户将去哪里以及如何导航,通过视觉方式表达产品的要求。 网页原型还有助于交流想法和规划网页,提高团队沟通的效率和质量,进行高效协作。 设计团队与客户沟通变得容易,能够有效地减少了返工和误解,降低…

weblogic 漏洞复现

1.环境地址信息http://192.168.116.112:7001/console/ 2.使用漏洞检测工具,检测对应漏洞 选中对应漏洞检查,发现存在对应漏洞 3.漏洞利用 命令执行 内存马上传使用冰蝎连接 连接成功

详细解析ORB-SLAM3的源码

随着计算机视觉和机器人技术的发展,SLAM(同步定位与地图构建)技术在自动导航、机器人和无人机等领域中起着至关重要的作用。作为当前最先进的SLAM系统之一,ORB-SLAM3因其卓越的性能和开源特性,备受关注。本文将详细解析ORB-SLAM3的源码 ,帮助读者更好地理解其内部机制。 …

H3C之IRF典型配置举例(BFD MAD检测方式)

IRF典型配置举例(BFD MAD检测方式) 1、组网需求由于网络规模迅速扩大,当前中心设备(Device A)安全业务处理能力已经不能满足需求,现在需要另增一台设备Device B,将这两台设备组成一个IRF(如图所示),并配置BFD MAD进行分裂检测。2、组网图 IRF典型配置组网图(BFD MAD…

【攻防技术系列+反溯源】入侵痕迹清理

#溯源 #入侵痕迹清理 #攻防演练 在授权攻防演练中,攻击结束后,如何不留痕迹的清除日志和操作记录,以掩盖入侵踪迹,这其实是一个细致的技术活。 在蓝队的溯源中,攻击者的攻击路径都将记录在日志中,所遗留的工具也会被蓝队进行分析,在工具中可以查找特征,红队自研工具更容…

ProfibusDP主站转Modbus模块连接综合保护装置配置案例

常见的协议有Modbus协议,ModbusTCP协议,Profinet协议,Profibus协议,Profibus DP协议,EtherCAT协议,EtherNET协议等。本案例描述了如何使用ProfibusDP主站转Modbus模块(XD-MDPBM20)来连接综合保护装置(综保),实现数据交换和远程控制。通过配置ProfibusDP主站和Modbus…

HL集训日记(更新ing)

Day -inf 听说又要去海亮,感到恐慌,想起了被xxs碾压的日子,遂卷; Day -1 与学校说再见 Day 0 去机场,这次倒是没有人迟到力; 下大雨,冷,明明天气预报上气温是比DL热的,却这么冷!!! 到了HL,这回住进了24小时摆烂中心(确信,空调吹得好难受,,, gg并没有收手机,…

【计算机网络】TCP如何保证稳定性

连接管理 校验和 序列号/确认应答 流量控制 最大消息长度 超时重传 拥塞控制资料来源连接管理 TCP 使用三次握手和四次挥手保证可靠地建立连接和释放连接。 校验和 TCP 将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果接收端…

数据分析 | 数据清理的方法

数据清理的步骤# 一、读取数据 导入NumPy和Pandas数据库,用Pandas的read_csv函数读取原始数据集’e_commerce.csv’,使其转换成DataFrame格式,并赋值给变量df。 展示数据集的前5行和后5行。# 二、评估数据(整洁度、干净度) 创建一个新的变量cleaned_data = df(相当于复制…

iMovie视频剪辑入门

iMovie学习笔记自己不是摄影爱好者📹(也许以后是,说不准),想学视频剪辑的原因如下:大一的一些小组作业有拍视频的任务,有时需要我承担剪辑的工作。因为不熟练,只能用剪映瞎折腾,浪费不少时间。系统地学习可以让我更好地完成剪辑工作。 想了解iMovie本身。本文是我的i…

域渗透之CSMSF联动权限传递

cs与msf会话的相互派生,记个笔记以免忘记具体的操作。cs派生给msf环境:msf6 + cs4.8条件:目标已上线cs,获得控制权1)在msf上启用reverse_http模块监听本地端口: use exploit/multi/handler set payload windows/meterpreter/reverse_http set lhost 192.168.88.128 set l…

免费可视化工具能为我们的工作带来什么好处?

免费可视化工具能为我们的工作带来什么好处?在如今数据密集的工作环境中,如何高效地处理和展示数据成为了每个行业的重要任务。传统的工具如Excel虽然强大,但在处理复杂数据和创建高级图表时往往显得力不从心。而免费可视化工具的出现,彻底改变了这一局面,不仅大大提升了工…

MCU点灯实验小结

设备采用芯片:STM32F407ZET6 4个LED灯,网络标号分别为LED0 ,LED1,FSMC D10,FSMC D11。对应的引脚号分别为PF9,PF10,PE12,PE13。 GPIO外设基本概念 General-Purpose Input Output,通用型输入输出的,也简称I/O口,有时也简写为IO口。用于电信号的传递,以实现与外部器件的通信…

南昌航空大学-23201406-耿乐-第三次OOP博客作业

对于最后两次题目集中的题目和做题情况的总结,包括电路模拟程序3和4。 一、前言总结两次题目集的题目情况关于题目集 最后两次题目集中都只有一道题目,是电路模拟程序的系列迭代,没有额外的题目。 题目本身 第七次题目集中的电路模拟3并不是太难,只要理清题目中的要求和电路…

记录c#开发Windows服务的过程

1.新建Windows服务项目 2.重命名Service1.cs 3.添加安装程序 4.修改service,修改信息如下:备注:ServiceName和安装后的服务名保持一致哦!!!5.修改serviceProcessInstaller1 6.编辑TransService代码 7.添加新项目窗体应用程序 代码:1 using System;2 using System.Win…

操作系统启动的过程

目录操作系统,启动!大致过程重要程序bootsect.ssetup.shead.s 操作系统,启动! 大致过程 ​ 计算机的工作方式是取指执行,而执行其的前提是内存中有代码。操作系统刚开始并不是在内存中,而是在磁盘上,因此第一步需要将其以一定的方式从磁盘读入内存。(1)x86PC刚开机时C…

(我个人的)atcoder我的补题记录汇总

2024.06.27 : 根据题目难度和standing最后提交人数是否补题,大概在难度600及以下的 补完2024.06.22及之前的