天猫首页的视差Banner

简介

在天猫消费升级、品质升级的大背景下,一个交互体验优秀的APP变得尤为重要。而首页的首焦Banner更应该精雕细琢,因此我们挖掘了体验上的小创新,希望能给用户眼前一亮的体验小惊喜。我们实现了多层基于不同位移系数,接收重力感应及手势滑动的banner,从而达到视差的效果,让整个产品更生动富有细节。

img

绘制视图

Banner上每一个Item其实是相互独立的,而想做到视差,必须将有不同相对速度的视图放在不同的层,将相对静止的视图放在同一层。

数据结构:

{
  "data": [
    {
      "imgUrl": "https://img.alicdn.com/tps/i4/TB1DdQ7RVXXXXa.XFXXwu0bFXXX.png",
      "gapSpeed": "1.2",
      "intensity": "10"
    },
    {
      "imgUrl": "https://img.alicdn.com/tps/i4/TB1fM8uSXXXXXauXXXXwu0bFXXX.png",
      "gapSpeed": "1.4",
      "intensity": "20"
    }
  ],
    "imgUrl": "//img.alicdn.com/imgextra/i1/36/TB22s9LuiC9MuFjSZFoXXbUzFXa_!!36-2-luban.png",
  "bgImgUrl": "https://img.alicdn.com/tps/i4/TB1fNIERVXXXXcUapXXSutbFXXX.jpg"
}
字段 二级字段 作用
imgUrl - 默认静态图
bgImgUrl - 背景图片
data - 用户存放item上每一个视差对象
- imgUrl 视差图片
- gapSpeed 滚动视差的相对速度
- intensity 重力视差的最大位移

视图结构

天猫首页的视差Banner_第1张图片
img

根据data中object数目,创建多个视图,根据object的下标确定视图的先后位置。如上图所示:根据gapSpeed来确定视图的长度,创建出‘1.2X’和‘1.6X’两个视图,并且将图片居中展示。

加载过程

初始化后,我们先根据imgUrl字段在1X的视图中加载默认静态图片,然后等data中的资源全部下载完成之后,将1X中得图片替换成bgImgUrl对应的图片,并且展示1.2X和1.6X两个视图。此时,用户对图片的变化并没有感知,但当banner滚动或转动手机时,用户会发现banner已经拥有3D视差组件了哦。

滚动视差

天猫首页的视差Banner_第2张图片

正如上图所示,我们可以将视差组件“大牌热促”分成三个过程:“进入屏幕前”、“在屏幕中”、“离开屏幕后”

进入屏幕前

天猫首页的视差Banner_第3张图片

进入屏幕前,所有的视图居左对齐,以达到最大的靠右视差。

在屏幕中

天猫首页的视差Banner_第4张图片

当banner从右往左滚动时,1.2X的视图将以banner滚动速度的0.1倍的速度在坑位内滚动,同理,1.6X的视图以banner滚动速度的0.3倍的速度在坑位内滚动,当banner正好将此卡片滚到屏幕正中间时,上面的所有视图与1X视图居中,所有的Image都重叠在一起,协作展示完整的Item内容.

离开屏幕后

天猫首页的视差Banner_第5张图片

当banner将Item从屏幕左侧滑出之后,所有的视图正好向右对齐,此时达到最大的靠左视差。

实现代码

理解完了原理,我们就直接上代码吧,由于每一个Item无法获取Banner的scrollview.delegate,所以,我们用TangramBug,将此消息传递给每一个Item,并在这个方法中,完成以上操作:

-(void)didScrollView:(TangramContext *)context
{
    UIScrollView *scrollView = [context.event.params objectForKeyCheck:@"scrollView"];
    CGPoint point = [self convertPoint:CGPointZero toView:scrollView];
    CGFloat offsetX = point.x - scrollView.contentOffset.x - scrollView.width + (scrollView.width - self.width) / 2;
    if (self.data.count > 0)
    {
        CGFloat gapSpeed = [[self.data dictionaryAtIndex:0] floatForKey:@"gapSpeed" defaultValue:1.0];
        self.gapView1.left = offsetX * (gapSpeed- 1)/ 2;
    }
    if (self.data.count > 1)
    {
        CGFloat gapSpeed = [[self.data dictionaryAtIndex:1] floatForKey:@"gapSpeed" defaultValue:1.0];
        self.gapView2.left = offsetX * (gapSpeed- 1)/ 2;
    }
    if (self.data.count > 2)
    {
        CGFloat gapSpeed = [[self.data dictionaryAtIndex:2] floatForKey:@"gapSpeed" defaultValue:1.0];
        self.gapView3.left = offsetX * (gapSpeed- 1)/ 2;
    }
}

重力视差

说到重力,肯定会谈起ios的陀螺仪,相关CMDeviceMotion的知识我就不在重复讲解,相关介绍可以参考这篇文档:http://www.tuicool.com/articles/2YZRvq。 为了让猫客内的UIView以后更轻松的接入重力感应,所以我把其能力作为了UIView的category实现。

添加属性名称 类型 作用说明
parallaxIntensity CGFloat 重力感应最大位移距离,0为无视觉差,正数为和设备方向一致,负数为和设备方向相反
parallaxDirection NSInteger 0:所有方向;1:只能在X轴上移动;2:只能在y轴上移动

根据之前的json中的intensity字段,我们把1.2X的视图的parallaxIntensity设为10,同理把1.6X视图的parallaxIntensity设为20。

开启重力视差

parallaxIntensity字段不为0时,我就会调用方法-(void)startDeviceMotionUpdatesToQueue:withHandler:,并且将deviceMotionUpdateInterval属性设置为一个较高的刷新频率,目的是为了动画流畅,响应及时。
特别需要注意的是,当手机从正面转到反面时,roll会有一个临界值的变化需要经过特殊处理,否则会发现对象会突然跳到另一边,因此,我们需要做如下操作:

if (tmMuiCurrentAttitude.roll > -M_PI_2 && tmMuiCurrentAttitude.roll < M_PI_2) 
{
        currentPoint = CGPointMake(tmMuiCurrentAttitude.roll, tmMuiCurrentAttitude.pitch);
}
else
{
    if (tmMuiCurrentAttitude.roll > M_PI_2)
    {
        currentPoint = CGPointMake(M_PI - tmMuiCurrentAttitude.roll,tmMuiCurrentAttitude.pitch);
    }
    else
    {
        currentPoint = CGPointMake(-M_PI - tmMuiCurrentAttitude.roll, tmMuiCurrentAttitude.pitch);
    }
}

之后我们得到了一个变换后的roll和pitch,将其和初始的数值做对比之后,我们就得到了一个变化量,将这个变化量换算成与最大值的百分比,再根据parallaxIntensity换算出一个变化的point,然后我们根据parallaxDirection对其transform进行设置,根据parallaxDirection来确定只设置某一个方向还是全方向都设置。

关闭重力视差

parallaxIntensity字段为0时,我们就调用-(void)stopDeviceMotionUpdates方法,停止对陀螺仪的监听,并且将视图位置恢复原状。

结束语

就这样,我们一步步地打造了一个拥有滚动视差、重力视差的banner组件。虽然这只是一个极小的细节,但是交互的升级,必将带给用户新鲜的体验。试问:一个lowlow的app怎么可能让用户升级呢?首页作为天猫最重要的页面,必须精雕细琢每一个细节!

你可能感兴趣的:(天猫首页的视差Banner)