iOS: implement slider style gallery

This commit is contained in:
王劲鹏
2023-06-13 20:28:22 +08:00
committed by osborn
parent bd5c5c48ee
commit 3393a474f9
5 changed files with 244 additions and 79 deletions

View File

@@ -0,0 +1,29 @@
/*
* Copyright [2023] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
//
// DoricSliderNode.h
// Doric
//
// Created by jingpeng.wang on 2023/6/13.
//
#import <UIKit/UIKit.h>
@interface DoricSliderLayout : UICollectionViewFlowLayout
@property(nonatomic, assign) CGFloat galleryItemWidth;
@property(nonatomic, assign) CGFloat galleryMinScale;
@property(nonatomic, assign) CGFloat galleryMinAlpha;
@end

View File

@@ -0,0 +1,60 @@
/*
* Copyright [2023] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
//
// DoricSliderNode.m
// Doric
//
// Created by jingpeng.wang on 2023/6/13.
//
#import "DoricSliderLayout.h"
@implementation DoricSliderLayout
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
NSArray *attributes = [super layoutAttributesForElementsInRect:rect];
CGRect visitRect = {self.collectionView.contentOffset,self.collectionView.bounds.size};
NSMutableArray *attributesCopy = [NSMutableArray array];
for (UICollectionViewLayoutAttributes *attribute in attributes) {
UICollectionViewLayoutAttributes *attributeCopy = [attribute copy];
[attributesCopy addObject:attributeCopy];
}
for (UICollectionViewLayoutAttributes *attribute in attributesCopy) {
CGFloat distance = CGRectGetMidX(visitRect) - attribute.center.x;
float diff = fabs(distance);
CATransform3D scaleTransform = CATransform3DIdentity;
if (diff >= 0 && diff <= self.galleryItemWidth) {
float scale = 1 - (1 - self.galleryMinScale) * (diff / self.galleryItemWidth);
scaleTransform = CATransform3DMakeScale(scale, scale, scale);
attribute.alpha = 1 - (1 - self.galleryMinAlpha) * (diff / self.galleryItemWidth);
} else {
float scale = self.galleryMinScale;
scaleTransform = CATransform3DMakeScale(scale, scale, scale);
attribute.alpha = self.galleryMinAlpha;
}
attribute.transform3D = scaleTransform;
}
return attributesCopy;
}
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
return YES;
}
@end

View File

@@ -23,6 +23,7 @@
#import "DoricSliderNode.h"
#import "Doric.h"
#import "DoricSlideItemNode.h"
#import "DoricSliderLayout.h"
@interface DoricSliderViewCell : UICollectionViewCell
@property(nonatomic, strong) DoricSlideItemNode *doricSlideItemNode;
@@ -47,7 +48,11 @@ @interface DoricSliderNode () <UICollectionViewDataSource, UICollectionViewDeleg
@property(nonatomic, strong) NSString *slideStyle;
@property(nonatomic, assign) CGFloat minScale;
@property(nonatomic, assign) CGFloat maxScale;
@property(nonatomic, assign) CGFloat galleryMinScale;
@property(nonatomic, assign) CGFloat galleryMinAlpha;
@property(nonatomic, assign) CGFloat galleryItemWidth;
@property(nonatomic, assign) BOOL scheduledLayout;
@property (nonatomic, assign) NSInteger currentPage;
@end
@interface DoricSliderView : UICollectionView
@@ -75,7 +80,7 @@ - (instancetype)initWithContext:(DoricContext *)doricContext {
}
- (UICollectionView *)build {
UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
DoricSliderLayout *flowLayout = [[DoricSliderLayout alloc] init];
[flowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
return [[[DoricSliderView alloc] initWithFrame:CGRectZero
collectionViewLayout:flowLayout]
@@ -113,8 +118,21 @@ - (void)blendView:(UICollectionView *)view forPropName:(NSString *)name propValu
} else if ([@"slideStyle" isEqualToString:name]) {
if ([prop isKindOfClass:NSDictionary.class]) {
self.slideStyle = prop[@"type"];
self.maxScale = [prop[@"maxScale"] floatValue];
self.minScale = [prop[@"minScale"] floatValue];
if ([self.slideStyle isEqualToString:@"zoomOut"]) {
self.maxScale = [prop[@"maxScale"] floatValue];
self.minScale = [prop[@"minScale"] floatValue];
} else if([self.slideStyle isEqualToString:@"gallery"]) {
self.galleryMinScale = [prop[@"minScale"] floatValue];
self.galleryMinAlpha = [prop[@"minAlpha"] floatValue];
self.galleryItemWidth = [prop[@"itemWidth"] floatValue];
self.view.pagingEnabled = NO;
DoricSliderLayout *layout = (DoricSliderLayout*)self.view.collectionViewLayout;
layout.galleryItemWidth = self.galleryItemWidth;
layout.galleryMinScale = self.galleryMinScale;
layout.galleryMinAlpha = self.galleryMinAlpha;
}
} else if ([prop isKindOfClass:NSString.class]) {
self.slideStyle = prop;
}
@@ -161,11 +179,18 @@ - (void)afterBlended:(NSDictionary *)props {
__strong typeof(_self) self = _self;
[self.view reloadData];
NSUInteger position = (self.loop ? 1 : 0) + self.slidePosition;
if (self.view.width == 0) {
float itemWidth;
if ([self.slideStyle isEqualToString:@"gallery"]) {
itemWidth = self.galleryItemWidth;
} else {
itemWidth = self.view.width;
}
if (itemWidth == 0) {
self.needResetScroll = true;
} else {
self.needResetScroll = false;
[self.view setContentOffset:CGPointMake(position * self.view.width, self.view.contentOffset.y) animated:false];
[self.view setContentOffset:CGPointMake(position * itemWidth, self.view.contentOffset.y) animated:false];
}
});
}
@@ -180,6 +205,12 @@ - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSe
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
if ([self.slideStyle isEqualToString:@"gallery"]) {
UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.view.collectionViewLayout;
int margin = (self.view.width - self.galleryItemWidth) / 2;
layout.sectionInset = UIEdgeInsetsMake(0, margin, 0, margin);
return CGSizeMake(self.galleryItemWidth, self.view.height);
}
return CGSizeMake(self.view.width, self.view.height);
}
@@ -208,7 +239,12 @@ - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collection
DoricSlideItemNode *node = cell.doricSlideItemNode;
node.viewId = model[@"id"];
[node blend:props];
CGFloat maxWidth = collectionView.width;
CGFloat maxWidth;
if ([self.slideStyle isEqualToString:@"gallery"]) {
maxWidth = self.galleryItemWidth;
} else {
maxWidth = collectionView.width;
}
if (collectionView.doricLayout.widthSpec == DoricLayoutFit) {
maxWidth = [[UIScreen mainScreen] bounds].size.width;
}
@@ -322,7 +358,12 @@ - (void)blendSubNode:(NSDictionary *)subModel {
if (viewNode) {
CGSize originSize = viewNode.view.frame.size;
[viewNode blend:subModel[@"props"]];
[viewNode.view.doricLayout apply:CGSizeMake(self.view.width, self.view.height)];
if ([self.slideStyle isEqualToString:@"gallery"]) {
[viewNode.view.doricLayout apply:CGSizeMake(self.galleryItemWidth, self.view.height)];
} else {
[viewNode.view.doricLayout apply:CGSizeMake(self.view.width, self.view.height)];
}
[viewNode requestLayout];
if (CGSizeEqualToSize(originSize, viewNode.view.frame.size)) {
skipReload = YES;
@@ -343,6 +384,39 @@ - (void)blendSubNode:(NSDictionary *)subModel {
}];
}
//paging by cell | paging with one cell at a time
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
if ([self.slideStyle isEqualToString:@"gallery"]) {
CGFloat cellWidth = self.galleryItemWidth;
self.currentPage = (scrollView.contentOffset.x - cellWidth / 2) / (cellWidth) + 1;
}
}
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
if ([self.slideStyle isEqualToString:@"gallery"]) {
CGFloat cellWidth = self.galleryItemWidth;
NSInteger page = (scrollView.contentOffset.x - cellWidth / 2) / (cellWidth) + 1;
if (velocity.x > 0) page++;
if (velocity.x < 0) page--;
page = MAX(page, 0);
NSInteger prePage = self.currentPage - 1;
if(prePage > 0 && page < prePage){
page = prePage;
} else if (page > self.currentPage + 1){
page = self.currentPage + 1;
}
self.currentPage = page;
CGFloat newOffset = page * (cellWidth);
targetContentOffset->x = newOffset;
}
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if ([self.slideStyle isEqualToString:@"zoomOut"]) {
CGFloat centerX = scrollView.width / 2.f;
@@ -357,15 +431,28 @@ - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
NSUInteger pageIndex = (NSUInteger) (scrollView.contentOffset.x / scrollView.width);
scrollView.contentOffset = CGPointMake(pageIndex * scrollView.width, scrollView.contentOffset.y);
NSUInteger pageIndex;
if ([self.slideStyle isEqualToString:@"gallery"]) {
pageIndex = (NSUInteger) (scrollView.contentOffset.x / self.galleryItemWidth);
scrollView.contentOffset = CGPointMake(pageIndex * self.galleryItemWidth, scrollView.contentOffset.y);
} else {
pageIndex = (NSUInteger) (scrollView.contentOffset.x / scrollView.width);
scrollView.contentOffset = CGPointMake(pageIndex * scrollView.width, scrollView.contentOffset.y);
}
if (self.loop) {
float itemWidth;
if ([self.slideStyle isEqualToString:@"gallery"]) {
itemWidth = self.galleryItemWidth;
} else {
itemWidth = self.view.width;
}
if (pageIndex == 0) {
[self.view setContentOffset:CGPointMake(self.itemCount * self.view.width, self.view.contentOffset.y) animated:false];
[self.view setContentOffset:CGPointMake(self.itemCount * itemWidth, self.view.contentOffset.y) animated:false];
pageIndex = self.itemCount - 1;
} else if (pageIndex == self.itemCount + 1) {
[self.view setContentOffset:CGPointMake(1 * self.view.width, self.view.contentOffset.y) animated:false];
[self.view setContentOffset:CGPointMake(1 * itemWidth, self.view.contentOffset.y) animated:false];
pageIndex = 0;
} else {
pageIndex = pageIndex - 1;
@@ -384,10 +471,17 @@ - (void)slidePage:(NSDictionary *)params withPromise:(DoricPromise *)promise {
NSUInteger pageIndex = [params[@"page"] unsignedIntegerValue];
BOOL smooth = [params[@"smooth"] boolValue];
if (self.loop) {
[self.view setContentOffset:CGPointMake((pageIndex + 1) * self.view.width, self.view.contentOffset.y) animated:smooth];
float itemWidth;
if ([self.slideStyle isEqualToString:@"gallery"]) {
itemWidth = self.galleryItemWidth;
} else {
[self.view setContentOffset:CGPointMake(pageIndex * self.view.width, self.view.contentOffset.y) animated:smooth];
itemWidth = self.view.width;
}
if (self.loop) {
[self.view setContentOffset:CGPointMake((pageIndex + 1) * itemWidth, self.view.contentOffset.y) animated:smooth];
} else {
[self.view setContentOffset:CGPointMake(pageIndex * itemWidth, self.view.contentOffset.y) animated:smooth];
}
[promise resolve:nil];
@@ -398,7 +492,13 @@ - (void)slidePage:(NSDictionary *)params withPromise:(DoricPromise *)promise {
}
- (NSNumber *)getSlidedPage {
NSUInteger pageIndex = (NSUInteger) (self.view.contentOffset.x / self.view.width);
NSUInteger pageIndex;
if ([self.slideStyle isEqualToString:@"gallery"]) {
pageIndex = (NSUInteger) (self.view.contentOffset.x / self.galleryItemWidth);
} else {
pageIndex = (NSUInteger) (self.view.contentOffset.x / self.view.width);
}
if (self.loop) {
return @(pageIndex - 1);
} else {