Android自定义View——从零开始实现书籍翻页效果(二)

版权声明:本文为博主原创文章,未经博主允许不得转载。
系列教程:Android开发之从零开始系列
源码:github.com/AnliaLee/BookPage,欢迎star

大家要是看到有错误的地方或者有啥好的建议,欢迎留言评论

前言:在上篇Android自定义View——从零开始实现书籍翻页效果(一)博客中,我们实现了 基本的上下翻页效果右侧最大翻页距离的限制,这期我们要将这个view的翻页效果以及动画补齐

本篇只着重于思路和实现步骤,里面用到的一些知识原理不会非常细地拿来讲,如果有不清楚的api或方法可以在网上搜下相应的资料,肯定有大神讲得非常清楚的,我这就不献丑了。本着认真负责的精神我会把相关知识的博文链接也贴出来(其实就是懒不想写那么多哈哈),大家可以自行传送。为了照顾第一次阅读系列博客的小伙伴,本篇会出现一些在之前系列博客就讲过的内容,看过的童鞋自行跳过该段即可

国际惯例,先上效果图,本次主要补全了翻页效果以及增加取消翻页的动画

目录
  • 完善右侧最大翻页距离的限制
  • 添加横向翻页效果
  • 增加取消翻页的动画

完善右侧最大翻页距离的限制

开讲之前,我先把标识点的位置图贴出来让大家回顾一下

在上篇博客中我们讲了如何限制翻页的最大距离,也就是c点的x坐标不能小于0,虽然目的达到了,但是用户体验并不好,可以很明显地观察到当c点x坐标处于临界值时,翻页会静止不动,如果此时触摸点大范围移动后,会出现翻页“瞬移”,造成一种卡顿的感觉,如下图

要消除这种“瞬移”的现象,我们要在c点x坐标小于0的情况下,让c点强制处于临界位置(左下角),然后翻页页角继续跟随触摸点移动的方向移动,要做到这一点得用到一些相似三角形的数学知识,重新计算出a点的位置绘制View,先来看下实现的原理(请无视我的渣画工╮(╯▽╰)╭ )

图中我们将触摸点标为a1,与a1对应的c点标为c1,此时c1的x坐标是小于0。而a2是我们重新计算得到的a点,也就是最后进行绘制的a点,其对应的c点标为c2c2位于View的左下角(x坐标为0),容易观察到直角三角形a1 c1 m1直角三角形a2 c2 m2相似,此时f点位于View的右下角(在右上角同理),因此我们可以通过相应的公式计算得到a2的坐标为(f.x-w2,f.y-h2),得到计算a2的方法后,我们修改原来的BookPageView

/**
 * 设置触摸点
 * @param x
 * @param y
 * @param style
 */
public void setTouchPoint(float x, float y, String style){
    switch (style){
        case STYLE_TOP_RIGHT:
            f.x = viewWidth;
            f.y = 0;
            break;
        case STYLE_LOWER_RIGHT:
            f.x = viewWidth;
            f.y = viewHeight;
            break;
        default:
            break;
    }
    a.x = x;
    a.y = y;
    calcPointsXY(a,f);

    MyPoint touchPoint = new MyPoint(x,y);
    if(calcPointCX(touchPoint,f)<0){//如果c点x坐标小于0则重新测量a点坐标
        calcPointAByTouchPoint();
        calcPointsXY(a,f);
    }
    postInvalidate();
}

/**
 * 如果c点x坐标小于0,根据触摸点重新测量a点坐标
 */
private void calcPointAByTouchPoint(){
    float w0 = viewWidth - c.x;

    float w1 = Math.abs(f.x - a.x);
    float w2 = viewWidth * w1 / w0;
    a.x = Math.abs(f.x - w2);

    float h1 = Math.abs(f.y - a.y);
    float h2 = w2 * h1 / w1;
    a.y = Math.abs(f.y - h2);
}

效果如图


添加横向翻页效果

既然我们实现的是仿真的翻页效果,翻页除了从上下两角翻,自然还能横向水平进行翻页。我们先将View划分成上下左右中五个区域,如图

我们根据触摸起始的位置所位于的区域,定义五种不同的手势操作,其中上下(top,low)对应的是上下角进行翻页,左右(left,right)对应的横向水平翻页,中间(middle)则是为了以后作为呼出菜单而保留的区域。为了提高代码复用率和尽可能小的改动,我们实现横向翻页只需将a点的y坐标强制等于View的高度减1即可(当然大家也可以根据自己的需要重新计算横向翻页时的绘制区域,我这里就简单实现了),修改我们的BookPageView

private String style;
public static final String STYLE_LEFT = "STYLE_LEFT";//点击左边区域
public static final String STYLE_RIGHT = "STYLE_RIGHT";//点击右边区域
public static final String STYLE_MIDDLE = "STYLE_MIDDLE";//点击中间区域
public static final String STYLE_TOP_RIGHT = "STYLE_TOP_RIGHT";//f点在右上角
public static final String STYLE_LOWER_RIGHT = "STYLE_LOWER_RIGHT";//f点在右下角

/**
 * 设置触摸点
 * @param x
 * @param y
 * @param style
 */
public void setTouchPoint(float x, float y, String style){
    MyPoint touchPoint = new MyPoint();
    a.x = x;
    a.y = y;
    this.style = style;
    switch (style){
        case STYLE_TOP_RIGHT:
            f.x = viewWidth;
            f.y = 0;
            calcPointsXY(a,f);
            touchPoint = new MyPoint(x,y);
            if(calcPointCX(touchPoint,f)<0){//如果c点x坐标小于0则重新测量a点坐标
                calcPointAByTouchPoint();
                calcPointsXY(a,f);
            }
            postInvalidate();
            break;
        case STYLE_LEFT:
        case STYLE_RIGHT:
            a.y = viewHeight-1;
            f.x = viewWidth;
            f.y = viewHeight;
            calcPointsXY(a,f);
            postInvalidate();
            break;
        case STYLE_LOWER_RIGHT:
            f.x = viewWidth;
            f.y = viewHeight;
            calcPointsXY(a,f);
            touchPoint = new MyPoint(x,y);
            if(calcPointCX(touchPoint,f)<0){//如果c点x坐标小于0则重新测量a点坐标
                calcPointAByTouchPoint();
                calcPointsXY(a,f);
            }
            postInvalidate();
            break;
        default:
            break;
    }
}

在Activity中监听触摸操作

public class PageActivity extends AppCompatActivity {
    private BookPageView bookPageView;
    private String style = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_page);

        bookPageView = (BookPageView) findViewById(R.id.view_book_page);
        bookPageView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()){
                    case MotionEvent.ACTION_DOWN:
                        float x = event.getX();
                        float y = event.getY();
                        float width = bookPageView.getViewWidth();
                        float height = bookPageView.getViewHeight();
                        if(x<=width/3){//左
                            style = bookPageView.STYLE_LEFT;
//                            Toast.makeText(PageActivity.this,"点击了左部",Toast.LENGTH_SHORT).show();
                            bookPageView.setTouchPoint(x,y,style);

                        }else if(x>width/3 && y<=height/3){//上
                            style = bookPageView.STYLE_TOP_RIGHT;
//                            Toast.makeText(PageActivity.this,"点击了上部",Toast.LENGTH_SHORT).show();
                            bookPageView.setTouchPoint(x,y,style);

                        }else if(x>width*2/3 && y>height/3 && y<=height*2/3){//右
                            style = bookPageView.STYLE_RIGHT;
//                            Toast.makeText(PageActivity.this,"点击了右部",Toast.LENGTH_SHORT).show();
                            bookPageView.setTouchPoint(x,y,style);

                        }else if(x>width/3 && y>height*2/3){//下
                            style = bookPageView.STYLE_LOWER_RIGHT;
//                            Toast.makeText(PageActivity.this,"点击了下部",Toast.LENGTH_SHORT).show();
                            bookPageView.setTouchPoint(x,y,style);

                        }else if(x>width/3 && x<width*2/3 && y>height/3 && y<height*2/3){//中
                            style = bookPageView.STYLE_MIDDLE;
//                            Toast.makeText(PageActivity.this,"点击了中部",Toast.LENGTH_SHORT).show();
//                            bookPageView.setTouchPoint(x,y,bookPageView.STYLE_MIDDLE);
                        }
                        break;
                    case MotionEvent.ACTION_MOVE:
                        bookPageView.setTouchPoint(event.getX(),event.getY(),style);
                        break;
                    case MotionEvent.ACTION_UP:
                        bookPageView.setDefaultPath();
                        break;
                }
                return false;
            }
        });
    }
}

效果如图


增加取消翻页的动画

android scroller类的使用
Android学习之 Scroller的介绍与使用
Android Scroller完全解析,关于Scroller你所需知道的一切
Android -- Interpolator
android动画 之Interpolator类

因为我们还没实现将书籍内容导入View中,所以我们先来实现取消翻页的动画效果。这里我们结合ScrollerInterpolator插值器,实现当我们手指离开屏幕时,a点能自动滑落到右下角(右上角)的效果,有关ScrollerInterpolator方面的知识,我将相关博客链接贴出来了,大家可以相互对照着理解,我就不详细阐述了。修改BookPageView

private Scroller mScroller;

private void init(Context context, @Nullable AttributeSet attrs){
    //省略部分代码...
    mScroller = new Scroller(context,new LinearInterpolator());//以常量速率滑动即可
}

@Override
public void computeScroll() {
    if (mScroller.computeScrollOffset()) {
        float x = mScroller.getCurrX();
        float y = mScroller.getCurrY();
        
        if(style.equals(STYLE_TOP_RIGHT)){
            setTouchPoint(x,y,STYLE_TOP_RIGHT);
        }else {
            setTouchPoint(x,y,STYLE_LOWER_RIGHT);
        }
        if (mScroller.getFinalX() == x && mScroller.getFinalY() == y){
            setDefaultPath();//重置默认界面
        }
    }
}

/**
 * 取消翻页动画,计算滑动位置与时间
 */
public void startCancelAnim(){
    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, 400);
}

在Activity中监听手指抬起操作

case MotionEvent.ACTION_UP:
    bookPageView.startCancelAnim();
    break;

效果如图

至此本篇教程就告一段落了,这期比较短,主要是对上期的补充。下期会实现书籍内容填充或者绘制阴影,看情况吧๑乛◡乛๑。如果大家看了感觉还不错麻烦点个赞,你们的支持是我最大的动力~


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容