Android开发 RecyclerView.Adapter点击后的数组越界问题 与 getAdapterPosition() 与 getLayoutPosition() 的区别
<h1><span style="color: rgba(0, 128, 128, 1)">问题描述</span></h1><p><span style="color: rgba(0, 128, 128, 1)"> <span style="color: rgba(0, 0, 0, 1)">在使用RecyclerView实现列表的时候会有极低的概率出现点击后数组越界的报错的问题。</span></span></p>
<p> </p>
<h1><span style="color: rgba(0, 128, 128, 1)">问题原因</span></h1>
<p><span style="color: rgba(0, 0, 0, 1)"> 请看下面这个几行在RecyclerView.Adapter里的一段代码</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"> @NonNull
@Override
</span><span style="color: rgba(0, 0, 255, 1)">public</span> ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> viewType) {
View view </span>= LayoutInflater.from(parent.getContext()).inflate(R.layout.item_guardian_home_video, parent, <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">);
ViewHolder viewHolder </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ViewHolder(view);
view.setOnClickListener(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> View.OnClickListener() {
@Override
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> onClick(View v) {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (mListener != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
mSelectedPosition </span>=<span style="color: rgba(0, 0, 0, 1)"> viewHolder.getAdapterPosition();
mListener.onClick(mList.get(mSelectedPosition), mSelectedPosition);
}
}
});
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> viewHolder;
}</span></pre>
</div>
<p>数组越界的关键点就是使用了<span style="color: rgba(0, 0, 0, 1)">getAdapterPosition()</span>;来获取点击的位置。而<span style="color: rgba(0, 0, 0, 1)">getAdapterPosition();方法获取位置有概率在Adapter在刷新视图的时候返回 -1 这个值。这个时候就会导致数组越界了。</span></p>
<p> </p>
<h1><span style="color: rgba(0, 128, 128, 1)">复现问题</span></h1>
<p><span style="color: rgba(0, 128, 128, 1)"><span style="color: rgba(0, 0, 0, 1)"> 为什么要复现问题? 因为我是测试转开发,个人习惯解决问题的时候同时去找到复现问题的条件。一般正常情况下,使用getAdapterPosition()方法在上面的代码中点击后获取的数据的位置,在人工进行测试的时候是极难复现这个数组越界问题的。只因为你是人类你没有这个手速,你没办法在刷新视图的一瞬间(只有16ms的时间)去点击item获取<span style="color: rgba(0, 128, 128, 1)"><span style="color: rgba(0, 0, 0, 1)">getAdapterPosition()</span></span>数据位置,触发这个bug。所以,此问题一般是Android设备UI线程轻微被堵塞或者在跑monkey的情况下才会出现这个报错。</span><br></span></p>
<p><span style="color: rgba(0, 128, 128, 1)"><span style="color: rgba(0, 0, 0, 1)"> 但是,从代码上刻意去制作条件复现这个问题没这么难,我们可以增加在获取<span style="color: rgba(0, 128, 128, 1)"><span style="color: rgba(0, 0, 0, 1)"><span style="color: rgba(0, 128, 128, 1)"><span style="color: rgba(0, 0, 0, 1)">getAdapterPosition()</span></span></span></span>前面添加一行notifyDataSetChanged() 刷新视图即可马上复现此问题。代码如下:</span></span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"> @NonNull
@Override
</span><span style="color: rgba(0, 0, 255, 1)">public</span> ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> viewType) {
View view </span>= LayoutInflater.from(parent.getContext()).inflate(R.layout.item_guardian_home_video, parent, <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">);
ViewHolder viewHolder </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ViewHolder(view);
view.setOnClickListener(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> View.OnClickListener() {
@Override
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> onClick(View v) {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (mListener != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
notifyDataSetChanged(); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">增加这行代码</span>
mSelectedPosition =<span style="color: rgba(0, 0, 0, 1)"> viewHolder.getAdapterPosition();
mListener.onClick(mList.get(mSelectedPosition), mSelectedPosition);
}
}
});
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> viewHolder;
}</span></pre>
</div>
<p> </p>
<h1><span style="color: rgba(0, 128, 128, 1)">解决问题</span></h1>
<p><span style="color: rgba(0, 0, 0, 1)"> 最主要的还是理解getAdapterPosition() 与 getLayoutPosition() 的区别,根据实际情况使用对应的方法。下面有说明<span style="color: rgba(0, 0, 0, 1)">getAdapterPosition() 与 getLayoutPosition() 的区别,这里提供几种获取位置的解决办法的思维。</span></span></p>
<p><span style="color: rgba(0, 0, 0, 1)"> 方式1,如果是在数据很少变化的情况下,将getAdapterPosition()方法替换成getLayoutPosition()方法就可以解决此问题了,代码如下:</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"> @NonNull
@Override
</span><span style="color: rgba(0, 0, 255, 1)">public</span> ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> viewType) {
View view </span>= LayoutInflater.from(parent.getContext()).inflate(R.layout.item_guardian_home_video, parent, <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">);
ViewHolder viewHolder </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ViewHolder(view);
view.setOnClickListener(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> View.OnClickListener() {
@Override
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> onClick(View v) {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (mListener != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
mSelectedPosition </span>=<span style="color: rgba(0, 0, 0, 1)"> viewHolder.getLayoutPosition();
mListener.onClick(mList.get(mSelectedPosition), mSelectedPosition);
}
}
});
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> viewHolder;
}</span></pre>
</div>
<p><span style="color: rgba(0, 0, 0, 1)"> 方式2,在public void onBindViewHolder(@NonNull ViewHolder holder, int position) {} 方法里实现点击,因为直接就有position了。但是这种方法缺点是影响性能,</span><span style="color: rgba(0, 0, 0, 1)"><span style="color: rgba(0, 0, 0, 1)">onBindViewHolder方法调用的十分频繁。这里方法里负责给View装载数据。在滚动的时候会大量触发,这里频繁new 一个接口进去不是非常好。<br></span></span></p>
<p><span style="color: rgba(0, 0, 0, 1)"> 方式3,使用<span style="color: rgba(0, 0, 0, 1)">findContainingViewHolder</span>来定位点击的<span style="color: rgba(0, 0, 0, 1)">ViewHolder</span>在获取位置</span></p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
* Returns the ViewHolder that contains the given view.
*
* </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> view The view that is a descendant of the RecyclerView.
*
* </span><span style="color: rgba(128, 128, 128, 1)">@return</span><span style="color: rgba(0, 128, 0, 1)"> The ViewHolder that contains the given view or null if the provided view is not a
* descendant of this RecyclerView.
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
@Nullable
</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> ViewHolder findContainingViewHolder(@NonNull View view) {
View itemView </span>=<span style="color: rgba(0, 0, 0, 1)"> findContainingItemView(view);
</span><span style="color: rgba(0, 0, 255, 1)">return</span> itemView == <span style="color: rgba(0, 0, 255, 1)">null</span> ? <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)"> : getChildViewHolder(itemView);
}</span></pre>
</div>
<p> </p>
<h1><span style="color: rgba(0, 128, 128, 1)"> getAdapterPosition() 与 getLayoutPosition() 的区别</span></h1>
<p> getAdapterPosition 与 getLayoutPosition 方法是google替代 getPosition提供的新api。 你们也可以在Android studio上看他们的注释了解下google的想法。</p>
<p><span style="color: rgba(0, 0, 0, 1)"> 个人认为getAdapterPosition() 是提供数据在刷新的时候提供一个-1的返回值,来告知视图其实正在重新绘制。这个时候点击位置与你想要的数据正在变化中是不一致的。(因为绘制View与数据位置这2组内容其实是异步的,所以<span style="color: rgba(0, 0, 0, 1)">Position理所当然的是不准确的</span>)</span></p>
<p><span style="color: rgba(0, 0, 0, 1)"> 而getLayoutPosition</span><span style="color: rgba(0, 0, 0, 1)">()更加简单暴力,你点击不会告诉你数据是否正在刷新,始终会返回一个位置值。这个位置值有可能是之前的视图item位置,也有可能是刷新视图后的item位置。</span><span style="color: rgba(0, 128, 128, 1)"><span style="color: rgba(0, 0, 0, 1)"><br></span></span></p>
<p> </p>
<p><span style="color: rgba(0, 0, 0, 1)"> 那么问题来了,应该如何取舍他们或者在什么情况下使用他们呢?</span></p>
<p><span style="color: rgba(0, 0, 0, 1)"> <span style="color: rgba(0, 0, 0, 1)">getLayoutPosition<span style="color: rgba(0, 0, 0, 1)">()</span></span> 更适合在短时间内数据变动少,View刷新不频繁的情况下使用,或者是固定列表数据,一切简单化没这么复杂。<br></span></p>
<p><span style="color: rgba(0, 0, 0, 1)"> <span style="color: rgba(0, 0, 0, 1)">getAdapterPosition() 更适合在频繁变动数据的情况下使用,指那种数据刷新极快而且是连续刷新的情况下使用,-1的无位置的返回值告诉你视图正在变化,你需要判断是否执行这次点击。如下代码:<br></span></span></p>
<p> </p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"> @Override
</span><span style="color: rgba(0, 0, 255, 1)">public</span> ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> viewType) {
View view </span>= LayoutInflater.from(parent.getContext()).inflate(R.layout.item_guardian_home_video, parent, <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">);
ViewHolder viewHolder </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ViewHolder(view);
view.setOnClickListener(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> View.OnClickListener() {
@Override
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> onClick(View v) {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (mListener != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
mSelectedPosition </span>=<span style="color: rgba(0, 0, 0, 1)"> viewHolder.getAdapterPosition();
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (mSelectedPosition ==<span style="color: rgba(0, 0, 0, 1)"> RecyclerView.NO_POSITION){
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
}
mListener.onClick(mList.get(mSelectedPosition), mSelectedPosition);
}
}
});
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> viewHolder;
}</span></pre>
</div>
<pre>onBindViewHolder</pre>
</div>
<div id="MySignature" role="contentinfo">
<div style="text-align: center">
<p style="color:orange;font-size:16px;" >本文来自博客园,作者:观心静 ,转载请注明原文链接:https://www.cnblogs.com/guanxinjing/p/12192114.html </p>
<div style="color:orange;font-size:16px;">本文版权归作者和博客园共有,欢迎转载,但必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利。 </div>
</div><br><br>
来源:https://www.cnblogs.com/guanxinjing/p/12192114.html
頁:
[1]