优化礼物消息的动画效果,减少视觉冲突。修改消息插入动画为淡入效果,延迟滚动执行,确保动画流畅性。同时新增礼物消息识别方法,优化 Cell 更新逻辑以避免动画期间的布局更新。

This commit is contained in:
edwinQQQ
2025-08-28 15:16:26 +08:00
parent d22ddaefcf
commit d4ac93adbb
3 changed files with 193 additions and 32 deletions

View File

@@ -29,6 +29,8 @@
@property (nonatomic,strong) UITapGestureRecognizer *tapEmptyRecognizer;
@property (nonatomic, strong) UIView *bottomSpace;
///
@property (nonatomic, assign) BOOL isAnimating;
@end
@@ -153,26 +155,42 @@
#pragma mark - Getters And Setters
- (void)setMessageInfo:(XPMessageInfoModel *)messageInfo {
//
//
if (_messageInfo &&
[messageInfo.content isEqualToAttributedString:_messageInfo.content] &&
[messageInfo.bubbleImageUrl isEqualToString:_messageInfo.bubbleImageUrl] &&
[messageInfo.boomImageUrl isEqualToString:_messageInfo.boomImageUrl]) {
[messageInfo.boomImageUrl isEqualToString:_messageInfo.boomImageUrl] &&
messageInfo.first == _messageInfo.first &&
messageInfo.second == _messageInfo.second) {
return;
}
_messageInfo = messageInfo;
if (messageInfo) {
// attributedTexthasBubble
BOOL hasBubble = ![NSString isEmpty:messageInfo.bubbleImageUrl];
if (self.contentLabel.hasBubble != hasBubble) {
self.contentLabel.hasBubble = hasBubble;
//
if (self.isAnimating) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self setMessageInfo:messageInfo];
});
return;
}
self.contentLabel.attributedText = messageInfo.content;
// 使 performWithoutAnimation
[UIView performWithoutAnimation:^{
// attributedTexthasBubble
BOOL hasBubble = ![NSString isEmpty:messageInfo.bubbleImageUrl];
if (self.contentLabel.hasBubble != hasBubble) {
self.contentLabel.hasBubble = hasBubble;
}
self.contentLabel.attributedText = messageInfo.content;
}];
//
if (self.isLeftBigImage && messageInfo.boomImageUrl) {
self.leftBigImageView.imageUrl = messageInfo.boomImageUrl;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.leftBigImageView.imageUrl = messageInfo.boomImageUrl;
});
}
}
}
@@ -190,9 +208,13 @@
return;
}
[self.contentLabel mas_updateConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo(size.width);
}];
// 使
[UIView animateWithDuration:0.2 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
[self.contentLabel mas_updateConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo(size.width);
}];
[self layoutIfNeeded];
} completion:nil];
// [self.bubbleImageView mas_updateConstraints:^(MASConstraintMaker *make) {
// if (size.width + 32 >= kRoomMessageMaxWidth) {
@@ -206,13 +228,17 @@
if (hasBubble) {
@kWeakify(self);
[self.bubbleImageView loadImageWithUrl:self.messageInfo.bubbleImageUrl
completion:^(UIImage * _Nonnull image, NSURL * _Nonnull url) {
@kStrongify(self);
UIImage *image1 = [UIImage imageWithCGImage:image.CGImage scale:2.0 orientation:UIImageOrientationUp];
UIImage *cutImage = [image1 cropRightAndBottomPixels:2];
self.bubbleImageView.image = [self resizableImage:cutImage];
}];
//
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.05 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.bubbleImageView loadImageWithUrl:self.messageInfo.bubbleImageUrl
completion:^(UIImage * _Nonnull image, NSURL * _Nonnull url) {
@kStrongify(self);
UIImage *image1 = [UIImage imageWithCGImage:image.CGImage scale:2.0 orientation:UIImageOrientationUp];
UIImage *cutImage = [image1 cropRightAndBottomPixels:2];
self.bubbleImageView.image = [self resizableImage:cutImage];
}];
});
[self.bubbleImageView mas_updateConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(self.contentLabel).insets(UIEdgeInsetsMake(-10, -10, -10, -10));
}];

View File

@@ -258,7 +258,11 @@ NSString * const kRoomShowTopicKey = @"kRoomShowTopicKey";
for (NSInteger i = 0; i < tempNewDatas.count; i++) {
[indexPaths addObject:[NSIndexPath indexPathForRow:startIndex + i inSection:0]];
}
[self.messageTableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
// 使
[UIView animateWithDuration:0.2 animations:^{
[self.messageTableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade];
}];
} else {
[self.messageTableView reloadData];
}
@@ -386,6 +390,23 @@ NSString * const kRoomShowTopicKey = @"kRoomShowTopicKey";
}
}
///
- (BOOL)isGiftMessage:(id)messageData {
if ([messageData isKindOfClass:[NIMMessage class]]) {
NIMMessage *message = (NIMMessage *)messageData;
if ([message.messageObject isKindOfClass:[NIMCustomObject class]]) {
NIMCustomObject *obj = (NIMCustomObject *)message.messageObject;
if (obj.attachment && [obj.attachment isKindOfClass:[AttachmentModel class]]) {
AttachmentModel *attachment = (AttachmentModel *)obj.attachment;
return (attachment.first == CustomMessageType_Gift ||
attachment.first == CustomMessageType_AllMicroSend ||
attachment.first == CustomMessageType_Super_Gift);
}
}
}
return NO;
}
- (NSInteger)getCurrentDataSourceCount {
NSInteger count = 0;
switch (self.displayType) {
@@ -572,7 +593,15 @@ NSString * const kRoomShowTopicKey = @"kRoomShowTopicKey";
self.messageTipsBtn.hidden = NO;
[self findAtMeNumber];
} else {
[self appendAndScrollToBottom];
// 使
if ([self isGiftMessage:messageData]) {
//
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.05 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self appendAndScrollToBottom];
});
} else {
[self appendAndScrollToBottom];
}
}
}
@@ -623,22 +652,29 @@ NSString * const kRoomShowTopicKey = @"kRoomShowTopicKey";
if (expectedRows != [self.messageTableView numberOfRowsInSection:0]) {
[self.messageTableView reloadData];
} else {
// indexPath使
NSMutableArray *indexPaths = @[].mutableCopy;
NSInteger startIndex = currentRows;
if (startIndex >= 0 && startIndex <= [self.messageTableView numberOfRowsInSection:0]) {
for (NSInteger i = 0; i < tempArray.count; i++) {
[indexPaths addObject:[NSIndexPath indexPathForRow:startIndex + i inSection:0]];
}
[self.messageTableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
} else {
[self.messageTableView reloadData];
// indexPath使
NSMutableArray *indexPaths = @[].mutableCopy;
NSInteger startIndex = currentRows;
if (startIndex >= 0 && startIndex <= [self.messageTableView numberOfRowsInSection:0]) {
for (NSInteger i = 0; i < tempArray.count; i++) {
[indexPaths addObject:[NSIndexPath indexPathForRow:startIndex + i inSection:0]];
}
// 使
[UIView animateWithDuration:0.2 animations:^{
[self.messageTableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade];
}];
} else {
[self.messageTableView reloadData];
}
}
}
//
[self scrollToBottom:NO];
//
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self scrollToBottom:NO];
});
}
///
@@ -669,7 +705,10 @@ NSString * const kRoomShowTopicKey = @"kRoomShowTopicKey";
[self.messageTableView scrollToRowAtIndexPath:ip atScrollPosition:UITableViewScrollPositionBottom animated:YES];
});
} else {
[self.messageTableView scrollToRowAtIndexPath:ip atScrollPosition:UITableViewScrollPositionBottom animated:NO];
// 使
[UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
[self.messageTableView scrollToRowAtIndexPath:ip atScrollPosition:UITableViewScrollPositionBottom animated:NO];
} completion:nil];
}
self.atCount = 0;

View File

@@ -0,0 +1,96 @@
# 礼物动画优化方案B实施总结
## 问题描述
用户发送礼物时,消息列表会不断闪烁,特别是在连续送礼场景下问题更明显。
## 根本原因
1. 礼物动画与消息列表更新同时进行,产生视觉冲突
2. 消息列表使用 `UITableViewRowAnimationNone` 导致突兀的更新效果
3. 滚动时机与动画执行时机冲突
## 实施方案B优化消息更新动画
### 修改内容
#### 1. 优化消息插入动画
-`UITableViewRowAnimationNone` 改为 `UITableViewRowAnimationFade`
- 添加 `UIView.animateWithDuration:0.2` 包装动画
- 应用位置:`appendAndScrollToBottom``appendAndScrollToAtUser` 方法
#### 2. 优化滚动时机
- 延迟滚动执行:`dispatch_after(0.1秒)`
- 使用更平滑的滚动动画:`UIViewAnimationOptionCurveEaseOut`
- 动画时长0.3秒
#### 3. 添加礼物消息识别
- 新增 `isGiftMessage:` 方法识别礼物消息
- 支持 `CustomMessageType_Gift``CustomMessageType_AllMicroSend``CustomMessageType_Super_Gift`
#### 4. 礼物消息特殊处理
-`addRoomMessage:` 中对礼物消息添加0.05秒延迟
- 让动画先执行,再更新消息列表
#### 5. 优化 Cell 更新逻辑(新增)
-`XPRoomMessageTableViewCell.m` 中优化 `setMessageInfo:` 方法
- 使用 `UIView.performWithoutAnimation` 避免布局动画
- 延迟图片加载,避免与礼物动画冲突
- 添加动画状态检查,避免动画期间的布局更新
- 使用更平滑的布局更新动画
### 修改的文件
- `YuMi/Modules/YMRoom/View/MessageContainerView/XPRoomMessageContainerView.m`
- `YuMi/Modules/YMRoom/View/MessageContainerView/View/XPRoomMessageTableViewCell.m`
### 关键代码变更
```objc
// 1. 消息插入动画优化
[UIView animateWithDuration:0.2 animations:^{
[self.messageTableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade];
}];
// 2. 滚动时机优化
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self scrollToBottom:NO];
});
// 3. 平滑滚动动画
[UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
[self.messageTableView scrollToRowAtIndexPath:ip atScrollPosition:UITableViewScrollPositionBottom animated:NO];
} completion:nil];
// 4. 礼物消息延迟处理
if ([self isGiftMessage:messageData]) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.05 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self appendAndScrollToBottom];
});
}
```
## 预期效果
1. 减少礼物动画与消息更新的视觉冲突
2. 提供更平滑的用户体验
3. 保持消息的实时性
4. 适用于连续送礼场景
## 风险评估
- **低风险**只修改UI更新逻辑不影响核心功能
- **兼容性**保持现有API不变
- **性能**:轻微提升,减少视觉冲突
## 测试建议
1. 测试单个礼物发送
2. 测试连续快速送礼
3. 测试不同礼物类型
4. 测试消息列表滚动状态下的表现