专栏名称: Anlia
Android工程师
目录
相关文章推荐
开发者全社区  ·  “热巴”爸爸 ·  17 小时前  
开发者全社区  ·  吃瓜!华x公司渣男天花板 ·  昨天  
开发者全社区  ·  55 ... ·  昨天  
开发者全社区  ·  逛街遇到两个已婚领导约会 ·  2 天前  
51好读  ›  专栏  ›  Anlia

Android自定义View——从零开始实现覆盖翻页效果

Anlia  · 掘金  · android  · 2017-12-14 20:46

正文

版权声明:本文为博主原创文章,未经博主允许不得转载

系列教程: Android开发之从零开始系列

源码: AnliaLee/BookPage ,欢迎star

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

前言 :之前讲了 仿真书籍翻页效果 ,效果如图

我们从原理分析、功能实现到性能优化完整地过了一遍,反响不错,于是有小伙伴私信让我把 覆盖翻页效果 也讲了,所以这期的主角就是它了 ~

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

国际惯例,先上效果图


创建页面内容工厂类

Android自定义View——从零开始实现书籍翻页效果(三) 一文中提到了向 View 填充内容实际上就是将所有 页面元素 绘制到一个 bitmap 上,然后再将这个 bitmap 绘制到 View 中。我们把绘制 页面内容bitmap 的过程封装起来,方便用户调用,创建 PageFactory 抽象类,在内部实现绘制页面内容的抽象方法

public abstract class PageFactory {
    public boolean hasData = false;//是否含有数据
    public int pageTotal = 0;//页面总数

    public PageFactory(){}

    /**
     * 绘制上一页bitmap
     * @param bitmap
     * @param pageNum
     */
    public abstract void drawPreviousBitmap(Bitmap bitmap, int pageNum);

    /**
     * 绘制当前页bitmap
     * @param bitmap
     * @param pageNum
     */
    public abstract void drawCurrentBitmap(Bitmap bitmap, int pageNum);

    /**
     * 绘制下一页bitmap
     * @param bitmap
     * @param pageNum
     */
    public abstract void drawNextBitmap(Bitmap bitmap, int pageNum);

    /**
     * 通过索引在集合中获取相应内容
     * @param index
     * @return
     */
    public abstract Bitmap getBitmapByIndex(int index);
}

我们以纯图像内容的绘制为例,创建 PicturesPageFactory 继承 PageFactory ,除了实现内容绘制的具体逻辑以外,设置 多种初始化方法 ,方便用户使用 不同路径下的图像集合

public class PicturesPageFactory extends PageFactory {
    private Context context;
    
    public int style;//集合类型
    public final static int STYLE_IDS = 1;//drawable目录图片集合类型
    public final static int STYLE_URIS = 2;//手机本地目录图片集合类型

    private int[] picturesIds;
    /**
     * 初始化drawable目录下的图片id集合
     * @param context
     * @param pictureIds
     */
    public PicturesPageFactory(Context context, int[] pictureIds){
        this.context = context;
        this.picturesIds = pictureIds;
        this.style = STYLE_IDS;
        if (pictureIds.length > 0){
            hasData = true;
            pageTotal = pictureIds.length;
        }
    }

    private String[] picturesUris;
    /**
     * 初始化本地目录下的图片uri集合
     * @param context
     * @param picturesUris
     */
    public PicturesPageFactory(Context context, String[] picturesUris){
        this.context = context;
        this.picturesUris = picturesUris;
        this.style = STYLE_URIS;
        if (picturesUris.length > 0){
            hasData = true;
            pageTotal = picturesUris.length;
        }
    }

    @Override
    public void drawPreviousBitmap(Bitmap bitmap, int pageNum) {
        Canvas canvas = new Canvas(bitmap);
        canvas.drawBitmap(getBitmapByIndex(pageNum-2),0,0,null);
    }

    @Override
    public void drawCurrentBitmap(Bitmap bitmap, int pageNum) {
        Canvas canvas = new Canvas(bitmap);
        canvas.drawBitmap(getBitmapByIndex(pageNum-1),0,0,null);
    }

    @Override
    public void drawNextBitmap(Bitmap bitmap, int pageNum) {
        Canvas canvas = new Canvas(bitmap);
        canvas.drawBitmap(getBitmapByIndex(pageNum),0,0,null);
    }

    @Override
    public Bitmap getBitmapByIndex(int index) {
        if(hasData){
            switch (style){
                case STYLE_IDS:
                    return getBitmapFromIds(index);
                case STYLE_URIS:
                    return getBitmapFromUris(index);
                default:
                    return null;
            }
        }else {
            return null;
        }
    }

    /**
     * 从id集合获取bitmap
     * @param index
     * @return
     */
    private Bitmap getBitmapFromIds(int index){
        return BitmapUtils.drawableToBitmap(
                context.getResources().getDrawable(picturesIds[index]),
                ScreenUtils.getScreenWidth(context),
                ScreenUtils.getScreenHeight(context)
        );
    }

    /**
     * 从uri集合获取bitmap
     * @param index
     * @return
     */
    private Bitmap getBitmapFromUris(int index){
        return null;//这个有空再写啦,大家可自行补充完整
    }
}

基本架构就是这样( BitmapUtils ScreenUtils 两个工具类大家自己去看下源码吧,就不在这展开说了~),至于小说文本类的解析比较复杂,以后可能会出一个番外篇专门讲这个。下面我们开始介绍如何在自定义View中使用这个工厂类


使用工厂类获取页面内容并绘制

创建 CoverPageView ,提供一个对外的接口用以设置工厂类

public class CoverPageView extends View {
    private int defaultWidth;//默认宽度
    private int defaultHeight;//默认高度
    private int viewWidth;
    private int viewHeight;
    private int pageNum;//当前页数

    private PageFactory pageFactory;

    private Bitmap currentPage;//当前页bitmap

    public CoverPageView(Context context) {
        super(context);
        init(context);
    }

    public CoverPageView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    private void init(Context context){
        defaultWidth = 600;
        defaultHeight = 1000;
        pageNum = 1;
    }

    /**
     * 设置工厂类
     * @param factory
     */
    public void setPageFactory(final PageFactory factory){
        //保证View已经完成了测量工作,各页bitmap已初始化
        getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
            @Override
            public boolean onPreDraw() {
                getViewTreeObserver().removeOnPreDrawListener(this);
                if(factory.hasData){
                    pageFactory = factory;
                    pageFactory.drawCurrentBitmap(currentPage,pageNum);
                    postInvalidate();
                }
                return true;
            }
        });
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int height = ViewUtils.measureSize(defaultHeight, heightMeasureSpec);
        int width = ViewUtils.measureSize(defaultWidth, widthMeasureSpec);
        setMeasuredDimension(width, height);

        viewWidth = width;
        viewHeight = height;

        currentPage = Bitmap.createBitmap(viewWidth, viewHeight, Bitmap.Config.RGB_565);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if(pageFactory !=null){
            drawCurrentPage(canvas);
        }
    }

    /**
     * 绘制当前页
     * @param canvas
     */
    private void drawCurrentPage(Canvas canvas){
        canvas.drawBitmap(currentPage, 0, 0,null);
    }
}

Activity 中进行初始化,这里我用了 drawable 目录下的一些图片作为页面内容

int[] pIds = new int[]{R.drawable.test1,R.drawable.test2,R.drawable.test3};
coverPageView = (CoverPageView) findViewById(R.id.view_cover_page);
coverPageView.setPageFactory(new PicturesPageFactory(this,pIds));
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:splitMotionEvents="false">
    <com.anlia.pageturn.view.CoverPageView
        android:id="@+id/view_cover_page"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="10dp"/>
</RelativeLayout>

CoverPageView 设置了工厂类对象后便会绘制出当前页内容,效果如图


实现页面滑动效果

页面滑动效果的原理其实很简单,之前我们调用了 canvas.drawBitmap 方法将当前页内容绘制到 View 中,要实现页面滑动,只需要设置 drawBitmap 方法中的 left 值( bitmap 的左边界值)即可。也就是说,我们可以通过 记录手指在X轴上的滑动距离 ,计算出 left 值,从而改变当前页内容 bitmap的起始位置 ,实现滑动效果,如图

修改 CoverPageView ,监听触摸事件

public class CoverPageView extends View {
	//省略部分代码...
    private float xDown;//记录初始触摸的x坐标
    private float scrollPageLeft;//滑动页左边界
	
    private MyPoint touchPoint;//触摸点
    private Bitmap nextPage;//下一页bitmap

    private int touchStyle;//触摸类型
    public static final int TOUCH_MIDDLE = 0;//点击中间区域
    public static final int TOUCH_LEFT = 1;//点击左边区域
    public static final int TOUCH_RIGHT = 2;//点击右边区域

    private void init(Context context){
        //省略部分代码...
        scrollPageLeft = 0;
        touchStyle = TOUCH_RIGHT;
        touchPoint = new MyPoint(-1,-1);
    }

    /**
     * 设置工厂类
     * @param factory
     */
    public void setPageFactory(final PageFactory factory){
        记得使用pageFactory.drawNextBitmap(nextPage,pageNum)绘制下一页的内容,不然滑动当前页时会出现背景空白没有内容
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if(pageFactory !=null){
            if(touchPoint.x ==-1 && touchPoint.y ==-1){
                drawCurrentPage(canvas);
            }else{
                drawNextPage(canvas);
                drawCurrentPage(canvas);
            }
        }
    }

    /**
     * 绘制当前页
     * @param canvas
     */
    private void drawCurrentPage(Canvas canvas){
        canvas.drawBitmap(currentPage, scrollPageLeft, 0,null);//修改left值
    }

    /**
     * 绘制下一页
     * @param






请到「今天看啥」查看全文