• Leaves iOS上一种图书翻页效果的实现


    Leaves是由Tow Brow开发的一个简单的图书翻页控件,它巧妙地结合了镜像层、阴影层(用于半透明页)和渐变层(用于阴影)来实现图书的翻页效果。其翻页效果如下图所示:


    特性
    Leaves支持:
    文本、图像、PDF等任何可被渲染到Graphics Context上的对象
    通过拖动或点击来翻页
    支持ipad和iphone大小的显示区域
    Levels目前不支持以下特性
    页面上的交互元素
    轻扫动作

    类和接口
    Leaves中主要有三个类:LevelsView、LevelsViewController、LevelsCache:
    LevelsCache:是一个辅助类,用于缓存显示页。它将显示的内容缓存为图片并保存。
    LevelsView:是翻页视图,翻页的主要效果便在些实现。它定义了一系列的层对象,并通过操作这些层对象来实现翻页中各种效果。
    LevelsViewController: LevelsView的控制器
    类似于UITableView, LevelsView也有一个相关的数据源类(LeaveViewDataSource)与委托类(LeavesViewDelegate),它们分别有两个方法,如下所示

    复制代码
    @protocol LeavesViewDataSource <NSObject>
    - (NSUInteger) numberOfPagesInLeavesView:(LeavesView*)leavesView;
    - (void) renderPageAtIndex:(NSUInteger)index inContext:(CGContextRef)ctx;
    @end
    @protocol LeavesViewDelegate <NSObject>
    @optional
    - (void) leavesView:(LeavesView *)leavesView willTurnToPageAtIndex:(NSUInteger)pageIndex;
    - (void) leavesView:(LeavesView *)leavesView didTurnToPageAtIndex:(NSUInteger)pageIndex;
    @end

    层树结构
    LevelsView中的层树结构如下图所示:

    每一个层(Layer)都有其特殊的用途,或作为内容的显示层,或作为阴影层,具体说明如下:
    topPage层:显示当前页的内容。
    topPageOverlay层:在翻页过程中,该层覆盖于topPage层上,且颜色偏暗,从而使topPage未翻转的部分变暗,有阴影的感觉。
    topPageShadow层:在翻页过程中,该层用于表达topPage被翻转部分所形成的阴影。
    topPageReverse层:翻页过程中,topPage被翻转部分的反面的容器层。
    topPageReverseImage层:反面的内容页。在竖屏下,用于显示topPage被翻转部分的内容,这些内容被映射到该层,给人感觉书是透明的。在横屏下,显示的是下一页的内容。
    topPageReverseOverlay层:该层用于覆盖topPageReverse层,效果与topPageOverlay类似。
    topPageReverseShading层:该层在topPageReverse层右侧形成一个阴影。
    bottomPage层:topPage页的下一页所在的层。
    bottomPageShadow层:该层为在翻页过程中在 bottomPage左侧形成的一个阴影层。
    leftPage层:该层为横屏模式下左侧页所在的层。
    leftPageOverlay层:该层覆盖于为 leftPage层,效果与topPageOverlay类似。
    由上可以看出,层树中的层主要分为三类:
    内容显示层:topPage、topPageReverseImage、bottomPage、leftPage
    阴影层:topPageShadow、topPageReverseShading、bottomPageShadow
    覆盖层:topPageOverlay、topPageReverseOverlay、leftPageOverlay


    图片缓存
    Tow Brow在处理不同的内容(文本、图像、PDF)时显示时,所采取的方法是一样的。他将内容缓存为图像,并显示在屏幕上。基本方法是将内容写进CGContextRef中,然后根据CGContextRef中的信息创建图像,具体方法如下:

    复制代码
    -(CGImageRef) imageForPageIndex:(NSUInteger)pageIndex {
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef context = CGBitmapContextCreate(NULL,
    pageSize.width,
    pageSize.height,
    8, /* bits per component*/
    pageSize.width * 4, /* bytes per row */
    colorSpace,
    kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
    CGColorSpaceRelease(colorSpace);
    CGContextClipToRect(context, CGRectMake(0, 0, pageSize.width, pageSize.height));
    [dataSource renderPageAtIndex:pageIndex inContext:context];
    CGImageRef image = CGBitmapContextCreateImage(context);
    CGContextRelease(context);
    [UIImage imageWithCGImage:image];
    CGImageRelease(image);
    return image;
    }


    当然程序没有缓存所有页的内容,而是根据横竖屏的不同缓存适当数量的内容。每次翻页时会重新整理缓存中的内容。

    翻页动画实现
    在Leaves中,翻页的基本原理其实很简单:翻页过程中,根据手指的划动来不断的调整层树结构中每个层的frame,翻页结束后,重新调整内容显示层所显示的内容。
    为此,LevelsView中设置了一个leafEdge变量,该变量是手指在屏幕上划动时Touch Point在屏幕x轴上的百分比位置,这个操作在touchesMoved:withEvent中完成:

    复制代码
    - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    ......
    UITouch *touch = [event.allTouches anyObject];
    CGPoint touchPoint = [touch locationInView:self];
    [CATransaction begin];
    [CATransaction setValue:[NSNumber numberWithFloat:0.07]
    forKey:kCATransactionAnimationDuration];
    self.leafEdge = touchPoint.x / self.bounds.size.width;
    [CATransaction commit];
    }


    而在leafEdge的set方法中,我们根据leafEdge的值来重新设定各个Layer的frame属性

    复制代码
    - (void) setLayerFrames {
    CGRect rightPageBoundsRect = self.layer.bounds;
    CGRect leftHalf, rightHalf;
    CGRectDivide(rightPageBoundsRect, &leftHalf, &rightHalf, CGRectGetWidth(rightPageBoundsRect) / 2.0f, CGRectMinXEdge);
    if (self.mode == LeavesViewModeFacingPages) {
    rightPageBoundsRect = rightHalf;
    }
    topPage.frame = CGRectMake(rightPageBoundsRect.origin.x,
    rightPageBoundsRect.origin.y,
    leafEdge * rightPageBoundsRect.size.width,
    rightPageBoundsRect.size.height);
    topPageReverse.frame = CGRectMake(rightPageBoundsRect.origin.x + (2*leafEdge-1) * rightPageBoundsRect.size.width,
    rightPageBoundsRect.origin.y,
    (1-leafEdge) * rightPageBoundsRect.size.width,
    rightPageBoundsRect.size.height);
    ......
    }


    最后便是当手指离开屏幕时,如何处理翻页结果(将当前页翻过去还是没有翻过去)。这个操作在 这个操作在touchesEnded:withEvent中完成

    复制代码
    - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    ......
    UITouch *touch = [event.allTouches anyObject];
    CGPoint touchPoint = [touch locationInView:self];
    BOOL dragged = distance(touchPoint, touchBeganPoint) > [self dragThreshold];
    [CATransaction begin];
    float duration;
    if ((dragged && self.leafEdge < 0.5) || (!dragged && [self touchedNextPage])) {
    [self willTurnToPageAtIndex:currentPageIndex + numberOfVisiblePages];
    self.leafEdge = 0;
    duration = leafEdge;
    ......
    }
    else {
    [self willTurnToPageAtIndex:currentPageIndex];
    self.leafEdge = 1.0;
    duration = 1 - leafEdge;
    .......
    }
    [CATransaction setValue:[NSNumber numberWithFloat:duration]
    forKey:kCATransactionAnimationDuration];
    [CATransaction commit];
    }


    如果需要在翻页后执行某些操作(如在屏幕上显示当前页数等),则可以在继承自 LevelsViewController的控制器中实现leavesView:didTurnToPageAtIndex方法。

    在此需要注意的就是 topPageReverseImage在竖屏时做了如下的变换

    复制代码
    topPageReverseImage.contentsGravity = kCAGravityRight;
    topPageReverseImage.transform = CATransform3DMakeScale(-1, 1, 1);

    从而使topPageReverseImage显示的内容让人感觉是透过纸张,看到topPage的内容。

    横屏与竖屏
    Leaves还有一个特点就是其支持横屏时,能同时看到两页的内容(该效果是由Ole Begemann改进的)。该改进最关键的地方就是增加了leftPage层,同时在横屏显示时将屏幕一分为二,在左侧显示leftPage。同进在翻页 的过程中,topPageReverseImage显示的是topPage页下一页的内容。在翻转屏幕时,会根据方向重新调整内容的显示。

    参考资料:App Store-safe Page Curl animations
    源码下载地址:https://github.com/brow/leaves

  • 相关阅读:
    Django之Models(一)
    数据库学习之事务
    pymysql的使用
    pymysql:Mysql拒绝从远程访问的解决办法
    Django之模板基础
    Django之视图函数总结
    POJ1942
    poj2115[扩展欧几里德]
    POJ1850&&POJ1496
    [Catalan数]1086 栈、3112 二叉树计数、3134 Circle
  • 原文地址:https://www.cnblogs.com/zhw511006/p/2212187.html
Copyright © 2020-2023  润新知