如何实现List下滑指定距离才展开的效果
====================

#### 问题现象

List组件可以下拉上滑，期望实现以下效果：

1.  组件1默认高度为0，不展示，当组件2下拉时，组件1根据下拉偏移量展示高度，当下拉手势结束时判断偏移量是否超出组件1全部展示高度的一半，如果超出，则组件1全部展示，否则，组件1不展示并触发回弹效果。
2.  当组件1全部展示后，组件2触发上滑时，组件1根据上滑偏移量展示剩余高度，当上滑手势结束时，触发和下拉一致的效果。

#### 效果预览

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/77/v3/BtsvA1X8Q5WPi6urwhpGnQ/zh-cn_image_0000002628764160.gif?HW-CC-KV=V1&HW-CC-Date=20260709T134050Z&HW-CC-Expire=86400&HW-CC-Sign=FA5EAB497E3187263FD616183D29CD33ACC8C25A1D8878A591239DF2F0608602 "点击放大")

#### 背景知识

*   [List](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-container-list)：List是用来显示列表的组件，包含一系列相同宽度的列表项，适合连续、多行地呈现同类数据。
*   [onTouch](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-universal-events-touch#ontouch)：手指触摸动作触发该回调。鼠标左键按下时对应的事件也会转化成触摸事件并触发该回调。

#### 解决方案

可以使用onTouch来监听下拉/上滑手势操作，并通过监听下拉距离用来计算是否展开，具体实现如下：

*   使用onTouch监听触摸事件，根据触摸类型执行相应的操作。当触摸类型为Down时，记录当前触摸位置和图片初始高度。当触摸类型为Move时，根据图片展开状态和触摸移动的距离调整图片高度。当触摸类型为Up时，判断是否需要展开图片，并使用getUIContext().animateTo执行动画，根据图片高度变化展开或收起图片。动画结束后，设置isAnimating为false，表示动画结束。
    
    .onTouch((event: TouchEvent) \=\> {
      if (!this.isAnimating) { _// 非动画中_
        const currentY \= event.touches\[0\].y;
        switch (event.type) {
          case TouchType.Down: _// 记录当前触摸位置和图片初始高度_
            this.touchStartY \= currentY;
            this.touchStartHeight \= this.imageHeight;
            break;
    
          case TouchType.Move: _//_ _根据图片展开状态和触摸移动的距离调整图片高度_
            const deltaY \= currentY \- this.touchStartY;
            if (this.isExpanded) {
              if (deltaY \>\= 0) {
                return;
              } else {
                const newHeight \= Math.max(this.touchStartHeight \+ deltaY, 0);
                if (Math.abs(newHeight \- this.imageHeight) \> 1) {
                  this.imageHeight \= newHeight;
                }
              }
            } else {
              if (this.startIndex \=== 0 && deltaY \> 0) {
                const newHeight \= Math.min(deltaY, this.maxImageHeight);
                if (Math.abs(newHeight \- this.imageHeight) \> 1) {
                  this.imageHeight \= newHeight;
                }
              }
            }
            break;
    
          case TouchType.Up: _// 手指抬起，判断当前组件展开的高度进行完全展开或收起动作_
            const movedDistance \= Math.abs(currentY \- this.touchStartHeight); _// 移动距离小于5不做操作_
            const isMove \= movedDistance \> 5;
    
            if (isMove) {
              this.isAnimating \= true;
              this.getUIContext()?.animateTo({
                duration: 400,
                curve: curves.cubicBezierCurve(0.2, 0.8, 0.1, 1.0),
                onFinish: () \=\> {
                  this.isAnimating \= false;
                }
              }, () \=\> {
                if (this.imageHeight \> this.threshold) {
                  this.imageHeight \= this.maxImageHeight;
                  this.isExpanded \= true;
                } else {
                  this.imageHeight \= 0;
                  this.isExpanded \= false;
                }
              });
            }
            break;
        }
      }
    })
    

完整示例参考如下：

import curves from '@ohos.curves';

@Entry
@Component
struct ListDownDemo {
  @State imageHeight: number \= 0; _//_ _图片容器高度_
  @State startIndex: number \= 0; _// 列表起始索引_
  @State isExpanded: boolean \= false; _// 图片展开状态_
  private maxImageHeight: number \= 240; _//_ _图片最大高度_
  private touchStartY: number \= 0; _// 触摸起始位置_
  private threshold: number \= 120; _//_ _触发阈值_
  private isAnimating: boolean \= false; _// 是否正在动画中_
  private touchStartHeight: number \= 0; _//_ _触摸图片起始高度_

  build() {
    Column() {
      Column() {
        Text('下拉组件内容区')
          .fontSize(20)
      }
      .width('100%')
      .backgroundColor('#f1f3f5')
      .height(this.imageHeight)
      .clip(true)
      .margin({ bottom: 10 })
      .align(Alignment.Bottom)
      .justifyContent(FlexAlign.End)
      .transition(TransitionEffect.opacity(1).animation({ duration: 200 }))

      List() {
        ForEach(\[1, 2, 3, 4, 5, 6, 7, 8, 9, 10\], (item: number) \=\> {
          ListItem() {
            Row() {
              Column() {
                Text(\`组件二内容： ${item} \`)
                  .fontSize(14)
                  .opacity(0.7)
              }
              .layoutWeight(1)
              .margin({ right: 10 })
            }
            .padding(8)
            .backgroundColor('#FFFFFF')
            .height(100)
            .margin({
              top: 8,
              bottom: 8,
              left: 16,
              right: 16
            })
            .shadow({
              radius: 4,
              color: '#00000020',
              offsetX: 1,
              offsetY: 1
            })
          }
        })
      }
      .width('100%')
      .height('100%')
      .enableScrollInteraction(this.isExpanded ? false : true)
      .onScrollIndex((start: number) \=\> {
        this.startIndex \= start;
      })
      .layoutWeight(1)
      .edgeEffect(EdgeEffect.None)
    }
    .onTouch((event: TouchEvent) \=\> {
      if (!this.isAnimating) { _// 非动画中_
        const currentY \= event.touches\[0\].y;
        switch (event.type) {
          case TouchType.Down: _// 记录当前触摸位置和图片初始高度_
            this.touchStartY \= currentY;
            this.touchStartHeight \= this.imageHeight;
            break;

          case TouchType.Move: _//_ _根据图片展开状态和触摸移动的距离调整图片高度_
            const deltaY \= currentY \- this.touchStartY;
            if (this.isExpanded) {
              if (deltaY \>\= 0) {
                return;
              } else {
                const newHeight \= Math.max(this.touchStartHeight \+ deltaY, 0);
                if (Math.abs(newHeight \- this.imageHeight) \> 1) {
                  this.imageHeight \= newHeight;
                }
              }
            } else {
              if (this.startIndex \=== 0 && deltaY \> 0) {
                const newHeight \= Math.min(deltaY, this.maxImageHeight);
                if (Math.abs(newHeight \- this.imageHeight) \> 1) {
                  this.imageHeight \= newHeight;
                }
              }
            }
            break;

          case TouchType.Up: _// 手指抬起，判断当前组件展开的高度进行完全展开或收起动作_
            const movedDistance \= Math.abs(currentY \- this.touchStartHeight); _// 移动距离小于5不做操作_
            const isMove \= movedDistance \> 5;

            if (isMove) {
              this.isAnimating \= true;
              this.getUIContext()?.animateTo({
                duration: 400,
                curve: curves.cubicBezierCurve(0.2, 0.8, 0.1, 1.0),
                onFinish: () \=\> {
                  this.isAnimating \= false;
                }
              }, () \=\> {
                if (this.imageHeight \> this.threshold) {
                  this.imageHeight \= this.maxImageHeight;
                  this.isExpanded \= true;
                } else {
                  this.imageHeight \= 0;
                  this.isExpanded \= false;
                }
              });
            }
            break;
        }
      }
    })
  }
}