
For What?
Twitter客户端在个人Tab有这样的一个效果:

向下拖动ScrollView(TableView)时,ScrollView上方的图片会随着手指的拖动而放大并且变模糊。松开手指之后,图片随着ScrollView的回复原来位置而恢复原样,如上图。
What I Do?
So,我们可以怎么实现一个类似的效果呢?
下面是我刚写的一个Demo截图,非常简单,而且也是用了网上开源的毛玻璃代码。

咱来个高端的工具,看看,这里面是怎么的一个架构?!

通过树状的视图效果,我们可以看出,背景图片backgroundImageView(放大,毛玻璃效果)是一个UIImageView的子类,TableView会占据真个Window的bounds。其中TableView的tableHeaderView正好覆盖在backgroundImageView的上方,并且背景是透明色,才能看到下层backgroundImageView的变化情况。
依赖开源代码
毛玻璃图片开源代码:https://github.com/CoCrash/DKLiveBlur
1 // 2 // DKLiveBlurView.h 3 // LiveBlur 4 // 5 // Created by Dmitry Klimkin on 16/6/13. 6 // Copyright (c) 2013 Dmitry Klimkin. All rights reserved. 7 // 8 9 #import <UIKit/UIKit.h> 10 11 #define kDKBlurredBackgroundDefaultLevel 0.9f 12 #define kDKBlurredBackgroundDefaultGlassLevel 0.2f 13 #define kDKBlurredBackgroundDefaultGlassColor [UIColor whiteColor] 14 15 @interface DKLiveBlurView : UIImageView 16 17 @PRoperty (nonatomic, strong) UIImage *originalImage; 18 @property (nonatomic, weak) UIScrollView *scrollView; 19 @property (nonatomic, assign) float initialBlurLevel; 20 @property (nonatomic, assign) float initialGlassLevel; 21 @property (nonatomic, assign) BOOL isGlassEffectOn; 22 @property (nonatomic, strong) UIColor *glassColor; 23 24 - (void)setBlurLevel:(float)blurLevel; 25 26 @endDKLiveBlurView.h
1 //
2 // DKLiveBlurView.m
3 // LiveBlur
4 //
5 // Created by Dmitry Klimkin on 16/6/13.
6 // Copyright (c) 2013 Dmitry Klimkin. All rights reserved.
7 //
8
9 #import "DKLiveBlurView.h"
10 #import <Accelerate/Accelerate.h>
11
12 @interface DKLiveBlurView ()
13
14 @property (nonatomic, strong) UIImageView *backgroundImageView;
15 @property (nonatomic, strong) UIView *backgroundGlassView;
16
17 @end
18
19 @implementation DKLiveBlurView
20
21 @synthesize originalImage = _originalImage;
22 @synthesize backgroundImageView = _backgroundImageView;
23 @synthesize scrollView = _scrollView;
24 @synthesize initialBlurLevel = _initialBlurLevel;
25 @synthesize backgroundGlassView = _backgroundGlassView;
26 @synthesize initialGlassLevel = _initialGlassLevel;
27 @synthesize isGlassEffectOn = _isGlassEffectOn;
28 @synthesize glassColor = _glassColor;
29
30 - (id)initWithFrame:(CGRect)frame {
31 self = [super initWithFrame:frame];
32 if (self) {
33 // Initialization code
34
35 _initialBlurLevel = kDKBlurredBackgroundDefaultLevel;
36 _initialGlassLevel = kDKBlurredBackgroundDefaultGlassLevel;
37 _glassColor = kDKBlurredBackgroundDefaultGlassColor;
38
39 _backgroundImageView = [[UIImageView alloc] initWithFrame: self.bounds];
40
41 _backgroundImageView.alpha = 0.0;
42 _backgroundImageView.contentMode = UIViewContentModeScaleToFill;
43 _backgroundImageView.backgroundColor = [UIColor clearColor];
44
45 _backgroundImageView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
46
47 [self addSubview: _backgroundImageView];
48
49 _backgroundGlassView = [[UIView alloc] initWithFrame: self.bounds];
50
51 _backgroundGlassView.alpha = 0.0;
52 _backgroundGlassView.backgroundColor = kDKBlurredBackgroundDefaultGlassColor;
53
54 _backgroundGlassView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
55
56 [self addSubview: _backgroundGlassView];
57 }
58 return self;
59 }
60
61 - (void)setGlassColor:(UIColor *)glassColor {
62 _glassColor = glassColor;
63 _backgroundGlassView.backgroundColor = glassColor;
64 }
65
66 - (void)setScrollView:(UIScrollView *)scrollView {
67 [_scrollView removeObserver: self forKeyPath: @"contentOffset"];
68
69 _scrollView = scrollView;
70
71 [_scrollView addObserver: self forKeyPath: @"contentOffset" options: 0 context: nil];
72 }
73
74 - (UIImage *)blurryImage:(UIImage *)image withBlurLevel:(CGFloat)blur {
75 if ((blur < 0.0f) || (blur > 1.0f)) {
76 blur = 0.5f;
77 }
78
79 int boxSize = (int)(blur * 100);
80 boxSize -= (boxSize % 2) + 1;
81
82 CGImageRef img = image.CGImage;
83
84 vImage_Buffer inBuffer, outBuffer;
85 vImage_Error error;
86 void *pixelBuffer;
87
88 CGDataProviderRef inProvider = CGImageGetDataProvider(img);
89 CFDataRef inBitmapData = CGDataProviderCopyData(inProvider);
90
91 inBuffer.width = CGImageGetWidth(img);
92 inBuffer.height = CGImageGetHeight(img);
93 inBuffer.rowBytes = CGImageGetBytesPerRow(img);
94 inBuffer.data = (void*)CFDataGetBytePtr(inBitmapData);
95
96 pixelBuffer = malloc(CGImageGetBytesPerRow(img) * CGImageGetHeight(img));
97
98 outBuffer.data = pixelBuffer;
99 outBuffer.width = CGImageGetWidth(img);
100 outBuffer.height = CGImageGetHeight(img);
101 outBuffer.rowBytes = CGImageGetBytesPerRow(img);
102
103 error = vImageBoxConvolve_ARGB8888(&inBuffer, &outBuffer, NULL,
104 0, 0, boxSize, boxSize, NULL,
105 kvImageEdgeExtend);
106
107
108 if (error) {
109 NSLog(@"error from convolution %ld", error);
110 }
111
112 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
113 CGContextRef ctx = CGBitmapContextCreate(
114 outBuffer.data,
115 outBuffer.width,
116 outBuffer.height,
117 8,
118 outBuffer.rowBytes,
119 colorSpace,
120 CGImageGetBitmapInfo(image.CGImage));
121
122 CGImageRef imageRef = CGBitmapContextCreateImage (ctx);
123 UIImage *returnImage = [UIImage imageWithCGImage:imageRef];
124
125 //clean up
126 CGContextRelease(ctx);
127 CGColorSpaceRelease(colorSpace);
128
129 free(pixelBuffer);
130 CFRelease(inBitmapData);
131
132 CGColorSpaceRelease(colorSpace);
133 CGImageRelease(imageRef);
134
135 return returnImage;
136 }
137
138 - (void)setOriginalImage:(UIImage *)originalImage {
139 _originalImage = originalImage;
140
141 self.image = originalImage;
142
143 dispatch_queue_t queue = dispatch_queue_create("Blur queue", NULL);
144
145 dispatch_async(queue, ^ {
146
147 UIImage *blurredImage = [self blurryImage: self.originalImage withBlurLevel: self.initialBlurLevel];
148
149 dispatch_async(dispatch_get_main_queue(), ^{
150
151 self.backgroundImageView.alpha = 0.0;
152 self.backgroundImageView.image = blurredImage;
153 });
154 });
155
156 dispatch_release(queue);
157 }
158
159 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
160 change:(NSDictionary *)change context:(void *)context {
161
162 // closer to zero, less blur applied
163 [self setBlurLevel:(self.scrollView.contentInset.top - self.scrollView.contentOffset.y) / (3* CGRectGetHeight(self.bounds) / 5)];
164 }
165
166 - (void)setBlurLevel:(float)blurLevel {
167 self.backgroundImageView.alpha = blurLevel;
168
169 if (self.isGlassEffectOn) {
170 self.backgroundGlassView.alpha = MAX(0.0, MIN(self.backgroundImageView.alpha - self.initialGlassLevel, self.initialGlassLevel));
171 }
172 }
173
174 @end
DKLiveBlurView.m
其实可以不用开DKLiveBlur的开源Demo,他主要实现了UIImageView的一个子类DKLiveBlurView,增加了毛玻璃效果。
- setBlurLevel:方法给我们有能力去设置毛玻璃的程度,这也是我们实现类似Twitter cover效果的主要效果,毛玻璃效果随着tableview的偏移程度,逐渐改变的过程。
我又写了几行代码?
为了实现这个效果,我又写了几行代码?
用户在拖动scrollView(tableView)导致content offset改变的时候就会调用-scrollViewDidScroll:方法,因此我们想在用户拖动scrollview的时候,改变背景图片的大小和毛玻璃程度,则需要实现-scrollViewDidScroll:方法,并计算scrollview的content offset在Y轴下的改变值,得到图片伸缩和毛玻璃效果的比例。
除了做这个界面的布局代码,实际有用的代码就这几行:
1 - (void)scrollViewDidScroll:(UIScrollView *)scrollView{
2 CGPoint offset1 = scrollView.contentOffset;
3 [backgroundView setBlurLevel:(- offset1.y/120)];
4
5 CGRect baseFrame = backgroundView.frame;
6 float visable_height = 162 - offset1.y;
7 if (visable_height > 160) {
8 //放大的情况
9 baseFrame.origin.y = 0;
10 baseFrame.size.height = visable_height;
11 }else{
12 //正常情况
13 baseFrame.origin.y = (visable_height - 160)/2.0f;
14 baseFrame.size.height = 160;
15 }
16 backgroundView.frame = baseFrame;
17
18 }
国内还有哪些高端大气的APP用了类似的效果?

看到这个界面大家是不是很熟悉?哈哈,陌陌同学的个人资料页就有类似的一个效果,不过TA平凡点,没有毛玻璃效果。或者毛玻璃有点像打马赛克一样,让陌陌同学很是不适吧。
曾记否,TX的手Q小企鹅也有过一个小小界面是用了类似的效果的。我相信是个大家都未曾听说过的东东:公开群···
在TA的资料页也是用了类似偏移缩放的效果。现在公开群的入口貌似被老大们屏蔽了,那就算TA已经挂掉了吧,这里我们就没办法截取效果图给大家看了。
SO,你看,那么多人喜欢用到这个效果,行过路过,别忘了进来看看哈。哈哈。Daisy,我写博客了···