Android ViewPager2 实现阅读器横向翻页效果(三)--- 实时动态分页及章节切换效果的原理及实现

文章目录

  • Android ViewPager2 实现阅读器横向翻页效果(三)--- 实时动态分页及章节切换效果的原理及实现
    • 关键概念引入
    • 初始数据准备
    • ViewPager Adapter 动态分页 及 第一次分页
    • 分页后更新窗口 及 首页尾页的特殊处理
    • 翻页状态监听 及 动态章节切换

Android ViewPager2 实现阅读器横向翻页效果(三)— 实时动态分页及章节切换效果的原理及实现

本系列最终效果图如下:
Android ViewPager2 实现阅读器横向翻页效果(三)--- 实时动态分页及章节切换效果的原理及实现_第1张图片
本文实现的效果如下:

关键概念引入

在第二章中,我们引入了Java Bean NovelContentPage,它可以表示文本分页之后一个页面的文字内容,也可用通过isTempPage属性表示一个完整的(临时的)章节,如下图所示:
Android ViewPager2 实现阅读器横向翻页效果(三)--- 实时动态分页及章节切换效果的原理及实现_第2张图片
那么这些NovelContentPage,还需要一个类来存放和管理它们,这里我们就使用一个窗的概念(Window of Pages)来封装我们的页面:
Class NovelPageWindow

public class NovelPageWindow {
    private ArrayList<NovelContentPage> pages;
    private boolean isSingleWindow = true;//表示当前Window仅装载一个章节的内容
    private int chapID;//仅single window有效

    public ArrayList<NovelContentPage> getPages() {
        return pages;
    }

    public void setPages(ArrayList<NovelContentPage> pages) {
        this.pages = pages;
    }

    public int getPageNum(){
        return pages!=null?pages.size():1;
    }

    public boolean isSingleWindow() {
        return isSingleWindow;
    }

    public void setSingleWindow(boolean singleWindow) {
        isSingleWindow = singleWindow;
    }

    public int getChapID() {
        return chapID;
    }

    public void setChapID(int chapID) {
        this.chapID = chapID;
    }
}

从意义上来说,NovelPageWindow存放的是当前ViewPager将要展示的页面内容,根据实际可能的情形,Window可分为如下几种:

为表征当前页面相对于缓冲区(临时页)的位置,映入枚举类型WindowType

private enum WindowType{left,right,match}

其中,left和right主要是为了在用户翻到本章节的首页或尾页时,向ViewPager追加内容,以实现跨章节翻页的无缝衔接。
为了对window中的页面有清晰的索引,我们需要引入两个索引变量:

private int window_page_index;//页面在整个window中的索引
private int chap_page_index;//页面在自身章节中的索引

其对应关系如下图所示:
Android ViewPager2 实现阅读器横向翻页效果(三)--- 实时动态分页及章节切换效果的原理及实现_第3张图片

下面,我将以一组章节为例,按照动态分页的逻辑顺序来进行讲解:

初始数据准备

String content = "第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n"
                +"第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n"
                +"第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n"
                +"第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n"
                +"第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n"
                +"第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n"
                +"第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n"
                +"第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n"
                +"第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n"
                +"第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n"
                +"第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n"
                +"第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n"
                +"第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n第一章第一章第一章第一章第一章第一章第一章\n";

        String content2 = "第二章第二章第二章第二章第二章第二章第二章\n第二章第二章第二章第二章第二章第二章第二章\n第二章第二章第二章第二章第二章第二章第二章\n第二章第二章第二章第二章第二章第二章第二章\n"
                +"第二章第二章第二章第二章第二章第二章第二章\n第二章第二章第二章第二章第二章第二章第二章\n第二章第二章第二章第二章第二章第二章第二章\n第二章第二章第二章第二章第二章第二章第二章\n"
                +"第二章第二章第二章第二章第二章第二章第二章\n第二章第二章第二章第二章第二章第二章第二章\n第二章第二章第二章第二章第二章第二章第二章\n第二章第二章第二章第二章第二章第二章第二章\n"
                +"第二章第二章第二章第二章第二章第二章第二章\n第二章第二章第二章第二章第二章第二章第二章\n第二章第二章第二章第二章第二章第二章第二章\n第二章第二章第二章第二章第二章第二章第二章\n"
                +"第二章第二章第二章第二章第二章第二章第二章\n第二章第二章第二章第二章第二章第二章第二章\n第二章第二章第二章第二章第二章第二章第二章\n第二章第二章第二章第二章第二章第二章第二章\n"
                +"第二章第二章第二章第二章第二章第二章第二章\n第二章第二章第二章第二章第二章第二章第二章\n第二章第二章第二章第二章第二章第二章第二章\n第二章第二章第二章第二章第二章第二章第二章\n"
                +"第二章第二章第二章第二章第二章第二章第二章\n第二章第二章第二章第二章第二章第二章第二章\n第二章第二章第二章第二章第二章第二章第二章\n第二章第二章第二章第二章第二章第二章第二章\n"
                +"第二章第二章第二章第二章第二章第二章第二章\n第二章第二章第二章第二章第二章第二章第二章\n第二章第二章第二章第二章第二章第二章第二章\n第二章第二章第二章第二章第二章第二章第二章\n"
                +"第二章第二章第二章第二章第二章第二章第二章\n第二章第二章第二章第二章第二章第二章第二章\n第二章第二章第二章第二章第二章第二章第二章\n第二章第二章第二章第二章第二章第二章第二章\n"
                +"第二章第二章第二章第二章第二章第二章第二章\n第二章第二章第二章第二章第二章第二章第二章\n第二章第二章第二章第二章第二章第二章第二章\n第二章第二章第二章第二章第二章第二章第二章\n";


        String content3 = "第三章第三章第三章第三章第三章第三章第三章\n第三章第三章第三章第三章第三章第三章第三章\n第三章第三章第三章第三章第三章第三章第三章\n第三章第三章第三章第三章第三章第三章第三章\n"
                +"第三章第三章第三章第三章第三章第三章第三章\n第三章第三章第三章第三章第三章第三章第三章\n第三章第三章第三章第三章第三章第三章第三章\n第三章第三章第三章第三章第三章第三章第三章\n"
                +"第三章第三章第三章第三章第三章第三章第三章\n第三章第三章第三章第三章第三章第三章第三章\n第三章第三章第三章第三章第三章第三章第三章\n第三章第三章第三章第三章第三章第三章第三章\n"
                +"第三章第三章第三章第三章第三章第三章第三章\n第三章第三章第三章第三章第三章第三章第三章\n第三章第三章第三章第三章第三章第三章第三章\n第三章第三章第三章第三章第三章第三章第三章\n"
                +"第三章第三章第三章第三章第三章第三章第三章\n第三章第三章第三章第三章第三章第三章第三章\n第三章第三章第三章第三章第三章第三章第三章\n第三章第三章第三章第三章第三章第三章第三章\n"
                +"第三章第三章第三章第三章第三章第三章第三章\n第三章第三章第三章第三章第三章第三章第三章\n第三章第三章第三章第三章第三章第三章第三章\n第三章第三章第三章第三章第三章第三章第三章\n"
                +"第三章第三章第三章第三章第三章第三章第三章\n第三章第三章第三章第三章第三章第三章第三章\n第三章第三章第三章第三章第三章第三章第三章\n第三章第三章第三章第三章第三章第三章第三章\n"
                +"第三章第三章第三章第三章第三章第三章第三章\n第三章第三章第三章第三章第三章第三章第三章\n第三章第三章第三章第三章第三章第三章第三章\n第三章第三章第三章第三章第三章第三章第三章\n"
                +"第三章第三章第三章第三章第三章第三章第三章\n第三章第三章第三章第三章第三章第三章第三章\n第三章第三章第三章第三章第三章第三章第三章\n第三章第三章第三章第三章第三章第三章第三章\n"
                +"第三章第三章第三章第三章第三章第三章第三章\n第三章第三章第三章第三章第三章第三章第三章\n第三章第三章第三章第三章第三章第三章第三章\n第三章第三章第三章第三章第三章第三章第三章\n";

        NovelContentPage full_content1 = new NovelContentPage();
        full_content1.setPage_id(0);full_content1.setPage_content(content);full_content1.setTempPage(true);full_content1.setBelong_to_chapID(0);
        NovelContentPage full_content2 = new NovelContentPage();
        full_content2.setPage_id(1);full_content2.setPage_content(content2);full_content2.setTempPage(true);full_content2.setBelong_to_chapID(1);
        NovelContentPage full_content3 = new NovelContentPage();
        full_content3.setPage_id(2);full_content3.setPage_content(content3);full_content3.setTempPage(true);full_content3.setBelong_to_chapID(2);

        ArrayList<NovelContentPage> pages;

        NovelPageWindow chap1 = new NovelPageWindow();
        pages = new ArrayList<>();pages.add(full_content1);
        chap1.setPages(pages);
        NovelPageWindow chap2 = new NovelPageWindow();
        pages = new ArrayList<>();pages.add(full_content2);
        chap2.setPages(pages);
        NovelPageWindow chap3 = new NovelPageWindow();
        pages = new ArrayList<>();pages.add(full_content3);
        chap3.setPages(pages);
        chapList.add(chap1);
        chapList.add(chap2);
        chapList.add(chap3);

准备三个章节的文字内容,将它们分别以临时页的形式装入NovelContentPage中,再将这三个临时Page装入三个window中,最后用一个ArrayList chapList来存放所有的三个章节。
接下来进行章节初始化:
选定上述三个章节中的其中一章作为起始章节,显然当前的windowType是Match,当前的window是single window:

chap_index = 0;
chap_page_index = 0
window_page_index = chap_page_index;
currentNovelChap = chapList.get(chap_index);
windowType = WindowType.match;
currentNovelChap.setSingleWindow(true);

当前的窗口如下图所示
Android ViewPager2 实现阅读器横向翻页效果(三)--- 实时动态分页及章节切换效果的原理及实现_第4张图片

ViewPager Adapter 动态分页 及 第一次分页

为了保证分页的准确性和灵活性,并且能适应动态的字体调整,这里选择本系列第二章所讲的Attach后分页的方式。这就要求预先绘制TextView再进行分页。所以我们采用在adapter中先用临时页进行绘制,在进行分页后覆盖的方式:

public class PageAdapter extends RecyclerView.Adapter<PageAdapter.PageViewHolder> {

    private ArrayList<NovelContentPage> pages;
    private Context context;
    private NovelPageWindow chap;
    private PageListener pageListener;

    public PageAdapter(Context context, NovelPageWindow novelPageWindow){
        this.chap = novelPageWindow;
        this.context = context;
    }

    public void setPageListener(PageListener pageListener) {
        this.pageListener = pageListener;
    }

    @NonNull
    @Override
    public PageViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        PageViewHolder pageViewHolder = new PageViewHolder(LayoutInflater.from(context).inflate(R.layout.page_layout, parent, false));
        return pageViewHolder;
    }

    @Override
    public void onBindViewHolder(@NonNull PageViewHolder holder, int position) {
        pages = chap.getPages();
        NovelContentPage novelContentPage = pages.get(position);
        holder.tv_content.setText(novelContentPage.getPage_content());
        holder.tv_id.setText(String.valueOf(novelContentPage.getPage_id()));
        if (novelContentPage.isTempPage()) {
            Log.d("bind view","found temp page: "+position);
            holder.tv_content.post(()->{
                ArrayList<NovelContentPage> page = PageSplit.getPage(novelContentPage.getPage_content(), holder.tv_content);
                if (chap.isSingleWindow())pages = page;
                else {
                    int i = pages.indexOf(novelContentPage);
                    pages.addAll(i,page);
                    pages.remove(novelContentPage);
                }
                chap.setPages(pages);
                if (pageListener!=null)pageListener.onPageSplitDone(chap,page,novelContentPage.getBelong_to_chapID());
            });
        }

    }

    @Override
    public int getItemCount() {
        return chap.getPageNum();
    }

    class PageViewHolder extends RecyclerView.ViewHolder {
        public RelativeLayout mContainer;
        public TextView tv_id;
        public TextView tv_content;

        public PageViewHolder(@NonNull View itemView) {
            super(itemView);
            tv_id = itemView.findViewById(R.id.page_id);
            tv_content = itemView.findViewById(R.id.page_content);
            mContainer = itemView.findViewById(R.id.card_container);
        }
    }

    public interface PageListener{
        void onPageSplitDone(NovelPageWindow update_chap, ArrayList<NovelContentPage> new_pages, int chapID);
    }
}

其中的重点在onBindViewHolder,对tv_content设置好临时的长章节文本后,利用post方法,在TextView绘制后对其进行分页。并且将分页的结果利用PageListener接口进行回调。
分页后此时的窗口如下图所示:
Android ViewPager2 实现阅读器横向翻页效果(三)--- 实时动态分页及章节切换效果的原理及实现_第5张图片

分页后更新窗口 及 首页尾页的特殊处理

分页后,分页的信息由PageListener接口回调,要更新窗口则需要实现onPageSplitDone方法,如下:

pageListener = (update_chap, pages1, chapID) -> {
            chapList.get(chapID).setPages(pages1);
            //根据chap_page_index 调整window_page_index
            int total_chap = currentNovelChap.getPageNum();
            int total_window = pageView.getAdapter().getItemCount();
            switch(windowType){
                case left:{
                    NovelPageWindow left_chap = chapList.get(chap_index - 1);
                    window_page_index = chap_page_index + left_chap.getPageNum();
                }
                break;
                case right:{
                    window_page_index = chap_page_index;
                }
                break;
                case match:{
                    window_page_index = chap_page_index;
                    if (chap_page_index!=0 && init_page)init_page = false;
                }
                    break;
                default:
            }
            pageView.post(()->{
                PageAdapter adapter = new PageAdapter(context,update_chap);
                adapter.setName("split update");
                adapter.setPageListener(pageListener);
                pageView.setAdapter(adapter);
                pageView.setCurrentItem(window_page_index,false);
            });

            Log.d("page split",String.format("chap %d/%d",chap_page_index, total_chap -1));
            Log.d("page split",String.format("window %d/%d",window_page_index, total_window -1));
}

分页后首先要根据当前页相对于新分页内容的位置,更新窗口页面索引window_page_index,这里在初次分页时,窗口类型为match无需对索引进行更改。接下来需要更新PageView以应用分页结果,注意这里一定不能用notifyDataSetChanged()方法进行更新,这样会导致部分页面未加载而产生空白页,必须新建一个adapter对原来的进行覆盖,该操作需要在原布局绘制完成后再进行。
下面是使用notifyDataSetChanged()方法和新建adapter覆盖的效果比较:

如果当前页正好是一章的开头或者结尾,则需要提前将上一章/下一章的临时页添加到窗口中:

pageListener = (update_chap, pages1, chapID) -> {
            //窗口更新部分同上,省略
            ...
            //首页尾页的特殊处理
            if (chap_page_index == 0 && window_page_index == 0){
                Log.d("page split","start from the first page of a chap");
                if(chap_index > 0){
                    ArrayList<NovelContentPage> temp_pages = new ArrayList<>(chapList.get(chap_index - 1).getPages());
                    window_page_index += temp_pages.size();
                    temp_pages.addAll(currentNovelChap.getPages());
                    NovelPageWindow novelPageWindow = new NovelPageWindow();
                    novelPageWindow.setPages(temp_pages);
                    novelPageWindow.setSingleWindow(false);
                    after_update = true;
                    PageAdapter adapter = new PageAdapter(context, novelPageWindow);
                    adapter.setPageListener(pageListener);
                    adapter.setName("split addon");
                    pageView.post(()->{
                        pageView.setAdapter(adapter);
                    });
                    if (init_page)init_page = false;
                    windowType = WindowType.left;
                }
                else {
                    Log.d("page split","start from the first page of the novel");
                    windowType = WindowType.match;
                    if (init_page)init_page = false;
                }
            }
            if (chap_page_index == (total_chap-1) && window_page_index == (total_window-1)){
                Log.d("page split","start from the last page of a chap");
                if((chap_index+1)<chapList.size()){
                    window_page_index = chap_page_index;
                    ArrayList<NovelContentPage> temp_pages = new ArrayList<>(currentNovelChap.getPages());
                    temp_pages.addAll(chapList.get(chap_index+1).getPages());//single page
                    NovelPageWindow novelPageWindow = new NovelPageWindow();
                    novelPageWindow.setPages(temp_pages);
                    novelPageWindow.setSingleWindow(false);
                    after_update = true;
                    PageAdapter adapter = new PageAdapter(context, novelPageWindow);
                    adapter.setPageListener(pageListener);
                    pageView.post(()->{
                        pageView.setAdapter(adapter);
                    });
                    if (init_page)init_page = false;
                    windowType = WindowType.right;
                }
                else {
                    Log.d("page split","start from the last page of the novel");
                    windowType = WindowType.match;
                    if (init_page)init_page = false;
                }
            }
        };

此时的窗口如下图所示:
Android ViewPager2 实现阅读器横向翻页效果(三)--- 实时动态分页及章节切换效果的原理及实现_第6张图片
结合上一节讲过的adapter的内容,可知,==pageView.post()==执行后,新增的临时页将被分解为一组属于上一章的新的页面。但注意此时的窗口类型不再是match,而是根据临时页的位置,被设定为left或者right,第二次分页后窗口索引需要随之更改。分页后的窗口如下图所示:
Android ViewPager2 实现阅读器横向翻页效果(三)--- 实时动态分页及章节切换效果的原理及实现_第7张图片

翻页状态监听 及 动态章节切换

完成了初始章节的分页后,接下来需要更具用户的翻页情况,实时获取当前的页数章节数,并及时新增章节,以达到流畅的阅读体验。关于页面切换监听,可以查阅谷歌官方文档如下:
Android ViewPager2 实现阅读器横向翻页效果(三)--- 实时动态分页及章节切换效果的原理及实现_第8张图片
我们只需要实现其中的onPageSelected 和 onPageScrollStateChanged两个方法,翻页过程中其调用顺序为:onPageScrollStateChanged(SCROLL_STATE_DRAGGING) -> onPageScrollStateChanged(SCROLL_STATE_SETTLING) -> onPageSelected -> onPageScrollStateChanged(SCROLL_STATE_IDLE)
重点注意的是:onPageSelected只有翻到一个新的页时才会回调,若翻到一半再翻回去则不会触发。这种情况只会调用onPageScrollStateChanged。
于是,我们在onPageSelected回调时更新 window_page_index 和 chap_page_index 两个索引;在onPageScrollStateChanged回调时作章节切换的判断,代码如下:

pageView.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {

            @Override
            public void onPageSelected(int position) {
                super.onPageSelected(position);
                if (init_page)return;
                if (after_update){
                    after_update = false;
                    return;
                }
                System.out.println("position = " + position);
                int total_window_pages = pageView.getAdapter().getItemCount();
                int total_chap_pages = currentNovelChap.getPageNum();
                window_page_index = position;
                switch(windowType){
                    case left:{
                        NovelPageWindow left_chap = chapList.get(chap_index - 1);
                        chap_page_index = position - left_chap.getPages().size();
                    }
                    break;
                    case right:{
                        chap_page_index = position;
                    }
                    break;
                    case match:chap_page_index = position;
                    break;
                    default:
                }
                Log.d("page change",String.format("window %d/%d",window_page_index, total_window_pages-1));
                Log.d("page change",String.format("chap %d/%d",chap_page_index, total_chap_pages-1));
            }

            @Override
            public void onPageScrollStateChanged(int state) {
                super.onPageScrollStateChanged(state);
                if (state == SCROLL_STATE_IDLE){

                    int total_window_pages = pageView.getAdapter().getItemCount();
                    int total_chap_pages = currentNovelChap.getPages().size();

                    if (window_page_index == total_window_pages -1){
                        Log.d("page change","reach the last page of the window");
                        if ((chap_index+1)>=chapList.size()){
                            Log.d("page change","also the last chap of the book!");
                            return;
                        }
                        window_page_index = chap_page_index;
                        ArrayList<NovelContentPage> temp_pages = new ArrayList<>(currentNovelChap.getPages());
                        temp_pages.addAll(chapList.get(chap_index+1).getPages());//single page
                        NovelPageWindow novelPageWindow = new NovelPageWindow();
                        novelPageWindow.setPages(temp_pages);
                        novelPageWindow.setSingleWindow(false);
                        after_update = true;
                        PageAdapter adapter = new PageAdapter(context, novelPageWindow);
                        adapter.setPageListener(pageListener);
                        pageView.post(()->{
                            pageView.setAdapter(adapter);
                            pageView.setCurrentItem(window_page_index,false);
                        });
                        windowType = WindowType.right;
                    }
                    if (window_page_index == 0){
                        Log.d("page change","reach the first page of the window");
                        if ((chap_index-1)<0){
                            Log.d("page change","also the first chap of the book!");
                            return;
                        }
                        ArrayList<NovelContentPage> temp_pages = new ArrayList<>(chapList.get(chap_index - 1).getPages());
                        window_page_index += temp_pages.size();
                        temp_pages.addAll(currentNovelChap.getPages());
                        NovelPageWindow novelPageWindow = new NovelPageWindow();
                        novelPageWindow.setPages(temp_pages);
                        novelPageWindow.setSingleWindow(false);
                        after_update = true;
                        PageAdapter adapter = new PageAdapter(context, novelPageWindow);
                        adapter.setPageListener(pageListener);
                        pageView.post(()->{
                            pageView.setAdapter(adapter);
                            pageView.setCurrentItem(window_page_index,false);
                        });
                        windowType = WindowType.left;

                    }
                    if (chap_page_index == total_chap_pages){
                        Log.d("page change","start the new chap (next)"+(chap_index+1));
                        chap_index++;
                        if (chap_index>=chapList.size())return;
                        currentNovelChap = chapList.get(chap_index);
                        chap_page_index = 0;
                        windowType = WindowType.left;
                    }
                    if (chap_page_index == -1){
                        Log.d("page change","start the new chap (last)"+(chap_index-1));
                        chap_index--;
                        if (chap_index < 0)return;
                        currentNovelChap = chapList.get(chap_index);
                        chap_page_index = currentNovelChap.getPages().size()-1;
                        windowType = WindowType.right;
                    }
                }
            }
        });

回调函数onPageSelected中的索引更新逻辑与上一节中window_page_index索引更新的逻辑类似,这里不再赘述。
回调函数onPageScrollStateChanged中,状态 SCROLL_STATE_IDLE 表示翻页动作完全结束,此时通过window_page_index索引来判断是否到达了窗口的第一页或最后一页,此时就需要将新的临时页面添加到窗口中来,为保证窗口不会过大,需要丢弃旧缓存,如下图所示:
Android ViewPager2 实现阅读器横向翻页效果(三)--- 实时动态分页及章节切换效果的原理及实现_第9张图片
回调函数onPageScrollStateChanged中,我们还通过chap_page_index索引来判断是否跨章,需要注意的是,跨章后窗口的类型需要根据缓存相对于当前页面的位置进行更新。
接下来,上图中的临时页会在adapter绘制时被分页,并重新分配窗口,一切都循环起来,便实现了流畅的翻页过程。
最后,别忘了将上述的adpter,listner添加到pageView对象中,并按照本系列第一章所述的为pageview添加PageTransformer:

 PageAdapter adapter = new PageAdapter(this,currentNovelChap);
        adapter.setPageListener(pageListener);
        pageView.setAdapter(adapter);
        pageView.setOffscreenPageLimit(5);
        pageView.setCurrentItem(window_page_index,false);
        pageView.setPageTransformer(new ViewPager2.PageTransformer() {
            @Override
            public void transformPage(@NonNull View page, float position) {
                TextView viewId = page.findViewById(R.id.page_id);
                String s_id = viewId.getText().toString();
                if (position <= 0.0f) {
                    page.setTranslationX(0.0f);
                    page.setTranslationZ(0.0f);
                } else {
                    page.setTranslationX((-page.getWidth() * position));
                    page.setTranslationZ(-position);
                }
            }
        });

你可能感兴趣的:(Android,阅读器,android,java,androidx,android,jetpack)