ViewPager实现Recycle机制和响应notifyDataSetChanged
1.目标
主界面要求水平移动翻页效果,每次只能翻一页,可以翻无数页。
2.实现思路
针对“每次只能翻一页”这个要求,简单使用SDK的话只有用ViewPager。ViewPager的PageAdapter是没有实现RecyclerView的ViewHolder.itemView回收机制的。即使是子类FragmentStatePagerAdapter,也只是保存状态后销毁Fragment,Fragment本身是在不断地创建和销毁,没有重复利用。
正确地使用PageAdapter,必须先理解Object instantiateItem (ViewGroup container, int position)函数中返回的Object的意义。如果View能重复利用,则表明它仅用于展示数据,没有业务逻辑。数据的部分,我们通常用Model表示。instantiateItem函数的返回值Object,正是要求返回在position位置的Model数据对象,与实现这个函数时创建的View形成映射关系。
3.自定义PageAdapter
直接在代码注释中讲解:
// 转载请注明出处:http://blog.csdn.net/hursing
private class ViewPageAdapter extends PagerAdapter {
// 用List保存本来要销毁的View,需要的时候再取出来
private List<ItemView> mViewRecycler = new LinkedList<>();
// 用Map保存Model Object(这里是OnePiece)和View的映射关系
private Map<OnePiece, ItemView> mOnePieceItemViewMap = new HashMap<>();
@Override
public int getCount() {
// Model保存着OnePiece的对象数组
return mPieceModel.pieces.size();
}
@Override
public boolean isViewFromObject(View view, Object object) {
// 如果不需要回收利用View,都是用`return view == object`实现的,
// 与此对应`instantiateItem()`返回View,
// `destroyItem()`要销毁View。
OnePiece onePiece = (OnePiece) object;
return view == mOnePieceItemViewMap.get(onePiece);
}
@Override
public Object instantiateItem (ViewGroup container, int position) {
ItemView itemView;
// 这个if else是实现回收利用的关键处之一。如果有缓存则取缓存,
// 没有则创建对象
if (mViewRecycler.size() > 0) {
itemView = mViewRecycler.remove(0);
} else {
itemView = (ItemView) LayoutInflater.from(MainActivity.this)
.inflate(R.layout.item_view, container, false);
}
// container实际是ViewPager对象。从这个函数的实现看,
// ViewPager并不知道对它add的View是做什么用的,所以
// 需要`isViewFromObject()`函数来询问这个View是否有
// 意义(是ItemView),如果是,后面会对它做正确layout
container.addView(itemView);
OnePiece onePiece = mPieceModel.pieces.get(position);
// 这里是itemView根据Model object来重新设置界面元素的时机。
itemView.setOnePiece(onePiece);
mOnePieceItemViewMap.put(onePiece, itemView);
// 如果不需要回收利用View,这里的实现是`return itemView`。
// 要回收,就要建立Model object和View的映射关系,因此
// 这里返回Model object
return onePiece;
}
@Override
public void destroyItem (ViewGroup container, int position, Object object) {
OnePiece onePiece = (OnePiece) object;
ItemView itemView = mOnePieceItemViewMap.remove(onePiece);
// 必须自行remove itemView
container.removeView(itemView);
// itemView要被回收放入缓存了,这里提供一个时机做清理。
itemView.reset();
// 实现回收的关键处,把itemView保存到List里。而不是任由GC销毁。
mViewRecycler.add(itemView);
}
// 必须重写此函数。super固定地`return POSITION_UNCHANGED`,
// 这就会导致`notifyDataSetChanged()`没有效果,因为它的意义
// 是说这个Model object的位置没变化,自然不需要刷新。
@Override
public int getItemPosition(Object object) {
OnePiece onePiece = (OnePiece) object;
if (mPieceModel.pieces.contains(onePiece)) {
return mPieceModel.pieces.indexOf(onePiece);
} else {
// 返回POSITION_NONE说明这个model object已经被废弃了,
// 接下来就会由`destroyItem()`回调来销毁它对应的View。
return POSITION_NONE;
}
}
}
4.完整代码
代码放在 https://github.com/hursing/ViewPagerDemo
界面结构:5个按钮,分别是增、删、重置Model object。下面是ViewPager,按完按钮滚动区域会有变化。每一页有三个TextView,其中最上面的是显示ItemView本身的引用信息,留意它的引用地址(如截图中的12f46ad2),会发现最多创建4个ItemView(缓存了左、右、当前,还有一个预备),再怎么滑都会重复利用。
截图:
下面是贴些代码,可不看了:
OnePiece.java
public class OnePiece {
public char letter;
public int number;
public OnePiece(char letter, int number) {
this.letter = letter;
this.number = number;
}
}
item_view.xml
<com.example.liuhx.viewpagerdemo.ItemView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ccc"
android:orientation="vertical"
android:paddingTop="40dp"
android:paddingBottom="40dp"
android:paddingLeft="20dp"
android:paddingRight="20dp">
<TextView
android:id="@+id/this_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="reference info" />
<TextView
android:id="@+id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="sample1" />
<TextView
android:id="@+id/text2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="sample2" />
</com.example.liuhx.viewpagerdemo.ItemView>
ItemView.java
public class ItemView extends LinearLayout {
private TextView mTextView1;
private TextView mTextView2;
private OnePiece mOnePiece;
public ItemView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setOnePiece(OnePiece onePiece) {
mOnePiece = onePiece;
mTextView1.setText(String.valueOf(onePiece.letter));
mTextView2.setText(String.valueOf(onePiece.number));
}
public void reset() {
// Release resource here: network connection, database cursor, ...
mOnePiece = null;
}
@Override
public void onFinishInflate() {
// To differentiate View objects.
((TextView) findViewById(R.id.this_info)).setText(this.toString());
mTextView1 = (TextView) findViewById(R.id.text1);
mTextView2 = (TextView) findViewById(R.id.text2);
}
}
PieceModel.java
public class PieceModel {
public interface OnPiecesChangedListener {
void onPiecesChanged();
}
public List<OnePiece> pieces = new LinkedList<>();
private OnPiecesChangedListener mOnPiecesChangedListener;
private char mLetterSeed = 'A';
private int mNumberSeed = 0;
public void setOnPiecesChangedListener(OnPiecesChangedListener listener) {
mOnPiecesChangedListener = listener;
}
public void addPiece(boolean toTail) {
OnePiece onePiece = new OnePiece(mLetterSeed, mNumberSeed++);
if (toTail)
pieces.add(onePiece);
else
pieces.add(0, onePiece);
mOnPiecesChangedListener.onPiecesChanged();
}
public void removePiece(boolean fromTail) {
if (fromTail)
pieces.remove(pieces.size() - 1);
else
pieces.remove(0);
mOnPiecesChangedListener.onPiecesChanged();
}
public void changePieces() {
++mLetterSeed;
mNumberSeed = 0;
int size = pieces.size();
pieces.clear();
for (; mNumberSeed < size; ++mNumberSeed) {
OnePiece onePiece = new OnePiece(mLetterSeed, mNumberSeed);
pieces.add(onePiece);
}
mOnPiecesChangedListener.onPiecesChanged();
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.liuhx.viewpagerdemo.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:id="@+id/add_head"
android:text="add head"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:id="@+id/add_tail"
android:text="add tail"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:id="@+id/remove_head"
android:text="remove head"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:id="@+id/remove_tail"
android:text="remove tail"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:id="@+id/change"
android:text="change"/>
</LinearLayout>
<android.support.v4.view.ViewPager
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/view_pager" />
</LinearLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private ViewPager mViewPager;
private ViewPageAdapter mViewPageAdapter;
private PieceModel mPieceModel;
private class ViewPageAdapter extends PagerAdapter {
private List<ItemView> mViewRecycler = new LinkedList<>();
private Map<OnePiece, ItemView> mOnePieceItemViewMap = new HashMap<>();
@Override
public int getCount() {
return mPieceModel.pieces.size();
}
@Override
public boolean isViewFromObject(View view, Object object) {
OnePiece onePiece = (OnePiece) object;
return view == mOnePieceItemViewMap.get(onePiece);
}
@Override
public Object instantiateItem (ViewGroup container, int position) {
ItemView itemView;
if (mViewRecycler.size() > 0) {
itemView = mViewRecycler.remove(0);
} else {
itemView = (ItemView) LayoutInflater.from(MainActivity.this)
.inflate(R.layout.item_view, container, false);
}
container.addView(itemView);
OnePiece onePiece = mPieceModel.pieces.get(position);
itemView.setOnePiece(onePiece);
mOnePieceItemViewMap.put(onePiece, itemView);
return onePiece;
}
@Override
public void destroyItem (ViewGroup container, int position, Object object) {
OnePiece onePiece = (OnePiece) object;
ItemView itemView = mOnePieceItemViewMap.remove(onePiece);
container.removeView(itemView);
itemView.reset();
mViewRecycler.add(itemView);
}
// Must override this method, or else notifyDataSetChanged() has no effect.
@Override
public int getItemPosition(Object object) {
OnePiece onePiece = (OnePiece) object;
if (mPieceModel.pieces.contains(onePiece)) {
return mPieceModel.pieces.indexOf(onePiece);
} else {
return POSITION_NONE;
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mPieceModel = new PieceModel();
mPieceModel.setOnPiecesChangedListener(new PieceModel.OnPiecesChangedListener() {
@Override
public void onPiecesChanged() {
mViewPageAdapter.notifyDataSetChanged();
}
});
mViewPager = (ViewPager) findViewById(R.id.view_pager);
mViewPageAdapter = new ViewPageAdapter();
mViewPager.setAdapter(mViewPageAdapter);
mViewPager.setOffscreenPageLimit(1);
findViewById(R.id.add_head).setOnClickListener(this);
findViewById(R.id.add_tail).setOnClickListener(this);
findViewById(R.id.remove_head).setOnClickListener(this);
findViewById(R.id.remove_tail).setOnClickListener(this);
findViewById(R.id.change).setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.add_head:
mPieceModel.addPiece(false);
break;
case R.id.add_tail:
mPieceModel.addPiece(true);
break;
case R.id.remove_head:
if (mPieceModel.pieces.size() == 0) {
Toast.makeText(this, "Please press ADD first", Toast.LENGTH_SHORT).show();
} else {
mPieceModel.removePiece(false);
}
break;
case R.id.remove_tail:
if (mPieceModel.pieces.size() == 0) {
Toast.makeText(this, "Please press ADD first", Toast.LENGTH_SHORT).show();
} else {
mPieceModel.removePiece(true);
}
break;
case R.id.change:
if (mPieceModel.pieces.size() == 0) {
Toast.makeText(this, "Please press ADD first", Toast.LENGTH_SHORT).show();
} else {
mPieceModel.changePieces();
}
break;
default:
break;
}
}
}
转载请注明出处:http://blog.csdn.net/hursing
ViewPager实现Recycle机制和响应notifyDataSetChanged的更多相关文章
- 【转载】Recycle机制
首先要明白,Recycle机制并不是Java中的垃圾回收机制,而是相当于一种设计模式 思想:当一个对象不再使用时,储存起来,而不是让虚拟机回收,需要的时候再用,避免对象被回收之后重分配 适用范围:对于 ...
- HTTP请求响应机制与响应状态码
转载来源:http://blog.csdn.net/xyw591238/article/details/51907143 HTTP协议 Internate的基本协议是TCP/IP(传输控制协议和网际协 ...
- ViewPager的缓存机制
1.实现Viewpager的页面懒加载: 在某些情况下,例如使用ViewPager查看多张大图,此时多张图片不能一次性载入,只有在浏览该页面时才载入(或者预先载入下一页面)页面的具体内容. 2.可控V ...
- ViewPager 嵌套Listview 让Listview响应 ViewPager 左右滑事件
一段拦截判断而已. 之前一直误解了一个拦截的描述.导致搞了半天. 结论: onInterceptTouchEvent 返回true,就由本身View的onTouchEvent进行事件消费. /** ...
- ViewPager和View的事件响应规则
案例背景: 当我们实现viewpager的自动切换界面操作的时候,如果需要增加点击图片viewpager停止自动切换,松开手指viewpager自动切换又继续执行的逻辑,正常思维下实现代码如下所示: ...
- 【Android开发日记】之入门篇(十五)——ViewPager+自定义无限ViewPager
ViewPager 在 Android 控件中,ViewPager 一直算是使用率比较高的控件,包括首页的banner,tab页的切换都能见到ViewPager的身影. viewpager 来源自 v ...
- TabLayoutViewPagerDemo【TabLayout+ViewPager可滑动】
版权声明:本文为HaiyuKing原创文章,转载请注明出处! 前言 使用TabLayout搭配ViewPager实现可滑动的顶部选项卡效果. 效果图 代码分析 1.演示常规的设置. 2.通过自定义Vi ...
- 【Android】Fragment懒加载和ViewPager的坑
效果 老规矩,先来看看效果 ANDROID和福利两个Fragment是设置的Fragment可见时加载数据,也就是懒加载.圆形的旋转加载图标只有一个,所以,如果当前Fragment正处于加载状态,在离 ...
- Android使用ViewPager做轮播
ViewPager.html div.oembedall-githubrepos { border: 1px solid #DDD; list-style-type: none; margin: 0 ...
随机推荐
- javascript 计算文件MD5 浏览器 javascript读取文件内容
原则上说,浏览器是一个不安全的环境.早期浏览器的内容是静态的,用户上网冲浪,一般就是拉取网页查看.后来,随着互联网的发展,浏览器提供了非常丰富的用户交互功能.从早期的表单交互,到现在的websocke ...
- 生成代码的代码 之 POJO生成器
我们在写Java代码时候,有时候需要写一些POJO类,也就是只有一些属性和get, set方法的类.例如,在写REST 服务时候,利用Jersery + Jackson,可以把输入的JSON字符串自动 ...
- How To Scan QRCode For UWP (4)
QR Code的全称是Quick Response Code,中文翻译为快速响应矩阵图码,有关它的简介可以查看维基百科. 我准备使用ZXing.Net来实现扫描二维码的功能,ZXing.Net在Cod ...
- 8 Ways to Become a Better Coder
It’s time to get serious about improving your programming skills. Let’s do it! That’s an easy career ...
- Java 内存分配及垃圾回收机制初探
一.运行时内存分配 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域. 这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则 ...
- Python:线程指南
1. 线程基础 1.1. 线程状态 线程有5种状态,状态转换的过程如下图所示: 1.2. 线程同步(锁) 多线程的优势在于可以同时运行多个任务(至少感觉起来是这样).但是当线程需要共享数据时,可能存在 ...
- C# 字符串操作基本过程(Equals、Compare、EndsWith等处理方法)
本文只介绍了比较方法,但是EndsWith,IndexOf等方法均采用相同的过程,先设置CultureInfo(一般情况下调用当前线程的CultureInfo,该语言文化可以通过控制面板设置),然后调 ...
- 深入理解Spring的异步机制
一.Spring中实现异步执行 在这里我先以事件的机制举例,注意默认情况下事件的发布与监听都是同步执行的.那么我们来看一看基于异步事件的例子该怎么写 首先还是定义事件: package com.bdq ...
- GO入门——2. 变量
1 基本类型 零值并不等于空值,而是当变量被声明为某种类型后的默认值, 通常情况下值类型的默认值为0,bool为false,string为空字符串,引用为nil. 1.1 布尔类型 关键字:bool ...
- mysql5.7主从复制配置——读写分离实现
为什么使用主从架构?1.实现服务器负载均衡:2.通过复制实现数据的异地备份:3.提高数据库系统的可用性:4.可以分库[垂直拆分],分表[水平拆分]: 主从配置的前提条件1.MySQL版本一致:2.My ...