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

news2024/11/17 21:45:11

文章目录

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

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

本系列最终效果图如下:
在这里插入图片描述
本文实现的效果如下:
在这里插入图片描述

关键概念引入

在第二章中,我们引入了Java Bean NovelContentPage,它可以表示文本分页之后一个页面的文字内容,也可用通过isTempPage属性表示一个完整的(临时的)章节,如下图所示:
在这里插入图片描述
那么这些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;//页面在自身章节中的索引

其对应关系如下图所示:
在这里插入图片描述

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

初始数据准备

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);

当前的窗口如下图所示
在这里插入图片描述

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接口进行回调。
分页后此时的窗口如下图所示:
在这里插入图片描述

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

分页后,分页的信息由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;
                }
            }
        };

此时的窗口如下图所示:
在这里插入图片描述
结合上一节讲过的adapter的内容,可知,==pageView.post()==执行后,新增的临时页将被分解为一组属于上一章的新的页面。但注意此时的窗口类型不再是match,而是根据临时页的位置,被设定为left或者right,第二次分页后窗口索引需要随之更改。分页后的窗口如下图所示:
在这里插入图片描述

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

完成了初始章节的分页后,接下来需要更具用户的翻页情况,实时获取当前的页数章节数,并及时新增章节,以达到流畅的阅读体验。关于页面切换监听,可以查阅谷歌官方文档如下:
在这里插入图片描述
我们只需要实现其中的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索引来判断是否到达了窗口的第一页或最后一页,此时就需要将新的临时页面添加到窗口中来,为保证窗口不会过大,需要丢弃旧缓存,如下图所示:
在这里插入图片描述
回调函数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);
                }
            }
        });

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/113254.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

BIT.4 Linux进程控制

目录进程创建fork函数初识写实拷贝fork常规用法fork调用失败的原因补充知识进程终止进程退出场景进程常见退出方法exit函数与_exit函数return 退出补充知识进程等待进程等待必要性进程等待的方法wait方法waitpid方法wait / waitpid 阻塞代码WIFEXITEDwait / waitpid 非阻塞代码…

LeetCode刷题复盘笔记—一文搞懂动态规划之718. 最长重复子数组问题(动态规划系列第三十一篇)

今日主要总结一下动态规划的一道题目&#xff0c;718. 最长重复子数组 题目&#xff1a;718. 最长重复子数组 Leetcode题目地址 题目描述&#xff1a; 给两个整数数组 nums1 和 nums2 &#xff0c;返回 两个数组中 公共的 、长度最长的子数组的长度 。 示例 1&#xff1a; …

华熙LIVE·五棵松商业北区明年国庆亮相 互动体验升级

京城着名的活力聚集地——华熙LIVE五棵松明年将增添两万多平米商业区&#xff0c;新增商业区位于现有商业区北侧并与之相连通&#xff0c;业态在承袭现有沉浸式互动体验业态基础上&#xff0c;将引进元宇宙等前沿科技和跳楼机等娱乐设施&#xff0c;使互动体验进一步升级。项目…

一文搞懂Linux内核中断机制原理与实现

为什么需要中断&#xff1f; 如果让内核定期对设备进行轮询&#xff0c;以便处理设备&#xff0c;那会做很多无用功&#xff0c;因为外设的处理速度一般慢于CPU&#xff0c;而CPU不能一直等待外部事件。所以能让设备在需要内核时主动通知内核&#xff0c;会是一个聪明的方式&a…

JWT渗透与攻防(一)

目录 前言 JWT漏洞介绍 案列演示之Leaky_JWT JWT漏洞具体的实现方式&#xff1a; 案列演示之JWT None Algorithm JWT漏洞工具的利用 JWT利用工具介绍 jwt_tool 漏洞利用 jwt-cracker c-jwt-cracker 前言 Json web token (JWT)相关漏洞对于渗透测试人员而言可能是一种…

node.js+uni计算机毕设项目店内点餐微信小程序LW(程序+小程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程。欢迎交流 项目运行 环境配置&#xff1a; Node.js Vscode Mysql5.7 HBuilderXNavicat11VueExpress。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分离等…

【Pandas入门教程】如何从现有列派生新列

如何从现有列派生新列 来源&#xff1a;Pandas官网&#xff1a;https://pandas.pydata.org/docs/getting_started/intro_tutorials/index.html 笔记托管&#xff1a;https://gitee.com/DingJiaxiong/machine-learning-study 文章目录如何从现有列派生新列导包数据集准备【1】如…

C++——STL之stack和queue详解

C——STL之stack和queue详解&#x1f3d0;什么是stack和queue&#x1f3d0;stack和queue的实现&#x1f3c0;什么是deque&#x1f3c0;stack的模拟实现&#x1f3c0;queue的模拟实现&#x1f3d0;优先级队列&#xff08;priority_queue)&#x1f3c0;优先级队列的实现⚽push⚽p…

Spring Authorization Server1.0 介绍与使用

一、版本使用 1、Java&#xff1a;17或者更高的版本。 2、springboot 3.0 3、Spring Authorization Server 1.0版本。 <dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-oauth2-authorization-server</ar…

使用proxy_pool来为爬虫程序自动更换代理IP

文章目录1. 前言2. 教程3. 官网4. 在线demo4.1. 本地部署4.2. 安装4.2.1. Python源码构建安装4.2.1.1. 安装redis数据库4.2.1.1.1. 下载redis源码4.2.1.1.2. 启动redis服务4.2.1.1.3. 安装redis服务4.2.1.1.4. 再次通过命令启动redis服务4.2.1.1.5. 测试redis服务是否可用4.2.1…

node.js+uni计算机毕设项目基于微信小程序的车位共享系统LWPPT(程序+小程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程。欢迎交流 项目运行 环境配置&#xff1a; Node.js Vscode Mysql5.7 HBuilderXNavicat11VueExpress。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分离等…

C语言程序设计--银行管理系统

主界面 登入界面 #include <stdio.h> #include <malloc.h> #include <conio.h> #include <time.h> #include <windows.h> #define MB_ICONINFORMATION MB_ICONASTERISK //对 错误 struct account_information /…

腾讯云轻量应用服务器使用Matomo 应用镜像搭建网站流量统计系统!

Matomo 是一款开源的网站数据统计软件&#xff0c;可以用于跟踪、分析您的网站的流量&#xff0c;同时充分保障数据安全性、隐私性。该镜像基于 CentOS 7.6 64位操作系统&#xff0c;已预置 Nginx、MariaDB、PHP 软件。本文介绍如何使用 Matomo 快速搭建您的网站流量统计系统。…

【文本检测】2、DBNet++ | 为 DBNet 引入多级特征图聚合模块 ASF

文章目录一、背景二、方法2.1 Adaptive Scale Fusion (ASF) 模块2.2 Binarization2.3 Adaptive Threshold2.4 Deformable Convolution2.5 Label Generation2.6 Optimization三、效果论文&#xff1a;Real-Time Scene Text Detection with Differentiable Binarization and Adap…

Hadoop综合项目——二手房统计分析(可视化篇)

Hadoop综合项目——二手房统计分析&#xff08;可视化篇&#xff09; 文章目录Hadoop综合项目——二手房统计分析&#xff08;可视化篇&#xff09;0、 写在前面1、数据可视化1.1 二手房四大一线城市总价Top51.2 统计各个楼龄段的二手房比例1.3 统计各个城市二手房标签的各类比…

人工智能轨道交通行业周刊-第27期(2022.12.12-12.25)

本期关键词&#xff1a;虚拟中台、智轨、数字员工客服、钢轨光带异常、小目标检测 1 整理涉及公众号名单 1.1 行业类 RT轨道交通中关村轨道交通产业服务平台人民铁道世界轨道交通资讯网铁路信号技术交流北京铁路轨道交通网上榜铁路视点ITS World轨道交通联盟VSTR铁路与城市轨…

4、前端笔记-JS-数据类型

1、数据类型简介 1.1为什么需要数据类型 不同的数据占用的存储空间不同&#xff0c;为了充分利用存储空间&#xff0c;便于把数据分成所需内存大小不同的数据&#xff0c;定义了不同的数据类型 1.2变量的数据类型 js是弱类型&#xff08;动态语言&#xff09;的语言&#x…

这12类Oracle日期函数,全都给你总结了

在使用Oracle数据库过程中&#xff0c;对日期的使用不可避免&#xff0c;那Oracle中的日期函数有哪些呢&#xff1f;本篇就日期函数进行整理了&#xff0c;不一定全部了解记住&#xff0c;但是要做到心中有数&#xff0c;以后在写脚本的时候就不会绕弯子了。 1、sysdate、curr…

大话设计模型 Task05 状态、适配、单例

目录一、状态模式问题描述问题分析模式定义代码实现二、适配器模式问题描述问题分析模式定义代码实现三、单例模式问题描述问题分析模式定义代码实现一、状态模式 问题描述 假设我们要描述一名员工一天不同时间的工作状态&#xff0c;正常来看是比较简单的&#xff0c;直接从…

卡塔尔世界杯半自动越位识别技术(SAOT)的工作原理

随着卡塔尔世界杯的深入举行&#xff0c;半自动越位识别技术 (Semi-automated offside technology&#xff0c;简称为 SAOT) 这项数字技术正在被越来越多的国内外球迷所熟知。 作为 VAR(Video Assistant Referee&#xff0c;视频助理裁判) 的扩展&#xff0c;SAOT 的引入是为了…