实现Twitter-UI效果

在使用Twitter的APP后,我已开发者的视觉并注意到整体与部分之间相互协调是件极其有意思的事情。这引起了我的好奇心:这是怎么做到的?

让我们具体地讨论下这个视图布局:此效果不优雅吗?它看起开就像本应如此,但你仔细的观察后就会发现更多。随着Scrollview的偏移,图层的覆盖,动作和比例缩放是那么的平滑连贯… … 实在是太喜欢这个效果了。

So,就让我们立刻实现这个效果吧。

首先,先看下最终效果:

结构描述


在写代码之前,我想给你一个关于如何构建UI的简单意见。

打开Main.storyboard文件,在这个控制器里面你会发现2个主要的对象。第一个是一个呈现Header的视图,第二个是Scrollview,它包含了Avatar和账号相关的其他信息,如:username标签和Follow按钮。还有一个被叫做Sizer的视图,它是为了确保Scrollview拥有足够大的垂直滑动的空间。

就像你看到的那样,这个结构非常的简单。稍微注意一下就可发现Header的外部放置了一个Scrollview,而不是与其他元素放置在一起。虽然没必严格如此,但这样会使它的结构变动更加灵活。

编码


如果你仔细的看了最后的动画,将会注意到你要管理2个不同的动作:

  1. 向下拉(当Scrollview已经停靠在屏幕的顶部的时候)
  2. 上下滑动

第二个动作可以细分为4个小步骤:

  • 向上滑动,一直到导航条默认的大小并停靠在屏幕的顶部。
  • 向上滑动,Avatar开始逐渐变小。
  • Header被固定后,Avatar会移动到它的下边。
  • username标签抵达Header的顶部时,一个新的白色Label将会从Header中心的底部展现。这时Header的背景图片将会用高斯模糊渲染。

打开ViewController让我们一个一个的实现这些步骤。

构建管理者


首先要做的事情很明显,就是获取关于Scrollview的偏移量offset。我们可以通过UIScrollViewDelegate协议实现scrollViewDidScroll方法。

在一个View上执行最简单地动画方式是使用Core Animation逐渐的进行三维变换,并给layer.transform赋予新值。

关于Core Animation可以参考这篇文章

http://www.thinkandbuild.it/playing-around-with-core-graphics-core-animation-and-touch-events-part-1/

这些是scrollViewDidScroll:方法的第一部分

    CGFloat offset = scrollView.contentOffset.y;
    CATransform3D avatarTransform = CATransform3DIdentity;
    CATransform3D headerTransform = CATransform3DIdentity;
在这里我们获取一个当前垂直偏移量`offset`,并初始化2个`CATransform3D`变量。

下拉


下拉动作的管理:

if (offset < 0) {
        CGFloat headerScaleFactor = -(offset) / header.bounds.size.height;
        CGFloat headerSizevariation = (header.bounds.size.height * (1.0 + headerScaleFactor) - header.bounds.size.height) / 2.0;
        headerTransform = CATransform3DTranslate(headerTransform, 0, headerSizevariation, 0);
        headerTransform = CATransform3DScale(headerTransform, 1.0 + headerScaleFactor, 1.0 + headerScaleFactor, 0);
        header.layer.transform = headerTransform;

    }

首先,我们检查offset是否为负数:用户在下拉的过程中,将会进入Scrollview的弹性区域。

剩下的代码就是简单的数学逻辑。

Header的扩大是因为它的上边缘固定于屏幕的顶部,而底部的图片在等比缩放。

the transformation is made by scaling and subsequently translating to the top for a value equal to the size variation of the view.实际上,移动ImageView图层的中点到顶部并同时缩放它,你可以获得相同的效果。

headerScaleFactor是用来被计算的一部分。我们想用offset适当的对Header进行缩放。换句话说,当offsetHeader高度的2倍时,headerScaleFactor必须是2.0。

我们需要管理的第二个动作是上下滑动。让我们看看,如何一步步通过UI的主要元素完成变换的。

头部(第一阶段)


当前的offset应该大于0。Header应该随offset进行垂直变换,直到它期望的高度(我们后面将会讲解Header的高斯模糊)。

headerTransform = CATransform3DTranslate(headerTransform, 0, MAX(-offset_HeaderStop, -offset), 0);
这句代码非常简单。我们只需定义一个让`Header`在此停止移动的最小值。 让我感到羞愧的是我比较懒!所以我写死了一些数值,像`offset_HeaderStop`。其实,我们可以通过计算UI元素的位置来获取相同的效果。下次有空再改吧。

头像


Avatar的缩放与我们处理下拉的逻辑一样,只是在这种情况下,图片是到达底部而不是顶部。这段代码和上边的比较相似,除了减小缩放的比例为1.4。

        // Avatar -----------
        CGFloat avatarScaleFactor = MIN(offset_HeaderStop, offset) / avatarImage.bounds.size.height / 1.4;
        CGFloat avatarSizevariation = (avatarImage.bounds.size.height * (1.0 + avatarScaleFactor) - avatarImage.bounds.size.height) / 2.0;
        avatarTransform = CATransform3DTranslate(avatarTransform, 0, avatarSizevariation, 0);
        avatarTransform = CATransform3DScale(avatarTransform, 1.0-avatarScaleFactor, 1.0-avatarScaleFactor, 0);
就像你看到的,当`Header`停止变化时,我们用`MIN`函数来使`Avatar`的缩放停止。 此时,我们根据当前`offset`设置最顶层的图层。除非`offset`小于等于`offset_HeaderStop`,最顶层的图层是`Avatar`,否则是`Header`。
if (offset <= offset_HeaderStop) {
            if (avatarImage.layer.zPosition < header.layer.zPosition) {
                header.layer.zPosition = 0;
            }
        } else {
            if (avatarImage.layer.zPosition >= header.layer.zPosition) {
                header.layer.zPosition = 2;
            }
        }
    }

白色Label


这段代码是白色Label的动画:

        //  ------------ Label
        CATransform3D labelTransform = CATransform3DMakeTranslation(0, MAX(-distance_W_LabelHeader, offset_B_LabelHeader - offset), 0);
        headerLabel.layer.transform = labelTransform;
这里有2个令我感到羞愧的变量值:当`offset`等于`offset_B_LabelHeader`时,黑色的`username`标签刚到触碰到`Header`的底部。

distance_W_LabelHeaderHeader底部与白色Label终点之间的距离。

这个变换是通过此逻辑计算:黑色Label触碰到Header,白色Label就会立即出现,并且到达Header中点位置就停止移动。所以我们使用下面代码创建Y值:

MAX(-distance_W_LabelHeader, offset_B_LabelHeader - offset)

高斯模糊


最后一个效果是Header的模糊。为了得到合适的解决方案,我用了3个不同的库… … 我也尝试过用OpenGL ES创建基类,但实时更新模糊总是非常缓慢。

然后我意识到我可以对模糊仅仅计算一次,将不模糊和模糊的图片进行重叠,只是改变alpha值。我非常确信,Twitter就是这样做的。

viewDidAppear中,我们计算Header的模糊值并隐藏它,设置alpha值为0。

    // Header - Blurred Image
    headerBlurImageView = [[UIImageView alloc] initWithFrame:header.bounds];
    headerBlurImageView.image = [[UIImage imageNamed:@"header_bg"] blurredImageWithRadius:10 iterations:20 tintColor:[UIColor clearColor]];
    headerBlurImageView.contentMode = UIViewContentModeScaleAspectFill;
    headerBlurImageView.alpha = 0.0;
    [header insertSubview:headerBlurImageView belowSubview:headerLabel];
    header.clipsToBounds = YES;

模糊视图是用过FXBlurView实现的。

scrollViewDidScroll:方法中,我们只需根据offset设置alpha:

//  ------------ Blur
        headerBlurImageView.alpha = MIN(1.0, (offset - offset_B_LabelHeader) / distance_W_LabelHeader);

这个计算的背后逻辑是:alpha最大值是1,当黑色Label触碰到Header时模糊效果开始出现,当白色到达最终位置时,也将停止继续模糊。

就这样!

我希望你喜欢这个教程。学习如何重现这种很棒的动画效果对我来说是很大的乐趣。

Swift代码:Download Source
OC代码:Download Source


原版:IMPLEMENTING THE TWITTER IOS APP UI

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章