<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      Jetpack Compose(8)——嵌套滾動

      前言

      所謂嵌套滾動,就是兩個組件之間出現滾動事件沖突了,要給與特定的處理邏輯。在傳統 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.dp
          val maxHeight = 200.dp
          val density = LocalDensity.current
      
          val 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.y
                              return Offset(x = 0f, y = available.y)
                          } else {  // 如果當前可用的滑動距離足夠達到最小高度,就只消費掉需要的距離。剩余的給到子組件。
                              topHeightPx += dH
                              return Offset(x = 0f, y = dH)
                          }
                      } else { // 下滑
                          val dH = maxHeightPx - topHeightPx  // 向下滑動過程中,還差多少達到最大高度
                          if (available.y < dH) {  // 如果當前可用的滑動距離全部消費都不足以達到最大高度,就將當前可用距離全部消費掉
                              topHeightPx += available.y
                              return Offset(x = 0f, y = available.y)
                          } else {  // 如果當前可用的滑動距離足夠達到最大高度,就只消費掉需要的距離。剩余的給到子組件。
                              topHeightPx += dH
                              return 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.dp
          val maxHeight = 200.dp
          val density = LocalDensity.current
      
          val minHeightPx = with(density) {
              minHeight.toPx()
          }
      
          val maxHeightPx = with(density) {
              maxHeight.toPx()
          }
      
          var topHeightPx by remember {
              mutableStateOf(maxHeightPx)
          }
      
          val connection = remember {
              object : NestedScrollConnection {}
          }
      
          val dispatcher = remember { 
              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.dp
          val maxHeight = 200.dp
          val density = LocalDensity.current
      
          val 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.y
                                  return Offset(x = 0f, y = available.y)
                              } else {  // 如果當前可用的滑動距離足夠達到最小高度,就只消費掉需要的距離。剩余的給到子組件。
                                  topHeightPx += dH
                                  return Offset(x = 0f, y = dH)
                              }
                          } else { // 下滑
                              val dH = maxHeightPx - topHeightPx  // 向下滑動過程中,還差多少達到最大高度
                              if (available.y < dH) {  // 如果當前可用的滑動距離全部消費都不足以達到最大高度,就將當前可用距離全部消費掉
                                  topHeightPx += available.y
                                  return Offset(x = 0f, y = available.y)
                              } else {  // 如果當前可用的滑動距離足夠達到最大高度,就只消費掉需要的距離。剩余的給到子組件。
                                  topHeightPx += dH
                                  return 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(),感興趣的朋友可以繼續研究。
      posted @ 2024-06-27 22:18  SharpCJ  閱讀(2136)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 无码丰满人妻熟妇区| 国产精品一二三中文字幕| 精品一区二区av天堂| 中文字幕人妻无码一区二区三区| 亚洲自偷自拍熟女另类| 18禁视频一区二区三区| 仁布县| 国产精品一区二区三区日韩| 一区二区和激情视频| 91中文字幕一区在线| 亚洲精品中文综合第一页| 最新亚洲人成无码网站欣赏网| 亚洲av综合av一区| 国产一区二区不卡精品视频| 成全影视大全在线观看| www亚洲精品| 亚洲综合伊人五月天中文| 少妇人妻偷人一区二区| 伊人久久大香线蕉AV网禁呦| 白嫩少妇无套内谢视频| 日韩一区二区三区日韩精品| av资源在线看免费观看| 免费吃奶摸下激烈视频| av午夜福利亚洲精品福利| 香港特级三A毛片免费观看| 成人网站免费观看永久视频下载 | 久久精品亚洲热综合一区二区| 日韩熟女精品一区二区三区| 又粗又大又黄又硬又爽免费看 | 最新国产精品好看的精品| 狠狠色噜噜狠狠狠狠色综合网| 国产精品一亚洲av日韩| 亚洲夜色噜噜av在线观看| 日本肥老妇色xxxxx日本老妇| 免费特黄夫妻生活片| 免费观看全黄做爰大片| 亚洲国产成熟视频在线多多| 一道本AV免费不卡播放| 国产精品妇女一区二区三区| 99热国产成人最新精品| 无码人妻斩一区二区三区|