节切换效果的原理及实现
Posted zzy0516alex
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了节切换效果的原理及实现相关的知识,希望对你有一定的参考价值。
文章目录
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 WindowTypeleft,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.android界面切换增加了animation特效,但是没有实现动画效果(已经执行到setAni
Android移动应用开发之使用异步调用进度条及实现幻灯片切换效果
[Android] 任意时刻从子线程切换到主线程的实现原理及加强版
Android零基础入门第70节:ViewPager轻松完成TabHost效果