RecyclerView局部刷新爬坑之路
xuxiaochun
8年前
<p>前几天看到的一篇文章,感觉和我的爬坑经历很像,感觉相见恨晚。</p> <p>有图有真相,首先来对比一下局部刷新前后的效果:</p> <p>优化之前的效果:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/e9298fce721dac3d0392e71e3e9421db.gif"></p> <p>优化之后的效果:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/1b119c2cbf25b4ea2ff9806599fba9fc.gif"></p> <p>可以看到,优化之后,列表中的这张大图不在有一闪一闪亮晶晶的效果了! 那么,这是如何做到的呢?这是本文的重点,本文的大纲主要包括:</p> <ol> <li> <p>分析为什么会闪一下</p> </li> <li> <p>对分析的可能造成闪动的问题进行解决</p> </li> <li> <p>验证是否解决</p> </li> </ol> <h3>一、为什么会闪一下?</h3> <p>我们的需求是大家已经看到了,点击打分,弹出一个对话框,点击一个分数,这时候,通过一些列复杂的转换(当然不是本文的论述的重点),这时候到了要更新列表项了,如是很自然,我们会这么做:</p> <p><img src="https://simg.open-open.com/show/4cf509527952795e0d09a3a3347151b7.png"></p> <p>因为,操作的那个列表项你是知道他的position,所以你可以这么做,(当然,我之前是直接notifyDataSetChanged的,这个会照成所以不不要的item也会刷新)然而,闪动还是出现了,那么我开始怀疑: 流传甚为广泛的一种说法。</p> <ol> <li> <p>ImageView的宽高不固定导致的(wrap_content)?</p> </li> <li> <p>这个是RecyclerView自带的更新动画效果导致的?</p> </li> <li> <p>这个是因为图片加载框架(glide 的 animte)的动画效果导致的?</p> </li> <li> <p>getView中(RecyclerView中是onBindViewHolder)加载图片的时候,设置一个tag,当发现这个imageView的tag和之前的tag一致时就不加载</p> </li> </ol> <h3>二、带着思考,就去尝试吧!</h3> <p>1、对于第一种,我的做法是自己写了一个自定义的imageView,重写omMeasure方法,如下:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/df6cb738c8f405505021f7e48892617c.png"></p> <p>因为我们的这个列表项中的图片是(高=宽)的,因此,我才这么写,这样写也有一个好处,不用在onBindViewHolder中去动态的计算出高度,然后在已layoutParm的方式设置给imageView,相信不少小伙伴都做过了吧!</p> <p>然而,遗憾的是,他并没有解决闪一下的问题!此时这个闪动的原因显然不在这里,但是这里做的,可以保留下来。</p> <p>2、对于第二种说法,我参考了这里 <a href="/misc/goto?guid=4959745952534367584" rel="nofollow,noindex">http://stackoverflow.com/questions/29331075/recyclerview-blinking-after-notifydatasetchanged</a> 的做法:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/b9188c27c12e77b72dd3bc3448894d0b.png"></p> <p>以及也尝试了这种</p> <p><img src="https://simg.open-open.com/show/cd4e07541ce9b550d832871093af2970.png"></p> <p>然而,那种渐变的闪动消失了,但是,取而代之的是一种更加不可接受的闪动,这里就不用gif展示了,因此原因也并不在此处。</p> <p>3、对于对三种说法,我也去尝试了一下将glide加载改为:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/74963c6aad22ca0caae2b442aa236328.png"></p> <p>然而得到的依然是一个失望的结果,依然没有解决闪动的问题,原因也不在此处。</p> <p>4、那么,就剩下最后一个猜测了,那么会不会是它呢?那就试试吧,于是代码改为:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/27c3f8406bc815d29c43eee1b874807e.png"></p> <p>这里的做法其实就是设置Tag,那么是骡子是马,拉出来溜溜吧,结果更加令人发指,如图:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/9acbecff07b05626f1e5b5edb190be25.gif"></p> <p>好吧,此时已经有点崩溃了,显然这个也不是我要的结果,那么此时是否应该在静下来想一想,自己对于可能的几种原因做过的一些对策,是否有哪里遗漏了。经过思考,发现并没有!!那么一定是还有其他的原因,没有考虑到!</p> <p>还是去翻一翻RecyclerView的api吧,我注意到了这个api:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/72d4b9657651b900a556308f9f0ef9a7.png"> <img src="https://simg.open-open.com/show/2973db019c5f03e391dad511d889930a.png"></p> <p>可以看到这里有一个payload的参数,use null to identify a “full” update这是说如果传null就是全部更新,回过头去看一看我们之前的调用方式:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/919af454bd8841338e394e957116e593.png"></p> <p>看一下源码,发现</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/28bfcebaf539048c89d44d0cf2cf20a1.png"></p> <p>实际上,payload这个参数就是传的null,那也就是说如果传一个不为null的参数,就可以对列表项中的具体控件更新了? <a href="/misc/goto?guid=4959745952623249902" rel="nofollow,noindex">http://stackoverflow.com/questions/33176336/need-an-example-about-recyclerview-adapter-notifyitemchangedint-position-objec</a> 我了解到这个方法的使用方式是这样的:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/459bbf2b2c33641a25cd7474e8cfc603.png"></p> <p>然来,onBindViewHolder有这么一个重载方式,如是我也这么做了,在下面这个重载中,去更新我想更新的控件:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/d8083299c828b6bf4175a737f5df960f.png"></p> <p>然后,更新的方式变成了这种:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/3d738d3ae488fe8c9104fe8abb8c158f.png"></p> <p>是骡子是马,那就在遛一遛吧!</p> <p>然而,依然是会闪一下!!!这这么会!!!还是调试一下吧,新重载onBindViewHolder方法有没有被执行,一更代码,发现果然没有被执行!</p> <p>那么,究竟是什么鬼?去网上查了一下,有人给出了一个解决办法: <a href="/misc/goto?guid=4959745952717430394" rel="nofollow,noindex">http://stackoverflow.com/questions/32463136/recyclerview-adapter-notifyitemchanged-never-passes-payload-to-onbindviewholde</a></p> <p style="text-align:center"><img src="https://simg.open-open.com/show/c4394ee4539ce967e8e1c7b57b4f5817.png"></p> <p>需要重写这个动画,让永远返回true,已达到newHolder和olderHolder是同一个,然而,这真的就是我的救命稻草吗?</p> <p>那么,是骡子是马,拉出来溜溜吧,然而,并不是马!!进源码看一看</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/bcfb87c3e734bf578eb62683489759f1.png"></p> <p>发现其实只要我们传入的payload不为空,那么返回的就是true?重写有意义吗?显然,我重载的onBindViewHolder方法并没有执行的原因显然不是这个。</p> <p>那么,到底,到底问题出在何处?会不会是XrecyclerView的问题?根据调用栈,我看到第一个onBindViewHolder被执行了,往上面跟,发现XrecyclerView的实现果然存在问题!</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/39847a10464762b14b77799fa6ff6a9e.png"></p> <p>如图,作者仅仅只实现了,不带payload的方法,最后adapter调用的只有不带paylaod的方法!所以,重写一个吧!</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/a0806e8721ddd8ced67c0ec508ae2dbd.png"></p> <p>最后!终于达到了想要的效果了,经过这次爬坑,选择一个开源的框架真滴是需要慎重再慎重。</p> <h3>总结</h3> <p>实际上RecyclerView做局部刷新是非常容易的,其实就是使用好带payload参数的这个notifyItemRangeChanged方法,以及override带payload的这个onBindViewHolder方法,在onBindViewHolder中去刷新你想更新的控件即可,并非是网上传闻的那些原因,当然此处爬坑时间之长,也可能更选用开源控件不当有关,所以,选择开源控件,要谨慎再谨慎!</p> <p> </p> <p>来自:http://blog.csdn.net/qq_17766199/article/details/65452436</p> <p> </p>