书籍翻页效果

首先贴个链接:https://www.jianshu.com/p/d02362fbd9f2
本文是在该博主的基础上优化的,建议先阅读。非常感谢博主。

实现效果: 仿真书籍的翻书效果。

过程: 本来是想在网上找个的控件,能实现仿真书籍翻页就可以了,但是找了很多,要么太冗杂,要么使用效果不流畅,特耗内存。中间也参考了一些博主的文章,不是很理想,上面贴出的链接完整的介绍了编码书籍翻书的基本原理,可以了解一下。

开始: 先上图
在这里插入图片描述

上图是最终的效果,整个翻书效果用一个自定义控件实现,上一页,下一页,初始化等。

首页要明白整个翻书的效果是由三个部分组成的。如下
在这里插入图片描述
我们把它分为A、B、C 三个区域,翻书的过程其实就是不断改变A、B、C的显示区域。可以比喻为三张矩形的纸张,然后每一张剪切相应的位置,组合在一起,不断改变,最后形成了翻书的效果。

所以翻书效果最主要是如何计算每个区域的显示大小、曲线、阴影等等。需要我们掌握自定义View的相关知识,比如曲线绘制,翻页动画、阴影。
必备知识点:
1 自定义View中曲线的绘制(二阶贝塞尔曲线),绘制流程。
2 相关的数学基本公式,(园,椭圆,曲线方程)
3 自定义View Matrix原理、区域裁剪

在文字开始贴出的链接:《Android自定义View——从零开始实现书籍翻页效果》系列文章中,已经比较清楚的介绍了整体的翻书效果,性能优化,不过并不能完全使用(只介绍了翻页的原理。并没有完成整个翻页的动画)。本文将在其基础上,加上完整的翻页效果。建议先阅读 Android自定义View——从零开始实现书籍翻页效果

在这里插入图片描述
上图中说到的A、B、C其实就代表着当前页,当前页的背面,以及下一页。在绘制时,一定要先准备这三个区域的Bitmap,然后在画上去。我们分别了解下。

在这里插入图片描述其中 pageNum 代表当前绘制的是第几页,bitmaps 代表的是整个翻书控件总的页数
在这里插入图片描述 第二个方法是画当前页的背景,本来是需要画当前页的背面,但由于翻页中背面 bitmap 旋转反转之后,在绘制截取曲线时存在问题,所以这里用一个纯色的 Bitmap 来当作背景。
在这里插入图片描述第三个方法同第一个方法一样。下面我们来看看 onMeasure()方法
在这里插入图片描述在测量的时候就将三个页面画上去。当作第一次的初始化,当然显示的时候只是显示第一个页面。

然后看看 onTouchEvent() 方法

  @Override
    public boolean onTouchEvent(MotionEvent event) {
        this.event =  event;
        super.onTouchEvent(event);
        if(pageState == PAGE_STAY) {
            isSongs = false;
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    float x = event.getX();
                    float y = event.getY();
                    // 1 在按下的时候绘制。有一种情况不用绘制 :当前页是最后一页,就不用绘制了
                    if (x <= viewWidth / 3) {//左
                        style = STYLE_LEFT;
                        style_ectype = STYLE_LEFT;
                        if(pageNum <= 0) { //说明这就是第一页,不能在往上翻页了
                        //    Toast.makeText(getContext(),"已经是第一页了",Toast.LENGTH_SHORT).show();
                        }else{
                            setTouchPoint(x, y, style);
                        }
                        pageNum -- ;
                        if(pageNum>=0) {
                            drawableCurrtPage(pathAContentBitmap, pathAPaint);
                            drawableNextPage(pathBContentBitmap, pathBPaint);
                            drawableBackgoundPage(pathCContentBitmap,pathCPaint);
                         //   drawableCurrtPage(pathCContentBitmap, pathCPaint);

                        }


                    } else if (x > viewWidth / 3 && y <= viewHeight / 3) {//上
                        drawableCurrtPage(pathAContentBitmap, pathAPaint);
                        drawableNextPage(pathBContentBitmap, pathBPaint);
                        drawableBackgoundPage(pathCContentBitmap,pathCPaint);
                       // drawableCurrtPage(pathCContentBitmap, pathCPaint);
                        style = STYLE_TOP_RIGHT;
                        style_ectype = STYLE_TOP_RIGHT;
                        pageNum++;
                        if(pageNum >= bitmaps.size() ){//说明已经是最后一页了

                        }else{
                            setTouchPoint(x, y, style);
                        }

                    } else if (x > viewWidth * 2 / 3 && y > viewHeight / 3 && y <= viewHeight * 2 / 3) {//右
                        drawableCurrtPage(pathAContentBitmap, pathAPaint);
                        drawableNextPage(pathBContentBitmap, pathBPaint);
                        drawableBackgoundPage(pathCContentBitmap,pathCPaint);
                       // drawableCurrtPage(pathCContentBitmap, pathCPaint);
                        style = STYLE_RIGHT;
                        style_ectype = STYLE_RIGHT;
                        pageNum++;
                        if(pageNum >= bitmaps.size() ){//说明已经是最后一页了

                        }else{
                            setTouchPoint(x, y, style);
                        }
                      /*  if(listener!=null){
                            listener.getFocus();
                        }*/

                    } else if (x > viewWidth / 3 && y > viewHeight * 2 / 3) {//下
                        drawableCurrtPage(pathAContentBitmap, pathAPaint);
                        drawableNextPage(pathBContentBitmap, pathBPaint);
                        drawableBackgoundPage(pathCContentBitmap,pathCPaint);
                       // drawableCurrtPage(pathCContentBitmap, pathCPaint);
                        style = STYLE_LOWER_RIGHT;
                        style_ectype = STYLE_LOWER_RIGHT;
                        pageNum++;
                        if(pageNum >= bitmaps.size() ){//说明已经是最后一页了

                        }else{
                            setTouchPoint(x, y, style);
                        }
                       /* if(listener!=null){
                            listener.getFocus();
                        }*/
                    } else if (x > viewWidth / 3 && x < viewWidth * 2 / 3 && y > viewHeight / 3 && y < viewHeight * 2 / 3) {//中
                        style = STYLE_MIDDLE;
                        style_ectype = STYLE_MIDDLE;
                        //在这里呼出菜单
                        if(listener!=null){ 
                            listener.onCilckCentreMenu();
                        }
                    }
                    break;
                case MotionEvent.ACTION_MOVE:
                    if(pageNum < 0){ //说明这就是第一页

                    }else if(pageNum == bitmaps.size()){//说明这就是最后一页

                    }else{
                        setTouchPoint(event.getX(), event.getY(), style);
                    }
                    break;
                case MotionEvent.ACTION_UP:
                    isSongs = true;
                    if(pageNum < 0){ //说明这就是第一页
                        pageNum = 0;
                    }else if(pageNum == bitmaps.size()){
                        pageNum = bitmaps.size() -1;
                    }else{
                        if (style == STYLE_LEFT) {
                            calcPointsXY(testa, f); //用来调整翻页翻过去的贝塞尔曲线的限制
                            startlPreviousAnim();
                        } else if(style == STYLE_MIDDLE ){
                            pageState = PAGE_STAY;
                        }else{
                            calcPointsXY(testa, f);
                            startNextAnim();
                        }
                    }
                    break;
            }
        }
      //  postInvalidate();
        return true;

    }

这里是点击不同的区域然后有不同的操作,见下图
在这里插入图片描述
当我们点击某个区域后,就要执行翻页的动画,就需要调用该方法完成整个动作。即上一页下一页的两种动画

    public void startlPreviousAnim(){
        pageState = PAGE_PREVIOUS;
        int dx,dy;
        //让a滑动到f点所在位置,留出1像素是为了防止当a和f重叠时出现View闪烁的情况
        if(style.equals(STYLE_TOP_RIGHT)){
            dx = (int) (viewWidth-1-a.x);
            dy = (int) (1-a.y);
        }else {
            dx = (int) (viewWidth-1-a.x);
            dy = (int) (viewHeight-1-a.y);
        }
        mScroller.startScroll((int) a.x, (int) a.y, dx, dy, 350);
       postInvalidate();
    }
 public void startNextAnim(){
        pageState = PAGE_NEXT;
        int dx = viewWidth,dy;
        //让a滑动到f点所在位置,留出1像素是为了防止当a和f重叠时出现View闪烁的情况
        if(style.equals(STYLE_TOP_RIGHT)){
            dy = (int) (1-a.y);
            if(a.x<= viewWidth/2){
                //计算 翻页的 a 点的横坐标
                dx = (int) -(viewWidth+a.x/2);
                mScroller.startScroll((int) a.x, (int) a.y, dx, dy, 350);
            }else if( a.x> viewWidth/2){
                //计算 翻页的 a 点的横坐标
                dx = (int) -(2*a.x+a.x/5);
                mScroller.startScroll((int) a.x, (int) a.y, dx, dy, 350);
            }
        }else {
            dy = (int) (viewHeight-1-a.y);
            if(a.x<= viewWidth/2){
                dx = (int) -(viewWidth+a.x/2);
                mScroller.startScroll((int) a.x, (int) a.y, dx, dy, 350);
            }else if( a.x> viewWidth/2){
                dx = (int) -(2*a.x+a.x/5);
                mScroller.startScroll((int) a.x, (int) a.y, dx, dy, 350);
            }
        }
        postInvalidate();
    }

这样 基本就能够完成整个的翻页效果了。
这上面有两个问题没有很好的解决:
1 : 当 C 页面的 Bitmap 不为纯色时,即为图片的 bitmap 在翻页的时候 C 区域的底部和右部会有白边或者黑边出现,主要的原因是,对 C 区域的Bitmap 并没有做曲边处理。相关解决方案可参加这篇文章 Android翻页效果原理实现之模拟扭曲

2 : 点击不同区域执行翻页时,翻页的时间不固定。这是因为我设置好滚动的终点坐标不一样,而代码中的滚动时间确是一样照成的,参见上面的代码,我设置的时间是 350 。而时间中应该根据终点的位置来计算设置时间。有时间我在优化发出来。

通过以上就能基本实现翻书的效果了。但是这其中还有很对优化的地方。后面我会不断完善。

有不明白的,写得不对的,欢迎各位指出讨论,共同进步。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值