This commit is contained in:
2025-11-06 19:19:12 +08:00
parent a75afbe4c1
commit 6ba1339c0b
195 changed files with 13443 additions and 2729 deletions

View File

@@ -0,0 +1,14 @@
//
// JXCategoryIndicatorBackgroundView.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/17.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorComponentView.h"
/// BackgroundView 样式的指示器
@interface JXCategoryIndicatorBackgroundView : JXCategoryIndicatorComponentView
@end

View File

@@ -0,0 +1,101 @@
//
// JXCategoryIndicatorBackgroundView.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/17.
// Copyright © 2018 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorBackgroundView.h"
#import "JXCategoryFactory.h"
@implementation JXCategoryIndicatorBackgroundView
#pragma mark - Initialize
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self configureDefaulteValue];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
[self configureDefaulteValue];
}
return self;
}
- (void)configureDefaulteValue {
self.indicatorWidth = JXCategoryViewAutomaticDimension;
self.indicatorHeight = JXCategoryViewAutomaticDimension;
self.indicatorCornerRadius = JXCategoryViewAutomaticDimension;
self.indicatorColor = [UIColor lightGrayColor];
self.indicatorWidthIncrement = 10;
}
#pragma mark - JXCategoryIndicatorProtocol
- (void)jx_refreshState:(JXCategoryIndicatorParamsModel *)model {
self.layer.cornerRadius = [self indicatorCornerRadiusValue:model.selectedCellFrame];
self.backgroundColor = self.indicatorColor;
CGFloat width = [self indicatorWidthValue:model.selectedCellFrame];
CGFloat height = [self indicatorHeightValue:model.selectedCellFrame];
CGFloat x = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - width)/2;
CGFloat y = (model.selectedCellFrame.size.height - height)/2 - self.verticalMargin;
self.frame = CGRectMake(x, y, width, height);
}
- (void)jx_contentScrollViewDidScroll:(JXCategoryIndicatorParamsModel *)model {
CGRect rightCellFrame = model.rightCellFrame;
CGRect leftCellFrame = model.leftCellFrame;
CGFloat percent = model.percent;
CGFloat targetX = 0;
CGFloat targetWidth = [self indicatorWidthValue:leftCellFrame];
if (percent == 0) {
targetX = leftCellFrame.origin.x + (leftCellFrame.size.width - targetWidth)/2.0;
}else {
CGFloat leftWidth = targetWidth;
CGFloat rightWidth = [self indicatorWidthValue:rightCellFrame];
CGFloat leftX = leftCellFrame.origin.x + (leftCellFrame.size.width - leftWidth)/2;
CGFloat rightX = rightCellFrame.origin.x + (rightCellFrame.size.width - rightWidth)/2;
targetX = [JXCategoryFactory interpolationFrom:leftX to:rightX percent:percent];
if (self.indicatorWidth == JXCategoryViewAutomaticDimension) {
targetWidth = [JXCategoryFactory interpolationFrom:leftWidth to:rightWidth percent:percent];
}
}
//frame12
if (self.isScrollEnabled == YES || (self.isScrollEnabled == NO && percent == 0)) {
CGRect toFrame = self.frame;
toFrame.origin.x = targetX;
toFrame.size.width = targetWidth;
self.frame = toFrame;
}
}
- (void)jx_selectedCell:(JXCategoryIndicatorParamsModel *)model {
CGFloat width = [self indicatorWidthValue:model.selectedCellFrame];
CGRect toFrame = self.frame;
toFrame.origin.x = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - width)/2;
toFrame.size.width = width;
if (self.isScrollEnabled) {
[UIView animateWithDuration:self.scrollAnimationDuration delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
self.frame = toFrame;
} completion:^(BOOL finished) {
}];
}else {
self.frame = toFrame;
}
}
@end

View File

@@ -0,0 +1,17 @@
//
// JXCategoryIndicatorBallView.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/21.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorComponentView.h"
/// QQ 小红点样式的指示器
@interface JXCategoryIndicatorBallView : JXCategoryIndicatorComponentView
// 球沿的 X 轴方向上的偏移量。默认值为 20
@property (nonatomic, assign) CGFloat ballScrollOffsetX;
@end

View File

@@ -0,0 +1,199 @@
//
// JXCategoryIndicatorBallView.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/21.
// Copyright © 2018 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorBallView.h"
#import "JXCategoryFactory.h"
@interface JXCategoryIndicatorBallView ()
@property (nonatomic, strong) UIView *smallBall;
@property (nonatomic, strong) UIView *bigBall;
@property (nonatomic, strong) CAShapeLayer *shapeLayer;
@end
@implementation JXCategoryIndicatorBallView
#pragma mark - Initialize
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self configureIndicatorBall];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
[self configureIndicatorBall];
}
return self;
}
- (void)configureIndicatorBall {
self.indicatorWidth = 15;
self.indicatorHeight = 15;
_ballScrollOffsetX = 20;
_smallBall = [[UIView alloc] init];
[self addSubview:self.smallBall];
_bigBall = [[UIView alloc] init];
[self addSubview:self.bigBall];
_shapeLayer = [CAShapeLayer layer];
[self.layer addSublayer:self.shapeLayer];
}
#pragma mark - JXCategoryIndicatorProtocol
- (void)jx_refreshState:(JXCategoryIndicatorParamsModel *)model {
CGFloat ballWidth = [self indicatorWidthValue:model.selectedCellFrame];
CGFloat ballHeight = [self indicatorHeightValue:model.selectedCellFrame];
[CATransaction begin];
[CATransaction setDisableActions:YES];
self.shapeLayer.fillColor = self.indicatorColor.CGColor;
[CATransaction commit];
self.smallBall.backgroundColor = self.indicatorColor;
self.smallBall.layer.cornerRadius = ballHeight/2;
self.bigBall.backgroundColor = self.indicatorColor;
self.bigBall.layer.cornerRadius = ballHeight/2;
CGFloat x = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - ballWidth)/2;
CGFloat y = self.superview.bounds.size.height - ballHeight - self.verticalMargin;
if (self.componentPosition == JXCategoryComponentPosition_Top) {
y = self.verticalMargin;
}
self.smallBall.frame = CGRectMake(x, y, ballWidth, ballHeight);
self.bigBall.frame = CGRectMake(x, y, ballWidth, ballHeight);
}
- (void)jx_contentScrollViewDidScroll:(JXCategoryIndicatorParamsModel *)model {
CGFloat ballWidth = [self indicatorWidthValue:model.leftCellFrame];
CGFloat ballHeight = [self indicatorHeightValue:model.leftCellFrame];
CGRect rightCellFrame = model.rightCellFrame;
CGRect leftCellFrame = model.leftCellFrame;
CGFloat percent = model.percent;
CGFloat targetXOfBigBall = 0;
CGFloat targetXOfSmallBall = leftCellFrame.origin.x + (leftCellFrame.size.width - ballWidth)/2;
CGFloat targetWidthOfSmallBall = ballWidth;
if (percent == 0) {
targetXOfBigBall = leftCellFrame.origin.x + (leftCellFrame.size.width - ballWidth)/2.0;
targetXOfSmallBall = leftCellFrame.origin.x + (leftCellFrame.size.width - targetWidthOfSmallBall)/2.0;
}else {
CGFloat leftX = leftCellFrame.origin.x + (leftCellFrame.size.width - ballWidth)/2;
CGFloat rightX = rightCellFrame.origin.x + (rightCellFrame.size.width - ballWidth)/2;
//50%bigBallxsmallBall50%bigBallxsmallBallsmallBallx
if (percent <= 0.5) {
targetXOfBigBall = [JXCategoryFactory interpolationFrom:leftX to:(rightX - self.ballScrollOffsetX) percent:percent*2];
targetWidthOfSmallBall = [JXCategoryFactory interpolationFrom:ballWidth to:ballWidth/2 percent:percent*2];
}else {
targetXOfBigBall = [JXCategoryFactory interpolationFrom:(rightX - self.ballScrollOffsetX) to:rightX percent:(percent - 0.5)*2];
targetWidthOfSmallBall = [JXCategoryFactory interpolationFrom:ballWidth/2 to:0 percent:(percent - 0.5)*2];
targetXOfSmallBall = [JXCategoryFactory interpolationFrom:leftX to:rightX percent:(percent - 0.5)*2];
}
}
//frame12
if (self.isScrollEnabled == YES || (self.isScrollEnabled == NO && percent == 0)) {
CGRect bigBallFrame = self.bigBall.frame;
bigBallFrame.origin.x = targetXOfBigBall;
self.bigBall.frame = bigBallFrame;
self.bigBall.layer.cornerRadius = bigBallFrame.size.height/2;
CGFloat targetYOfSmallBall = self.superview.bounds.size.height - ballHeight/2 - targetWidthOfSmallBall/2 - self.verticalMargin;
if (self.componentPosition == JXCategoryComponentPosition_Top) {
targetYOfSmallBall = ballHeight/2 - targetWidthOfSmallBall/2 + self.verticalMargin;
}
self.smallBall.frame = CGRectMake(targetXOfSmallBall, targetYOfSmallBall, targetWidthOfSmallBall, targetWidthOfSmallBall);
self.smallBall.layer.cornerRadius = targetWidthOfSmallBall/2;
[CATransaction begin];
[CATransaction setDisableActions:YES];
self.shapeLayer.path = [self getBezierPathWithSmallCir:self.smallBall andBigCir:self.bigBall].CGPath;
[CATransaction commit];
}
}
- (void)jx_selectedCell:(JXCategoryIndicatorParamsModel *)model {
CGFloat ballWidth = [self indicatorWidthValue:model.selectedCellFrame];
CGFloat ballHeight = [self indicatorHeightValue:model.selectedCellFrame];
CGFloat x = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - ballWidth)/2;
CGFloat y = self.superview.bounds.size.height - ballHeight - self.verticalMargin;
if (self.componentPosition == JXCategoryComponentPosition_Top) {
y = self.verticalMargin;
}
CGRect toFrame = CGRectMake(x, y, ballWidth, ballHeight);
if (self.isScrollEnabled) {
[UIView animateWithDuration:self.scrollAnimationDuration delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.smallBall.frame = toFrame;
self.bigBall.frame = toFrame;
self.smallBall.layer.cornerRadius = ballHeight/2;
self.bigBall.layer.cornerRadius = ballHeight/2;
} completion:^(BOOL finished) {
}];
}else {
self.smallBall.frame = toFrame;
self.bigBall.frame = toFrame;
self.smallBall.layer.cornerRadius = ballHeight/2;
self.bigBall.layer.cornerRadius = ballHeight/2;
}
}
- (UIBezierPath *)getBezierPathWithSmallCir:(UIView *)smallCir andBigCir:(UIView *)bigCir{
//
if (bigCir.frame.size.width < smallCir.frame.size.width) {
UIView *view = bigCir;
bigCir = smallCir;
smallCir = view;
}
//
CGFloat d = self.bigBall.center.x - self.smallBall.center.x;
if (d == 0) {
return nil;
}
CGFloat x1 = smallCir.center.x;
CGFloat y1 = smallCir.center.y;
CGFloat r1 = smallCir.bounds.size.width/2;
//
CGFloat x2 = bigCir.center.x;
CGFloat y2 = bigCir.center.y;
CGFloat r2 = bigCir.bounds.size.width/2;
//
CGFloat sinA = (y2 - y1)/d;
CGFloat cosA = (x2 - x1)/d;
//
CGPoint pointA = CGPointMake(x1 - sinA*r1, y1 + cosA * r1);
CGPoint pointB = CGPointMake(x1 + sinA*r1, y1 - cosA * r1);
CGPoint pointC = CGPointMake(x2 + sinA*r2, y2 - cosA * r2);
CGPoint pointD = CGPointMake(x2 - sinA*r2, y2 + cosA * r2);
// 便线
CGPoint pointO = CGPointMake(pointA.x + d / 2 * cosA , pointA.y + d / 2 * sinA);
CGPoint pointP = CGPointMake(pointB.x + d / 2 * cosA , pointB.y + d / 2 * sinA);
//
UIBezierPath *path =[UIBezierPath bezierPath];
[path moveToPoint:pointA];
[path addLineToPoint:pointB];
[path addQuadCurveToPoint:pointC controlPoint:pointP];
[path addLineToPoint:pointD];
[path addQuadCurveToPoint:pointA controlPoint:pointO];
return path;
}
@end

View File

@@ -0,0 +1,113 @@
//
// JXCategoryComponentBaseView.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/17.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "JXCategoryIndicatorProtocol.h"
#import "JXCategoryViewDefines.h"
@interface JXCategoryIndicatorComponentView : UIView <JXCategoryIndicatorProtocol>
/**
指示器的位置
可设置的枚举类型:
- 底部JXCategoryComponentPosition_Bottom
- 顶部JXCategoryComponentPosition_Top
*/
@property (nonatomic, assign) JXCategoryComponentPosition componentPosition;
/**
指示器的宽度
默认值为 JXCategoryViewAutomaticDimension表示与 cell 的宽度相等)。
内部通过 `- (CGFloat)indicatorWidthValue:(CGRect)cellFrame` 方法获取实际的值。
*/
@property (nonatomic, assign) CGFloat indicatorWidth;
/**
指示器的宽度增量
例如:需求是指示器宽度比 cell 宽度多 10pt。就可以将该属性赋值为 10。
最终指示器的宽度 = indicatorWidth + indicatorWidthIncrement。
*/
@property (nonatomic, assign) CGFloat indicatorWidthIncrement;
/**
指示器的高度
默认值为 3。
内部通过 `- (CGFloat)indicatorHeightValue:(CGRect)cellFrame` 方法获取实际的值。
*/
@property (nonatomic, assign) CGFloat indicatorHeight;
/**
指示器的 CornerRadius 圆角半径
默认值为 JXCategoryViewAutomaticDimension (等于 indicatorHeight/2
内部通过 `- (CGFloat)indicatorCornerRadiusValue:(CGRect)cellFrame` 方法获取实际的值。
*/
@property (nonatomic, assign) CGFloat indicatorCornerRadius;
/**
指示器的颜色
*/
@property (nonatomic, strong) UIColor *indicatorColor;
/**
指示器在垂直方向上的偏移量
数值越大越靠近中心。默认值为 0。
*/
@property (nonatomic, assign) CGFloat verticalMargin;
/**
是否允许手势滚动
点击切换的时候,是否允许滚动,默认值为 YES。
*/
@property (nonatomic, assign, getter=isScrollEnabled) BOOL scrollEnabled;
/**
指示器滚动样式
点击切换的时候,如果允许滚动,分为简单滚动和复杂滚动。
默认值为 JXCategoryIndicatorScrollStyleSimple
目前仅JXCategoryIndicatorLineView、JXCategoryIndicatorDotLineView支持其他子类暂不支持。
*/
@property (nonatomic, assign) JXCategoryIndicatorScrollStyle scrollStyle;
/**
滚动动画的时间,默认值为 0.25s
*/
@property (nonatomic, assign) NSTimeInterval scrollAnimationDuration;
/**
传入 cellFrame 获取指示器的最终宽度
@param cellFrame cellFrame
@return 指示器的最终宽度
*/
- (CGFloat)indicatorWidthValue:(CGRect)cellFrame;
/**
传入 cellFrame 获取指示器的最终高度
@param cellFrame cellFrame
@return 指示器的最终高度
*/
- (CGFloat)indicatorHeightValue:(CGRect)cellFrame;
/**
传入 cellFrame 获取指示器的最终圆角
@param cellFrame cellFrame
@return 指示器的最终圆角
*/
- (CGFloat)indicatorCornerRadiusValue:(CGRect)cellFrame;
@end

View File

@@ -0,0 +1,81 @@
//
// JXCategoryComponentBaseView.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/17.
// Copyright © 2018 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorComponentView.h"
@implementation JXCategoryIndicatorComponentView
#pragma mark - Initialize
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self configureDefaultValue];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
[self configureDefaultValue];
}
return self;
}
- (void)configureDefaultValue {
_componentPosition = JXCategoryComponentPosition_Bottom;
_scrollEnabled = YES;
_verticalMargin = 0;
_scrollAnimationDuration = 0.25;
_indicatorWidth = JXCategoryViewAutomaticDimension;
_indicatorWidthIncrement = 0;
_indicatorHeight = 3;
_indicatorCornerRadius = JXCategoryViewAutomaticDimension;
_indicatorColor = [UIColor redColor];
_scrollStyle = JXCategoryIndicatorScrollStyleSimple;
}
#pragma mark - Public
- (CGFloat)indicatorWidthValue:(CGRect)cellFrame {
if (self.indicatorWidth == JXCategoryViewAutomaticDimension) {
return cellFrame.size.width + self.indicatorWidthIncrement;
}
return self.indicatorWidth + self.indicatorWidthIncrement;
}
- (CGFloat)indicatorHeightValue:(CGRect)cellFrame {
if (self.indicatorHeight == JXCategoryViewAutomaticDimension) {
return cellFrame.size.height;
}
return self.indicatorHeight;
}
- (CGFloat)indicatorCornerRadiusValue:(CGRect)cellFrame {
if (self.indicatorCornerRadius == JXCategoryViewAutomaticDimension) {
return [self indicatorHeightValue:cellFrame]/2;
}
return self.indicatorCornerRadius;
}
#pragma mark - JXCategoryIndicatorProtocol
- (void)jx_refreshState:(JXCategoryIndicatorParamsModel *)model {
}
- (void)jx_contentScrollViewDidScroll:(JXCategoryIndicatorParamsModel *)model {
}
- (void)jx_selectedCell:(JXCategoryIndicatorParamsModel *)model {
}
@end

View File

@@ -0,0 +1,17 @@
//
// JXCategoryIndicatorDotLineView.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/22.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorComponentView.h"
/// 点线效果的指示器
@interface JXCategoryIndicatorDotLineView : JXCategoryIndicatorComponentView
// 线状态的最大宽度,默认值为 50
@property (nonatomic, assign) CGFloat lineWidth;
@end

View File

@@ -0,0 +1,148 @@
//
// JXCategoryIndicatorDotLineView.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/22.
// Copyright © 2018 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorDotLineView.h"
#import "JXCategoryFactory.h"
#import "JXCategoryViewAnimator.h"
@interface JXCategoryIndicatorDotLineView ()
@property (nonatomic, strong) JXCategoryViewAnimator *animator;
@end
@implementation JXCategoryIndicatorDotLineView
#pragma mark - Initialize
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self configureDefaulteValue];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
[self configureDefaulteValue];
}
return self;
}
- (void)configureDefaulteValue {
self.indicatorWidth = 10;
self.indicatorHeight = 10;
_lineWidth = 50;
}
#pragma mark - JXCategoryIndicatorProtocol
- (void)jx_refreshState:(JXCategoryIndicatorParamsModel *)model {
CGFloat dotWidth = [self indicatorWidthValue:model.selectedCellFrame];
CGFloat dotHeight = [self indicatorHeightValue:model.selectedCellFrame];
self.backgroundColor = self.indicatorColor;
self.layer.cornerRadius = [self indicatorHeightValue:model.selectedCellFrame]/2;
CGFloat x = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - dotWidth)/2;
CGFloat y = self.superview.bounds.size.height - dotHeight - self.verticalMargin;
if (self.componentPosition == JXCategoryComponentPosition_Top) {
y = self.verticalMargin;
}
self.frame = CGRectMake(x, y, dotWidth, dotHeight);
}
- (void)jx_contentScrollViewDidScroll:(JXCategoryIndicatorParamsModel *)model {
if (self.animator.isExecuting) {
[self.animator invalid];
self.animator = nil;
}
CGFloat dotWidth = [self indicatorWidthValue:model.selectedCellFrame];
CGRect rightCellFrame = model.rightCellFrame;
CGRect leftCellFrame = model.leftCellFrame;
CGFloat percent = model.percent;
CGFloat targetX = 0;
CGFloat targetWidth = dotWidth;
CGFloat leftWidth = dotWidth;
CGFloat rightWidth = dotWidth;
CGFloat leftX = leftCellFrame.origin.x + (leftCellFrame.size.width - leftWidth)/2;
CGFloat rightX = rightCellFrame.origin.x + (rightCellFrame.size.width - rightWidth)/2;
CGFloat centerX = leftX + (rightX - leftX - self.lineWidth)/2;
//50%x50%xwidth
if (percent <= 0.5) {
targetX = [JXCategoryFactory interpolationFrom:leftX to:centerX percent:percent*2];
targetWidth = [JXCategoryFactory interpolationFrom:dotWidth to:self.lineWidth percent:percent*2];
}else {
targetX = [JXCategoryFactory interpolationFrom:centerX to:rightX percent:(percent - 0.5)*2];
targetWidth = [JXCategoryFactory interpolationFrom:self.lineWidth to:dotWidth percent:(percent - 0.5)*2];
}
//frame12
if (self.isScrollEnabled == YES || (self.isScrollEnabled == NO && percent == 0)) {
CGRect frame = self.frame;
frame.origin.x = targetX;
frame.size.width = targetWidth;
self.frame = frame;
}
}
- (void)jx_selectedCell:(JXCategoryIndicatorParamsModel *)model {
CGFloat dotWidth = [self indicatorWidthValue:model.selectedCellFrame];
CGFloat x = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - dotWidth)/2;
CGRect targetIndicatorFrame = self.frame;
targetIndicatorFrame.origin.x = x;
if (self.isScrollEnabled) {
if (self.scrollStyle == JXCategoryIndicatorScrollStyleSameAsUserScroll && (model.selectedType == JXCategoryCellSelectedTypeClick | model.selectedType == JXCategoryCellSelectedTypeCode)) {
if (self.animator.isExecuting) {
[self.animator invalid];
self.animator = nil;
}
CGFloat leftX = 0;
CGFloat rightX = 0;
BOOL isNeedReversePercent = NO;
if (self.frame.origin.x > model.selectedCellFrame.origin.x) {
leftX = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - dotWidth)/2;;
rightX = self.frame.origin.x;
isNeedReversePercent = YES;
}else {
leftX = self.frame.origin.x;
rightX = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - dotWidth)/2;
}
CGFloat centerX = leftX + (rightX - leftX - self.lineWidth)/2;
__weak typeof(self) weakSelf = self;
self.animator = [[JXCategoryViewAnimator alloc] init];
self.animator.progressCallback = ^(CGFloat percent) {
if (isNeedReversePercent) {
percent = 1 - percent;
}
CGFloat targetX = 0;
CGFloat targetWidth = 0;
if (percent <= 0.5) {
targetX = [JXCategoryFactory interpolationFrom:leftX to:centerX percent:percent*2];
targetWidth = [JXCategoryFactory interpolationFrom:dotWidth to:self.lineWidth percent:percent*2];
}else {
targetX = [JXCategoryFactory interpolationFrom:centerX to:rightX percent:(percent - 0.5)*2];
targetWidth = [JXCategoryFactory interpolationFrom:self.lineWidth to:dotWidth percent:(percent - 0.5)*2];
}
CGRect toFrame = weakSelf.frame;
toFrame.origin.x = targetX;
toFrame.size.width = targetWidth;
weakSelf.frame = toFrame;
};
[self.animator start];
}else if (self.scrollStyle == JXCategoryIndicatorScrollStyleSimple) {
[UIView animateWithDuration:self.scrollAnimationDuration delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.frame = targetIndicatorFrame;
} completion: nil];
}
}else {
self.frame = targetIndicatorFrame;
}
}
@end

View File

@@ -0,0 +1,20 @@
//
// JXCategoryIndicatorImageView.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/17.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorComponentView.h"
@interface JXCategoryIndicatorImageView : JXCategoryIndicatorComponentView
// 指示器图片
@property (nonatomic, strong, readonly) UIImageView *indicatorImageView;
// 图片是否开启滚动,默认值为 NO
@property (nonatomic, assign) BOOL indicatorImageViewRollEnabled;
// 图片的尺寸,默认值为 CGSizeMake(30, 20)
@property (nonatomic, assign) CGSize indicatorImageViewSize;
@end

View File

@@ -0,0 +1,117 @@
//
// JXCategoryIndicatorImageView.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/17.
// Copyright © 2018 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorImageView.h"
#import "JXCategoryFactory.h"
@implementation JXCategoryIndicatorImageView
#pragma mark - Initialize
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setupIndicatorImageView];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
[self setupIndicatorImageView];
}
return self;
}
- (void)setupIndicatorImageView {
_indicatorImageViewSize = CGSizeMake(30, 20);
_indicatorImageViewRollEnabled = NO;
_indicatorImageView = [[UIImageView alloc] init];
self.indicatorImageView.frame = CGRectMake(0, 0, self.indicatorImageViewSize.width, self.indicatorImageViewSize.height);
self.indicatorImageView.contentMode = UIViewContentModeScaleAspectFit;
[self addSubview:self.indicatorImageView];
}
#pragma mark - Custom Accessors
- (void)setIndicatorImageViewSize:(CGSize)indicatorImageViewSize {
_indicatorImageViewSize = indicatorImageViewSize;
self.indicatorImageView.frame = CGRectMake(0, 0, self.indicatorImageViewSize.width, self.indicatorImageViewSize.height);
}
#pragma mark - JXCategoryIndicatorProtocol
- (void)jx_refreshState:(JXCategoryIndicatorParamsModel *)model {
CGFloat x = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - self.indicatorImageViewSize.width)/2;
CGFloat y = self.superview.bounds.size.height - self.indicatorImageViewSize.height - self.verticalMargin;
if (self.componentPosition == JXCategoryComponentPosition_Top) {
y = self.verticalMargin;
}
self.frame = CGRectMake(x, y, self.indicatorImageViewSize.width, self.indicatorImageViewSize.height);
}
- (void)jx_contentScrollViewDidScroll:(JXCategoryIndicatorParamsModel *)model {
CGRect rightCellFrame = model.rightCellFrame;
CGRect leftCellFrame = model.leftCellFrame;
CGFloat percent = model.percent;
CGFloat targetWidth = self.indicatorImageViewSize.width;
CGFloat targetX = 0;
if (percent == 0) {
targetX = leftCellFrame.origin.x + (leftCellFrame.size.width - targetWidth)/2.0;
}else {
CGFloat leftX = leftCellFrame.origin.x + (leftCellFrame.size.width - targetWidth)/2;
CGFloat rightX = rightCellFrame.origin.x + (rightCellFrame.size.width - targetWidth)/2;
targetX = [JXCategoryFactory interpolationFrom:leftX to:rightX percent:percent];
}
//frame12
if (self.isScrollEnabled == YES || (self.isScrollEnabled == NO && percent == 0)) {
CGRect frame = self.frame;
frame.origin.x = targetX;
self.frame = frame;
if (self.indicatorImageViewRollEnabled) {
self.indicatorImageView.transform = CGAffineTransformMakeRotation(M_PI*2*percent);
}
}
}
- (void)jx_selectedCell:(JXCategoryIndicatorParamsModel *)model {
CGRect toFrame = self.frame;
toFrame.origin.x = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - self.indicatorImageViewSize.width)/2;
if (self.isScrollEnabled) {
[UIView animateWithDuration:self.scrollAnimationDuration delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
self.frame = toFrame;
} completion:^(BOOL finished) {
}];
if (self.indicatorImageViewRollEnabled && (model.selectedType == JXCategoryCellSelectedTypeCode || model.selectedType == JXCategoryCellSelectedTypeClick)) {
[self.indicatorImageView.layer removeAnimationForKey:@"rotate"];
CABasicAnimation *rotateAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
if (model.selectedIndex > model.lastSelectedIndex) {
rotateAnimation.fromValue = @(0);
rotateAnimation.toValue = @(M_PI*2);
}else {
rotateAnimation.fromValue = @(M_PI*2);
rotateAnimation.toValue = @(0);
}
rotateAnimation.fillMode = kCAFillModeBackwards;
rotateAnimation.removedOnCompletion = YES;
rotateAnimation.duration = 0.25;
[self.indicatorImageView.layer addAnimation:rotateAnimation forKey:@"rotate"];
}
}else {
self.frame = toFrame;
}
}
@end

View File

@@ -0,0 +1,28 @@
//
// JXCategoryIndicatorLineView.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/17.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorComponentView.h"
typedef NS_ENUM(NSUInteger, JXCategoryIndicatorLineStyle) {
JXCategoryIndicatorLineStyle_Normal = 0,
JXCategoryIndicatorLineStyle_Lengthen = 1,
JXCategoryIndicatorLineStyle_LengthenOffset = 2,
};
@interface JXCategoryIndicatorLineView : JXCategoryIndicatorComponentView
@property (nonatomic, assign) JXCategoryIndicatorLineStyle lineStyle;
/**
line 滚动时沿 x 轴方向上的偏移量,默认值为 10。
lineStyle 为 JXCategoryIndicatorLineStyle_LengthenOffset 有用。
*/
@property (nonatomic, assign) CGFloat lineScrollOffsetX;
@end

View File

@@ -0,0 +1,202 @@
//
// JXCategoryIndicatorLineView.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/17.
// Copyright © 2018 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorLineView.h"
#import "JXCategoryFactory.h"
#import "JXCategoryViewDefines.h"
#import "JXCategoryViewAnimator.h"
@interface JXCategoryIndicatorLineView ()
@property (nonatomic, strong) JXCategoryViewAnimator *animator;
@end
@implementation JXCategoryIndicatorLineView
#pragma mark - Initialize
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self configureDefaulteValue];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
[self configureDefaulteValue];
}
return self;
}
- (void)configureDefaulteValue {
_lineStyle = JXCategoryIndicatorLineStyle_Normal;
_lineScrollOffsetX = 10;
self.indicatorHeight = 3;
}
#pragma mark - JXCategoryIndicatorProtocol
- (void)jx_refreshState:(JXCategoryIndicatorParamsModel *)model {
self.backgroundColor = self.indicatorColor;
self.layer.cornerRadius = [self indicatorCornerRadiusValue:model.selectedCellFrame];
CGFloat selectedLineWidth = [self indicatorWidthValue:model.selectedCellFrame];
CGFloat x = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - selectedLineWidth)/2;
CGFloat y = self.superview.bounds.size.height - [self indicatorHeightValue:model.selectedCellFrame] - self.verticalMargin;
if (self.componentPosition == JXCategoryComponentPosition_Top) {
y = self.verticalMargin;
}
self.frame = CGRectMake(x, y, selectedLineWidth, [self indicatorHeightValue:model.selectedCellFrame]);
}
- (void)jx_contentScrollViewDidScroll:(JXCategoryIndicatorParamsModel *)model {
if (self.animator.isExecuting) {
[self.animator invalid];
self.animator = nil;
}
CGRect rightCellFrame = model.rightCellFrame;
CGRect leftCellFrame = model.leftCellFrame;
CGFloat percent = model.percent;
CGFloat targetX = leftCellFrame.origin.x;
CGFloat targetWidth = [self indicatorWidthValue:leftCellFrame];
CGFloat leftWidth = targetWidth;
CGFloat rightWidth = [self indicatorWidthValue:rightCellFrame];
CGFloat leftX = leftCellFrame.origin.x + (leftCellFrame.size.width - leftWidth)/2;
CGFloat rightX = rightCellFrame.origin.x + (rightCellFrame.size.width - rightWidth)/2;
if (self.lineStyle == JXCategoryIndicatorLineStyle_Normal) {
targetX = [JXCategoryFactory interpolationFrom:leftX to:rightX percent:percent];
if (self.indicatorWidth == JXCategoryViewAutomaticDimension) {
targetWidth = [JXCategoryFactory interpolationFrom:leftWidth to:rightWidth percent:percent];
}
}else if (self.lineStyle == JXCategoryIndicatorLineStyle_Lengthen) {
CGFloat maxWidth = rightX - leftX + rightWidth;
//50%width50%xwidth
if (percent <= 0.5) {
targetX = leftX;
targetWidth = [JXCategoryFactory interpolationFrom:leftWidth to:maxWidth percent:percent*2];
}else {
targetX = [JXCategoryFactory interpolationFrom:leftX to:rightX percent:(percent - 0.5)*2];
targetWidth = [JXCategoryFactory interpolationFrom:maxWidth to:rightWidth percent:(percent - 0.5)*2];
}
}else if (self.lineStyle == JXCategoryIndicatorLineStyle_LengthenOffset) {
//50%widthx50%xwidth
CGFloat offsetX = self.lineScrollOffsetX;//x
CGFloat maxWidth = rightX - leftX + rightWidth - offsetX*2;
if (percent <= 0.5) {
targetX = [JXCategoryFactory interpolationFrom:leftX to:leftX + offsetX percent:percent*2];;
targetWidth = [JXCategoryFactory interpolationFrom:leftWidth to:maxWidth percent:percent*2];
}else {
targetX = [JXCategoryFactory interpolationFrom:(leftX + offsetX) to:rightX percent:(percent - 0.5)*2];
targetWidth = [JXCategoryFactory interpolationFrom:maxWidth to:rightWidth percent:(percent - 0.5)*2];
}
}
//frame12
if (self.isScrollEnabled == YES || (self.isScrollEnabled == NO && percent == 0)) {
CGRect frame = self.frame;
frame.origin.x = targetX;
frame.size.width = targetWidth;
self.frame = frame;
}
}
- (void)jx_selectedCell:(JXCategoryIndicatorParamsModel *)model {
CGRect targetIndicatorFrame = self.frame;
CGFloat targetIndicatorWidth = [self indicatorWidthValue:model.selectedCellFrame];
targetIndicatorFrame.origin.x = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - targetIndicatorWidth)/2.0;
targetIndicatorFrame.size.width = targetIndicatorWidth;
if (self.isScrollEnabled) {
if (self.scrollStyle == JXCategoryIndicatorScrollStyleSameAsUserScroll && (model.selectedType == JXCategoryCellSelectedTypeClick | model.selectedType == JXCategoryCellSelectedTypeCode)) {
if (self.animator.isExecuting) {
[self.animator invalid];
self.animator = nil;
}
CGFloat leftX = 0;
CGFloat rightX = 0;
CGFloat leftWidth = 0;
CGFloat rightWidth = 0;
BOOL isNeedReversePercent = NO;
if (self.frame.origin.x > model.selectedCellFrame.origin.x) {
leftWidth = [self indicatorWidthValue:model.selectedCellFrame];
rightWidth = self.frame.size.width;
leftX = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - leftWidth)/2;;
rightX = self.frame.origin.x;
isNeedReversePercent = YES;
}else {
leftWidth = self.frame.size.width;
rightWidth = [self indicatorWidthValue:model.selectedCellFrame];
leftX = self.frame.origin.x;
rightX = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - rightWidth)/2;
}
__weak typeof(self) weakSelf = self;
if (self.lineStyle == JXCategoryIndicatorLineStyle_Normal) {
[UIView animateWithDuration:self.scrollAnimationDuration delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.frame = targetIndicatorFrame;
} completion: nil];
}else if (self.lineStyle == JXCategoryIndicatorLineStyle_Lengthen) {
CGFloat maxWidth = rightX - leftX + rightWidth;
//50%width50%xwidth
self.animator = [[JXCategoryViewAnimator alloc] init];
self.animator.progressCallback = ^(CGFloat percent) {
if (isNeedReversePercent) {
percent = 1 - percent;
}
CGFloat targetX = 0;
CGFloat targetWidth = 0;
if (percent <= 0.5) {
targetX = leftX;
targetWidth = [JXCategoryFactory interpolationFrom:leftWidth to:maxWidth percent:percent*2];
}else {
targetX = [JXCategoryFactory interpolationFrom:leftX to:rightX percent:(percent - 0.5)*2];
targetWidth = [JXCategoryFactory interpolationFrom:maxWidth to:rightWidth percent:(percent - 0.5)*2];
}
CGRect toFrame = weakSelf.frame;
toFrame.origin.x = targetX;
toFrame.size.width = targetWidth;
weakSelf.frame = toFrame;
};
[self.animator start];
}else if (self.lineStyle == JXCategoryIndicatorLineStyle_LengthenOffset) {
//50%widthx50%xwidth
CGFloat offsetX = self.lineScrollOffsetX;//x
CGFloat maxWidth = rightX - leftX + rightWidth - offsetX*2;
self.animator = [[JXCategoryViewAnimator alloc] init];
self.animator.progressCallback = ^(CGFloat percent) {
if (isNeedReversePercent) {
percent = 1 - percent;
}
CGFloat targetX = 0;
CGFloat targetWidth = 0;
if (percent <= 0.5) {
targetX = [JXCategoryFactory interpolationFrom:leftX to:leftX + offsetX percent:percent*2];;
targetWidth = [JXCategoryFactory interpolationFrom:leftWidth to:maxWidth percent:percent*2];
}else {
targetX = [JXCategoryFactory interpolationFrom:(leftX + offsetX) to:rightX percent:(percent - 0.5)*2];
targetWidth = [JXCategoryFactory interpolationFrom:maxWidth to:rightWidth percent:(percent - 0.5)*2];
}
CGRect toFrame = weakSelf.frame;
toFrame.origin.x = targetX;
toFrame.size.width = targetWidth;
weakSelf.frame = toFrame;
};
[self.animator start];
}
}else if (self.scrollStyle == JXCategoryIndicatorScrollStyleSimple) {
[UIView animateWithDuration:self.scrollAnimationDuration delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.frame = targetIndicatorFrame;
} completion: nil];
}
}else {
self.frame = targetIndicatorFrame;
}
}
@end

View File

@@ -0,0 +1,30 @@
//
// JXCategoryIndicatorRainbowLineView.h
// JXCategoryView
//
// Created by jiaxin on 2018/12/13.
// Copyright © 2018 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorLineView.h"
NS_ASSUME_NONNULL_BEGIN
/**
彩虹效果的指示器
!!!: 会无视 JXCategoryIndicatorLineView 的 indicatorColor 属性,以 indicatorColors 为准。
*/
@interface JXCategoryIndicatorRainbowLineView : JXCategoryIndicatorLineView
/**
指示器颜色数组
数量需要与 cell 的数量相等。没有提供默认值,必须要赋值该属性。
categoryView 在 reloadData 的时候,也要一并更新该属性,不然会出现数组越界。
*/
@property (nonatomic, strong) NSArray <UIColor *> *indicatorColors;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,38 @@
//
// JXCategoryIndicatorRainbowLineView.m
// JXCategoryView
//
// Created by jiaxin on 2018/12/13.
// Copyright © 2018 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorRainbowLineView.h"
#import "JXCategoryFactory.h"
@implementation JXCategoryIndicatorRainbowLineView
- (void)jx_refreshState:(JXCategoryIndicatorParamsModel *)model {
[super jx_refreshState:model];
UIColor *color = self.indicatorColors[model.selectedIndex];
self.backgroundColor = color;
}
- (void)jx_contentScrollViewDidScroll:(JXCategoryIndicatorParamsModel *)model {
[super jx_contentScrollViewDidScroll:model];
UIColor *leftColor = self.indicatorColors[model.leftIndex];
UIColor *rightColor = self.indicatorColors[model.rightIndex];
UIColor *color = [JXCategoryFactory interpolationColorFrom:leftColor to:rightColor percent:model.percent];
self.backgroundColor = color;
}
- (void)jx_selectedCell:(JXCategoryIndicatorParamsModel *)model {
[super jx_selectedCell:model];
UIColor *color = self.indicatorColors[model.selectedIndex];
self.backgroundColor = color;
}
@end

View File

@@ -0,0 +1,14 @@
//
// JXCategoryIndicatorTriangleView.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/17.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorComponentView.h"
/// 三角形样式的指示器
@interface JXCategoryIndicatorTriangleView : JXCategoryIndicatorComponentView
@end

View File

@@ -0,0 +1,109 @@
//
// JXCategoryIndicatorTriangleView.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/17.
// Copyright © 2018 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorTriangleView.h"
#import "JXCategoryFactory.h"
@interface JXCategoryIndicatorTriangleView ()
@property (nonatomic, strong) CAShapeLayer *triangleLayer;
@end
@implementation JXCategoryIndicatorTriangleView
#pragma mark - Initialize
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self configureDefaulteValue];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
[self configureDefaulteValue];
}
return self;
}
- (void)configureDefaulteValue {
self.indicatorWidth = 14;
self.indicatorHeight = 10;
_triangleLayer = [CAShapeLayer layer];
[self.layer addSublayer:self.triangleLayer];
}
#pragma mark - JXCategoryIndicatorProtocol
- (void)jx_refreshState:(JXCategoryIndicatorParamsModel *)model {
CGFloat x = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - [self indicatorWidthValue:model.selectedCellFrame])/2;
CGFloat y = self.superview.bounds.size.height - [self indicatorHeightValue:model.selectedCellFrame] - self.verticalMargin;
if (self.componentPosition == JXCategoryComponentPosition_Top) {
y = self.verticalMargin;
}
self.frame = CGRectMake(x, y, [self indicatorWidthValue:model.selectedCellFrame], [self indicatorHeightValue:model.selectedCellFrame]);
[CATransaction begin];
[CATransaction setDisableActions:NO];
self.triangleLayer.fillColor = self.indicatorColor.CGColor;
self.triangleLayer.frame = self.bounds;
UIBezierPath *path = [UIBezierPath bezierPath];
if (self.componentPosition == JXCategoryComponentPosition_Bottom) {
[path moveToPoint:CGPointMake(self.bounds.size.width/2, 0)];
[path addLineToPoint:CGPointMake(0, self.bounds.size.height)];
[path addLineToPoint:CGPointMake(self.bounds.size.width, self.bounds.size.height)];
} else {
[path moveToPoint:CGPointMake(0, 0)];
[path addLineToPoint:CGPointMake(self.bounds.size.width, 0)];
[path addLineToPoint:CGPointMake(self.bounds.size.width/2, self.bounds.size.height)];
}
[path closePath];
self.triangleLayer.path = path.CGPath;
[CATransaction commit];
}
- (void)jx_contentScrollViewDidScroll:(JXCategoryIndicatorParamsModel *)model {
CGRect rightCellFrame = model.rightCellFrame;
CGRect leftCellFrame = model.leftCellFrame;
CGFloat percent = model.percent;
CGFloat targetWidth = [self indicatorWidthValue:model.leftCellFrame];
CGFloat targetX = 0;
if (percent == 0) {
targetX = leftCellFrame.origin.x + (leftCellFrame.size.width - targetWidth)/2.0;
} else {
CGFloat leftX = leftCellFrame.origin.x + (leftCellFrame.size.width - targetWidth)/2;
CGFloat rightX = rightCellFrame.origin.x + (rightCellFrame.size.width - targetWidth)/2;
targetX = [JXCategoryFactory interpolationFrom:leftX to:rightX percent:percent];
}
//frame12
if (self.isScrollEnabled == YES || (self.isScrollEnabled == NO && percent == 0)) {
CGRect frame = self.frame;
frame.origin.x = targetX;
self.frame = frame;
}
}
- (void)jx_selectedCell:(JXCategoryIndicatorParamsModel *)model {
CGRect toFrame = self.frame;
toFrame.origin.x = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - [self indicatorWidthValue:model.selectedCellFrame])/2;
if (self.isScrollEnabled) {
[UIView animateWithDuration:self.scrollAnimationDuration delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
self.frame = toFrame;
} completion:^(BOOL finished) {
}];
} else {
self.frame = toFrame;
}
}
@end

View File

@@ -0,0 +1,13 @@
//
// JXCategoryComponetCell.h
// DQGuess
//
// Created by jiaxin on 2018/7/25.
// Copyright © 2018年 jingbo. All rights reserved.
//
#import "JXCategoryBaseCell.h"
@interface JXCategoryIndicatorCell : JXCategoryBaseCell
@end

View File

@@ -0,0 +1,52 @@
//
// JXCategoryComponetCell.m
// DQGuess
//
// Created by jiaxin on 2018/7/25.
// Copyright © 2018 jingbo. All rights reserved.
//
#import "JXCategoryIndicatorCell.h"
#import "JXCategoryIndicatorCellModel.h"
@interface JXCategoryIndicatorCell ()
@property (nonatomic, strong) UIView *separatorLine;
@end
@implementation JXCategoryIndicatorCell
- (void)initializeViews {
[super initializeViews];
self.separatorLine = [[UIView alloc] init];
self.separatorLine.hidden = YES;
[self.contentView addSubview:self.separatorLine];
}
- (void)layoutSubviews {
[super layoutSubviews];
JXCategoryIndicatorCellModel *model = (JXCategoryIndicatorCellModel *)self.cellModel;
CGFloat lineWidth = model.separatorLineSize.width;
CGFloat lineHeight = model.separatorLineSize.height;
self.separatorLine.frame = CGRectMake(self.bounds.size.width - lineWidth + self.cellModel.cellSpacing/2, (self.bounds.size.height - lineHeight)/2.0, lineWidth, lineHeight);
}
- (void)reloadData:(JXCategoryBaseCellModel *)cellModel {
[super reloadData:cellModel];
JXCategoryIndicatorCellModel *model = (JXCategoryIndicatorCellModel *)cellModel;
self.separatorLine.backgroundColor = model.separatorLineColor;
self.separatorLine.hidden = !model.isSepratorLineShowEnabled;
if (model.isCellBackgroundColorGradientEnabled) {
if (model.isSelected) {
self.contentView.backgroundColor = model.cellBackgroundSelectedColor;
}else {
self.contentView.backgroundColor = model.cellBackgroundUnselectedColor;
}
}
}
@end

View File

@@ -0,0 +1,24 @@
//
// JXCategoryComponentCellModel.h
// DQGuess
//
// Created by jiaxin on 2018/7/25.
// Copyright © 2018年 jingbo. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "JXCategoryBaseCellModel.h"
@interface JXCategoryIndicatorCellModel : JXCategoryBaseCellModel
@property (nonatomic, assign, getter=isSepratorLineShowEnabled) BOOL sepratorLineShowEnabled;
@property (nonatomic, strong) UIColor *separatorLineColor;
@property (nonatomic, assign) CGSize separatorLineSize;
@property (nonatomic, assign) CGRect backgroundViewMaskFrame; // 底部指示器的 frame 转换到 cell 的 frame
@property (nonatomic, assign, getter=isCellBackgroundColorGradientEnabled) BOOL cellBackgroundColorGradientEnabled;
@property (nonatomic, strong) UIColor *cellBackgroundSelectedColor;
@property (nonatomic, strong) UIColor *cellBackgroundUnselectedColor;
@end

View File

@@ -0,0 +1,13 @@
//
// JXCategoryComponentCellModel.m
// DQGuess
//
// Created by jiaxin on 2018/7/25.
// Copyright © 2018 jingbo. All rights reserved.
//
#import "JXCategoryIndicatorCellModel.h"
@implementation JXCategoryIndicatorCellModel
@end

View File

@@ -0,0 +1,48 @@
//
// JXCategoryComponentView.h
// DQGuess
//
// Created by jiaxin on 2018/7/25.
// Copyright © 2018年 jingbo. All rights reserved.
//
#import "JXCategoryBaseView.h"
#import "JXCategoryIndicatorCell.h"
#import "JXCategoryIndicatorCellModel.h"
#import "JXCategoryIndicatorProtocol.h"
@interface JXCategoryIndicatorView : JXCategoryBaseView
@property (nonatomic, strong) NSArray <UIView<JXCategoryIndicatorProtocol> *> *indicators;
//----------------------ellBackgroundColor-----------------------//
//cell的背景色是否渐变。默认NO
@property (nonatomic, assign, getter=isCellBackgroundColorGradientEnabled) BOOL cellBackgroundColorGradientEnabled;
//cell普通状态的背景色。默认[UIColor clearColor]
@property (nonatomic, strong) UIColor *cellBackgroundUnselectedColor;
//cell选中状态的背景色。默认[UIColor grayColor]
@property (nonatomic, strong) UIColor *cellBackgroundSelectedColor;
//----------------------separatorLine-----------------------//
//是否显示分割线。默认为NO
@property (nonatomic, assign, getter=isSeparatorLineShowEnabled) BOOL separatorLineShowEnabled;
//分割线颜色。默认为[UIColor lightGrayColor]
@property (nonatomic, strong) UIColor *separatorLineColor;
//分割线的size。默认为CGSizeMake(1/[UIScreen mainScreen].scale, 20)
@property (nonatomic, assign) CGSize separatorLineSize;
@end
@interface JXCategoryIndicatorView (UISubclassingIndicatorHooks)
/**
当contentScrollView滚动时候处理跟随手势的过渡效果。
根据cellModel的左右位置、是否选中、ratio进行过滤数据计算。
@param leftCellModel 左边的cellModel
@param rightCellModel 右边的cellModel
@param ratio 从左往右方向计算的百分比
*/
- (void)refreshLeftCellModel:(JXCategoryBaseCellModel *)leftCellModel rightCellModel:(JXCategoryBaseCellModel *)rightCellModel ratio:(CGFloat)ratio NS_REQUIRES_SUPER;
@end

View File

@@ -0,0 +1,209 @@
//
// JXCategoryIndicatorView.m
// DQGuess
//
// Created by jiaxin on 2018/7/25.
// Copyright © 2018 jingbo. All rights reserved.
//
#import "JXCategoryIndicatorView.h"
#import "JXCategoryIndicatorBackgroundView.h"
#import "JXCategoryFactory.h"
@interface JXCategoryIndicatorView()
@end
@implementation JXCategoryIndicatorView
- (void)initializeData {
[super initializeData];
_separatorLineShowEnabled = NO;
_separatorLineColor = [UIColor lightGrayColor];
_separatorLineSize = CGSizeMake(1/[UIScreen mainScreen].scale, 20);
_cellBackgroundColorGradientEnabled = NO;
_cellBackgroundUnselectedColor = [UIColor whiteColor];
_cellBackgroundSelectedColor = [UIColor lightGrayColor];
}
- (void)initializeViews {
[super initializeViews];
}
- (void)setIndicators:(NSArray<UIView<JXCategoryIndicatorProtocol> *> *)indicators {
_indicators = indicators;
self.collectionView.indicators = indicators;
}
- (void)refreshState {
[super refreshState];
CGRect selectedCellFrame = CGRectZero;
JXCategoryIndicatorCellModel *selectedCellModel;
for (int i = 0; i < self.dataSource.count; i++) {
JXCategoryIndicatorCellModel *cellModel = (JXCategoryIndicatorCellModel *)self.dataSource[i];
cellModel.sepratorLineShowEnabled = self.isSeparatorLineShowEnabled;
cellModel.separatorLineColor = self.separatorLineColor;
cellModel.separatorLineSize = self.separatorLineSize;
cellModel.backgroundViewMaskFrame = CGRectZero;
cellModel.cellBackgroundColorGradientEnabled = self.isCellBackgroundColorGradientEnabled;
cellModel.cellBackgroundSelectedColor = self.cellBackgroundSelectedColor;
cellModel.cellBackgroundUnselectedColor = self.cellBackgroundUnselectedColor;
if (i == self.dataSource.count - 1) {
cellModel.sepratorLineShowEnabled = NO;
}
if (i == self.selectedIndex) {
selectedCellModel = cellModel;
selectedCellFrame = [self getTargetCellFrame:i];
}
}
for (UIView<JXCategoryIndicatorProtocol> *indicator in self.indicators) {
if (self.dataSource.count <= 0) {
indicator.hidden = YES;
} else {
indicator.hidden = NO;
JXCategoryIndicatorParamsModel *indicatorParamsModel = [[JXCategoryIndicatorParamsModel alloc] init];
indicatorParamsModel.selectedIndex = self.selectedIndex;
indicatorParamsModel.selectedCellFrame = selectedCellFrame;
[indicator jx_refreshState:indicatorParamsModel];
if ([indicator isKindOfClass:[JXCategoryIndicatorBackgroundView class]]) {
CGRect maskFrame = indicator.frame;
maskFrame.origin.x = maskFrame.origin.x - selectedCellFrame.origin.x;
selectedCellModel.backgroundViewMaskFrame = maskFrame;
}
}
}
}
- (void)refreshSelectedCellModel:(JXCategoryBaseCellModel *)selectedCellModel unselectedCellModel:(JXCategoryBaseCellModel *)unselectedCellModel {
[super refreshSelectedCellModel:selectedCellModel unselectedCellModel:unselectedCellModel];
JXCategoryIndicatorCellModel *myUnselectedCellModel = (JXCategoryIndicatorCellModel *)unselectedCellModel;
myUnselectedCellModel.backgroundViewMaskFrame = CGRectZero;
myUnselectedCellModel.cellBackgroundUnselectedColor = self.cellBackgroundUnselectedColor;
myUnselectedCellModel.cellBackgroundSelectedColor = self.cellBackgroundSelectedColor;
JXCategoryIndicatorCellModel *myselectedCellModel = (JXCategoryIndicatorCellModel *)selectedCellModel;
myselectedCellModel.cellBackgroundUnselectedColor = self.cellBackgroundUnselectedColor;
myselectedCellModel.cellBackgroundSelectedColor = self.cellBackgroundSelectedColor;
}
- (void)contentOffsetOfContentScrollViewDidChanged:(CGPoint)contentOffset {
[super contentOffsetOfContentScrollViewDidChanged:contentOffset];
CGFloat ratio = contentOffset.x/self.contentScrollView.bounds.size.width;
if (ratio > self.dataSource.count - 1 || ratio < 0) {
//
return;
}
ratio = MAX(0, MIN(self.dataSource.count - 1, ratio));
NSInteger baseIndex = floorf(ratio);
if (baseIndex + 1 >= self.dataSource.count) {
//
return;
}
CGFloat remainderRatio = ratio - baseIndex;
CGRect leftCellFrame = [self getTargetCellFrame:baseIndex];
CGRect rightCellFrame = [self getTargetCellFrame:baseIndex + 1];
JXCategoryIndicatorParamsModel *indicatorParamsModel = [[JXCategoryIndicatorParamsModel alloc] init];
indicatorParamsModel.selectedIndex = self.selectedIndex;
indicatorParamsModel.leftIndex = baseIndex;
indicatorParamsModel.leftCellFrame = leftCellFrame;
indicatorParamsModel.rightIndex = baseIndex + 1;
indicatorParamsModel.rightCellFrame = rightCellFrame;
indicatorParamsModel.percent = remainderRatio;
if (remainderRatio == 0) {
for (UIView<JXCategoryIndicatorProtocol> *indicator in self.indicators) {
[indicator jx_contentScrollViewDidScroll:indicatorParamsModel];
}
} else {
JXCategoryIndicatorCellModel *leftCellModel = (JXCategoryIndicatorCellModel *)self.dataSource[baseIndex];
leftCellModel.selectedType = JXCategoryCellSelectedTypeUnknown;
JXCategoryIndicatorCellModel *rightCellModel = (JXCategoryIndicatorCellModel *)self.dataSource[baseIndex + 1];
rightCellModel.selectedType = JXCategoryCellSelectedTypeUnknown;
[self refreshLeftCellModel:leftCellModel rightCellModel:rightCellModel ratio:remainderRatio];
for (UIView<JXCategoryIndicatorProtocol> *indicator in self.indicators) {
[indicator jx_contentScrollViewDidScroll:indicatorParamsModel];
if ([indicator isKindOfClass:[JXCategoryIndicatorBackgroundView class]]) {
CGRect leftMaskFrame = indicator.frame;
leftMaskFrame.origin.x = leftMaskFrame.origin.x - leftCellFrame.origin.x;
leftCellModel.backgroundViewMaskFrame = leftMaskFrame;
CGRect rightMaskFrame = indicator.frame;
rightMaskFrame.origin.x = rightMaskFrame.origin.x - rightCellFrame.origin.x;
rightCellModel.backgroundViewMaskFrame = rightMaskFrame;
}
}
JXCategoryBaseCell *leftCell = (JXCategoryBaseCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:baseIndex inSection:0]];
[leftCell reloadData:leftCellModel];
JXCategoryBaseCell *rightCell = (JXCategoryBaseCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:baseIndex + 1 inSection:0]];
[rightCell reloadData:rightCellModel];
}
}
- (BOOL)selectCellAtIndex:(NSInteger)index selectedType:(JXCategoryCellSelectedType)selectedType {
NSInteger lastSelectedIndex = self.selectedIndex;
BOOL result = [super selectCellAtIndex:index selectedType:selectedType];
if (!result) {
return NO;
}
CGRect clickedCellFrame = [self getTargetSelectedCellFrame:index selectedType:selectedType];
JXCategoryIndicatorCellModel *selectedCellModel = (JXCategoryIndicatorCellModel *)self.dataSource[index];
selectedCellModel.selectedType = selectedType;
for (UIView<JXCategoryIndicatorProtocol> *indicator in self.indicators) {
JXCategoryIndicatorParamsModel *indicatorParamsModel = [[JXCategoryIndicatorParamsModel alloc] init];
indicatorParamsModel.lastSelectedIndex = lastSelectedIndex;
indicatorParamsModel.selectedIndex = index;
indicatorParamsModel.selectedCellFrame = clickedCellFrame;
indicatorParamsModel.selectedType = selectedType;
[indicator jx_selectedCell:indicatorParamsModel];
if ([indicator isKindOfClass:[JXCategoryIndicatorBackgroundView class]]) {
CGRect maskFrame = indicator.frame;
maskFrame.origin.x = maskFrame.origin.x - clickedCellFrame.origin.x;
selectedCellModel.backgroundViewMaskFrame = maskFrame;
}
}
JXCategoryIndicatorCell *selectedCell = (JXCategoryIndicatorCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0]];
[selectedCell reloadData:selectedCellModel];
return YES;
}
@end
@implementation JXCategoryIndicatorView (UISubclassingIndicatorHooks)
- (void)refreshLeftCellModel:(JXCategoryBaseCellModel *)leftCellModel rightCellModel:(JXCategoryBaseCellModel *)rightCellModel ratio:(CGFloat)ratio {
if (self.isCellBackgroundColorGradientEnabled) {
//cell
JXCategoryIndicatorCellModel *leftModel = (JXCategoryIndicatorCellModel *)leftCellModel;
JXCategoryIndicatorCellModel *rightModel = (JXCategoryIndicatorCellModel *)rightCellModel;
if (leftModel.isSelected) {
leftModel.cellBackgroundSelectedColor = [JXCategoryFactory interpolationColorFrom:self.cellBackgroundSelectedColor to:self.cellBackgroundUnselectedColor percent:ratio];
leftModel.cellBackgroundUnselectedColor = self.cellBackgroundUnselectedColor;
}else {
leftModel.cellBackgroundUnselectedColor = [JXCategoryFactory interpolationColorFrom:self.cellBackgroundSelectedColor to:self.cellBackgroundUnselectedColor percent:ratio];
leftModel.cellBackgroundSelectedColor = self.cellBackgroundSelectedColor;
}
if (rightModel.isSelected) {
rightModel.cellBackgroundSelectedColor = [JXCategoryFactory interpolationColorFrom:self.cellBackgroundUnselectedColor to:self.cellBackgroundSelectedColor percent:ratio];
rightModel.cellBackgroundUnselectedColor = self.cellBackgroundUnselectedColor;
}else {
rightModel.cellBackgroundUnselectedColor = [JXCategoryFactory interpolationColorFrom:self.cellBackgroundUnselectedColor to:self.cellBackgroundSelectedColor percent:ratio];
rightModel.cellBackgroundSelectedColor = self.cellBackgroundSelectedColor;
}
}
}
@end