ViewPager实现Recycle机制和响应notifyDataSetChanged

简介: 1.目标主界面要求水平移动翻页效果,每次只能翻一页,可以翻无数页。2.实现思路针对“每次只能翻一页”这个要求,简单使用SDK的话只有用ViewPager。

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

目录
相关文章
|
4月前
|
传感器 存储 Java
Android 3D效果的实现
本文详细讲解了如何在Android中实现3D效果,基于官方Demo并结合实际需求进行调整。通过传感器(Sensor)获取设备旋转数据,利用OpenGL ES绘制3D立方体,实现了动态旋转的视觉效果。文章分为需求分析、效果展示、实现步骤及源码解析,涵盖传感器注册与注销、OpenGL核心方法使用等内容,适合初学者学习参考。文末附完整代码,便于实践操作。
119 0
Android 3D效果的实现
|
开发框架 iOS开发
iOS开发之AVKit框架使用
iOS开发之AVKit框架使用
883 0
iOS开发之AVKit框架使用
|
9月前
|
存储 Linux API
深入探索Android系统架构:从内核到应用层的全面解析
本文旨在为读者提供一份详尽的Android系统架构分析,从底层的Linux内核到顶层的应用程序框架。我们将探讨Android系统的模块化设计、各层之间的交互机制以及它们如何共同协作以支持丰富多样的应用生态。通过本篇文章,开发者和爱好者可以更深入理解Android平台的工作原理,从而优化开发流程和提升应用性能。
|
Android开发 Kotlin
Android面试题之kotlin中怎么限制一个函数参数的取值范围和取值类型等
在Kotlin中,限制函数参数可通过类型系统、泛型、条件检查、数据类、密封类和注解实现。例如,使用枚举限制参数为特定值,泛型约束确保参数为Number子类,条件检查如`require`确保参数在特定范围内,数据类封装可添加验证,密封类限制为一组预定义值,注解结合第三方库如Bean Validation进行校验。
236 6
|
11月前
|
运维 供应链 安全
SD-WAN分布式组网:构建高效、灵活的企业网络架构
本文介绍了SD-WAN(软件定义广域网)在企业分布式组网中的应用,强调其智能化流量管理、简化的网络部署、弹性扩展能力和增强的安全性等核心优势,以及在跨国企业、多云环境、零售连锁和制造业中的典型应用场景。通过合理设计网络架构、选择合适的网络连接类型、优化应用流量优先级和定期评估网络性能等最佳实践,SD-WAN助力企业实现高效、稳定的业务连接,加速数字化转型。
SD-WAN分布式组网:构建高效、灵活的企业网络架构
|
Android开发
setOnItemClickListener不起作用解决方法
setOnItemClickListener不起作用解决方法
284 2
|
XML Android开发 数据格式
Android Studio App开发实战项目之实现淘宝电商App首页界面(附源码,可用于大作业参考)
Android Studio App开发实战项目之实现淘宝电商App首页界面(附源码,可用于大作业参考)
1497 1
|
存储 缓存 程序员
C++ 中的内存对齐——理论篇
> 本文作为上篇主要介绍内存对齐的理论基础,后续的下篇将侧重于 C++ 语言层面的实践,敬请期待! ### TL;DR - 处理器以若干字节的块而不是单字节的粒度访问内存,因此对于未对齐的内存需要额外的访存及计算开销,导致性能更差。 - 原子操作和矢量运算指令要求内存地址必须是对齐的,否则可能导致程序死循环和数据错误。 - 编译器通过 padding 自动对结构中的字段进行对齐,用以向后兼容以
918 0
C++ 中的内存对齐——理论篇
|
存储 编解码 vr&ar
色彩空间与像素格式
颜色是不同波长的光对人眼刺激产生的色彩感觉。色彩空间(Color Space)是颜色的数学表示,根据不同的表示方法分为不同的色彩模型。最常用的色彩模型有三类:RGB(用于计算机图形学), YUV(用于视频系统), CMYK(用于彩色印刷)。后文对色彩空间与色彩模型的叫法不作区分。本文仅讨论视频图像处理领域常用的 RGB 色彩空间和 YUV 色彩空间。
3123 0
色彩空间与像素格式