版本記錄
版本號(hào) | 時(shí)間 |
---|---|
V1.0 | 2020.05.04 星期一 |
前言
iOS中有關(guān)視圖控件用戶能看到的都在UIKit框架里面,用戶交互也是通過UIKit進(jìn)行的。感興趣的參考上面幾篇文章吠冤。
1. UIKit框架(一) —— UIKit動(dòng)力學(xué)和移動(dòng)效果(一)
2. UIKit框架(二) —— UIKit動(dòng)力學(xué)和移動(dòng)效果(二)
3. UIKit框架(三) —— UICollectionViewCell的擴(kuò)張效果的實(shí)現(xiàn)(一)
4. UIKit框架(四) —— UICollectionViewCell的擴(kuò)張效果的實(shí)現(xiàn)(二)
5. UIKit框架(五) —— 自定義控件:可重復(fù)使用的滑塊(一)
6. UIKit框架(六) —— 自定義控件:可重復(fù)使用的滑塊(二)
7. UIKit框架(七) —— 動(dòng)態(tài)尺寸UITableViewCell的實(shí)現(xiàn)(一)
8. UIKit框架(八) —— 動(dòng)態(tài)尺寸UITableViewCell的實(shí)現(xiàn)(二)
9. UIKit框架(九) —— UICollectionView的數(shù)據(jù)異步預(yù)加載(一)
10. UIKit框架(十) —— UICollectionView的數(shù)據(jù)異步預(yù)加載(二)
11. UIKit框架(十一) —— UICollectionView的重用尼酿、選擇和重排序(一)
12. UIKit框架(十二) —— UICollectionView的重用、選擇和重排序(二)
13. UIKit框架(十三) —— 如何創(chuàng)建自己的側(cè)滑式面板導(dǎo)航(一)
14. UIKit框架(十四) —— 如何創(chuàng)建自己的側(cè)滑式面板導(dǎo)航(二)
15. UIKit框架(十五) —— 基于自定義UICollectionViewLayout布局的簡(jiǎn)單示例(一)
16. UIKit框架(十六) —— 基于自定義UICollectionViewLayout布局的簡(jiǎn)單示例(二)
17. UIKit框架(十七) —— 基于自定義UICollectionViewLayout布局的簡(jiǎn)單示例(三)
18. UIKit框架(十八) —— 基于CALayer屬性的一種3D邊欄動(dòng)畫的實(shí)現(xiàn)(一)
19. UIKit框架(十九) —— 基于CALayer屬性的一種3D邊欄動(dòng)畫的實(shí)現(xiàn)(二)
20. UIKit框架(二十) —— 基于UILabel跑馬燈類似效果的實(shí)現(xiàn)(一)
21. UIKit框架(二十一) —— UIStackView的使用(一)
22. UIKit框架(二十二) —— 基于UIPresentationController的自定義viewController的轉(zhuǎn)場(chǎng)和展示(一)
23. UIKit框架(二十三) —— 基于UIPresentationController的自定義viewController的轉(zhuǎn)場(chǎng)和展示(二)
24. UIKit框架(二十四) —— 基于UICollectionViews和Drag-Drop在兩個(gè)APP間的使用示例 (一)
25. UIKit框架(二十五) —— 基于UICollectionViews和Drag-Drop在兩個(gè)APP間的使用示例 (二)
26. UIKit框架(二十六) —— UICollectionView的自定義布局 (一)
27. UIKit框架(二十七) —— UICollectionView的自定義布局 (二)
28. UIKit框架(二十八) —— 一個(gè)UISplitViewController的簡(jiǎn)單實(shí)用示例 (一)
29. UIKit框架(二十九) —— 一個(gè)UISplitViewController的簡(jiǎn)單實(shí)用示例 (二)
30. UIKit框架(三十) —— 基于UICollectionViewCompositionalLayout API的UICollectionViews布局的簡(jiǎn)單示例(一)
31. UIKit框架(三十一) —— 基于UICollectionViewCompositionalLayout API的UICollectionViews布局的簡(jiǎn)單示例(二)
32. UIKit框架(三十二) —— 替換Peek and Pop交互的基于iOS13的Context Menus(一)
33. UIKit框架(三十三) —— 替換Peek and Pop交互的基于iOS13的Context Menus(二)
34. UIKit框架(三十四) —— Accessibility的使用(一)
35. UIKit框架(三十五) —— Accessibility的使用(二)
36. UIKit框架(三十六) —— UICollectionView UICollectionViewDiffableDataSource的使用(一)
37. UIKit框架(三十七) —— UICollectionView UICollectionViewDiffableDataSource的使用(二)
效果展示
首先看一個(gè)轉(zhuǎn)盤效果
這個(gè)是一個(gè)禮物面板收毫,具有轉(zhuǎn)盤的效果攻走,這個(gè)是同事實(shí)現(xiàn)的自定義UI殷勘。
下面我們就看下使用UICollectionView
是怎么實(shí)現(xiàn)的。
代碼實(shí)現(xiàn)
下面我們就看一下源碼昔搂。
首先看下工程文件
下面就是源碼了
1. ViewController.m
#import "ViewController.h"
#import "JJGiftCollectionView.h"
#define kCollectionViewHeight 300.0
#define kScreenWidth [UIScreen mainScreen].bounds.size.width
#define kScreenHeight [UIScreen mainScreen].bounds.size.height
CGFloat radius_ = 308;
@interface ViewController ()
@property (nonatomic, strong) JJGiftCollectionView *collectionView;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self initUI];
}
#pragma mark - Object Private Function
- (void)initUI
{
self.collectionView.backgroundColor = [UIColor lightGrayColor];
self.collectionView.contentOffset = CGPointMake(86 * 5, 0.0);
}
#pragma mark - Getter && Setter
- (JJGiftCollectionView *)collectionView
{
if (!_collectionView) {
_collectionView = [[JJGiftCollectionView alloc] initWithFrame:CGRectMake(0.0, kScreenHeight - kCollectionViewHeight, kScreenWidth, kCollectionViewHeight)];
[self.view addSubview:_collectionView];
}
return _collectionView;
}
@end
2. JJGiftCollectionView.h
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface JJGiftCollectionView : UICollectionView
- (void)scrollToCenter;
@end
NS_ASSUME_NONNULL_END
3. JJGiftCollectionView.m
#import "JJGiftCollectionView.h"
#import "JJCollectionViewLayout.h"
#import "JJCollectionViewCell.h"
@interface JJGiftCollectionView() <UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
@property (nonatomic, assign) NSInteger selectedGiftIndex;
@property (nonatomic, assign) BOOL isScrolling;
@end
extern CGFloat radius_;
@implementation JJGiftCollectionView{
NSInteger itemCount_;
}
#pragma mark - Override Base Function
- (instancetype)initWithFrame:(CGRect)frame
{
JJCollectionViewLayout *layout = [[JJCollectionViewLayout alloc] init];
layout.cellItemSize = CGSizeMake(86, 100);
self = [super initWithFrame:frame collectionViewLayout:layout];
if (self) {
self.backgroundColor = UIColor.clearColor;
self.showsVerticalScrollIndicator = NO;
self.showsHorizontalScrollIndicator = NO;
self.delegate = self;
self.dataSource = self;
[self registerClass:[JJCollectionViewCell class] forCellWithReuseIdentifier:NSStringFromClass(JJCollectionViewCell.class)];
itemCount_ = 10;
_selectedGiftIndex = 0;
}
return self;
}
#pragma mark - Object Private Function
- (UIColor *)randomColor
{
CGFloat red = random() % 255 / 255.0;
CGFloat green = random() % 255 / 255.0;
CGFloat blue = random() % 255 / 255.0;
return [UIColor colorWithRed:red green:green blue:blue alpha:1.];
}
- (void)scrollToSelectedIndex:(NSInteger)index
{
self.selectedGiftIndex = index;
[self setContentOffset:CGPointMake([self itemPerWidthFromIphone]*index, 0) animated: YES];
}
#pragma mark - Object Public Function
- (void)scrollToCenter
{
if (itemCount_ == 0) {
return ;
}
[self scrollToSelectedIndex:floorf(itemCount_/2)];
}
- (CGFloat)itemPerWidthFromIphone
{
CGFloat width = self.contentSize.width;
NSInteger count = itemCount_;
CGFloat anglePerItem = atan((width/count) / (radius_+25));
CGFloat angleAtExtreme = [self numberOfItemsInSection:0] > 0 ? -([self numberOfItemsInSection:0] -1)*anglePerItem : 0;
CGFloat factor = -angleAtExtreme/(self.contentSize.width - CGRectGetWidth(self.bounds));
return anglePerItem/factor;
}
#pragma - mark - UICollectionViewDelegate
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return 20;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
JJCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass(JJCollectionViewCell.class) forIndexPath:indexPath];
cell.backgroundColor = [self randomColor];
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row == _selectedGiftIndex || indexPath.row >= itemCount_) {
return ;
}
_selectedGiftIndex = indexPath.row;
[self scrollToSelectedIndex:_selectedGiftIndex];
}
#pragma mark - ScrollViewDelegate
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
CGFloat perItemWidth = [self itemPerWidthFromIphone];
CGFloat index = (scrollView.contentOffset.x)/perItemWidth;
NSInteger selectIndex = round(index);
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:selectIndex inSection:0];
[self collectionView:self didSelectItemAtIndexPath:indexPath];
self.isScrolling = NO;
}
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView
{
self.isScrolling = YES;
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
self.isScrolling = YES;
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
self.isScrolling = YES;
}
@end
4. JJCollectionViewLayout.h
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface JJCollectionViewLayout : UICollectionViewLayout
@property (nonatomic, assign) CGSize cellItemSize;// 單元格大小
@property (nonatomic, assign) CGFloat radius; // 圓環(huán)半徑
@property (nonatomic, strong) NSIndexPath *selectedIndexPath; // 選中的單元格
@property (nonatomic, assign) CGFloat scrollContentOffsetX;
@end
@interface JJCollectionViewLayoutAttributes : UICollectionViewLayoutAttributes
@property (nonatomic, assign) CGPoint anchorPoint; // 錨點(diǎn)
@property (nonatomic, assign) CGFloat angle; // 角度
@end
NS_ASSUME_NONNULL_END
5. JJCollectionViewLayout.m
#import "JJCollectionViewLayout.h"
@interface JJCollectionViewLayout ()
@property (nonatomic, strong) NSMutableArray <JJCollectionViewLayoutAttributes *> *attributeItems;
@property (nonatomic, assign) CGRect changeRect;
@end
extern CGFloat radius_;
@implementation JJCollectionViewLayout
- (void)setRadius:(CGFloat)radius
{
_radius = radius;
[self invalidateLayout];
}
+ (Class)layoutAttributesClass
{
return JJCollectionViewLayoutAttributes.class;
}
// 當(dāng)滑到極端時(shí) 第0個(gè)item的角度
- (CGFloat)angleAtExtreme
{
return [self.collectionView numberOfItemsInSection:0] > 0 ? -([self.collectionView numberOfItemsInSection:0] -1)*self.anglePerItem : 0;
}
// 滑動(dòng)時(shí) 第0個(gè)角度
- (CGFloat)angle
{
return (self.angleAtExtreme * self.collectionView.contentOffset.x)/(self.collectionViewContentSize.width - self.collectionView.bounds.size.width);
}
- (CGFloat)anglePerItem
{
return atan((self.cellItemSize.width) / self.radius);
}
- (CGSize)collectionViewContentSize
{
return CGSizeMake([self.collectionView numberOfItemsInSection:0] * self.cellItemSize.width, CGRectGetHeight(self.collectionView.bounds));
}
- (void)setCellItemSize:(CGSize)cellItemSize
{
_cellItemSize = cellItemSize;
}
- (instancetype)init
{
if (self == [super init]) {
_radius = radius_ - 50;
_attributeItems = [[NSMutableArray alloc] init];
_cellItemSize = CGSizeMake(86, 100);
}
return self;
}
- (void)prepareLayout
{
[super prepareLayout];
CGFloat centerX = self.collectionView.contentOffset.x + CGRectGetWidth(self.collectionView.bounds)/2.0;
CGFloat anchorPointY = ((self.cellItemSize.height/2.0) + self.radius)/self.cellItemSize.height;
// 只計(jì)算在屏幕中的item 正切
CGFloat theTan = atan2(CGRectGetWidth(self.collectionView.bounds)/2.0, self.radius + self.cellItemSize.height/2.0 - CGRectGetHeight(self.collectionView.bounds)/2.0);
NSInteger startIndex = 0;
NSInteger endIndex = [self.collectionView numberOfItemsInSection:0] - 1;
if (self.angle < -theTan) {
startIndex = floor((-theTan - self.angle)/self.anglePerItem);
}
endIndex = MIN(endIndex, ceil((theTan - self.angle)/self.anglePerItem));
if (endIndex < startIndex) {
endIndex = 0;
startIndex = 0;
}
for (NSInteger index = startIndex; index <= endIndex; index ++) {
JJCollectionViewLayoutAttributes *attributes = [JJCollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:[NSIndexPath indexPathForItem:index inSection:0]];
attributes.size = self.cellItemSize;
attributes.center = CGPointMake(centerX, CGRectGetMidY(self.collectionView.bounds) - 25);
attributes.angle = self.angle + (self.anglePerItem * index);
attributes.anchorPoint = CGPointMake(0.5, anchorPointY);
[self.attributeItems addObject:attributes];
}
}
// 固定item在中間位置
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity {
CGPoint finalContentOffset = proposedContentOffset;
CGFloat factor = -self.angleAtExtreme/(self.collectionViewContentSize.width - CGRectGetWidth(self.collectionView.bounds));
CGFloat proposedAngle = proposedContentOffset.x * factor;
CGFloat ratio = proposedAngle/self.anglePerItem;
CGFloat multiplier = 0.0;
if (velocity.x > 0) {
multiplier = ceil(ratio);
} else if (velocity.x < 0) {
multiplier = floor(ratio);
} else {
multiplier = round(ratio);
}
finalContentOffset.x = multiplier*self.anglePerItem/factor;
self.scrollContentOffsetX = finalContentOffset.x;
return finalContentOffset;
}
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
{
return self.attributeItems;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
return self.attributeItems[indexPath.item];
}
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
return YES;
}
@end
@implementation JJCollectionViewLayoutAttributes
#pragma mark - Override Base Function
- (instancetype)init
{
if (self = [super init]) {
_anchorPoint = CGPointMake(0.5, 0.5);
_angle = 0;
}
return self;
}
- (id)copyWithZone:(NSZone *)zone
{
JJCollectionViewLayoutAttributes *attributes = [super copyWithZone:zone];
attributes.anchorPoint = self.anchorPoint;
attributes.angle = self.angle;
return attributes;
}
#pragma mark - Getter && Setter
- (void)setAnchorPoint:(CGPoint)anchorPoint
{
_anchorPoint = anchorPoint;
}
- (void)setAngle:(CGFloat)angle
{
_angle = angle;
self.zIndex = angle * 1000000;
self.transform = CGAffineTransformMakeRotation(angle);
}
@end
6. JJCollectionViewCell.h
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface JJCollectionViewCell : UICollectionViewCell
@end
NS_ASSUME_NONNULL_END
7. JJCollectionViewCell.m
#import "JJCollectionViewCell.h"
#import "JJCollectionViewLayout.h"
@implementation JJCollectionViewCell
#pragma mark - Override Base Function
- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
[super applyLayoutAttributes:layoutAttributes];
if ([layoutAttributes isKindOfClass:[JJCollectionViewLayoutAttributes class]]) {
JJCollectionViewLayoutAttributes *attributes = (JJCollectionViewLayoutAttributes *)layoutAttributes;
self.layer.anchorPoint = attributes.anchorPoint;
CGFloat y = self.layer.position.y;
y += (attributes.anchorPoint.y - 0.5) * CGRectGetHeight(self.bounds);
CGPoint center = CGPointMake(self.layer.position.x, y);
self.layer.position = center;
self.contentView.transform = CGAffineTransformMakeRotation(-attributes.angle);
}
}
@end
下面就是實(shí)際實(shí)現(xiàn)效果玲销,具體每個(gè)item里面放什么view就需要自己去定義和實(shí)現(xiàn)了。
后記
本篇主要講述了基于
CollectionView
轉(zhuǎn)盤效果的實(shí)現(xiàn)摘符,感興趣的給個(gè)贊或者關(guān)注~~~