2016年3月6日日曜日

RecyclerView で IndexOutOfBoundsException が起こる話

注意:以下の現象は support library v23.2.0 では起こりません。

support library v23.1.1 で以下のコードを実行すると落ちます。

java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 0(offset:20).state:20 at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:4405) ...
  1. public class RecyclerViewGoneActivity extends AppCompatActivity {  
  2.   
  3.     private TextAdapter textAdapter;  
  4.   
  5.     @Override  
  6.     protected void onCreate(Bundle savedInstanceState) {  
  7.         super.onCreate(savedInstanceState);  
  8.         setContentView(R.layout.activity_recycler_view_gone);  
  9.   
  10.         final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);  
  11.         recyclerView.setLayoutManager(new LinearLayoutManager(this));  
  12.   
  13.         // 1. 初期状態がGONE  
  14.         recyclerView.setVisibility(View.GONE);  
  15.   
  16.         // 2. setHasFixedSize(true);  
  17.         recyclerView.setHasFixedSize(true);  
  18.   
  19.         textAdapter = new TextAdapter();  
  20.         recyclerView.setAdapter(textAdapter);  
  21.   
  22.         new Handler().postDelayed(new Runnable() {  
  23.             @Override  
  24.             public void run() {  
  25.                 ArrayList<String> data = new ArrayList<>();  
  26.                 for (int i = 0; i < 20; i++) {  
  27.                     data.add("data : " + i);  
  28.                 }  
  29.                 // 3. notifyItemRangeInserted(); を呼ぶ  
  30.                 textAdapter.addAll(data);  
  31.                 // 4. visibility を VISIBLE にする  
  32.                 recyclerView.setVisibility(View.VISIBLE);  
  33.             }  
  34.         }, 1000);  
  35.     }  
  36.   
  37.     private static class TextAdapter extends RecyclerView.Adapter<ViewHolder> {  
  38.   
  39.         private final ArrayList<String> data = new ArrayList<>();  
  40.   
  41.         @Override  
  42.         public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {  
  43.             LayoutInflater inflater = LayoutInflater.from(parent.getContext());  
  44.             return new ViewHolder(inflater.inflate(ViewHolder.LAYOUT_ID, parent, false));  
  45.         }  
  46.   
  47.         @Override  
  48.         public void onBindViewHolder(ViewHolder holder, int position) {  
  49.             holder.textView.setText(data.get(position));  
  50.         }  
  51.   
  52.         @Override  
  53.         public int getItemCount() {  
  54.             return data.size();  
  55.         }  
  56.   
  57.         public void add(String s) {  
  58.             final int positionStart = getItemCount();  
  59.             data.add(s);  
  60.             notifyItemInserted(positionStart);  
  61.         }  
  62.   
  63.         public void addAll(ArrayList<String> d) {  
  64.             final int positionStart = getItemCount();  
  65.             final int itemCount = d.size();  
  66.             data.addAll(d);  
  67.             notifyItemRangeInserted(positionStart, itemCount);  
  68.         }  
  69.   
  70.         public void clear() {  
  71.             final int itemCount = getItemCount();  
  72.             data.clear();  
  73.             notifyItemRangeRemoved(0, itemCount);  
  74.         }  
  75.     }  
  76.   
  77.     private static class ViewHolder extends RecyclerView.ViewHolder {  
  78.   
  79.         public static final int LAYOUT_ID = android.R.layout.simple_list_item_1;  
  80.   
  81.         final TextView textView;  
  82.   
  83.         public ViewHolder(View itemView) {  
  84.             super(itemView);  
  85.             this.textView = (TextView) itemView.findViewById(android.R.id.text1);  
  86.         }  
  87.     }  
  88. }  



落ちなくする方法がいくつかあります。
  • 1. setHasFixedSize(true); を呼ばないようにする(ただしその分パフォーマンスに影響する)
  • 2. あらかじめデータを入れておいて VISIBLE にする前に消す
    1. textAdapter = new TextAdapter();  
    2. recyclerView.setAdapter(textAdapter);  
    3.   
    4. textAdapter.add("dummy"); // これを追加  
    5.   
    6. new Handler().postDelayed(new Runnable() {  
    7.     @Override  
    8.     public void run() {  
    9.         textAdapter.clear(); // これを追加  
    10.         ArrayList<String> data = new ArrayList<>();  
    11.         for (int i = 0; i < 20; i++) {  
    12.             data.add("data : " + i);  
    13.         }  
    14.         textAdapter.addAll(data);  
    15.         recyclerView.setVisibility(View.VISIBLE);  
    16.     }  
    17. }, 1000);  
関連


0 件のコメント:

コメントを投稿