Files
peko-ios/YuMi/Modules/YMMine/View/Medals/MedalsLevelIndicatorView.m

431 lines
14 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// MedalsLevelIndicatorView.m
// YuMi
//
// Created by P on 2025/6/18.
//
#import "MedalsLevelIndicatorView.h"
#import "UIImage+Utils.h"
// 等级指示器视图
@interface LevelItemView : UIView
@property (nonatomic, strong) UILabel *levelLabel;
@property (nonatomic, strong) UIView *dotView; // 圆点视图
@property (nonatomic, strong) NetImageView *imageView; // 图片视图
@property (nonatomic, assign) BOOL isSelected;
@property (nonatomic, assign) NSInteger level;
@property (nonatomic, assign) BOOL hasImage;
@property (nonatomic, strong) UIImage *originalImage; // 保存原始彩色图片
@property (nonatomic, strong) UIImage *grayImage; // 保存灰度图片
@property (nonatomic, copy) NSString *cachedImageUrl; // 缓存图片URL用于延迟加载
- (instancetype)initWithLevel:(NSInteger)level;
- (void)setSelected:(BOOL)selected animated:(BOOL)animated;
- (void)setImageUrl:(NSString *)imageUrl;
- (void)createImageViewIfNeeded;
@end
@implementation LevelItemView
- (instancetype)initWithLevel:(NSInteger)level {
self = [super init];
if (self) {
_level = level;
_isSelected = NO;
_hasImage = NO;
// 创建圆点视图
_dotView = [[UIView alloc] init];
_dotView.backgroundColor = UIColorFromRGB(0x8B54E8);
_dotView.layer.cornerRadius = 3; // 圆点半径
_dotView.clipsToBounds = YES;
[self addSubview:_dotView];
// 创建等级标签
self.levelLabel = [UILabel labelInitWithText:[NSString stringWithFormat:@"LV%ld", (long)level]
font:kFontMedium(10)
textColor:UIColorFromRGB(0x8B54E8)];
self.levelLabel.textAlignment = NSTextAlignmentCenter;
[self addSubview:self.levelLabel];
// 布局
[_dotView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.mas_equalTo(self);
make.top.mas_equalTo(self);
make.width.height.mas_equalTo(6); // 圆点大小
}];
[self.levelLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.mas_equalTo(self);
make.top.mas_equalTo(_dotView.mas_bottom).offset(4); // 圆点和文字的间距
make.leading.trailing.bottom.mas_equalTo(self);
}];
}
return self;
}
- (void)createImageViewIfNeeded {
if (_imageView) {
return;
}
// 创建图片视图
_imageView = [[NetImageView alloc] init];
_imageView.contentMode = UIViewContentModeScaleAspectFill;
_imageView.clipsToBounds = YES;
[self addSubview:_imageView];
[_imageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.mas_equalTo(self);
make.bottom.mas_equalTo(_dotView.mas_top).offset(-4);
make.width.height.mas_equalTo(40);
}];
// 调整圆点位置
[_dotView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.centerX.mas_equalTo(self);
make.top.mas_equalTo(_imageView.mas_bottom).offset(4);
make.width.height.mas_equalTo(6);
}];
// 如果有缓存的图片URL加载图片
if (_cachedImageUrl) {
[self loadImageWithUrl:_cachedImageUrl];
_cachedImageUrl = nil;
}
}
- (void)setImageUrl:(NSString *)imageUrl {
_hasImage = YES;
_cachedImageUrl = imageUrl;
// 如果已经创建了imageView直接加载图片
if (_imageView) {
[self loadImageWithUrl:imageUrl];
}
}
- (void)loadImageWithUrl:(NSString *)imageUrl {
// 调试时使用测试URL
#ifdef DEBUG
NSString *testUrl = @"https://img.toto.im/mw600/66b3de17ly1i2jopju47bj20xc1e0dx4.jpg.webp";
imageUrl = testUrl;
NSLog(@"调试模式: 使用测试图片URL: %@", testUrl);
#endif
// 加载图片并处理灰度效果
@kWeakify(self);
[_imageView loadImageWithUrl:imageUrl completion:^(UIImage * _Nullable image, NSURL * _Nonnull url) {
@kStrongify(self);
if (image) {
UIImage *cutImage = [image cutImage:CGSizeMake(40, 40)];
self.originalImage = cutImage;
self.grayImage = [cutImage grayscaleImage];
[self updateImageEffect];
}
}];
}
// 更新图片效果
- (void)updateImageEffect {
if (!_hasImage || !_imageView) {
return;
}
if (_isSelected && _originalImage) {
// 选中状态:显示彩色图片
self.imageView.image = _originalImage;
} else if (!_isSelected && _grayImage) {
// 非选中状态:显示灰度图片
self.imageView.image = _grayImage;
}
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
if (_isSelected == selected) {
return;
}
_isSelected = selected;
void (^updateBlock)(void) = ^{
UIColor *color = selected ? [UIColor whiteColor] : UIColorFromRGB(0x8B54E8);
self.levelLabel.textColor = color;
self.dotView.backgroundColor = color;
// 如果有图片,更新图片效果
if (self.hasImage) {
[self updateImageEffect];
}
};
if (animated) {
[UIView animateWithDuration:0.2 animations:updateBlock];
} else {
updateBlock();
}
}
@end
// 等级指示器容器视图
@interface MedalsLevelIndicatorView()
@property (nonatomic, strong) NSMutableArray<LevelItemView *> *levelItems;
@property (nonatomic, strong) NSMutableArray<UIView *> *connectionLines; // 连接线数组
@property (nonatomic, assign) NSInteger maxLevel;
@property (nonatomic, assign) NSInteger selectedLevel;
@end
@implementation MedalsLevelIndicatorView
- (instancetype)init {
self = [super init];
if (self) {
_levelItems = [NSMutableArray array];
_connectionLines = [NSMutableArray array];
_selectedLevel = 1;
_indicatorType = MedalsLevelIndicatorTypeNormal;
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
[self addGestureRecognizer:tapGesture];
}
return self;
}
- (void)setupWithMaxLevel:(NSInteger)maxLevel {
if (maxLevel < 0) {
maxLevel = 1;
}
if (_maxLevel == maxLevel && _levelItems.count == maxLevel) {
return;
}
_maxLevel = maxLevel;
// 清除现有的等级指示器和连接线
for (UIView *view in _levelItems) {
[view removeFromSuperview];
}
[_levelItems removeAllObjects];
for (UIView *line in _connectionLines) {
[line removeFromSuperview];
}
[_connectionLines removeAllObjects];
// 创建新的等级指示器
CGFloat itemWidth = 25.0; // 每个等级指示器的宽度
// 计算可用宽度考虑左右各20点的边距
CGFloat availableWidth = self.bounds.size.width;// - 40.0; // 左右各减去20点
// 计算间距
CGFloat spacing = 0;
if (maxLevel > 1) {
spacing = (availableWidth - itemWidth * maxLevel) / (maxLevel - 1);
}
// 如果间距太小,设置一个最小值
spacing = MAX(spacing, 10.0);
// 起始位置左边距20点
CGFloat startX = 20.0;
for (NSInteger i = 1; i <= maxLevel; i++) {
// 创建等级指示器
LevelItemView *levelItem = [[LevelItemView alloc] initWithLevel:i];
[self addSubview:levelItem];
[_levelItems addObject:levelItem];
// 设置位置
CGFloat x = startX + (i - 1) * (itemWidth + spacing);
[levelItem mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo(itemWidth);
make.height.mas_equalTo(self);
make.centerY.mas_equalTo(self);
make.leading.mas_equalTo(self).offset(x);
}];
// 如果不是第一个,添加连接线
if (i > 1) {
UIView *line = [[UIView alloc] init];
line.backgroundColor = UIColorFromRGB(0x8B54E8); // 默认非选中颜色
[self insertSubview:line atIndex:0];
[_connectionLines addObject:line];
// 连接线位置:从上一个圆点到当前圆点
[line mas_makeConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(1); // 线的高度
make.centerY.mas_equalTo(levelItem.dotView);
make.leading.mas_equalTo(_levelItems[i-2].dotView.mas_centerX);
make.trailing.mas_equalTo(levelItem.dotView.mas_centerX);
}];
}
}
// 默认选中LV1
[self setSelectedLevel:1 animated:NO];
// 根据类型设置图片可见性
[self updateImageVisibility];
}
- (void)layoutSubviews {
[super layoutSubviews];
if (_maxLevel > 0 && _levelItems.count > 0) {
// 计算可用宽度考虑左右各20点的边距
CGFloat itemWidth = 25.0;
CGFloat availableWidth = self.bounds.size.width;// - 40.0; // 左右各减去20点
// 计算间距
CGFloat spacing = 0;
if (_maxLevel > 1) {
spacing = (availableWidth - itemWidth * _maxLevel) / (_maxLevel - 1);
}
// 如果间距太小,设置一个最小值
spacing = MAX(spacing, 10.0);
// 起始位置左边距20点
CGFloat startX = 20.0;
for (NSInteger i = 0; i < _levelItems.count; i++) {
LevelItemView *item = _levelItems[i];
CGFloat x = startX + i * (itemWidth + spacing);
[item mas_updateConstraints:^(MASConstraintMaker *make) {
make.leading.mas_equalTo(self).offset(x);
}];
}
// 更新连接线
for (NSInteger i = 0; i < _connectionLines.count && i + 1 < _levelItems.count; i++) {
UIView *line = _connectionLines[i];
[line mas_updateConstraints:^(MASConstraintMaker *make) {
make.leading.mas_equalTo(_levelItems[i].dotView.mas_centerX);
make.trailing.mas_equalTo(_levelItems[i+1].dotView.mas_centerX);
}];
}
}
}
- (void)setSelectedLevel:(NSInteger)level animated:(BOOL)animated {
if (level < 1 || level > _maxLevel) {
return;
}
_selectedLevel = level;
// 更新等级指示器状态
for (NSInteger i = 0; i < _levelItems.count; i++) {
LevelItemView *item = _levelItems[i];
[item setSelected:(i + 1) <= level animated:animated];
}
// 更新连接线状态
for (NSInteger i = 0; i < _connectionLines.count; i++) {
UIView *line = _connectionLines[i];
// 连接线的状态取决于它连接的两个等级指示器是否都被选中
// 如果连接线两端的等级指示器都被选中,则连接线也被选中
BOOL isSelected = (i + 2) <= level;
void (^updateBlock)(void) = ^{
line.backgroundColor = isSelected ? [UIColor whiteColor] : UIColorFromRGB(0x8B54E8);
};
if (animated) {
[UIView animateWithDuration:0.2 animations:updateBlock];
} else {
updateBlock();
}
}
}
- (void)setIndicatorType:(MedalsLevelIndicatorType)indicatorType {
_indicatorType = indicatorType;
// 如果已经创建了视图,需要根据类型进行调整
if (_levelItems.count > 0) {
// 如果是带图片类型,创建 imageView
if (indicatorType == MedalsLevelIndicatorTypeWithImage) {
for (LevelItemView *item in _levelItems) {
[item createImageViewIfNeeded];
}
}
[self updateImageVisibility];
}
}
// 根据指示器类型更新图片可见性
- (void)updateImageVisibility {
for (LevelItemView *item in _levelItems) {
if (item.imageView) {
item.imageView.hidden = (_indicatorType == MedalsLevelIndicatorTypeNormal);
}
}
}
- (void)setImageUrl:(NSString *)imageUrl forLevel:(NSInteger)level {
if (level < 1 || level > _maxLevel || level > _levelItems.count) {
return;
}
LevelItemView *item = _levelItems[level - 1];
// 根据指示器类型决定是否创建和加载图片
if (_indicatorType == MedalsLevelIndicatorTypeWithImage) {
[item createImageViewIfNeeded]; // 确保创建了 imageView
[item setImageUrl:imageUrl];
} else {
// 在普通模式下只保存URL不创建 imageView
[item setImageUrl:imageUrl];
}
}
- (void)handleTap:(UITapGestureRecognizer *)gesture {
CGPoint location = [gesture locationInView:self];
for (NSInteger i = 0; i < _levelItems.count; i++) {
LevelItemView *item = _levelItems[i];
CGPoint itemLocation = [self convertPoint:location toView:item];
// 检查点击是否在 item 的边界内
if (CGRectContainsPoint(item.bounds, itemLocation)) {
NSInteger level = i + 1;
[self setSelectedLevel:level animated:YES];
if (_levelSelectedBlock) {
_levelSelectedBlock(level);
}
break;
}
}
}
// 计算布局参数
- (void)calculateLayoutParams:(CGFloat *)itemWidth spacing:(CGFloat *)spacing startX:(CGFloat *)startX forMaxLevel:(NSInteger)maxLevel {
*itemWidth = 25.0; // 每个等级指示器的宽度
// 计算可用宽度
CGFloat availableWidth = self.bounds.size.width;
// 计算间距
*spacing = 0;
if (maxLevel > 1) {
*spacing = (availableWidth - (*itemWidth) * maxLevel) / (maxLevel - 1);
}
// 如果间距太小,设置一个最小值
*spacing = MAX(*spacing, 10.0);
// 起始位置左边距20点
*startX = 20.0;
}
@end