698 lines
33 KiB
Mathematica
698 lines
33 KiB
Mathematica
|
|
//
|
|||
|
|
// JXCategoryBaseView.m
|
|||
|
|
// UI系列测试
|
|||
|
|
//
|
|||
|
|
// Created by jiaxin on 2018/3/15.
|
|||
|
|
// Copyright © 2018年 jiaxin. All rights reserved.
|
|||
|
|
//
|
|||
|
|
|
|||
|
|
#import "JXCategoryBaseView.h"
|
|||
|
|
#import "JXCategoryFactory.h"
|
|||
|
|
#import "JXCategoryViewAnimator.h"
|
|||
|
|
#import "RTLManager.h"
|
|||
|
|
|
|||
|
|
struct DelegateFlags {
|
|||
|
|
unsigned int didSelectedItemAtIndexFlag : 1;
|
|||
|
|
unsigned int didClickSelectedItemAtIndexFlag : 1;
|
|||
|
|
unsigned int didScrollSelectedItemAtIndexFlag : 1;
|
|||
|
|
unsigned int canClickItemAtIndexFlag : 1;
|
|||
|
|
unsigned int scrollingFromLeftIndexToRightIndexFlag : 1;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
@interface JXCategoryBaseView () <UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout>
|
|||
|
|
|
|||
|
|
@property (nonatomic, strong) JXCategoryCollectionView *collectionView;
|
|||
|
|
@property (nonatomic, assign) struct DelegateFlags delegateFlags;
|
|||
|
|
@property (nonatomic, assign) NSInteger selectedIndex;
|
|||
|
|
@property (nonatomic, assign) CGFloat innerCellSpacing;
|
|||
|
|
@property (nonatomic, assign) CGPoint lastContentViewContentOffset;
|
|||
|
|
@property (nonatomic, strong) JXCategoryViewAnimator *animator;
|
|||
|
|
// 正在滚动中的目标index。用于处理正在滚动列表的时候,立即点击item,会导致界面显示异常。
|
|||
|
|
@property (nonatomic, assign) NSInteger scrollingTargetIndex;
|
|||
|
|
@property (nonatomic, assign, getter=isNeedReloadByBecomeActive) BOOL needReloadByBecomeActive;
|
|||
|
|
@property (nonatomic, assign, getter=isFirstLayoutSubviews) BOOL firstLayoutSubviews;
|
|||
|
|
@property (nonatomic, assign, getter=isNeedConfigAutomaticallyAdjustsScrollViewInsets) BOOL needConfigAutomaticallyAdjustsScrollViewInsets;
|
|||
|
|
|
|||
|
|
@end
|
|||
|
|
|
|||
|
|
@implementation JXCategoryBaseView
|
|||
|
|
|
|||
|
|
- (void)dealloc {
|
|||
|
|
if (self.contentScrollView) {
|
|||
|
|
[self.contentScrollView removeObserver:self forKeyPath:@"contentOffset"];
|
|||
|
|
}
|
|||
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
|
|||
|
|
[self.animator stop];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (instancetype)initWithFrame:(CGRect)frame {
|
|||
|
|
self = [super initWithFrame:frame];
|
|||
|
|
if (self) {
|
|||
|
|
[self initializeData];
|
|||
|
|
[self initializeViews];
|
|||
|
|
}
|
|||
|
|
return self;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (instancetype)initWithCoder:(NSCoder *)coder {
|
|||
|
|
self = [super initWithCoder:coder];
|
|||
|
|
if (self) {
|
|||
|
|
[self initializeData];
|
|||
|
|
[self initializeViews];
|
|||
|
|
}
|
|||
|
|
return self;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (void)willMoveToSuperview:(UIView *)newSuperview {
|
|||
|
|
[super willMoveToSuperview:newSuperview];
|
|||
|
|
|
|||
|
|
[self configAutomaticallyAdjustsScrollViewInsets:newSuperview];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (void)reloadData {
|
|||
|
|
[self reloadDataWithoutListContainer];
|
|||
|
|
[self.listContainer reloadData];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (void)reloadDataWithoutListContainer {
|
|||
|
|
[self refreshDataSource];
|
|||
|
|
[self refreshState];
|
|||
|
|
[self.collectionView.collectionViewLayout invalidateLayout];
|
|||
|
|
[self.collectionView reloadData];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (void)reloadCellAtIndex:(NSInteger)index {
|
|||
|
|
if (index < 0 || index >= self.dataSource.count) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
JXCategoryBaseCellModel *cellModel = self.dataSource[index];
|
|||
|
|
cellModel.selectedType = JXCategoryCellSelectedTypeUnknown;
|
|||
|
|
[self refreshCellModel:cellModel index:index];
|
|||
|
|
JXCategoryBaseCell *cell = (JXCategoryBaseCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0]];
|
|||
|
|
[cell reloadData:cellModel];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (void)selectItemAtIndex:(NSInteger)index {
|
|||
|
|
[self selectCellAtIndex:index selectedType:JXCategoryCellSelectedTypeCode];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (void)layoutSubviews {
|
|||
|
|
[super layoutSubviews];
|
|||
|
|
|
|||
|
|
//部分使用者为了适配不同的手机屏幕尺寸,JXCategoryView的宽高比要求保持一样,所以它的高度就会因为不同宽度的屏幕而不一样。计算出来的高度,有时候会是位数很长的浮点数,如果把这个高度设置给UICollectionView就会触发内部的一个错误。所以,为了规避这个问题,在这里对高度统一向下取整。
|
|||
|
|
//如果向下取整导致了你的页面异常,请自己重新设置JXCategoryView的高度,保证为整数即可。
|
|||
|
|
CGRect targetFrame = CGRectMake(0, 0, self.bounds.size.width, floor(self.bounds.size.height));
|
|||
|
|
if (self.isFirstLayoutSubviews) {
|
|||
|
|
if (self.bounds.size.width == 0 || self.bounds.size.height == 0) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
if (self.isNeedConfigAutomaticallyAdjustsScrollViewInsets) {
|
|||
|
|
[self configAutomaticallyAdjustsScrollViewInsets:self.superview];
|
|||
|
|
}
|
|||
|
|
self.firstLayoutSubviews = NO;
|
|||
|
|
self.collectionView.frame = targetFrame;
|
|||
|
|
[self reloadDataWithoutListContainer];
|
|||
|
|
}else {
|
|||
|
|
if (!CGRectEqualToRect(self.collectionView.frame, targetFrame)) {
|
|||
|
|
self.collectionView.frame = targetFrame;
|
|||
|
|
[self refreshState];
|
|||
|
|
[self.collectionView.collectionViewLayout invalidateLayout];
|
|||
|
|
[self.collectionView reloadData];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#pragma mark - Setter
|
|||
|
|
|
|||
|
|
- (void)setDelegate:(id<JXCategoryViewDelegate>)delegate {
|
|||
|
|
_delegate = delegate;
|
|||
|
|
|
|||
|
|
_delegateFlags.didSelectedItemAtIndexFlag = [delegate respondsToSelector:@selector(categoryView:didSelectedItemAtIndex:)];
|
|||
|
|
_delegateFlags.didClickSelectedItemAtIndexFlag = [delegate respondsToSelector:@selector(categoryView:didClickSelectedItemAtIndex:)];
|
|||
|
|
_delegateFlags.didScrollSelectedItemAtIndexFlag = [delegate respondsToSelector:@selector(categoryView:didScrollSelectedItemAtIndex:)];
|
|||
|
|
_delegateFlags.canClickItemAtIndexFlag = [delegate respondsToSelector:@selector(categoryView:canClickItemAtIndex:)];
|
|||
|
|
_delegateFlags.scrollingFromLeftIndexToRightIndexFlag = [delegate respondsToSelector:@selector(categoryView:scrollingFromLeftIndex:toRightIndex:ratio:)];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (void)setDefaultSelectedIndex:(NSInteger)defaultSelectedIndex {
|
|||
|
|
_defaultSelectedIndex = defaultSelectedIndex;
|
|||
|
|
|
|||
|
|
self.selectedIndex = defaultSelectedIndex;
|
|||
|
|
[self.listContainer setDefaultSelectedIndex:defaultSelectedIndex];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (void)setContentScrollView:(UIScrollView *)contentScrollView {
|
|||
|
|
if (_contentScrollView != nil) {
|
|||
|
|
[_contentScrollView removeObserver:self forKeyPath:@"contentOffset"];
|
|||
|
|
}
|
|||
|
|
_contentScrollView = contentScrollView;
|
|||
|
|
|
|||
|
|
self.contentScrollView.scrollsToTop = NO;
|
|||
|
|
[self.contentScrollView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (void)setListContainer:(id<JXCategoryViewListContainer>)listContainer {
|
|||
|
|
_listContainer = listContainer;
|
|||
|
|
[listContainer setDefaultSelectedIndex:self.defaultSelectedIndex];
|
|||
|
|
self.contentScrollView = [listContainer contentScrollView];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#pragma mark - <UICollectionViewDataSource, UICollectionViewDelegate>
|
|||
|
|
|
|||
|
|
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
|
|||
|
|
return 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
|
|||
|
|
return self.dataSource.count;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
|
|||
|
|
return [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass([self preferredCellClass]) forIndexPath:indexPath];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
|
|||
|
|
JXCategoryBaseCellModel *cellModel = self.dataSource[indexPath.item];
|
|||
|
|
cellModel.selectedType = JXCategoryCellSelectedTypeUnknown;
|
|||
|
|
[(JXCategoryBaseCell *)cell reloadData:cellModel];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
|
|||
|
|
BOOL isTransitionAnimating = NO;
|
|||
|
|
for (JXCategoryBaseCellModel *cellModel in self.dataSource) {
|
|||
|
|
if (cellModel.isTransitionAnimating) {
|
|||
|
|
isTransitionAnimating = YES;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (!isTransitionAnimating) {
|
|||
|
|
//当前没有正在过渡的item,才允许点击选中
|
|||
|
|
[self clickSelectItemAtIndex:indexPath.row];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#pragma mark - <UICollectionViewDelegateFlowLayout>
|
|||
|
|
|
|||
|
|
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
|
|||
|
|
return UIEdgeInsetsMake(0, [self getContentEdgeInsetLeft], 0, [self getContentEdgeInsetRight]);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
|
|||
|
|
return CGSizeMake(self.dataSource[indexPath.item].cellWidth, self.collectionView.bounds.size.height);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section {
|
|||
|
|
return self.innerCellSpacing;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
|
|||
|
|
return self.innerCellSpacing;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#pragma mark - KVO
|
|||
|
|
|
|||
|
|
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
|
|||
|
|
if ([keyPath isEqualToString:@"contentOffset"]) {
|
|||
|
|
CGPoint contentOffset = [change[NSKeyValueChangeNewKey] CGPointValue];
|
|||
|
|
if ((self.contentScrollView.isTracking || self.contentScrollView.isDecelerating)) {
|
|||
|
|
//只处理用户滚动的情况
|
|||
|
|
[self contentOffsetOfContentScrollViewDidChanged:contentOffset];
|
|||
|
|
}
|
|||
|
|
self.lastContentViewContentOffset = contentOffset;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#pragma mark - Private
|
|||
|
|
|
|||
|
|
- (void)configAutomaticallyAdjustsScrollViewInsets:(UIView *)view {
|
|||
|
|
UIResponder *next = view;
|
|||
|
|
while (next != nil) {
|
|||
|
|
if ([next isKindOfClass:[UIViewController class]]) {
|
|||
|
|
#pragma clang diagnostic push
|
|||
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|||
|
|
((UIViewController *)next).automaticallyAdjustsScrollViewInsets = NO;
|
|||
|
|
#pragma clang diagnostic pop
|
|||
|
|
self.needConfigAutomaticallyAdjustsScrollViewInsets = NO;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
next = next.nextResponder;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (CGFloat)getContentEdgeInsetLeft {
|
|||
|
|
if (self.contentEdgeInsetLeft == JXCategoryViewAutomaticDimension) {
|
|||
|
|
return self.innerCellSpacing;
|
|||
|
|
}
|
|||
|
|
return self.contentEdgeInsetLeft;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (CGFloat)getContentEdgeInsetRight {
|
|||
|
|
if (self.contentEdgeInsetRight == JXCategoryViewAutomaticDimension) {
|
|||
|
|
return self.innerCellSpacing;
|
|||
|
|
}
|
|||
|
|
return self.contentEdgeInsetRight;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (CGFloat)getCellWidthAtIndex:(NSInteger)index {
|
|||
|
|
return [self preferredCellWidthAtIndex:index] + self.cellWidthIncrement;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (void)clickSelectItemAtIndex:(NSInteger)index {
|
|||
|
|
if (self.delegateFlags.canClickItemAtIndexFlag && ![self.delegate categoryView:self canClickItemAtIndex:index]) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
[self selectCellAtIndex:index selectedType:JXCategoryCellSelectedTypeClick];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (void)scrollSelectItemAtIndex:(NSInteger)index {
|
|||
|
|
[self selectCellAtIndex:index selectedType:JXCategoryCellSelectedTypeScroll];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (void)applicationDidBecomeActive:(NSNotification *)notification {
|
|||
|
|
if (self.isNeedReloadByBecomeActive) {
|
|||
|
|
self.needReloadByBecomeActive = NO;
|
|||
|
|
[self reloadData];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@end
|
|||
|
|
|
|||
|
|
@implementation JXCategoryBaseView (UISubclassingBaseHooks)
|
|||
|
|
|
|||
|
|
- (CGRect)getTargetCellFrame:(NSInteger)targetIndex {
|
|||
|
|
CGFloat x = [self getContentEdgeInsetLeft];
|
|||
|
|
for (int i = 0; i < targetIndex; i ++) {
|
|||
|
|
JXCategoryBaseCellModel *cellModel = self.dataSource[i];
|
|||
|
|
CGFloat cellWidth;
|
|||
|
|
if (cellModel.isTransitionAnimating && cellModel.isCellWidthZoomEnabled) {
|
|||
|
|
//正在进行动画的时候,cellWidthCurrentZoomScale是随着动画渐变的,而没有立即更新到目标值
|
|||
|
|
if (cellModel.isSelected) {
|
|||
|
|
cellWidth = [self getCellWidthAtIndex:cellModel.index]*cellModel.cellWidthSelectedZoomScale;
|
|||
|
|
}else {
|
|||
|
|
cellWidth = [self getCellWidthAtIndex:cellModel.index]*cellModel.cellWidthNormalZoomScale;
|
|||
|
|
}
|
|||
|
|
}else {
|
|||
|
|
cellWidth = cellModel.cellWidth;
|
|||
|
|
}
|
|||
|
|
x += cellWidth + self.innerCellSpacing;
|
|||
|
|
}
|
|||
|
|
CGFloat width;
|
|||
|
|
JXCategoryBaseCellModel *selectedCellModel = self.dataSource[targetIndex];
|
|||
|
|
if (selectedCellModel.isTransitionAnimating && selectedCellModel.isCellWidthZoomEnabled) {
|
|||
|
|
width = [self getCellWidthAtIndex:selectedCellModel.index]*selectedCellModel.cellWidthSelectedZoomScale;
|
|||
|
|
}else {
|
|||
|
|
width = selectedCellModel.cellWidth;
|
|||
|
|
}
|
|||
|
|
return CGRectMake(x, 0, width, self.bounds.size.height);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (CGRect)getTargetSelectedCellFrame:(NSInteger)targetIndex selectedType:(JXCategoryCellSelectedType)selectedType {
|
|||
|
|
CGFloat x = [self getContentEdgeInsetLeft];
|
|||
|
|
for (int i = 0; i < targetIndex; i ++) {
|
|||
|
|
JXCategoryBaseCellModel *cellModel = self.dataSource[i];
|
|||
|
|
x += [self getCellWidthAtIndex:cellModel.index] + self.innerCellSpacing;
|
|||
|
|
}
|
|||
|
|
CGFloat cellWidth = 0;
|
|||
|
|
JXCategoryBaseCellModel *selectedCellModel = self.dataSource[targetIndex];
|
|||
|
|
if (selectedCellModel.cellWidthZoomEnabled) {
|
|||
|
|
cellWidth = [self getCellWidthAtIndex:targetIndex]*selectedCellModel.cellWidthSelectedZoomScale;
|
|||
|
|
}else {
|
|||
|
|
cellWidth = [self getCellWidthAtIndex:targetIndex];
|
|||
|
|
}
|
|||
|
|
return CGRectMake(x, 0, cellWidth, self.bounds.size.height);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (void)initializeData {
|
|||
|
|
_firstLayoutSubviews = YES;
|
|||
|
|
_dataSource = [NSMutableArray array];
|
|||
|
|
_selectedIndex = 0;
|
|||
|
|
_cellWidth = JXCategoryViewAutomaticDimension;
|
|||
|
|
_cellWidthIncrement = 0;
|
|||
|
|
_cellSpacing = 20;
|
|||
|
|
_averageCellSpacingEnabled = YES;
|
|||
|
|
_cellWidthZoomEnabled = NO;
|
|||
|
|
_cellWidthZoomScale = 1.2;
|
|||
|
|
_cellWidthZoomScrollGradientEnabled = YES;
|
|||
|
|
_contentEdgeInsetLeft = JXCategoryViewAutomaticDimension;
|
|||
|
|
_contentEdgeInsetRight = JXCategoryViewAutomaticDimension;
|
|||
|
|
_lastContentViewContentOffset = CGPointZero;
|
|||
|
|
_selectedAnimationEnabled = NO;
|
|||
|
|
_selectedAnimationDuration = 0.25;
|
|||
|
|
_scrollingTargetIndex = -1;
|
|||
|
|
_contentScrollViewClickTransitionAnimationEnabled = YES;
|
|||
|
|
_needReloadByBecomeActive = NO;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (void)initializeViews {
|
|||
|
|
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
|||
|
|
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
|
|||
|
|
_collectionView = [[JXCategoryCollectionView alloc] initWithFrame:self.bounds collectionViewLayout:layout];
|
|||
|
|
self.collectionView.backgroundColor = [UIColor clearColor];
|
|||
|
|
self.collectionView.showsHorizontalScrollIndicator = NO;
|
|||
|
|
self.collectionView.showsVerticalScrollIndicator = NO;
|
|||
|
|
self.collectionView.scrollsToTop = NO;
|
|||
|
|
self.collectionView.dataSource = self;
|
|||
|
|
self.collectionView.delegate = self;
|
|||
|
|
[self.collectionView registerClass:[self preferredCellClass] forCellWithReuseIdentifier:NSStringFromClass([self preferredCellClass])];
|
|||
|
|
if (@available(iOS 10.0, *)) {
|
|||
|
|
self.collectionView.prefetchingEnabled = NO;
|
|||
|
|
}
|
|||
|
|
if (@available(iOS 11.0, *)) {
|
|||
|
|
if ([self.collectionView respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]) {
|
|||
|
|
self.collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if ([RTLManager supportRTL]) {
|
|||
|
|
self.collectionView.semanticContentAttribute = UISemanticContentAttributeForceLeftToRight;
|
|||
|
|
[RTLManager horizontalFlipView:self.collectionView];
|
|||
|
|
}
|
|||
|
|
[self addSubview:self.collectionView];
|
|||
|
|
|
|||
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (void)refreshDataSource {}
|
|||
|
|
|
|||
|
|
- (void)refreshState {
|
|||
|
|
if (self.selectedIndex < 0 || self.selectedIndex >= self.dataSource.count) {
|
|||
|
|
self.defaultSelectedIndex = 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
self.innerCellSpacing = self.cellSpacing;
|
|||
|
|
//总的内容宽度(左边距+cell总宽度+总cellSpacing+右边距)
|
|||
|
|
__block CGFloat totalItemWidth = [self getContentEdgeInsetLeft];
|
|||
|
|
//总的cell宽度
|
|||
|
|
CGFloat totalCellWidth = 0;
|
|||
|
|
for (int i = 0; i < self.dataSource.count; i++) {
|
|||
|
|
JXCategoryBaseCellModel *cellModel = self.dataSource[i];
|
|||
|
|
cellModel.index = i;
|
|||
|
|
cellModel.cellWidthZoomEnabled = self.cellWidthZoomEnabled;
|
|||
|
|
cellModel.cellWidthNormalZoomScale = 1;
|
|||
|
|
cellModel.cellWidthSelectedZoomScale = self.cellWidthZoomScale;
|
|||
|
|
cellModel.selectedAnimationEnabled = self.selectedAnimationEnabled;
|
|||
|
|
cellModel.selectedAnimationDuration = self.selectedAnimationDuration;
|
|||
|
|
cellModel.cellSpacing = self.innerCellSpacing;
|
|||
|
|
if (i == self.selectedIndex) {
|
|||
|
|
cellModel.selected = YES;
|
|||
|
|
cellModel.cellWidthCurrentZoomScale = cellModel.cellWidthSelectedZoomScale;
|
|||
|
|
}else {
|
|||
|
|
cellModel.selected = NO;
|
|||
|
|
cellModel.cellWidthCurrentZoomScale = cellModel.cellWidthNormalZoomScale;
|
|||
|
|
}
|
|||
|
|
if (self.isCellWidthZoomEnabled) {
|
|||
|
|
cellModel.cellWidth = [self getCellWidthAtIndex:i]*cellModel.cellWidthCurrentZoomScale;
|
|||
|
|
}else {
|
|||
|
|
cellModel.cellWidth = [self getCellWidthAtIndex:i];
|
|||
|
|
}
|
|||
|
|
totalCellWidth += cellModel.cellWidth;
|
|||
|
|
if (i == self.dataSource.count - 1) {
|
|||
|
|
totalItemWidth += cellModel.cellWidth + [self getContentEdgeInsetRight];
|
|||
|
|
}else {
|
|||
|
|
totalItemWidth += cellModel.cellWidth + self.innerCellSpacing;
|
|||
|
|
}
|
|||
|
|
[self refreshCellModel:cellModel index:i];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (self.isAverageCellSpacingEnabled && totalItemWidth < self.bounds.size.width) {
|
|||
|
|
//如果总的内容宽度都没有超过视图宽度,就将cellSpacing等分
|
|||
|
|
NSInteger cellSpacingItemCount = self.dataSource.count - 1;
|
|||
|
|
CGFloat totalCellSpacingWidth = self.bounds.size.width - totalCellWidth;
|
|||
|
|
//如果内容左边距是Automatic,就加1
|
|||
|
|
if (self.contentEdgeInsetLeft == JXCategoryViewAutomaticDimension) {
|
|||
|
|
cellSpacingItemCount += 1;
|
|||
|
|
}else {
|
|||
|
|
totalCellSpacingWidth -= self.contentEdgeInsetLeft;
|
|||
|
|
}
|
|||
|
|
//如果内容右边距是Automatic,就加1
|
|||
|
|
if (self.contentEdgeInsetRight == JXCategoryViewAutomaticDimension) {
|
|||
|
|
cellSpacingItemCount += 1;
|
|||
|
|
}else {
|
|||
|
|
totalCellSpacingWidth -= self.contentEdgeInsetRight;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
CGFloat cellSpacing = 0;
|
|||
|
|
if (cellSpacingItemCount > 0) {
|
|||
|
|
cellSpacing = totalCellSpacingWidth/cellSpacingItemCount;
|
|||
|
|
}
|
|||
|
|
self.innerCellSpacing = cellSpacing;
|
|||
|
|
[self.dataSource enumerateObjectsUsingBlock:^(JXCategoryBaseCellModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
|
|||
|
|
obj.cellSpacing = self.innerCellSpacing;
|
|||
|
|
}];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//---------------------定位collectionView到当前选中的位置----------------------
|
|||
|
|
//因为初始化的时候,collectionView并没有初始化完,cell都没有被加载出来。只有自己手动计算当前选中的index的位置,然后更新到contentOffset
|
|||
|
|
__block CGFloat frameXOfSelectedCell = [self getContentEdgeInsetLeft];
|
|||
|
|
__block CGFloat selectedCellWidth = 0;
|
|||
|
|
totalItemWidth = [self getContentEdgeInsetLeft];
|
|||
|
|
[self.dataSource enumerateObjectsUsingBlock:^(JXCategoryBaseCellModel * cellModel, NSUInteger idx, BOOL * _Nonnull stop) {
|
|||
|
|
if (idx < self.selectedIndex) {
|
|||
|
|
frameXOfSelectedCell += cellModel.cellWidth + self.innerCellSpacing;
|
|||
|
|
}else if (idx == self.selectedIndex) {
|
|||
|
|
selectedCellWidth = cellModel.cellWidth;
|
|||
|
|
}
|
|||
|
|
if (idx == self.dataSource.count - 1) {
|
|||
|
|
totalItemWidth += cellModel.cellWidth + [self getContentEdgeInsetRight];
|
|||
|
|
}else {
|
|||
|
|
totalItemWidth += cellModel.cellWidth + self.innerCellSpacing;
|
|||
|
|
}
|
|||
|
|
}];
|
|||
|
|
|
|||
|
|
CGFloat minX = 0;
|
|||
|
|
CGFloat maxX = totalItemWidth - self.bounds.size.width;
|
|||
|
|
CGFloat targetX = frameXOfSelectedCell - self.bounds.size.width/2.0 + selectedCellWidth/2.0;
|
|||
|
|
CGPoint collectionViewContentOffset = self.collectionView.contentOffset;
|
|||
|
|
collectionViewContentOffset.x = MAX(MIN(maxX, targetX), minX);
|
|||
|
|
|
|||
|
|
[self.collectionView setContentOffset:collectionViewContentOffset
|
|||
|
|
animated:NO];
|
|||
|
|
//---------------------定位collectionView到当前选中的位置----------------------
|
|||
|
|
|
|||
|
|
if (CGRectEqualToRect(self.contentScrollView.frame, CGRectZero) && self.contentScrollView.superview != nil) {
|
|||
|
|
//某些情况系统会出现JXCategoryView先布局,contentScrollView后布局。就会导致下面指定defaultSelectedIndex失效,所以发现contentScrollView的frame为zero时,强行触发其父视图链里面已经有frame的一个父视图的layoutSubviews方法。
|
|||
|
|
//比如JXSegmentedListContainerView会将contentScrollView包裹起来使用,该情况需要JXSegmentedListContainerView.superView触发布局更新
|
|||
|
|
UIView *parentView = self.contentScrollView.superview;
|
|||
|
|
while (parentView != nil && CGRectEqualToRect(parentView.frame, CGRectZero)) {
|
|||
|
|
parentView = parentView.superview;
|
|||
|
|
}
|
|||
|
|
[parentView setNeedsLayout];
|
|||
|
|
[parentView layoutIfNeeded];
|
|||
|
|
}
|
|||
|
|
//将contentScrollView的contentOffset定位到当前选中index的位置
|
|||
|
|
CGPoint contentScrollViewContentOffset = self.contentScrollView.contentOffset;
|
|||
|
|
contentScrollViewContentOffset.x = self.selectedIndex*self.contentScrollView.bounds.size.width;
|
|||
|
|
[self.contentScrollView setContentOffset:contentScrollViewContentOffset animated:NO];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (BOOL)selectCellAtIndex:(NSInteger)targetIndex selectedType:(JXCategoryCellSelectedType)selectedType {
|
|||
|
|
if (targetIndex < 0 || targetIndex >= self.dataSource.count) {
|
|||
|
|
return NO;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
self.needReloadByBecomeActive = NO;
|
|||
|
|
if (self.selectedIndex == targetIndex) {
|
|||
|
|
//目标index和当前选中的index相等,就不需要处理后续的选中更新逻辑,只需要回调代理方法即可。
|
|||
|
|
if (selectedType == JXCategoryCellSelectedTypeCode) {
|
|||
|
|
[self.listContainer didClickSelectedItemAtIndex:targetIndex];
|
|||
|
|
}else if (selectedType == JXCategoryCellSelectedTypeClick) {
|
|||
|
|
[self.listContainer didClickSelectedItemAtIndex:targetIndex];
|
|||
|
|
if (self.delegateFlags.didClickSelectedItemAtIndexFlag) {
|
|||
|
|
[self.delegate categoryView:self didClickSelectedItemAtIndex:targetIndex];
|
|||
|
|
}
|
|||
|
|
}else if (selectedType == JXCategoryCellSelectedTypeScroll) {
|
|||
|
|
if (self.delegateFlags.didScrollSelectedItemAtIndexFlag) {
|
|||
|
|
[self.delegate categoryView:self didScrollSelectedItemAtIndex:targetIndex];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (self.delegateFlags.didSelectedItemAtIndexFlag) {
|
|||
|
|
[self.delegate categoryView:self didSelectedItemAtIndex:targetIndex];
|
|||
|
|
}
|
|||
|
|
self.scrollingTargetIndex = -1;
|
|||
|
|
return NO;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//通知子类刷新当前选中的和将要选中的cellModel
|
|||
|
|
JXCategoryBaseCellModel *lastCellModel = self.dataSource[self.selectedIndex];
|
|||
|
|
lastCellModel.selectedType = selectedType;
|
|||
|
|
JXCategoryBaseCellModel *selectedCellModel = self.dataSource[targetIndex];
|
|||
|
|
selectedCellModel.selectedType = selectedType;
|
|||
|
|
[self refreshSelectedCellModel:selectedCellModel unselectedCellModel:lastCellModel];
|
|||
|
|
|
|||
|
|
//刷新当前选中的和将要选中的cell
|
|||
|
|
JXCategoryBaseCell *lastCell = (JXCategoryBaseCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:self.selectedIndex inSection:0]];
|
|||
|
|
[lastCell reloadData:lastCellModel];
|
|||
|
|
JXCategoryBaseCell *selectedCell = (JXCategoryBaseCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:targetIndex inSection:0]];
|
|||
|
|
[selectedCell reloadData:selectedCellModel];
|
|||
|
|
|
|||
|
|
if (self.scrollingTargetIndex != -1 && self.scrollingTargetIndex != targetIndex) {
|
|||
|
|
JXCategoryBaseCellModel *scrollingTargetCellModel = self.dataSource[self.scrollingTargetIndex];
|
|||
|
|
scrollingTargetCellModel.selected = NO;
|
|||
|
|
scrollingTargetCellModel.selectedType = selectedType;
|
|||
|
|
[self refreshSelectedCellModel:selectedCellModel unselectedCellModel:scrollingTargetCellModel];
|
|||
|
|
JXCategoryBaseCell *scrollingTargetCell = (JXCategoryBaseCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:self.scrollingTargetIndex inSection:0]];
|
|||
|
|
[scrollingTargetCell reloadData:scrollingTargetCellModel];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (self.isCellWidthZoomEnabled) {
|
|||
|
|
[self.collectionView.collectionViewLayout invalidateLayout];
|
|||
|
|
//延时为了解决cellwidth变化,点击最后几个cell,scrollToItem会出现位置偏移bu。需要等cellWidth动画渐变结束后再滚动到index的cell位置。
|
|||
|
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.selectedAnimationDuration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|||
|
|
[self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:targetIndex inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES];
|
|||
|
|
});
|
|||
|
|
} else {
|
|||
|
|
[self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:targetIndex inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (selectedType == JXCategoryCellSelectedTypeClick ||
|
|||
|
|
selectedType == JXCategoryCellSelectedTypeCode) {
|
|||
|
|
CGPoint offset = self.contentScrollView.contentOffset;
|
|||
|
|
offset.x =
|
|||
|
|
targetIndex*self.contentScrollView.bounds.size.width;
|
|||
|
|
[self.contentScrollView setContentOffset:offset
|
|||
|
|
animated:self.isContentScrollViewClickTransitionAnimationEnabled];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
self.selectedIndex = targetIndex;
|
|||
|
|
if (selectedType == JXCategoryCellSelectedTypeCode) {
|
|||
|
|
[self.listContainer didClickSelectedItemAtIndex:targetIndex];
|
|||
|
|
} else if (selectedType == JXCategoryCellSelectedTypeClick) {
|
|||
|
|
[self.listContainer didClickSelectedItemAtIndex:targetIndex];
|
|||
|
|
if (self.delegateFlags.didClickSelectedItemAtIndexFlag) {
|
|||
|
|
[self.delegate categoryView:self didClickSelectedItemAtIndex:targetIndex];
|
|||
|
|
}
|
|||
|
|
} else if(selectedType == JXCategoryCellSelectedTypeScroll) {
|
|||
|
|
if (self.delegateFlags.didScrollSelectedItemAtIndexFlag) {
|
|||
|
|
[self.delegate categoryView:self didScrollSelectedItemAtIndex:targetIndex];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (self.delegateFlags.didSelectedItemAtIndexFlag) {
|
|||
|
|
[self.delegate categoryView:self didSelectedItemAtIndex:targetIndex];
|
|||
|
|
}
|
|||
|
|
self.scrollingTargetIndex = -1;
|
|||
|
|
|
|||
|
|
return YES;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
- (void)refreshSelectedCellModel:(JXCategoryBaseCellModel *)selectedCellModel unselectedCellModel:(JXCategoryBaseCellModel *)unselectedCellModel {
|
|||
|
|
selectedCellModel.selected = YES;
|
|||
|
|
unselectedCellModel.selected = NO;
|
|||
|
|
|
|||
|
|
if (self.isCellWidthZoomEnabled) {
|
|||
|
|
if (selectedCellModel.selectedType == JXCategoryCellSelectedTypeCode ||
|
|||
|
|
selectedCellModel.selectedType == JXCategoryCellSelectedTypeClick) {
|
|||
|
|
self.animator = [[JXCategoryViewAnimator alloc] init];
|
|||
|
|
self.animator.duration = self.selectedAnimationDuration;
|
|||
|
|
__weak typeof(self) weakSelf = self;
|
|||
|
|
self.animator.progressCallback = ^(CGFloat percent) {
|
|||
|
|
selectedCellModel.transitionAnimating = YES;
|
|||
|
|
unselectedCellModel.transitionAnimating = YES;
|
|||
|
|
selectedCellModel.cellWidthCurrentZoomScale = [JXCategoryFactory interpolationFrom:selectedCellModel.cellWidthNormalZoomScale to:selectedCellModel.cellWidthSelectedZoomScale percent:percent];
|
|||
|
|
selectedCellModel.cellWidth = [weakSelf getCellWidthAtIndex:selectedCellModel.index] * selectedCellModel.cellWidthCurrentZoomScale;
|
|||
|
|
unselectedCellModel.cellWidthCurrentZoomScale = [JXCategoryFactory interpolationFrom:unselectedCellModel.cellWidthSelectedZoomScale to:unselectedCellModel.cellWidthNormalZoomScale percent:percent];
|
|||
|
|
unselectedCellModel.cellWidth = [weakSelf getCellWidthAtIndex:unselectedCellModel.index] * unselectedCellModel.cellWidthCurrentZoomScale;
|
|||
|
|
[weakSelf.collectionView.collectionViewLayout invalidateLayout];
|
|||
|
|
};
|
|||
|
|
self.animator.completeCallback = ^{
|
|||
|
|
selectedCellModel.transitionAnimating = NO;
|
|||
|
|
unselectedCellModel.transitionAnimating = NO;
|
|||
|
|
};
|
|||
|
|
[self.animator start];
|
|||
|
|
} else {
|
|||
|
|
selectedCellModel.cellWidthCurrentZoomScale = selectedCellModel.cellWidthSelectedZoomScale;
|
|||
|
|
selectedCellModel.cellWidth = [self getCellWidthAtIndex:selectedCellModel.index] * selectedCellModel.cellWidthCurrentZoomScale;
|
|||
|
|
unselectedCellModel.cellWidthCurrentZoomScale = unselectedCellModel.cellWidthNormalZoomScale;
|
|||
|
|
unselectedCellModel.cellWidth = [self getCellWidthAtIndex:unselectedCellModel.index] * unselectedCellModel.cellWidthCurrentZoomScale;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (void)contentOffsetOfContentScrollViewDidChanged:(CGPoint)contentOffset {
|
|||
|
|
if (self.dataSource.count == 0) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
CGFloat ratio = contentOffset.x/self.contentScrollView.bounds.size.width;
|
|||
|
|
if (ratio > self.dataSource.count - 1 || ratio < 0) {
|
|||
|
|
//超过了边界,不需要处理
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
if (contentOffset.x == 0 && self.selectedIndex == 0 && self.lastContentViewContentOffset.x == 0) {
|
|||
|
|
//滚动到了最左边,且已经选中了第一个,且之前的contentOffset.x为0
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
CGFloat maxContentOffsetX = self.contentScrollView.contentSize.width - self.contentScrollView.bounds.size.width;
|
|||
|
|
if (contentOffset.x == maxContentOffsetX && self.selectedIndex == self.dataSource.count - 1 && self.lastContentViewContentOffset.x == maxContentOffsetX) {
|
|||
|
|
//滚动到了最右边,且已经选中了最后一个,且之前的contentOffset.x为maxContentOffsetX
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
ratio = MAX(0, MIN(self.dataSource.count - 1, ratio));
|
|||
|
|
NSInteger baseIndex = floorf(ratio);
|
|||
|
|
CGFloat remainderRatio = ratio - baseIndex;
|
|||
|
|
|
|||
|
|
if (remainderRatio == 0) {
|
|||
|
|
//快速滑动翻页,用户一直在拖拽contentScrollView,需要更新选中状态
|
|||
|
|
//滑动一小段距离,然后放开回到原位,contentOffset同样的值会回调多次。例如在index为1的情况,滑动放开回到原位,contentOffset会多次回调CGPoint(width, 0)
|
|||
|
|
if (!(self.lastContentViewContentOffset.x == contentOffset.x && self.selectedIndex == baseIndex)) {
|
|||
|
|
[self scrollSelectItemAtIndex:baseIndex];
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
self.needReloadByBecomeActive = YES;
|
|||
|
|
if (self.animator.isExecuting) {
|
|||
|
|
[self.animator invalid];
|
|||
|
|
//需要重置之前animator.progessCallback为处理完的状态
|
|||
|
|
for (JXCategoryBaseCellModel *model in self.dataSource) {
|
|||
|
|
if (model.isSelected) {
|
|||
|
|
model.cellWidthCurrentZoomScale = model.cellWidthSelectedZoomScale;
|
|||
|
|
model.cellWidth = [self getCellWidthAtIndex:model.index] * model.cellWidthCurrentZoomScale;
|
|||
|
|
}else {
|
|||
|
|
model.cellWidthCurrentZoomScale = model.cellWidthNormalZoomScale;
|
|||
|
|
model.cellWidth = [self getCellWidthAtIndex:model.index] * model.cellWidthCurrentZoomScale;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
//快速滑动翻页,当remainderRatio没有变成0,但是已经翻页了,需要通过下面的判断,触发选中
|
|||
|
|
if (fabs(ratio - self.selectedIndex) > 1) {
|
|||
|
|
NSInteger targetIndex = baseIndex;
|
|||
|
|
if (ratio < self.selectedIndex) {
|
|||
|
|
targetIndex = baseIndex + 1;
|
|||
|
|
}
|
|||
|
|
[self scrollSelectItemAtIndex:targetIndex];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (self.selectedIndex == baseIndex) {
|
|||
|
|
self.scrollingTargetIndex = baseIndex + 1;
|
|||
|
|
} else {
|
|||
|
|
self.scrollingTargetIndex = baseIndex;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (self.isCellWidthZoomEnabled && self.isCellWidthZoomScrollGradientEnabled) {
|
|||
|
|
JXCategoryBaseCellModel *leftCellModel = (JXCategoryBaseCellModel *)self.dataSource[baseIndex];
|
|||
|
|
JXCategoryBaseCellModel *rightCellModel = (JXCategoryBaseCellModel *)self.dataSource[baseIndex + 1];
|
|||
|
|
leftCellModel.cellWidthCurrentZoomScale = [JXCategoryFactory interpolationFrom:leftCellModel.cellWidthSelectedZoomScale to:leftCellModel.cellWidthNormalZoomScale percent:remainderRatio];
|
|||
|
|
leftCellModel.cellWidth = [self getCellWidthAtIndex:leftCellModel.index] * leftCellModel.cellWidthCurrentZoomScale;
|
|||
|
|
rightCellModel.cellWidthCurrentZoomScale = [JXCategoryFactory interpolationFrom:rightCellModel.cellWidthNormalZoomScale to:rightCellModel.cellWidthSelectedZoomScale percent:remainderRatio];
|
|||
|
|
rightCellModel.cellWidth = [self getCellWidthAtIndex:rightCellModel.index] * rightCellModel.cellWidthCurrentZoomScale;
|
|||
|
|
[self.collectionView.collectionViewLayout invalidateLayout];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (self.delegateFlags.scrollingFromLeftIndexToRightIndexFlag) {
|
|||
|
|
[self.delegate categoryView:self scrollingFromLeftIndex:baseIndex toRightIndex:baseIndex + 1 ratio:remainderRatio];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (CGFloat)preferredCellWidthAtIndex:(NSInteger)index {
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (Class)preferredCellClass {
|
|||
|
|
return JXCategoryBaseCell.class;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (void)refreshCellModel:(JXCategoryBaseCellModel *)cellModel index:(NSInteger)index {
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@end
|