android viewpager你可能不知道的刷新操作分享-kb88凯时官网登录

来自:网络
时间:2023-07-25
阅读:
免费资源网,https://freexyz.cn/
目录

前言

哎呀,这个我会。不就是 mviewpageradapter.notifydatasetchanged(); 嘛,简单!

这个可能真不是那么简单,我们以常用的 viewpager fragment 的使用为例。你调用 notifydatasetchanged 刷新方法,会走到 getitemposition 方法中查询当前item是否需要刷新,而它的默认实现是:

    public int getitemposition(@nonnull object object) {
        return position_unchanged;
    }

永远标记不刷新,那么不管你是添加pager,删除pager,改变pager都是不生效的。

那有些同学就会说了,每次刷新还要做差分?我直接一把梭,直接重新设置一个 adapter 不就万事大吉了?

反正每次接口数据回来都重新设置 adapter ,还管什么性能不性能,效果实现了再说!

    mviewpager.setadapter(null);
    mviewpageradapter = new viewpageradapter(getchildfragmentmanager(),mfragmentlist);
    mviewpager.setadapter(mviewpageradapter);
    mviewpager.setoffscreenpagelimit(mfragmentlist.size() - 1);

但是就算如此也是有问题的,当我们一个页面中根据不同的筛选条件,服务端返回不同数量的数组,我们就要展示不同数量的 viewpager 如果这样刷新 viewpager 就可能出现显示问题。

怎么解决?几种方案,接下来往下看:

一、清缓存重置adapter的方案

如果除开性能问题,想直接每次直接替换一个 adapter 其实也是可行的,如果替换之后显示的还是之前的页面,或者显示的索引不对,大概率是 viewpager 之前缓存的 fragment 没有清掉的。

所以我们需要自定义一个 adapter , 在里面定义清除缓存的方法,每次设置 adapter 之前就调用清除缓存之后再设置 adapter 。

直接上代码:

/**
 *  可以清除缓存的viewpager
 */
public class viewpagerclearadapter extends fragmentpageradapter {
    private list mfragments;
    private fragmenttransaction mcurtransaction;
    private fragmentmanager mfragmentmanger;
    public viewpagerclearadapter(fragmentmanager fragmentmanager, list fragments) {
        this(fragmentmanager, fragments, 0);
    }
    public viewpagerclearadapter(fragmentmanager fragmentmanager, list fragments, int behavor) {
        super(fragmentmanager, behavor);
        mfragments = fragments;
        mfragmentmanger = fragmentmanager;
    }
    @override
    public fragment getitem(int position) {
        return mfragments.get(position);
    }
    @override
    public int getcount() {
        return mfragments.size() == 0 ? 0 : mfragments.size();
    }
    /**
     * 清除缓存fragment
     *
     * @param container viewpager
     */
    public void clear(viewgroup container) {
        if (this.mcurtransaction == null) {
            this.mcurtransaction = mfragmentmanger.begintransaction();
        }
        for (int i = 0; i < mfragments.size(); i  ) {
            long itemid = this.getitemid(i);
            string name = makefragmentname(container.getid(), itemid);
            fragment fragment = mfragmentmanger.findfragmentbytag(name);
            if (fragment != null) {//根据对应的id,找到fragment,删除
                mcurtransaction.remove(fragment);
            }
        }
        mcurtransaction.commitnowallowingstateloss();
    }
    /**
     * 等同于fragmentpageradapter的makefragmentname方法,
     */
    private static string makefragmentname(int viewid, long id) {
        return "android:switcher:"   viewid   ":"   id;
    }

使用的时候,先清除再设置即可:

       if (mviewpageradapter!=null){
            mviewpageradapter.clear(mviewpager);
        }
        mviewpager.setadapter(null);
        mviewpageradapter = new viewpagerclearadapter(getchildfragmentmanager(),mfragmentlist);
        mviewpager.setadapter(mviewpageradapter);
        if (mfragmentlist.size() > 1) {
            mviewpager.setoffscreenpagelimit(mfragmentlist.size() - 1);
        }

这样也算是间接的实现了刷新功能,但是有点傻,recyclerview 感觉到暴怒,那么有没有类似 recyclerview 那样的智能刷新呢?

二、tabview viewpager的差分刷新

前言中我们说到 viewpager 的 notifydatasetchanged 刷新方法,会走到 getitemposition 方法,而内部的默认实现是不做刷新。

而重点的 getitemposition 其实就是在 notifydatasetchanged 执行的时候拿到当前的 item 集合做的遍历操作,让每一个 item 都去自行判断你有没有变化。

那么难点就是如何判断当前的对象或索引位置有没有变化呢?

2.1 使用arguments的方式

在 viewpageradapter 中我们可以重写方法 instantiateitem 表示每次创建 fragment 的时候执行,创建一个 fragment 对象。

由于内部默认实现并没有添加 tag ,所以我们可以通过调用 super的方式拿到 fragment 对象,给他设置一个参数,并记录每一个 fragment 对应的索引位置。

然后我们再判断 fragment 是否需要刷新的时候,拿到对应的参数,并获取当前 fragment 的索引,判断fragment有没有变化,索引有没有变化。

当都没有变化,说明此 fragment 无需刷新,就返回 position_unchanged ,如果要刷新就返回 position_none 。

如果返回 position_none ,就会走到 destroyitem 的回调,会销毁 framgent,如果有需要会重新创建新的 fragment 。

完整的代码实现如下:

class viewpagerfragmentadapter @jvmoverloads constructor(
    private val fragmentmanager: fragmentmanager,
    private val fragments: list,
    private val pagetitles: list? = null,
    behavor: int = 0
) : fragmentstatepageradapter(fragmentmanager, behavor) {
    private val fragmentmap = mutablemapof()
    private val fragmentpositions = hashmapof()
    init {
        for ((index, fragment) in fragments.withindex()) {
            fragmentmap[index] = fragment
        }
    }
    override fun getitem(position: int): fragment {
        return fragments[position]
    }
    override fun getcount(): int {
        return if (fragments.isempty()) 0 else fragments.size
    }
    override fun getpagetitle(position: int): charsequence? {
        return if (pagetitles == null) "" else pagetitles[position]
    }
    override fun instantiateitem(container: viewgroup, position: int): any {
        yylogutils.w("viewpagerfragmentadapter-instantiateitem")
        val fragment = super.instantiateitem(container, position) as fragment
        val id = generateuniqueid()
        var args = fragment.arguments
        if (args == null) {
            args = bundle()
        }
        args.putint("_uuid", id)
        fragment.arguments = args
        // 存储 fragment 的位置信息
        fragmentpositions[id] = position
        return fragment
    }
    private fun generateuniqueid(): int {
        // 生成唯一的 id
        return uuid.randomuuid().hashcode()
    }
    override fun destroyitem(container: viewgroup, position: int, obj: any) {
        super.destroyitem(container, position, obj)
    }
    override fun getitemposition(obj: any): int {
        yylogutils.w("viewpagerfragmentadapter-getitemposition")
        val fragment = obj as fragment
        // 从 fragment 中获取唯一的 id
        val args = fragment.arguments
        if (args != null && args.containskey("_uuid")) {
            val id = args.getint("_uuid")
            // 根据 id 获取 fragment 在 adapter 中的位置
            val position = fragmentpositions[id]
            return if (position != null && position == fragments.indexof(fragment)) {
                // fragment 未发生变化,返回 position_unchanged
                position_unchanged
            } else {
                // fragment 发生变化,返回 position_none
                position_none
            }
        }
        // 如果不是 fragment,则返回默认值
        return super.getitemposition(obj)
    }
}

使用起来很简单,我们这里使用默认的tabview viewpager 懒加载fragment来看看效果:

    val fragments = mutablelistof(lazyload1fragment.obtainfragment(), lazyload2fragment.obtainfragment(), lazyload3fragment.obtainfragment());
    val titles = mutablelistof("demo1", "demo2", "demo3");
    val adapter = viewpagerfragmentadapter(supportfragmentmanager, fragments, titles)
    override fun init() {
        //默认的添加数据适配器
        mbinding.viewpager.adapter = adapter
        mbinding.viewpager.offscreenpagelimit = fragments.size - 1
        mbinding.tablayout.setupwithviewpager(mbinding.viewpager)
    }

我们这里使用的是原始懒加载的方案,关于每一种懒加载fragment的使用可以看我之前的文章:。

我们再标题栏加一个测试的按钮,查看增删改的功能是否能行?

        mbinding.easytitle.addrighttext("刷新") {
            //添加并刷新
//            fragments.add(lazyload1fragment.obtainfragment())
//            titles.add("demo4")
            //更新指定位置并刷新
//            fragments[2] = lazyload2fragment.obtainfragment()
//            titles[2] = "refresh1"
            //反转换位置呢
//            fragments.reverse()
//            titles.reverse()
            //删除并刷新
            fragments.removeat(2)
            titles.removeat(2)
            mbinding.viewpager.adapter?.notifydatasetchanged()
            mbinding.viewpager.offscreenpagelimit = fragments.size - 1
        }

添加的效果:

指定位置替换fragment效果:

反转集合,应该是第一个和第三个fragment需要重载:

删除指定的数据:

2.2 使用tag的方式

而使用 tag 的方式替换其实是类似的道理,需要在创建 fragment 的时候绑定 tag ,在查询是否需要刷新的方法中需要拿到tag进行判断:

        fragment fragment = getitem(position);
        fragmenttransaction ft = ((fragmentactivity) mcontext).getsupportfragmentmanager().begintransaction();
        ft.add(r.id.viewpager, fragment, "fragment"   position);
        ft.attach(fragment);
        ft.commit();
    @override
    public int getitemposition(@nonnull object object) {
        if (object instanceof fragment) {
            fragment fragment = (fragment) object;
            integer position = fragmentmap.get(fragment.gettag());
            if (position != null && position == fragments.indexof(fragment)) {
                // fragment 未发生变化,返回 position_unchanged
                return position_unchanged;
            } else {
                // fragment 发生变化,返回 position_none
                return position_none;
            }
        }
        // 如果不是 fragment,则返回默认值
        return super.getitemposition(object);
    }

这里就不做过多的介绍,如果是简单的操作也是是可行的。只是需要重写创建fragment流程。

由于我自用的并不是 tag 的方式,因为并不想修改内部的创建 fragment 方式,毕竟内部还涉及到 savedstate 与 behavior 的一些处理。

如果你感兴趣可以自行实现!

三、自定义tab或第三方tab

如果我们用到一些自定义tab的样式,或者使用一些第三方的tablayout,那么我们该如何做?

customtabview 还能绑定到 viewpager 吗?如果要做刷新又该如何操作?

例如我们使用自定义的tab样式:

  override fun init() {
        titles.foreach {
             addtab(it)
        }
        mbinding.viewpager.adapter = adapter
        mbinding.viewpager.offscreenpagelimit = fragments.size - 1
        //自定义tab不能这么设置了?
        mbinding.tablayout.setupwithviewpager(mbinding.viewpager)
  }
    private fun addtab(content: string) {
        val tab: tablayout.tab = mbinding.tablayout.newtab()
        val view: view = layoutinflater.inflate(r.layout.tab_custom_layout, null)
        tab.customview = view
        val textview = view.findviewbyid(r.id.tab_text)
        textview.text = content
        mbinding.tablayout.addtab(tab)
    }

是可以运行,但是不能用 setupwithviewpager 方式,如果用这种方式会默认给设置原生默认的 tabview 。而没有自定义 view 效果。

所以我们一般都是手动的监听实现效果:

        //自定义tab不能这么设置了?
//        mbinding.tablayout.setupwithviewpager(mbinding.viewpager)
       // 需要手动的写监听绑定
        mbinding.viewpager.addonpagechangelistener(object : viewpager.onpagechangelistener {
            override fun onpagescrolled(i: int, v: float, i1: int) {}
            override fun onpageselected(i: int) {
                mbinding.tablayout.setscrollposition(i, 0f, true)
            }
            override fun onpagescrollstatechanged(i: int) {}
        })
        mbinding.tablayout.addontabselectedlistener(object : tablayout.ontabselectedlistener {
            override fun ontabselected(tab: tablayout.tab) {
                val position = tab.position
                mbinding.viewpager.setcurrentitem(position, true)
            }
            override fun ontabunselected(tab: tablayout.tab) {}
            override fun ontabreselected(tab: tablayout.tab) {}
        })

效果:

那么增删改的操作又有什么区别呢?

   mbinding.easytitle.addrighttext("refresh") {
            //添加并刷新
            fragments.add(lazyload1fragment.obtainfragment())
            titles.add("demo4")
            addtab("demo4")
            //删除并刷新
            fragments.removeat(2)
            titles.removeat(2)
            mbinding.tablayout.removetabat(2)
            mbinding.viewpager.adapter?.notifydatasetchanged()
            mbinding.viewpager.offscreenpagelimit = fragments.size - 1
        }

由于没有 setupwithviewpager 的方式绑定,所以当viewpager变化之后我们需要手动的自己处理tab相关的赋值与删除等操作:

否则会出现,viewpager刷新了,但tabview不会刷新的问题:

自行处理tab之后的效果:

不管是第三方的tablayout,还是自定义的tabview,相比原生默认的 tabview 使用操作还是要复杂上一点。

四、viewpager2的区别

而tabview viewpager2 懒加载fragment 就更简单啦,都是基于rv实现的,我们可以直接调用rv的刷新方法。

  override fun init() {
        mbinding.viewpager2.bindfragment(
            supportfragmentmanager,
            this.lifecycle,
            fragments,
        )
        tablayoutmediator(mbinding.tablayout, mbinding.viewpager2) { tab, position ->
            //回调
            tab.text = titles[position]
        }.attach()
    }

内部数据适配器的adapter:

/**
 * 给viewpager2绑定fragment
 */
fun viewpager2.bindfragment(
    fm: fragmentmanager,
    lifecycle: lifecycle,
    fragments: list
): viewpager2 {
    offscreenpagelimit = fragments.size - 1
    adapter = object : fragmentstateadapter(fm, lifecycle) {
        override fun getitemcount(): int = fragments.size
        override fun createfragment(position: int): fragment = fragments[position]
    }
    return this
}

后面我们给它加上一些操作方法:

        mbinding.easytitle.addrighttext("refresh2") {
            //添加并刷新
//            titles.add("demo4")
//            fragments.add(lazy2fragment1.obtainfragment())
//            mbinding.viewpager2.adapter?.notifyiteminserted(fragments.size-1)
            //更新指定位置并刷新
//            fragments[1] = lazy2fragment1.obtainfragment()
//            titles[1] = "refresh2"
//            mbinding.viewpager2.adapter?.notifyitemchanged(1)
            //删除并刷新
            fragments.removeat(2)
            mbinding.viewpager2.adapter?.notifyitemremoved(2)
            mbinding.viewpager2.adapter?.notifyitemrangechanged(2, 1)
        }

可以看到我们是直接使用rv的apdater来操作的,也就不需要魔改一些 adapter 之类的代码。

可以看到一些效果如下:

真是简单又方便!

话是这么说,但是感觉 viewpager2 的风评并不是很好的样子,很多伙伴反馈有一些小问题。总是踩在坑上,不知道大家都是怎么选择的呢?

总结

本文中我们可以回顾一下viewpager的用法,fragment的懒加载用法,重要的是可变 viewpager 的情况下如何操作。

那么在实际开发的过程中,我们其实可以区分场景,如果是静态的viewpager,数量不可变的,可以直接用简单的数据适配器来实现,而如果是可变的viewpager,大家可以区分三种情况来使用,都是可行的。

我个人来说之前都是用清除缓存的方式,后来用的是修改 fragment 的 argument 的方式做的。

如果大家有类似的使用场景,其实按需选择即可,也不知道大家平时都是怎么做的,如果有更好的方案可以交流一下哦。

免费资源网,https://freexyz.cn/
返回顶部
顶部
网站地图