OpenHarmony开发者论坛

标题: Flutter到 OpenHarmony,不是有手就行吗? (仿掘金点赞按钮) [打印本页]

作者: zmtzawqlp    时间: 2024-2-27 14:09
标题: Flutter到 OpenHarmony,不是有手就行吗? (仿掘金点赞按钮)
[md]## 前言

`Flutter` 提供了丰富的动画支持,使开发者能够轻松创建各种类型的动画效果,从简单的渐变和旋转到复杂的交互式动画都可以实现。而 `ArkUI` 从 `api` 上面看起来更简单,更容易编写代码。我们今天迁移的是 `Flutter` 上面的 [like_button](https://github.com/fluttercandies/like_button), 它使我们的点赞效果更简单酷炫和简单。

![LikeButton.gif](https://forums-obs.openharmony.c ... j5jojni2nujzn31.gif "LikeButton.gif")

## 点赞按钮

`Like Button `支持推特点赞效果和喜欢数量动画的 `ArkUI` 库.

## 安装

`ohpm install @candies/like_button`

## 参数

### 配置参数

| 参数                       | 类型                   | 描述                                                                                                                                                             |
| -------------------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| likeWidgetSize             | number                 | LikeWidget 的大小(默认30)                                                                                                                                        |
| bubblesSize                | number                 | 动画时候的泡泡的大小(默认为 likeWidgetSize 的 2 倍)                                                                                                              |
| bubblesColor               | BubblesColor           | 动画时候的泡泡的颜色,可以分别设置 4 种(默认为  dotPrimaryColor: '#FFFFC107',dotSecondaryColor: '#FFFF9800',dotThirdColor: '#FFFF5722',dotLastColor: '#FFF44336' |
| circleSize                 | number                 | 动画时候的圈的最大大小(默认为 likeWidgetSize 的 0.8 倍)                                                                                                          |
| circleColor                | BubblesColor           | 动画时候的圈的颜色,需要设置2种 (默认为 start: '#FFFF5722', end: '#FFFFC107')                                                                                    |
| isLiked                    | boolean                | 是否喜欢。(默认 `false`)                                                                                                                                       |
| animationDuration          | number                 | LikeWidget 动画时长(默认 1000 毫秒)                                                                                                                              |
| likeCount                  | number                 | 喜欢数量。如果不设置(undefined),不显示 LikeCountWidget                                                                                                          |
| flexOptions                | FlexOptions            | LikeWidget 和 LikeCountWidget 2个组件位置的配置(默认为 direction: FlexDirection.Row, justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center)             |
| likeCountAnimationType     | LikeCountAnimationType | 喜欢数量动画的类型(none,part,all)。没有动画;只动画改变的部分;全部部分                                                                                          |
| widgetsMargin              | number                 | LikeWidget and LikeCountWidget 的距离                                                                                                                            |
| likeCountAnimationDuration | number                 | LikeCountWidget 动画时长(默认 1000 毫秒)                                                                                                                         |
| controller                 | Controller             | 可以通过调用 Controller 的 Tap 的方法,执行动画                                                                                                                  |

### 回调

这是一个异步回调,你可以等待服务返回之后再改变状态。也可以先改变状态,请求失败之后重置回状态

```typescript
onTap: (isLike: boolean) => Promise<boolean> = async (isLike: boolean) => {
    return !isLike;
  };
```

### 组件创建回调

| 回调                   | 参数                                                | 描述                       |
| ---------------------- | --------------------------------------------------- | -------------------------- |
| likeWidgetBuilder      | isLiked: boolean                                    | 用于自定义 LikeWidget      |
| likeCountWidgetBuilder | isLiked: boolean,likeCount: number,showText: string | 用于自定义 LikeCountWidget |

```typescript
@BuilderParam
  likeWidgetBuilder?: ($$: { isLiked: boolean }) => void = this.buildLikeWidget.bind(this);
  @BuilderParam
  likeCountWidgetBuilder?: ($$: {
    isLiked: boolean,
    likeCount: number,
    showText: string
  }) => void = this.buildLikeCountWidget.bind(this);
```

## 例子

### 无限点赞

将 `onTap` 固定返回 `true`,可以实现无限点赞的效果。

```typescript
LikeButton(
  {
    likeCount: 666,
    onTap: async (isLike: boolean): Promise<true> => {
      return true;
    },
  }
)
```

### LikeCountWidget 特殊处理逻辑

你可以根据 `likeCount` 的值的不同,做一些特殊的处理。

```typescript
LikeButton(
  {
    likeCount: 999,
    likeWidgetBuilder: this.buildLikeWidget8,
    bubblesColor: new BubblesColor({
      dotPrimaryColor: '#FF00796B',
      dotSecondaryColor: '#FF004D40',
    },),
    circleColor: new CircleColor({
      start: '#FF26A69A',
      end: '#FFB2DFDB',
    }),
    likeCountWidgetBuilder: this.buildLikeCountWidget2,
  }
)

@Builder
buildLikeCountWidget2($$: {
  isLiked: boolean,
  likeCount: number,
  showText: string
}) {
  if ($$.likeCount >= 1000)
    Text(`${($$.likeCount / 1000).toFixed(1)}k`).fontColor($$.isLiked ? '#FF004D40' : Color.Gray)
  else
    Text(`${$$.showText}`).fontColor($$.isLiked ? '#FF004D40' : Color.Gray)
}

@Builder
buildLikeWidget8($$: { isLiked: boolean }) {
  Image($r('app.media.save'))
    .fillColor($$.isLiked ? '#FF004D40' : Color.Gray)
}
```

### 设置 LikeWidget 和 LikeCountWidget 的位置

```typescript
LikeButton(
{
   likeCount: 888,
   flexOptions: {
     direction: FlexDirection.Column,
     justifyContent: FlexAlign.Center,
     alignItems: ItemAlign.Center,
   },
}
)
```

## 学废了

### 动画

`ArkUI` 里面使用动画蛮简单的。

[动画概述-使用动画-基于ArkTS的声明式开发范式-UI开发-开发-HarmonyOS应用开发](https://developer.harmonyos.com/ ... 0000001450755570-V3)

```typescript
@Entry
@Component
struct LayoutChange2 {
  @State myWidth: number = 100;
  @State myHeight: number = 50;
  // 标志位,true和false分别对应一组myWidth、myHeight值
  @State flag: boolean = false;

  build() {
    Column({ space: 10 }) {
      Button("text")
        .type(ButtonType.Normal)
        .width(this.myWidth)
        .height(this.myHeight)
        .margin(20)
      Button("area: click me")
        .fontSize(12)
        .margin(20)
        .onClick(() => {
          animateTo({ duration: 1000, curve: Curve.Ease }, () => {
            // 动画闭包中根据标志位改变控制第一个Button宽高的状态变量,使第一个Button做宽高动画
            if (this.flag) {
              this.myWidth = 100;
              this.myHeight = 50;
            } else {
              this.myWidth = 200;
              this.myHeight = 100;
            }
            this.flag = !this.flag;
          });
        })
    }
    .width("100%")
    .height("100%")
  }
}
```

但是你没法获取到每一帧的动画值。我们需要另外一个 `api` 来实现。

[@ohos.animator (动画)-UI界面-ArkTS接口参考-ArkTS API参考-HarmonyOS应用开发](https://developer.harmonyos.com/ ... 604__animatorresult)

```typescript
let options: AnimatorOptions = {
  duration: 1000,
  easing: "ease",
  delay: 0,
  fill: "forwards",
  direction: "normal",
  iterations: 1,
  begin: 0.0,
  end: 1.0
};

let animatorResult = animator.create(options);
animatorResult.onframe=(progress: number)=>{

};
animatorResult.onfinish=()=>{

};

animatorResult.play();
animatorResult.pause();
animatorResult.cancel();
animatorResult.reverse();
animatorResult.reset(options);
```

`onframe` 方法中的 `progress` 在 `begin` 和 `end` 之间的值。你可以在这个方法里面对某个属性做改变,达到动画的效果。

不过这个 `api` 没法像 `Flutter` 中一样, 使用同一个 `Controller` 对不同的属性,在不同时间段做不同的动画效果。比如下面的 `Controller` 的动画总时间为 `1000` 秒,那么就 `_innerCircleAnimation` 就是一个值从 `0.2` 开始 `1.0` 结束,并且是从 `200` 秒开始 `500` 秒结束的 `Curves.ease` 动画。虽然
`ArkUI` 中 `AnimatorOptions` 中可以设置 `delay` 来控制开始的时间,但是这也会影响整个动画的曲线。

```dart
_innerCircleAnimation = Tween<double>(
      begin: 0.2,
      end: 1.0,
    ).animate(
      CurvedAnimation(
        parent: _controller!,
        curve: const Interval(
          0.2,
          0.5,
          curve: Curves.ease,
        ),
      ),
    );
```

最终我的实现方案是将 `Flutter` 中的动画曲线迁移到 `ArkUI` 中,使用固定的 `0.0` 到 `1.0` 的 `AnimatorOptions` 来作为 `Flutter` 中的
`AnimationController` 来驱动动画。

```
let options: AnimatorOptions = {
  duration: this.animationDuration,
  easing: "ease",
  delay: 0,
  fill: "forwards",
  direction: "normal",
  iterations: 1,
  begin: 0.0,
  end: 1.0
};

_outerCircleCurve = new Interval({
  begin: 0.0,
  end: 0.3,
  curve: Cubic.ease,
  beginValue: 0.1,
  endValue: 1.0,
});
_innerCircleCurve = new Interval({
  begin: 0.2,
  end: 0.5,
  curve: Cubic.ease,
  beginValue: 0.2,
  endValue: 1.0,
});
_bubblesCurve = new Interval({
  begin: 0.1,
  end: 1.0,
  curve: new DecelerateCurve(),
  beginValue: 0.0,
  endValue: 1.0,
});
_iconScaleCurve = new Interval({
  begin: 0.35,
  end: 0.7,
  curve: new OvershootCurve(),
  beginValue: 0.1,
  endValue: 1.0
});

this._animatorResult.onframe = (value: number) => {
  this._outerCircleRadiusProgress = this._outerCircleCurve.transform(value);
  this._innerCircleRadiusProgress = this._innerCircleCurve.transform(value);
  this._bubblesProgress = this._bubblesCurve.transform(value);
  this._iconScale = this._iconScaleCurve.transform(value);
};
```

这样子就能实现跟 `Flutter` 一模一样的动画曲线了。

## 结语

从 `Flutter` 迁移到 `ArkUI` 过程蛮简单的,只需要去找准对应的 `api` 即可,关于 `Canvas` 的 `api` 各种平台的都大差不差的。欢迎大家加入 [Harmony Candies](https://github.com/HarmonyCandies) ,为鸿蒙的生态添加更多有趣的组件。

爱 OpenHarmony ,爱 `糖果`,欢迎加入[Harmony Candies](https://github.com/HarmonyCandies),一起生产可爱的.

![DM_20231219092555_019.jpg](https://forums-obs.openharmony.c ... u1twhjwi0i7tnw7.jpg "DM_20231219092555_019.jpg")

## 参考

* [动画概述-使用动画-基于ArkTS的声明式开发范式-UI开发-开发-HarmonyOS应用开发](https://developer.harmonyos.com/ ... 0000001450755570-V3)
* [@ohos.animator (动画)-UI界面-ArkTS接口参考-ArkTS API参考-HarmonyOS应用开发](https://developer.harmonyos.com/ ... 604__animatorresult)
[/md]




欢迎光临 OpenHarmony开发者论坛 (https://forums.openharmony.cn/) Powered by Discuz! X3.5