使 ZCImagePickerController 支持预览功能
来源:互联网 发布:mysql if 编辑:程序博客网 时间:2024/05/10 14:25
需求
ZCImagePickerController 支持图片多选,但无法在选图时预览大图。我们需要修改这个框架,让它支持预览大图功能。
准备图片资源
我们需要修改4张图片:
你可以打开这些图片看看。你会发现这是用于覆盖在 cell 上的透明图片,右下角有一个 checkmark 标志表示 cell 的选中状态。但它的大小占据了整个 cell:
这会导致整个 cell 都被这张透明图片遮住。这不是我们需要的。我们需要的是让它只保留右下角的1/4 的内容(即 checkmark 所在的位置),其余 3/4 我们需要截去。
请使用图片处理软件解决这个问题。将这4张图片做同样的处理,修改画布,将它们除了右下角之外的 3/4 面积裁切掉。
然后编辑每张图片,使用滤镜中的 color controls 功能,将颜色饱和度设为0,亮度调整为 0.43,如下图所示;
将编辑结果另存为其它名字,比如 SelectionOverlay1@2x.png,然后将图片拖到项目导航窗口中。4张图片处理完,将在项目中增加4张新图片,分别名为(我们在原文件名的结尾加上一个1):
修改 ZCAsset 类
修改接口
打开 ZCAsset.h,在ZCAssetProtocal协议声明中增加一个方法:
- (void)assetDidTouchDown:(ZCAsset*)asset;// MARK: yhy added
原来的协议方法 assetDidSelect: 方法用于处理当用户在 cell 右下角 1/4 区域内的点击,而新增的协议方法则用于处理用户在 cell 除右下角 1/4 区域之外的点击。
在类的 interface 中增加一个实例变量:
@interface ZCAsset : UIControl{ BOOL isSelect;// MARK: yhy added}
修改 initWithAsset:selected: 方法
打开 ZCAsst.m。在 initWithAsset: selected: 方法中,将 _selectionOverlayLayer.frame = thumbFrame; 一句修改为:
_selectionOverlayLayer.frame = CGRectMake(CGRectGetMidX(thumbFrame), CGRectGetMidY(thumbFrame), CGRectGetWidth(thumbFrame)/2.0, CGRectGetHeight(thumbFrame)/2.0);// MARK: yhy edit was:thumbFrame;
这样,使得原来的 checkmark 图片覆盖 cell 右下角 1/4 的位置。
然后注释后面的一段代码,这些代码我们将移植到 toggleSelection 方法中:
// NSString *imageName = [ZCHelper isiOS7orLater] ? @"SelectionOverlay~iOS71" : @"SelectionOverlay1";// if (![NSThread isMainThread]) {// dispatch_async(dispatch_get_main_queue(), ^{// _selectionOverlayLayer.contents = (id)[UIImage imageNamed:imageName].CGImage;// });// }// else {// _selectionOverlayLayer.contents = (id)[UIImage imageNamed:imageName].CGImage;// }// _selectionOverlayLayer.hidden = !selected;
再注释这一句(我们会用手势识别器来代替 action):
//[self addTarget:self action:@selector(toggleSelection) forControlEvents:UIControlEventTouchUpInside];
并添加如下代码:
[self setSelected:selected];UITapGestureRecognizer* tapGesture = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapHandler:)];[self addGestureRecognizer:tapGesture];
先初始化 selected 属性,然后再用我们的 tap 手势识别器取代原来的 action。
实现 selected 属性
首先需要实现 selected 属性的两个指定的 setter/getter 方法:
- (BOOL)isSelected { return isSelect;}- (void)setSelected:(BOOL)selected { isSelect = !selected; [self toggleSelection];}
在 setter/getter 方法中,我们使用了实例变量 isSelect 来保存属性的值。同时当属性值被修改时,我们会调用 toggleSelection 方法。这里你可能会奇怪,在调用 toggleSelection 方法之前为什么会是 isSelect = !selected 而不是 isSelect = selected。
这是因为 toggleSelection 方法中也有一句 isSelect = !selected,这样就会把原来的 isSelect 值又变回 selected 而不是 !select 了。
toggleSelection 方法
接下来看 toggleSelection 方法的实现:
- (void)toggleSelection { isSelect = !isSelect; // 1 NSString *normalImage = [ZCHelper isiOS7orLater] ? @"SelectionOverlay~iOS71" : @"SelectionOverlay1"; // 2 NSString *selImage = [ZCHelper isiOS7orLater] ? @"SelectionOverlay~iOS7" : @"SelectionOverlay"; // 3 NSString* imageName = isSelect ? selImage : normalImage; //4 if (![NSThread isMainThread]) { //5 dispatch_async(dispatch_get_main_queue(), ^{ _selectionOverlayLayer.contents = (id)[UIImage imageNamed:imageName].CGImage; }); }else { _selectionOverlayLayer.contents = (id)[UIImage imageNamed:imageName].CGImage; } if ([self.delegate respondsToSelector:@selector(assetDidSelect:)]) { //6 [self.delegate assetDidSelect:self]; }}
- 如方法名所暗示的,这个方法会将 isSelect 布尔值来回切换,如果原来是 YES,将它改成 NO,如果原来是 NO 的则改为 YES。
- 根据 iOS 版本选择相应的图片,用于表示 cell 未选中的状态。
- 根据 iOS 版本选择相应的图片,用于表示 cell 选中时的状态。
- 根据 isSelect 值判断当前需要显示的是选中状态,还是未选中状态。
- 在主线程中将 _selectionOverlayLayer 中的图片内容应用为指定图片。
- 调用委托对象的 assetDidSelect: 方法,表明用户在点击 cell 右下角的 1/4 区域。
tapHandler 方法
手势识别器会触发 tapHandler 方法。tapHandler 方法实现如下:
-(void)tapHandler:(UITapGestureRecognizer*)gesture{ CALayer* layerThatWasTapped = [_selectionOverlayLayer hitTest:[gesture locationInView:self]]; // 1 if(layerThatWasTapped == _selectionOverlayLayer){ // 2 [self toggleSelection]; }else{ // 3 if([self.delegate respondsToSelector:@selector(assetDidTouchDown:)]){ [self.delegate assetDidTouchDown:self]; } }}
在这个方法中,我们会判断用户点击的区域是 cell 右下角的 1/4 区域(即 _selectionOverlayLayer 这个 CALayer),还是其他地方。
- CALayer 的 hitTest 方法用于测试 UIView 中的某个点是否位于 layer 的 subLayer 中,并返回最远的那个 subLayer。但我们知道,_selectionOverlayLayer 是一个单层的 CALayer,它没有任何 subLayer。因此,如果触摸点位于 _selectionOverlayLayer 中,则返回结果只会是 _selectionOverlayLayer。
- 如果触摸点切实位于 _selectionOverlayLayer(通过 hitTest 方法返回值判断),则我们出发 toggleSelection 方法,表示用户想要的是一个选择/清除选择的动作。
- 否则,用户是想要一个预览全屏大图的动作,因此我们会调用委托方法 assetDidTouchDown:,将处理过程转移到委托对象(ZCAssetTablePicker)来处理。
修改 ZCAssetTablePicker 类
修改接口
打开 ZCAssetTablePicker.m,增加两个实例变量声明:
@interface ZCAssetTablePicker (){ UIView *_vDimmed; // MARK: yhy added UIImageView *_ivPreview; // MARK: yhy added}
_vDimmed 是一个黑色半透明的 UIView,我们用于显示全屏预览图时作为背景的遮罩层。
_ivPreview 是一个 UIImageView,用于显示全屏预览图。
实现 ZCAssetProtocal 协议中的新方法
我们需要实现先前我们在 ZCAssetProtocal 协议中增加的新方法,这个方法负责处理当用户想查看大图而不是选择/反选 cell 的点击:
- (void)assetDidTouchDown:(ZCAsset *)asset{ if (_vDimmed == nil) { // 1 _vDimmed = [[UIView alloc]initWithFrame:CGRectZero]; _vDimmed.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth; _vDimmed.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.7]; [self.view addSubview:_vDimmed]; _vDimmed.alpha = 0.0; UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTapOnDimmedView:)]; [_vDimmed addGestureRecognizer:tap]; } // _vDimmed 的位置必须跟随 tableView 滚动而滚动 _vDimmed.frame = CGRectMake(0, self.tableView.contentOffset.y, CGRectGetWidth(self.tableView.frame), CGRectGetHeight(self.tableView.frame)); [self.view bringSubviewToFront:_vDimmed]; // 2 // NSLog(@"view.frame = %@",[self printFrame:self.view.frame]); // NSLog(@"_vDimmed.frame = %@",[self printFrame:_vDimmed.frame]); _ivPreview = [[UIImageView alloc] initWithFrame:_vDimmed.bounds]; // 3 _ivPreview.contentMode = UIViewContentModeScaleAspectFit; _ivPreview.autoresizingMask = _vDimmed.autoresizingMask; [_vDimmed addSubview:_ivPreview]; CGImageRef ref=[asset.asset.defaultRepresentation fullResolutionImage]; // 4 UIImage* img=[[UIImage alloc]initWithCGImage:ref]; // 5 _ivPreview.image = img; // 6 // add gesture for close preview UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(onPanToClosePreview:)]; // 7 [_vDimmed addGestureRecognizer:pan]; [UIView animateWithDuration:0.2 animations:^(void) { // 8 _vDimmed.alpha = 1.0; }]; }
- 如果 _vDimmed 还没初始化,则初始化 _vDimmed,并设置它的属性,为它添加一个单击手势识别器(这样当用户点击 _vDimmed 时它会隐藏)。
- 将 _vDimmed 放到 view 最上层。
- 初始化 _ivPreview,并设置它的属性,使它根据能根据自己的内容自动调整大小(保持宽高比)。
- 通过 ALAsset 的 defaultRepresentation 属性获得全屏图片,这是一个 CGImageRef 对象,我们需要转换成 UIImage。
- CGImageRef 转换成 UIImage。
- 将 UIImage 赋给 _ivPreview 的 image 属性,以显示图片的内容。
- 然后在 _vDimmed 上添加一个滑动手势识别器,并调用 onPanToClosePreview: 方法进行处理。
- 增加一个淡出动画。
onTapOnDimView 方法
这个方法在用户点击 _vDimmed 时触发,这表明用户想退出图片的预览,因此我们简单调用了 hidePreview (后面实现)来隐藏 _vDimmed:
- (void)onTapOnDimmedView:(UITapGestureRecognizer *)tap{ if (tap.state == UIGestureRecognizerStateEnded) { if (_ivPreview != nil) [self hidePreview]; }}
onPanToClosePreview 方法
这个方法当用户在 _vDimmed 上滑动手指时触发,这时我们可以给用户一个更炫的“飞出”效果,并隐藏 _vDimmed:
- (void)onPanToClosePreview:(UIPanGestureRecognizer *)gestureRecognizer{ if(gestureRecognizer.numberOfTouches <= 1){ CGPoint translation = [gestureRecognizer translationInView:self.view]; CGPoint center = CGPointMake(CGRectGetMidX(_vDimmed.bounds),CGRectGetMidY(_vDimmed.bounds)); if (gestureRecognizer.state == UIGestureRecognizerStateEnded) // 1 { [UIView animateWithDuration:0.2 animations:^(void) { if (_vDimmed.alpha < 0.6) // 2 { CGPoint pt = _ivPreview.center; if (_ivPreview.center.y > _vDimmed.center.y) // 3 pt.y = self.view.frame.size.height * 1.5; else if (_ivPreview.center.y < _vDimmed.center.y) // 4 pt.y = -self.view.frame.size.height * 1.5; _ivPreview.center = pt; [self hidePreview]; } else // 5 { _vDimmed.alpha = 1.0; _ivPreview.center = center; NSLog(@"_vDimmed.center.y:%f",_vDimmed.center.y); } }]; } else // 6 { _ivPreview.center = CGPointMake(_ivPreview.center.x, _ivPreview.center.y + translation.y); _vDimmed.alpha = 1 - ABS(_ivPreview.center.y-center.y) / (center.y); NSLog(@"_vDimmed's alpha= 1-ABS(%f-%f)/(%f/2.0)=%f",_ivPreview.center.y,_vDimmed.center.y,_vDimmed.frame.size.height,_vDimmed.alpha); [gestureRecognizer setTranslation:CGPointMake(0, 0) inView:self.view]; } }}
这个动画的效果是,先让 _ivPreview.center 向上或向下移动,同时 _vDimmed 渐渐变透明,直至最后 _ivPreview 飞出屏幕,_vDimmed 完全透明。
- 如果滑动已经结束,我们就判断 vDimmed.alpha 值是否已经降低到 0.7 以下了。
- 如果是,判断 _ivPreview 移动的方向,并根据方向来移出屏幕。
- 如果 _ivPreview 是向下运动的,则向下移出屏幕。
- 如果 _ivPreview 是向上运动的,则向上移出屏幕。
- 如果 vDimmed.alpha 的值在手指滑动结束时还大于 0.7,表明用户滑动手指的距离不够长,可能用户不想关闭预览,因此我们将 _vDimmed 和 _ivPreview 恢复原样。
- 如果滑动未结束,则我们需要让 _ivPreview 根据手指的移动来运动(但是只考虑手势 y 坐标,表明只能对上滑、下滑进行反应,向左、右滑动并不会触发动作)。同时让 _vDimmed 的 alpha 值逐渐减小(即淡出效果)。
hidePreview 方法
这个方法很简单,隐藏 _vDimmed 而已:
- (void)hidePreview{ [_ivPreview removeFromSuperview];//1 _ivPreview = nil; [self.view sendSubviewToBack:_vDimmed];// 2 _vDimmed.alpha = 0.0; [_vDimmed removeGestureRecognizer:[_vDimmed.gestureRecognizers lastObject]];// 3}
- 移除 _ivPreview 并销毁内存。
- 将 _vDimmed 放到 view 的下层并置为透明。
- 将 pan 手势识别器从 _vDimmed 中移除而 tap 手势识别器仍然保留。因为后者只会添加一次,而前者每次显示 _vDimmed 时都会添加。
- 使 ZCImagePickerController 支持预览功能
- 支持预览功能的图片上传控件
- jsp图片上传支持预览功能
- js 实现图片预览功能支持ie8
- 预览功能
- 视频节目预览功能播放器下载(视频收费模式、支持电脑端与微信视频预览功能)
- ZCImagePickerController框架简介
- js实现web套打功能,支持拖动布局,支持按键移动支持打印预览,结果可保存至数据库中
- 鼠标悬浮图片上,显示上传按钮,点击实现预览功能,支持多浏览器
- 图片选择器, 支持多图选择和图片预览等功能
- jeecg 在linux下支持文档预览功能需要安装依赖 openoffce swftools
- 安卓选择图片上传功能【支持多选拍照预览等】
- 9.1 功能预览
- 10.1 功能预览
- 图片上传预览功能
- js图片预览功能
- 本地图片预览功能
- DELPHI 打印预览功能
- eclipse 里面heap 内存报告分析
- UML类图几种关系的总结
- Android中popupwindow的用法
- BGRA转灰度数据
- 【LeetCode 】 Median of Two Sorted Arrays 简单高效的解法
- 使 ZCImagePickerController 支持预览功能
- atexit()函数调用注册函数的顺序
- Linux基础命令 最后附教学视频
- Android Studio建立jni开发环境
- 【自制】3D全息投影
- create来save数据,表格中和post过来的数据必须一致;
- 【9203】众数
- snprintf与_snprintf的区别
- 怎么运用ABBYY FineReader实现JPG到PDF的转换