From 1190e0f6d92d0f738dcae6e213773681d5a30fbb Mon Sep 17 00:00:00 2001 From: edwinQQQ Date: Tue, 24 Jun 2025 18:50:45 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20XPMineUserInfoHeaderView.m?= =?UTF-8?q?=EF=BC=8C=E8=B0=83=E6=95=B4=E5=8B=8B=E7=AB=A0=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E9=80=BB=E8=BE=91=EF=BC=8C=E5=85=81=E8=AE=B8=E6=9C=80=E5=A4=9A?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=2010=20=E4=B8=AA=E5=8B=8B=E7=AB=A0=EF=BC=9B?= =?UTF-8?q?=E5=9C=A8=20UserRoomCardViewController.m=20=E4=B8=AD=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=20UserRoomCardMedalCell=20=E4=BB=A5=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=8B=8B=E7=AB=A0=E7=9A=84=E5=B1=95=E7=A4=BA=EF=BC=8C=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E5=8B=8B=E7=AB=A0=E5=8C=BA=E5=9F=9F=E7=9A=84=E9=AB=98?= =?UTF-8?q?=E5=BA=A6=E8=AE=A1=E7=AE=97=E5=92=8C=E6=9B=B4=E6=96=B0=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=8C=E4=BF=9D=E6=8C=81=E4=BB=A3=E7=A0=81=E7=BB=93?= =?UTF-8?q?=E6=9E=84=E4=B8=80=E8=87=B4=E6=80=A7=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MineInfo/XPMineUserInfoHeaderView.m | 2 +- .../View/UserRoomCardViewController.m | 302 ++++++++++++++++-- 2 files changed, 285 insertions(+), 19 deletions(-) diff --git a/YuMi/Modules/YMMine/View/SubViews/MineInfo/XPMineUserInfoHeaderView.m b/YuMi/Modules/YMMine/View/SubViews/MineInfo/XPMineUserInfoHeaderView.m index 1b78172e..ce259f3e 100644 --- a/YuMi/Modules/YMMine/View/SubViews/MineInfo/XPMineUserInfoHeaderView.m +++ b/YuMi/Modules/YMMine/View/SubViews/MineInfo/XPMineUserInfoHeaderView.m @@ -1693,7 +1693,7 @@ HWDMP4PlayDelegate> NSMutableArray *mp4Views = [NSMutableArray array]; // 最多显示 10 个勋章 - NSInteger count = MIN(medals.count, 2); + NSInteger count = MIN(medals.count, 10); for (NSInteger i = 0; i < count; i++) { BaseModelVo *medal = medals[i]; UIView *medalContainer = [[UIView alloc] init]; diff --git a/YuMi/Modules/YMRoom/View/UserCard/View/UserRoomCardViewController.m b/YuMi/Modules/YMRoom/View/UserCard/View/UserRoomCardViewController.m index d8bf8969..9fa0c226 100644 --- a/YuMi/Modules/YMRoom/View/UserCard/View/UserRoomCardViewController.m +++ b/YuMi/Modules/YMRoom/View/UserCard/View/UserRoomCardViewController.m @@ -9,6 +9,7 @@ #import "XPUserCardPresenter.h" #import "XPRoomGiftAnimationParser.h" +#import #import "XPSendGiftView.h" #import "XPRoomSendTextView.h" @@ -419,6 +420,213 @@ @end +@interface UserRoomCardMedalCell : UICollectionViewCell + +@property(nonatomic, strong) UserInfoModel *userInfo; +@property(nonatomic, strong) UIScrollView *medalsScrollView; +@property(nonatomic, strong) UIStackView *medalsStackView; +@property(nonatomic, strong) NSArray *medalMP4Views; +@property(nonatomic, strong) MASConstraint *scrollViewWidthConstraint; + ++ (void)registerTo:(UICollectionView *)collectionView; ++ (UserRoomCardMedalCell *)cellFor:(UICollectionView *)collectionView + indexPath:(NSIndexPath *)indexPath + userInfo:(UserInfoModel *)userInfo; + +@end + +@implementation UserRoomCardMedalCell + ++ (void)registerTo:(UICollectionView *)collectionView { + [collectionView registerClass:[self class] + forCellWithReuseIdentifier:NSStringFromClass([self class])]; +} + ++ (UserRoomCardMedalCell *)cellFor:(UICollectionView *)collectionView + indexPath:(NSIndexPath *)indexPath + userInfo:(UserInfoModel *)userInfo { + UserRoomCardMedalCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass([self class]) + forIndexPath:indexPath]; + [cell.contentView setBackgroundColor:[UIColor clearColor]]; + cell.userInfo = userInfo; + return cell; +} + +- (instancetype)initWithFrame:(CGRect)frame { + if (self = [super initWithFrame:frame]) { + [self.contentView addSubview:self.medalsScrollView]; + + // 计算 ScrollView 的宽度:最多显示5个勋章的宽度,但不超过父视图宽度减去边距 + CGFloat maxMedalsWidth = kGetScaleWidth(30) * 5 + 4 * 5; // 5个勋章,4个间距 + + [self.medalsScrollView mas_makeConstraints:^(MASConstraintMaker *make) { + make.centerX.mas_equalTo(self.contentView); + make.centerY.mas_equalTo(self.contentView); + make.height.mas_equalTo(kGetScaleWidth(30)); + // 初始设置一个默认宽度,后续会动态更新 + self.scrollViewWidthConstraint = make.width.mas_equalTo(maxMedalsWidth); + // 确保不超出父视图边界 + make.leading.mas_greaterThanOrEqualTo(self.contentView).offset(15); + make.trailing.mas_lessThanOrEqualTo(self.contentView).offset(-15); + }]; + + [self.medalsScrollView addSubview:self.medalsStackView]; + [self.medalsStackView mas_makeConstraints:^(MASConstraintMaker *make) { + make.top.bottom.leading.mas_equalTo(self.medalsScrollView); + make.height.mas_equalTo(self.medalsScrollView); + // 当内容宽度小于 ScrollView 时,居中显示 + make.centerX.mas_equalTo(self.medalsScrollView).priority(UILayoutPriorityDefaultLow); + }]; + } + return self; +} + +- (void)setUserInfo:(UserInfoModel *)userInfo { + _userInfo = userInfo; + [self updateMedalsDisplay]; +} + +- (void)updateMedalsDisplay { + // 清除旧的勋章视图 + for (UIView *subview in self.medalsStackView.arrangedSubviews) { + [self.medalsStackView removeArrangedSubview:subview]; + [subview removeFromSuperview]; + } + + // 停止之前的MP4播放 + if (self.medalMP4Views) { + for (VAPView *mp4View in self.medalMP4Views) { + [mp4View stopHWDMP4]; + } + } + + NSArray *medals = self.userInfo.medalsPic; + + if (medals.count == 0) { + self.medalsScrollView.hidden = YES; + return; + } else { +#if DEBUG + NSMutableArray *arr = [NSMutableArray arrayWithArray:medals]; + [arr addObjectsFromArray:arr.copy]; + [arr addObjectsFromArray:arr.copy]; + [arr addObjectsFromArray:arr.copy]; + [arr addObjectsFromArray:arr.copy]; + [arr addObjectsFromArray:arr.copy]; + medals = arr.copy; +#endif + } + + self.medalsScrollView.hidden = NO; + + NSMutableArray *mp4Views = [NSMutableArray array]; + NSInteger count = medals.count; // 显示所有勋章,但界面只显示5个 + + for (NSInteger i = 0; i < count; i++) { + BaseModelVo *medal = medals[i]; + + UIView *medalContainer = [[UIView alloc] init]; + medalContainer.backgroundColor = [UIColor clearColor]; + + [self.medalsStackView addArrangedSubview:medalContainer]; + [medalContainer mas_makeConstraints:^(MASConstraintMaker *make) { + make.width.height.mas_equalTo(kGetScaleWidth(30)).priority(UILayoutPriorityRequired); + }]; + + // 根据 picUrl 后缀判断是 MP4 还是图片 + if (![NSString isEmpty:medal.picUrl]) { + BOOL isMP4 = [[medal.picUrl.lowercaseString pathExtension] isEqualToString:@"mp4"]; + + if (isMP4) { + // 显示 MP4 动画 + VAPView *mp4View = [[VAPView alloc] init]; + mp4View.contentMode = UIViewContentModeScaleAspectFit; + mp4View.userInteractionEnabled = NO; + [medalContainer addSubview:mp4View]; + [mp4View mas_makeConstraints:^(MASConstraintMaker *make) { + make.edges.mas_equalTo(medalContainer); + }]; + + [mp4Views addObject:mp4View]; + + XPRoomGiftAnimationParser *parser = [[XPRoomGiftAnimationParser alloc] init]; + [parser parseWithURL:medal.picUrl + completionBlock:^(NSString * _Nullable videoUrl) { + if (videoUrl.length > 0) { + dispatch_async(dispatch_get_main_queue(), ^{ + [mp4View setMute:YES]; + [mp4View playHWDMP4:videoUrl repeatCount:-1 delegate:nil]; + }); + } + } failureBlock:nil]; + } else { + // 显示图片 + NetImageView *imageView = [[NetImageView alloc] init]; + imageView.contentMode = UIViewContentModeScaleAspectFit; + imageView.userInteractionEnabled = NO; + [medalContainer addSubview:imageView]; + [imageView mas_makeConstraints:^(MASConstraintMaker *make) { + make.edges.mas_equalTo(medalContainer); + }]; + [imageView loadImageWithUrl:medal.picUrl completion:nil fail:nil]; + } + } + } + + // 延迟更新 ScrollView 的宽度和 contentSize,确保布局完成 + dispatch_async(dispatch_get_main_queue(), ^{ + [self.medalsStackView layoutIfNeeded]; + CGFloat stackViewWidth = self.medalsStackView.frame.size.width; + if (stackViewWidth == 0) { + // 如果 StackView 还没有布局,计算预期宽度 + CGFloat expectedWidth = count * kGetScaleWidth(30) + (count - 1) * 5; + stackViewWidth = expectedWidth; + } + + // 计算实际需要的 ScrollView 宽度:如果内容少于最大宽度,使用内容宽度;否则使用最大宽度 + CGFloat maxDisplayWidth = kGetScaleWidth(30) * 5 + 4 * 5; // 5个勋章的最大显示宽度 + CGFloat scrollViewWidth = MIN(stackViewWidth, maxDisplayWidth); + + // 更新 ScrollView 的宽度约束 + [self.scrollViewWidthConstraint uninstall]; + [self.medalsScrollView mas_updateConstraints:^(MASConstraintMaker *make) { + self.scrollViewWidthConstraint = make.width.mas_equalTo(scrollViewWidth); + }]; + + self.medalsScrollView.contentSize = CGSizeMake(stackViewWidth, kGetScaleWidth(30)); + [self.contentView layoutIfNeeded]; + }); + + self.medalMP4Views = [mp4Views copy]; +} + +- (UIScrollView *)medalsScrollView { + if (!_medalsScrollView) { + _medalsScrollView = [[UIScrollView alloc] init]; + _medalsScrollView.backgroundColor = [UIColor clearColor]; + _medalsScrollView.showsHorizontalScrollIndicator = NO; + _medalsScrollView.showsVerticalScrollIndicator = NO; + _medalsScrollView.scrollEnabled = YES; + _medalsScrollView.bounces = YES; + _medalsScrollView.alwaysBounceHorizontal = NO; + } + return _medalsScrollView; +} + +- (UIStackView *)medalsStackView { + if (!_medalsStackView) { + _medalsStackView = [[UIStackView alloc] init]; + _medalsStackView.backgroundColor = [UIColor clearColor]; + _medalsStackView.axis = UILayoutConstraintAxisHorizontal; + _medalsStackView.distribution = UIStackViewDistributionFill; + _medalsStackView.alignment = UIStackViewAlignmentCenter; + _medalsStackView.spacing = 5; + } + return _medalsStackView; +} + +@end + @interface UserRoomCardUserActionCell : UICollectionViewCell @property(nonatomic, strong) UserInfoModel *userInfo; @@ -1299,6 +1507,9 @@ #pragma mark - - (void)onGetUserInfoSuccess:(UserInfoModel *)userInfo { +#if DEBUG +// userInfo.medalsPic = @[]; +#endif self.userInfoModel = userInfo; self.avatar.userInfo = userInfo; @kWeakify(self); @@ -1307,6 +1518,9 @@ [self gotoUserInfoVC]; }]; + // 立即更新卡片高度 + [self updateCollectionViewFrame]; + [self.collectionView reloadData]; BOOL needLoadVIPbg = true; @@ -1416,18 +1630,8 @@ self.micActions = temp; [self reloadAllUI]; - if (self.micActions.count == 0) { - self.collectionHeight = 120 + 30 + 60 + ([self configRoomDatingPickHeart] ? 68 : 0) + kSafeAreaBottomHeight; - } else { - self.collectionHeight = 120 + 30 + 60 + 66 + ([self configRoomDatingPickHeart] ? 68 : 0) + kSafeAreaBottomHeight; - } - CGFloat space_top = KScreenHeight - self.collectionHeight; - [UIView animateWithDuration:0.25 animations:^{ - self.collectionView.frame = CGRectMake(0, space_top, KScreenWidth, self.collectionHeight); - } completion:^(BOOL finished) { - self.view.alpha = 1; - [XNDJTDDLoadingTool hideHUD]; - }]; + // 重新计算高度,因为micActions数量可能影响高度 + [self updateCollectionViewFrame]; } -(void)onGetFollowDataSccess{ @@ -1715,7 +1919,7 @@ #pragma mark - - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { - return 4; + return 5; } - (CGSize)collectionView:(UICollectionView *)collectionView @@ -1729,9 +1933,13 @@ return CGSizeMake(self.collectionViewWidth, 30); break; case 2: - return CGSizeMake(self.collectionViewWidth, 60); + return CGSizeMake(self.collectionViewWidth, + self.userInfoModel.medalsPic.count == 0 ? 0 : 40); break; case 3: + return CGSizeMake(self.collectionViewWidth, 60); + break; + case 4: if (self.micActions.count > 0) { return CGSizeMake(self.collectionViewWidth, 38 + 16 + 12 + ([self configRoomDatingPickHeart] ? 36 + 32 : 0)); } else { @@ -1800,6 +2008,14 @@ } break; case 2: { + // 勋章显示 Cell + UserRoomCardMedalCell *cell = [UserRoomCardMedalCell cellFor:collectionView + indexPath:indexPath + userInfo:self.userInfoModel]; + return cell; + } + break; + case 3: { @kWeakify(self); UserRoomCardUserActionCell *cell = [UserRoomCardUserActionCell cellFor:collectionView indexPath:indexPath @@ -1812,7 +2028,7 @@ return cell; } break; - case 3: { + case 4: { @kWeakify(self); UserRoomCardMicActionCell *cell = [UserRoomCardMicActionCell cellFor:collectionView indexPath:indexPath @@ -1858,9 +2074,12 @@ UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; layout.minimumLineSpacing = 0; layout.minimumInteritemSpacing = 0; - self.collectionHeight = 120 + kSafeAreaBottomHeight + 30 + ([self configRoomDatingPickHeart] ? 68 : 0) + kSafeAreaBottomHeight; + + // 初始化时使用基础高度,后续会根据实际数据更新 + self.collectionHeight = [self calculateCollectionViewHeight]; CGFloat space_top = KScreenHeight - self.collectionHeight; self.collectionViewWidth = KScreenWidth; + _collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, space_top, KScreenWidth, @@ -1879,10 +2098,9 @@ _collectionView.clipsToBounds = NO; [UserRoomCardInfoCell registerTo:_collectionView]; [UserRoomCardNameplateCell registerTo:_collectionView]; + [UserRoomCardMedalCell registerTo:_collectionView]; [UserRoomCardUserActionCell registerTo:_collectionView]; [UserRoomCardMicActionCell registerTo:_collectionView]; - -// _collectionView.hidden = YES; } return _collectionView; } @@ -1922,4 +2140,52 @@ return _avatar; } +#pragma mark - Height Calculation +- (CGFloat)calculateCollectionViewHeight { + // 基础高度组成: + // Cell 0 (用户信息): 120 + // Cell 1 (铭牌): 30 + // Cell 2 (勋章): 根据勋章数量决定,0或40 + // Cell 3 (用户操作): 60 + // Cell 4 (麦克风操作): 根据数量决定,0或66 + // 相亲模式额外高度: 68或0 + // 底部安全区域: kSafeAreaBottomHeight + + CGFloat baseHeight = 120 + 30 + 60; // 固定部分 + + // 勋章区域高度 + CGFloat medalHeight = 0; + if (self.userInfoModel && self.userInfoModel.medalsPic.count > 0) { + medalHeight = 40; + } + + // 麦克风操作区域高度 + CGFloat micActionHeight = 0; + if (self.micActions.count > 0) { + micActionHeight = 66; + } + + // 相亲模式额外高度 + CGFloat datingHeight = [self configRoomDatingPickHeart] ? 68 : 0; + + return baseHeight + medalHeight + micActionHeight + datingHeight + kSafeAreaBottomHeight; +} + +- (void)updateCollectionViewFrame { + CGFloat newHeight = [self calculateCollectionViewHeight]; + if (ABS(self.collectionHeight - newHeight) > 0.1) { // 只有高度确实变化时才更新 + self.collectionHeight = newHeight; + CGFloat space_top = KScreenHeight - self.collectionHeight; + + [UIView animateWithDuration:0.25 animations:^{ + self.collectionView.frame = CGRectMake(0, space_top, KScreenWidth, self.collectionHeight); + } completion:^(BOOL finished) { + if (self.userInfoModel) { // 只有用户信息加载完成后才显示 + self.view.alpha = 1; + [XNDJTDDLoadingTool hideHUD]; + } + }]; + } +} + @end