鸿蒙跨端点赞按钮动画设计与实现

一、动画效果设计

基于HarmonyOS的动画能力,我们设计了一个跨设备同步的点赞按钮动画,确保在多设备游戏场景中点赞状态和动画效果能够实时同步。

https://example.com/like-animation-preview.gif

动画包含三个核心效果:

  1. ​缩放效果​​ - 点击时按钮先缩小后恢复
  2. ​颜色渐变​​ - 点赞状态切换时的颜色过渡
  3. ​粒子效果​​ - 点赞成功时的粒子飞散效果

二、核心代码实现

1. 基础点赞按钮组件(ArkTS)

// LikeButton.ets
@Component
export struct LikeButton {
  @State isLiked: boolean = false
  @State scaleValue: number = 1
  @State bgColor: Color = Color.White
  
  build() {
    Button() {
      Image(this.isLiked ? $r('app.media.liked') : $r('app.media.unliked'))
        .width(24)
        .height(24)
    }
    .width(48)
    .height(48)
    .borderRadius(24)
    .backgroundColor(this.bgColor)
    .scale({ x: this.scaleValue, y: this.scaleValue })
    .onClick(() => this.handleLike())
  }
  
  private handleLike() {
    // 触发动画
    this.startAnimation()
    
    // 切换点赞状态
    this.isLiked = !this.isLiked
    
    // 跨设备同步状态
    this.syncLikeState()
  }
  
  private startAnimation() {
    // 缩放动画
    animateTo({
      duration: 200,
      curve: Curve.EaseOut
    }, () => {
      this.scaleValue = 0.8
    })
    
    // 颜色变化动画
    animateTo({
      duration: 300,
      curve: Curve.EaseInOut,
      delay: 100
    }, () => {
      this.bgColor = this.isLiked ? 
        Color.White : 
        Color.fromRGB(255, 228, 225)
    })
    
    // 恢复动画
    animateTo({
      duration: 200,
      curve: Curve.EaseIn,
      delay: 200
    }, () => {
      this.scaleValue = 1
    })
  }
  
  private syncLikeState() {
    // 通过分布式数据管理同步状态
    distributedData.sync('like_state', {
      isLiked: this.isLiked,
      timestamp: new Date().getTime()
    })
  }
}

2. 增强版点赞动画(带粒子效果)

// AdvancedLikeButton.ets
@Component
export struct AdvancedLikeButton {
  @State isLiked: boolean = false
  @State scaleValue: number = 1
  @State bgColor: Color = Color.White
  @State particles: Particle[] = []
  
  @Builder
  ParticleEffect() {
    ForEach(this.particles, (particle) => {
      Circle()
        .width(particle.size)
        .height(particle.size)
        .position(particle.position)
        .backgroundColor(particle.color)
        .opacity(particle.opacity)
    })
  }
  
  build() {
    Stack() {
      // 粒子效果层
      this.ParticleEffect()
      
      // 按钮层
      Button() {
        Image(this.isLiked ? $r('app.media.liked_filled') : $r('app.media.unliked'))
          .width(28)
          .height(28)
      }
      .width(56)
      .height(56)
      .borderRadius(28)
      .backgroundColor(this.bgColor)
      .scale({ x: this.scaleValue, y: this.scaleValue })
      .onClick(() => this.handleLike())
    }
    .width(100)
    .height(100)
  }
  
  private handleLike() {
    // 触发动画
    this.startAnimation()
    
    // 切换点赞状态
    this.isLiked = !this.isLiked
    
    // 如果是点赞状态,生成粒子效果
    if (this.isLiked) {
      this.generateParticles()
    }
    
    // 跨设备同步状态
    this.syncLikeState()
  }
  
  private startAnimation() {
    // 第一阶段:缩小和颜色变化
    animateTo({
      duration: 150,
      curve: Curve.EaseOut
    }, () => {
      this.scaleValue = 0.85
      this.bgColor = this.isLiked ? 
        Color.fromRGB(255, 228, 225) : 
        Color.fromRGB(240, 240, 240)
    })
    
    // 第二阶段:恢复和最终颜色
    animateTo({
      duration: 250,
      curve: Curve.Spring,
      delay: 150
    }, () => {
      this.scaleValue = 1
      this.bgColor = this.isLiked ? 
        Color.fromRGB(255, 192, 203) : 
        Color.White
    })
  }
  
  private generateParticles() {
    // 清空现有粒子
    this.particles = []
    
    // 生成20个粒子
    const newParticles: Particle[] = []
    for (let i = 0; i < 20; i++) {
      newParticles.push(this.createParticle(i))
    }
    
    this.particles = newParticles
    
    // 粒子动画
    animateTo({
      duration: 800,
      curve: Curve.Friction
    }, () => {
      this.particles = this.particles.map(p => ({
        ...p,
        position: {
          x: p.position.x + p.velocity.x * 2,
          y: p.position.y + p.velocity.y * 2 - 0.5 * 9.8 // 重力效果
        },
        opacity: p.opacity - 0.02,
        size: p.size * 0.95
      }))
    })
  }
  
  private createParticle(index: number): Particle {
    const angle = (index / 20) * 2 * Math.PI
    const speed = 3 + Math.random() * 3
    
    return {
      id: index.toString(),
      size: 3 + Math.random() * 4,
      color: this.getRandomParticleColor(),
      position: { x: 0, y: 0 },
      velocity: {
        x: Math.cos(angle) * speed,
        y: Math.sin(angle) * speed
      },
      opacity: 0.8 + Math.random() * 0.2
    }
  }
  
  private getRandomParticleColor(): Color {
    const colors = [
      Color.Red,
      Color.Pink,
      Color.Yellow,
      Color.Orange,
      Color.Magenta
    ]
    return colors[Math.floor(Math.random() * colors.length)]
  }
  
  private syncLikeState() {
    // 通过分布式数据管理同步状态
    distributedData.sync('advanced_like', {
      isLiked: this.isLiked,
      timestamp: new Date().getTime(),
      particles: this.isLiked ? this.particles : []
    })
  }
}

interface Particle {
  id: string;
  size: number;
  color: Color;
  position: Position;
  velocity: Velocity;
  opacity: number;
}

interface Position {
  x: number;
  y: number;
}

interface Velocity {
  x: number;
  y: number;
}

3. 跨设备同步实现(Java)

// LikeSyncService.java
public class LikeSyncService extends Ability {
    private static final String TAG = "LikeSyncService";
    private static final String LIKE_CHANNEL = "like_sync_channel";
    
    private DistributedDataManager dataManager;
    private LikeStateListener likeListener;
    
    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        initDataManager();
        registerLikeListener();
    }
    
    private void initDataManager() {
        Context context = getContext();
        dataManager = DistributedDataManager.getInstance(context);
    }
    
    private void registerLikeListener() {
        likeListener = new LikeStateListener() {
            @Override
            public void onLikeStateChanged(String deviceId, boolean isLiked) {
                handleLikeStateChange(deviceId, isLiked);
            }
        };
        
        dataManager.registerListener(LIKE_CHANNEL, likeListener);
    }
    
    private void handleLikeStateChange(String deviceId, boolean isLiked) {
        HiLog.info(TAG, "设备 " + deviceId + " 点赞状态变化: " + isLiked);
        
        // 在主线程执行UI更新
        getUITaskDispatcher().asyncDispatch(() -> {
            if (isLiked) {
                triggerRemoteLikeAnimation(deviceId);
            }
        });
    }
    
    private void triggerRemoteLikeAnimation(String deviceId) {
        // 这里可以触发远程设备的点赞动画
        // 实际实现可能需要通过分布式能力发送动画触发指令
        HiLog.info(TAG, "触发设备 " + deviceId + " 的点赞动画");
    }
    
    public void syncLikeState(boolean isLiked) {
        Map<String, Object> likeData = new HashMap<>();
        likeData.put("isLiked", isLiked);
        likeData.put("timestamp", System.currentTimeMillis());
        
        dataManager.syncData(LIKE_CHANNEL, likeData);
    }
    
    @Override
    public void onStop() {
        super.onStop();
        if (dataManager != null && likeListener != null) {
            dataManager.unregisterListener(LIKE_CHANNEL, likeListener);
        }
    }
}

interface LikeStateListener {
    void onLikeStateChanged(String deviceId, boolean isLiked);
}

三、动画参数详解

1. 动画曲线对比

曲线类型 描述 适用场景
Curve.EaseIn 动画开始时慢,然后加速 元素离开屏幕
Curve.EaseOut 动画开始时快,然后减速 元素进入屏幕
Curve.EaseInOut 动画开始和结束都慢 大多数状态变化
Curve.Linear 匀速运动 需要一致速度的动画
Curve.Spring 弹性效果 有弹性的交互元素
Curve.Friction 摩擦减速效果 粒子系统、自然运动

2. 动画参数配置

// 动画配置示例
animateTo({
  duration: 300,       // 动画持续时间(ms)
  curve: Curve.EaseInOut, // 动画曲线
  delay: 100,         // 延迟开始时间(ms)
  iterations: 1,      // 重复次数(无限循环可设置-1)
  playMode: PlayMode.Normal // 播放模式(Normal/Reverse/Alternate)
}, () => {
  // 动画内容
  this.scaleValue = 1.2
  this.bgColor = Color.Pink
})

四、使用示例

1. 在游戏界面中使用点赞按钮

// GameScene.ets
import { LikeButton, AdvancedLikeButton } from './LikeButton'

@Entry
@Component
struct GameScene {
  @State playerLikes: number = 0
  
  build() {
    Column() {
      // 玩家头像和昵称
      PlayerInfo()
      
      // 游戏内容
      GameContent()
      
      // 点赞区域
      this.buildLikeSection()
    }
  }
  
  @Builder
  buildLikeSection() {
    Row() {
      Text(`点赞数: ${this.playerLikes}`)
        .fontSize(16)
      
      AdvancedLikeButton()
        .onLikeChanged((isLiked) => {
          if (isLiked) {
            this.playerLikes++
          } else {
            this.playerLikes--
          }
        })
    }
    .margin({ top: 20 })
  }
}

@Component
struct PlayerInfo {
  @State playerName: string = '游戏玩家'
  @State avatar: Resource = $r('app.media.default_avatar')
  
  build() {
    Row() {
      Image(this.avatar)
        .width(60)
        .height(60)
        .borderRadius(30)
      
      Text(this.playerName)
        .fontSize(18)
        .margin({ left: 10 })
    }
  }
}

2. 跨设备同步测试代码

// LikeSyncTest.ets
@Entry
@Component
struct LikeSyncTest {
  @State localLike: boolean = false
  @State remoteLike: boolean = false
  @State devices: string[] = ['设备1', '设备2', '设备3']
  
  build() {
    Column() {
      Text('跨设备点赞同步测试')
        .fontSize(20)
        .margin(10)
      
      // 本地设备状态
      this.buildLocalDevice()
      
      // 远程设备状态
      ForEach(this.devices, (device) => {
        this.buildRemoteDevice(device)
      })
    }
  }
  
  @Builder
  buildLocalDevice() {
    Column() {
      Text('当前设备')
        .fontSize(16)
      
      LikeButton({ isLiked: this.localLike })
        .onClick(() => {
          this.localLike = !this.localLike
          // 模拟同步到其他设备
          this.syncToAllDevices(this.localLike)
        })
    }
    .padding(10)
    .borderRadius(8)
    .backgroundColor('#F0F0F0')
    .margin(5)
  }
  
  @Builder
  buildRemoteDevice(device: string) {
    Column() {
      Text(device)
        .fontSize(16)
      
      LikeButton({ isLiked: this.remoteLike })
    }
    .padding(10)
    .borderRadius(8)
    .backgroundColor('#F5F5F5')
    .margin(5)
  }
  
  private syncToAllDevices(isLiked: boolean) {
    // 模拟网络延迟
    setTimeout(() => {
      this.remoteLike = isLiked
    }, 300)
  }
}

五、性能优化建议

  1. ​减少动画复杂度​​:避免同时运行过多动画
  2. ​使用硬件加速​​:对opacity和transform属性动画性能更好
  3. ​合理使用will-change​​:提前告知浏览器哪些属性会变化
  4. ​避免布局抖动​​:动画中避免触发布局重新计算
  5. ​适当降低帧率​​:非关键动画可使用较低帧率
// 性能优化示例
@Component
struct OptimizedLikeButton {
  @State isLiked: boolean = false
  
  build() {
    // 使用will-change优化
    Column()
      .willChange({ opacity: true, transform: true })
    {
      Button() {
        Image(this.isLiked ? $r('app.media.liked') : $r('app.media.unliked'))
      }
      .onClick(() => {
        // 使用轻量级动画
        animateTo({
          duration: 200,
          curve: Curve.EaseOut
        }, () => {
          this.isLiked = !this.isLiked
        })
      })
    }
  }
}

通过以上实现,我们创建了一个高性能、跨设备同步的点赞按钮动画组件,可以在鸿蒙跨设备游戏场景中提供一致的用户体验。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐