您的位置:首页 > 产品设计 > UI/UE

UIView重用展示(类似卡片堆叠效果),不明白tableView重用机制的可以参考一下

2015-06-03 21:54 399 查看
我们都知道自带重用展示效果的视图是UITableView和UICollectionView。
1,UITableView:
在app中最常看到的是UITableView,是因为重用机制非常好,而且自定义cell可实现特别多的UI效果,应用特别广泛。比如微信的聊天界面、好友列表界面、朋友圈,类似这些的都是UITableView。大家都会用,就不多说了。
2,UICollectionView:
在app中有时候会看到一些很炫的效果,而且是重用展示的,比如最常看到的瀑布流(屏幕中有两列(通常两列)然后没列的视图height不同,因此紧下面的视图的y也不一样)。还有一些横向滚动的视图,一般不用UIScrollView,因为一下所有视图全部显示在屏幕上会特别占内存而卡,所以需要重用展示。而且UICollectionView通过自定义layout可以获得很多炫酷的效果(至于什么效果可以自己查找)。

3,UIView重用展示:
UITableView和UICollectionView都是继承与UIScrollView的。而UIScrollView继承与UIView。所以我们要是想要卡片堆叠效果。而且通过手势翻牌,我们可以直接添加在UIViewController的view上,但是数量多的时候我们得需要重用展示。

比如:总共50条数据,我们不能创建50个UIView添加在view上。按理来说我们只需要两个UIView。一个用来最上面的展示,另一个则在拖拽最上面的UIView还没松手时展示在下面。所以这样会很省内存的。demo如下:
ViewController.m:
// ViewController.m// JFView//// Created by 21Lovetong on 15/5/25.// Copyright (c) 2015年 21Lovetong. All rights reserved.//
#import "ViewController.h"#import "JFViewCell.h"#import "JFView.h"
@interface ViewController ()<JFViewDataSource>{ CGPoint cellOrigin;}@property (nonatomic, strong) NSArray *colorArray;@property (nonatomic, strong) JFView *jfView;
@end
@implementation ViewController
- (void)viewDidLoad { [super viewDidLoad]; // 5种颜色的卡片,用来区分
self.colorArray = @[[UIColor greenColor], [UIColor blueColor], [UIColor yellowColor], [UIColor cyanColor], [UIColor orangeColor]]; [self.view addSubview:self.jfView]; // Do any additional setup after loading the view, typically from a nib.}
- (JFView *)jfView { if (!_jfView) { _jfView = [[JFView alloc] initWithFrame:CGRectMake(10, 40, WIDTH - 20, HEIGHT - 80)]; [_jfView setDataSource:self]; } return _jfView;}
// 用法可参照 UITableView- (NSInteger)numberOfItemInView:(JFView *)view { return _colorArray.count;}
- (JFViewCell *)view:(JFView *)view itemForViewAtIndex:(NSInteger)index { static NSString *cellIdentifier = @"cell"; JFViewCell *cell = [view dequeueReusableCellWithIdentifier:cellIdentifier]; if (!cell) { cell = [[JFViewCell alloc] initWithReusebleIdentifier:cellIdentifier]; } [cell setBackgroundColor:[_colorArray objectAtIndex:index]]; return cell;}
- (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated.}
@end

JFView.h:
// JFView.h// JFView//// Created by 21Lovetong on 15/5/25.// Copyright (c) 2015年 21Lovetong. All rights reserved.//
#import <UIKit/UIKit.h>#import "JFViewCell.h"
#define WIDTH [UIScreen mainScreen].bounds.size.width#define HEIGHT [UIScreen mainScreen].bounds.size.height
@class JFView;
@protocol JFViewDataSource <NSObject>@required// 可参照UITableView的UITableViewDataSource- (NSInteger)numberOfItemInView:(JFView *)view;- (JFViewCell *)view:(JFView *)view itemForViewAtIndex:(NSInteger)index;
@end
@interface JFView : UIView
@property (nonatomic, weak) id<JFViewDataSource>dataSource;
- (id)dequeueReusableCellWithIdentifier:(NSString *)identifier;
@end

JFView.m:

// JFView.m// JFLayout//// Created by 21Lovetong on 15/5/25.// Copyright (c) 2015年 21Lovetong. All rights reserved.//
#import "JFView.h"
// 重用池中 cell的总数量,只需要两个#define ReusableItemCount 2// 某方向的速度大于 1000时翻页,否则不翻页#define MaxVelocity 1000
@interface JFView (){ UIPanGestureRecognizer *jfViewPanGestureRecognizer; // cell的大小
CGSize itemSize; // 拖拽时的初始点
CGPoint initialPoint; // cell数量
NSInteger itemCount; // cell的下标
NSInteger currentIndex; // 下一页 cell实例
JFViewCell *nextViewCell;}
@property (nonatomic, strong) NSMutableSet *reusableCells;
@end
@implementation JFView
// 初始化- (instancetype)initWithFrame:(CGRect)frame{ self = [super initWithFrame:frame]; if (self) { jfViewPanGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(viewDidPan:)]; itemSize = frame.size; currentIndex = 0; [self addGestureRecognizer:jfViewPanGestureRecognizer]; } return self;}
// 初始化重用池- (NSMutableSet *)reusableCells { if (!_reusableCells) { _reusableCells = [NSMutableSet setWithCapacity:0]; } return _reusableCells;}
// 初始刷新页面- (void)willMoveToSuperview:(UIView *)newSuperview { [super willMoveToSuperview:newSuperview]; [self reloadData];}
// 刷新页面- (void)reloadData { itemCount = [self.dataSource numberOfItemInView:self]; JFViewCell *viewCell = [self.dataSource view:self itemForViewAtIndex:0]; [viewCell setFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)]; [self addSubview:viewCell];}
// 从重用池获取 cell,可参照 UITableView- (id)dequeueReusableCellWithIdentifier:(NSString *)identifier{ __block JFViewCell *reusableCell = nil; [self.reusableCells enumerateObjectsUsingBlock:^(JFViewCell * cell, BOOL *stop) { if (cell && [cell.identifier isEqualToString:identifier]) { reusableCell = cell; *stop = YES; } }]; if (reusableCell) { // 从重用池成功找到未使用的 cell,就把 cell从重用池移除,确保重用池内的 cell都是未使用的
[self.reusableCells removeObject:reusableCell]; } // 返回此 cell
return reusableCell;}
// 拖拽 cell时的动作,可自定义拖拽过程中的动画效果- (void)viewDidPan:(UIPanGestureRecognizer *)panGestureRecognizer { // 获取最上面的 cell
JFViewCell *viewCell = [self.subviews lastObject]; // 获取拖拽过程中的手指坐标
CGPoint touchedPoint = [panGestureRecognizer locationInView:self]; // 获取拖拽速度(每秒移动的像素,有 x方向和 y方向的分速度)
CGPoint velocity = [panGestureRecognizer velocityInView:self]; // 记录最后拖拽结束时视图的移动方向,松手后让视图沿着此方向移动直到移动到屏幕外
CGFloat swipeAngle = 0.0; // 监听拖拽状态,拖拽开始
if (panGestureRecognizer.state == UIGestureRecognizerStateBegan) { // 获取刚拖拽时手指触碰的坐标
initialPoint = [panGestureRecognizer locationInView:viewCell]; // 判断拖拽方向,是偏上还是偏下,用来判断下一个 cell显示上一条数据还是下一条数据(上翻还是下翻)
// 如果偏上,获取上一条数据
if (velocity.y < 0) { // 如果当前下标为 0,下一个 cell显示的还是第一条数据,否则数组越界crash
// 此段代码可简化(按照下翻的情况下的处理)
if (currentIndex == 0) { // 获取第一条数据
nextViewCell = [self.dataSource view:self itemForViewAtIndex:0]; [nextViewCell setFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)]; [self addSubview:nextViewCell]; // 把 cell移动到最上面(可以自己体会效果,注释掉看看)
[self bringSubviewToFront:viewCell]; } else { // 如果当前下标不为 0,获取上一条数据 nextViewCell = [self.dataSource view:self itemForViewAtIndex:--currentIndex]; [nextViewCell setFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)]; [self addSubview:nextViewCell]; // 把正在拖拽的视图移动到最上面,下一个 cell放下面 [self bringSubviewToFront:viewCell]; } } else { // 如果当前下标对应最后一条数据
if (currentIndex == itemCount - 1) { // 先对下标做“--”处理,然后获取数据的时候都统一做“++”操作,上面的上翻也可以这样简化
currentIndex--; } // 下一个cell显示下一条数据,如果当前是最后一条数据,由于上面的判断做了“--”,所以这里直接“++”
nextViewCell = [self.dataSource view:self itemForViewAtIndex:++currentIndex]; [nextViewCell setFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)]; [self addSubview:nextViewCell]; // 把正在拖拽的视图移动到最上面,下一个 cell放下面
[self bringSubviewToFront:viewCell]; } } [viewCell setFrame:CGRectMake(touchedPoint.x - initialPoint.x, touchedPoint.y - initialPoint.y, viewCell.frame.size.width, viewCell.frame.size.height)]; // 监听拖拽结束
if (panGestureRecognizer.state == UIGestureRecognizerStateEnded) { if (velocity.x) { // 获取拖拽结束时视图移动角度
swipeAngle = atan(velocity.y / velocity.x); } // 判断视图拖拽结束时移动速度,如果速度快则翻页,否则不翻页,最上面的视图还是刚才拖拽的视图 if (velocity.y > MaxVelocity || velocity.y < -MaxVelocity || velocity.x > MaxVelocity || velocity.x < -MaxVelocity) { // 翻页
// 给视图加延时动画
[UIView animateWithDuration:0.3f animations:^{ // 判断移动方向 if (velocity.y > 0) { // 右下方向
if (velocity.x > 0) { [viewCell setCenter:CGPointMake(WIDTH * 3 / 2.0f, HEIGHT / 2.0f + WIDTH * tan(swipeAngle))]; } else { // 左下方向
[viewCell setCenter:CGPointMake(-WIDTH / 2.0f, HEIGHT / 2.0f - WIDTH * tan(swipeAngle))]; } } else { // 右上方向
if (velocity.x > 0) { [viewCell setCenter:CGPointMake(WIDTH * 3 / 2.0f, HEIGHT / 2.0f + WIDTH * tan(swipeAngle))]; } else { // 左上方向
[viewCell setCenter:CGPointMake(-WIDTH / 2.0f, HEIGHT / 2.0f - WIDTH * tan(swipeAngle))]; } } } completion:^(BOOL finished) { // 动画结束时让当前的 cell从父视图中移除,并放进重用池
[self.reusableCells addObject:viewCell]; [viewCell removeFromSuperview]; }]; } else { // 不翻页
[UIView animateWithDuration:0.3f animations:^{ // 让当前视图回到原来的位置
[viewCell setFrame:CGRectMake(0, 0, viewCell.frame.size.width, viewCell.frame.size.height)]; if (currentIndex) { // 同时恢复当前视图数据的下标,可以考虑放到 block外(动画开始前,可以自己试试看)
if (velocity.y < 0) { currentIndex--; } else { currentIndex++; } } } completion:^(BOOL finished) { // 动画结束后,让刚才在下面显示的下一个 cell从父视图中移除,并放进重用池
[self.reusableCells addObject:nextViewCell]; [nextViewCell removeFromSuperview]; }]; } }}
/*// Only override drawRect: if you perform custom drawing.// An empty implementation adversely affects performance during animation.- (void)drawRect:(CGRect)rect { // Drawing code}*/
@end

JFViewCell.h:
// JFViewCell.h// JFView//// Created by 21Lovetong on 15/5/25.// Copyright (c) 2015年 21Lovetong. All rights reserved.//
#import <UIKit/UIKit.h>
@interface JFViewCell : UIView
@property (nonatomic, strong) NSString *identifier;
- (instancetype)initWithReusebleIdentifier:(NSString *)identifier;
@end

JFViewCell.m:
// JFViewCell.m// JFView//// Created by 21Lovetong on 15/5/25.// Copyright (c) 2015年 21Lovetong. All rights reserved.//
#import "JFViewCell.h"
@interface JFViewCell ()
@end
@implementation JFViewCell
- (instancetype)initWithReusebleIdentifier:(NSString *)identifier{ self = [super init]; if (self) { // 重用池标示符
self.identifier = identifier; } return self;}
/*// Only override drawRect: if you perform custom drawing.// An empty implementation adversely affects performance during animation.- (void)drawRect:(CGRect)rect { // Drawing code}*/
@end

本文出自 “封寒瑾” 博客,请务必保留此出处http://21lovetong.blog.51cto.com/9944813/1658109
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: