cdts/xdts-ios 3/TreeHole/CYHResetCode/CYH/QMUIKit/QMUIComponents/QMUICollectionViewPagingLayout.m

311 lines
14 KiB
Mathematica
Raw Permalink Normal View History

2023-07-27 09:20:00 +08:00
/**
* Tencent is pleased to support the open source community by making QMUI_iOS available.
* Copyright (C) 2016-2021 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/
//
// QMUICollectionViewPagingLayout.m
// qmui
//
// Created by QMUI Team on 15/9/24.
//
#import "QMUICollectionViewPagingLayout.h"
#import "QMUICore.h"
#import "UIScrollView+QMUI.h"
#import "CALayer+QMUI.h"
@interface QMUICollectionViewPagingLayout () {
CGFloat _maximumScale;
CGFloat _minimumScale;
CGFloat _rotationRatio;
CGFloat _rotationRadius;
CGSize _finalItemSize;
CGFloat _pagingThreshold;
BOOL _debug;
}
@property(nonatomic, strong) CALayer *debugLayer;
@end
@implementation QMUICollectionViewPagingLayout (DefaultStyle)
- (CGFloat)pagingThreshold {
return _pagingThreshold;
}
- (void)setPagingThreshold:(CGFloat)pagingThreshold {
_pagingThreshold = pagingThreshold;
}
- (BOOL)debug {
return _debug;
}
- (void)setDebug:(BOOL)debug {
_debug = debug;
if (self.style == QMUICollectionViewPagingLayoutStyleDefault && debug && !self.debugLayer) {
self.debugLayer = [CALayer layer];
[self.debugLayer qmui_removeDefaultAnimations];
self.debugLayer.backgroundColor = UIColorTestRed.CGColor;
UIView *backgroundView = self.collectionView.backgroundView;
if (!backgroundView) {
backgroundView = [[UIView alloc] init];
backgroundView.tag = 1024;
self.collectionView.backgroundView = backgroundView;
}
[backgroundView.layer addSublayer:self.debugLayer];
} else if (!debug) {
[self.debugLayer removeFromSuperlayer];
self.debugLayer = nil;
if (self.collectionView.backgroundView.tag == 1024) {
self.collectionView.backgroundView = nil;
}
}
}
@end
@implementation QMUICollectionViewPagingLayout (ScaleStyle)
- (CGFloat)maximumScale {
return _maximumScale;
}
- (void)setMaximumScale:(CGFloat)maximumScale {
_maximumScale = maximumScale;
}
- (CGFloat)minimumScale {
return _minimumScale;
}
- (void)setMinimumScale:(CGFloat)minimumScale {
_minimumScale = minimumScale;
}
@end
const CGFloat QMUICollectionViewPagingLayoutRotationRadiusAutomatic = -1.0;
@implementation QMUICollectionViewPagingLayout (RotationStyle)
- (CGFloat)rotationRatio {
return _rotationRatio;
}
- (void)setRotationRatio:(CGFloat)rotationRatio {
_rotationRatio = [self validatedRotationRatio:rotationRatio];
}
- (CGFloat)rotationRadius {
return _rotationRadius;
}
- (void)setRotationRadius:(CGFloat)rotationRadius {
_rotationRadius = rotationRadius;
}
- (CGFloat)validatedRotationRatio:(CGFloat)rotationRatio {
return MAX(0.0, MIN(1.0, rotationRatio));
}
@end
@implementation QMUICollectionViewPagingLayout
- (instancetype)initWithStyle:(QMUICollectionViewPagingLayoutStyle)style {
if (self = [super init]) {
_style = style;
self.velocityForEnsurePageDown = 0.4;
self.allowsMultipleItemScroll = YES;
self.multipleItemScrollVelocityLimit = 2.5;
self.pagingThreshold = 2.0 / 3.0;
self.maximumScale = 1.0;
self.minimumScale = 0.94;
self.rotationRatio = .5;
self.rotationRadius = QMUICollectionViewPagingLayoutRotationRadiusAutomatic;
self.minimumInteritemSpacing = 0;
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
}
return self;
}
- (instancetype)init {
return [self initWithStyle:QMUICollectionViewPagingLayoutStyleDefault];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
return [self init];
}
- (void)prepareLayout {
[super prepareLayout];
CGSize itemSize = self.itemSize;
id<UICollectionViewDelegateFlowLayout> layoutDelegate = (id<UICollectionViewDelegateFlowLayout>)self.collectionView.delegate;
if ([layoutDelegate respondsToSelector:@selector(collectionView:layout:sizeForItemAtIndexPath:)]) {
itemSize = [layoutDelegate collectionView:self.collectionView layout:self sizeForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
}
_finalItemSize = itemSize;
if (self.debugLayer) {
if (self.scrollDirection == UICollectionViewScrollDirectionVertical) {
self.debugLayer.frame = CGRectFlatMake(0, self.collectionView.adjustedContentInset.top + self.sectionInset.top + _finalItemSize.height / 2, CGRectGetWidth(self.collectionView.bounds), PixelOne);
} else {
self.debugLayer.frame = CGRectFlatMake(self.collectionView.adjustedContentInset.left + self.sectionInset.left + _finalItemSize.width / 2, 0, PixelOne, CGRectGetHeight(self.collectionView.bounds));
}
}
}
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
if (self.style == QMUICollectionViewPagingLayoutStyleScale || self.style == QMUICollectionViewPagingLayoutStyleRotation) {
return YES;
}
return !CGSizeEqualToSize(self.collectionView.bounds.size, newBounds.size);
}
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
if (self.style == QMUICollectionViewPagingLayoutStyleDefault) {
return [super layoutAttributesForElementsInRect:rect];
}
NSArray<UICollectionViewLayoutAttributes *> *resultAttributes = [[NSArray alloc] initWithArray:[super layoutAttributesForElementsInRect:rect] copyItems:YES];
CGFloat offset = CGRectGetMidX(self.collectionView.bounds);//
CGSize itemSize = _finalItemSize;
if (self.style == QMUICollectionViewPagingLayoutStyleScale) {
CGFloat distanceForMinimumScale = itemSize.width + self.minimumLineSpacing;
CGFloat distanceForMaximumScale = 0.0;
for (UICollectionViewLayoutAttributes *attributes in resultAttributes) {
CGFloat scale = 0;
CGFloat distance = ABS(offset - attributes.center.x);
if (distance >= distanceForMinimumScale) {
scale = self.minimumScale;
} else if (distance == distanceForMaximumScale) {
scale = self.maximumScale;
} else {
scale = self.minimumScale + (distanceForMinimumScale - distance) * (self.maximumScale - self.minimumScale) / (distanceForMinimumScale - distanceForMaximumScale);
}
attributes.transform3D = CATransform3DMakeScale(scale, scale, 1);
attributes.zIndex = 1;
}
return resultAttributes;
}
if (self.style == QMUICollectionViewPagingLayoutStyleRotation) {
if (self.rotationRadius == QMUICollectionViewPagingLayoutRotationRadiusAutomatic) {
self.rotationRadius = itemSize.height;
}
UICollectionViewLayoutAttributes *centerAttribute = nil;
CGFloat centerMin = 10000;
for (UICollectionViewLayoutAttributes *attributes in resultAttributes) {
CGFloat distance = self.collectionView.contentOffset.x + CGRectGetWidth(self.collectionView.bounds) / 2.0 - attributes.center.x;
CGFloat degress = - 90 * self.rotationRatio * (distance / CGRectGetWidth(self.collectionView.bounds));
CGFloat cosValue = ABS(cosf(AngleWithDegrees(degress)));
CGFloat translateY = self.rotationRadius - self.rotationRadius * cosValue;
CGAffineTransform transform = CGAffineTransformMakeTranslation(0, translateY);
transform = CGAffineTransformRotate(transform, AngleWithDegrees(degress));
attributes.transform = transform;
attributes.zIndex = 1;
if (ABS(distance) < centerMin) {
centerMin = ABS(distance);
centerAttribute = attributes;
}
}
centerAttribute.zIndex = 10;
return resultAttributes;
}
return resultAttributes;
}
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity {
CGFloat itemSpacing = (self.scrollDirection == UICollectionViewScrollDirectionHorizontal ? _finalItemSize.width : _finalItemSize.height) + self.minimumLineSpacing;
CGSize contentSize = self.collectionViewContentSize;
CGSize frameSize = self.collectionView.bounds.size;
UIEdgeInsets contentInset = self.collectionView.adjustedContentInset;
BOOL scrollingToRight = proposedContentOffset.x < self.collectionView.contentOffset.x;// collectionView
BOOL scrollingToBottom = proposedContentOffset.y < self.collectionView.contentOffset.y;
BOOL forcePaging = NO;
CGPoint translation = [self.collectionView.panGestureRecognizer translationInView:self.collectionView];
if (self.scrollDirection == UICollectionViewScrollDirectionVertical) {
if (!self.allowsMultipleItemScroll || ABS(velocity.y) <= ABS(self.multipleItemScrollVelocityLimit)) {
proposedContentOffset = self.collectionView.contentOffset;// proposedContentOffset contentOffset contentOffset
//
if (ABS(velocity.y) > self.velocityForEnsurePageDown) {
forcePaging = YES;
}
}
// /
if (proposedContentOffset.y < -contentInset.top || proposedContentOffset.y >= contentSize.height + contentInset.bottom - frameSize.height) {
// iOS 10 contentOffset iOS 9
return proposedContentOffset;
}
CGFloat progress = ((contentInset.top + proposedContentOffset.y) + _finalItemSize.height / 2/* item contentOffset.y item */) / itemSpacing;
NSInteger currentIndex = (NSInteger)progress;
NSInteger targetIndex = currentIndex;
// if 01 1/30 bug
if (translation.y < 0 && (ABS(translation.y) > _finalItemSize.height / 2 + self.minimumLineSpacing)) {
} else if (translation.y > 0 && ABS(translation.y > _finalItemSize.height / 2)) {
} else {
CGFloat remainder = progress - currentIndex;
CGFloat offset = remainder * itemSpacing;
BOOL shouldNext = (forcePaging || (offset / _finalItemSize.height >= self.pagingThreshold)) && !scrollingToBottom && velocity.y > 0;
BOOL shouldPrev = (forcePaging || (offset / _finalItemSize.height <= 1 - self.pagingThreshold)) && scrollingToBottom && velocity.y < 0;
targetIndex = currentIndex + (shouldNext ? 1 : (shouldPrev ? -1 : 0));
}
proposedContentOffset.y = -contentInset.top + targetIndex * itemSpacing;
}
else if (self.scrollDirection == UICollectionViewScrollDirectionHorizontal) {
if (!self.allowsMultipleItemScroll || ABS(velocity.x) <= ABS(self.multipleItemScrollVelocityLimit)) {
proposedContentOffset = self.collectionView.contentOffset;// proposedContentOffset contentOffset contentOffset
//
if (ABS(velocity.x) > self.velocityForEnsurePageDown) {
forcePaging = YES;
}
}
// /
if (proposedContentOffset.x < -contentInset.left || proposedContentOffset.x >= contentSize.width + contentInset.right - frameSize.width) {
// iOS 10 contentOffset iOS 9
return proposedContentOffset;
}
CGFloat progress = ((contentInset.left + proposedContentOffset.x) + _finalItemSize.width / 2/* item contentOffset.x item */) / itemSpacing;
NSInteger currentIndex = (NSInteger)progress;
NSInteger targetIndex = currentIndex;
// if 01 1/30 bug
if (translation.x < 0 && (ABS(translation.x) > _finalItemSize.width / 2 + self.minimumLineSpacing)) {
} else if (translation.x > 0 && ABS(translation.x > _finalItemSize.width / 2)) {
} else {
CGFloat remainder = progress - currentIndex;
CGFloat offset = remainder * itemSpacing;
// collectionView bounces / proposedContentOffset velocity velocity.x > 0 forcePaging && !scrollingToRight
BOOL shouldNext = (forcePaging || (offset / _finalItemSize.width >= self.pagingThreshold)) && !scrollingToRight && velocity.x > 0;
BOOL shouldPrev = (forcePaging || (offset / _finalItemSize.width <= 1 - self.pagingThreshold)) && scrollingToRight && velocity.x < 0;
targetIndex = currentIndex + (shouldNext ? 1 : (shouldPrev ? -1 : 0));
}
proposedContentOffset.x = -contentInset.left + targetIndex * itemSpacing;
}
return proposedContentOffset;
}
@end