2015-06-05 14 views
21

Gần đây tôi sử dụng RecyclerView và thêm chế độ xem tiêu đề tùy chỉnh (một loại chế độ xem mục khác) và cố gắng cập nhật khi dữ liệu đã thay đổi. Một cái gì đó kỳ lạ xảy ra. Bộ điều hợp tạo ra một HeaderViewHolder mới và sử dụng cả HeaderViewHolder mới và cái cũ.Tại sao RecyclerView.notifyItemChanged() sẽ tạo một ViewHolder mới và sử dụng cả ViewHolder cũ và mới?

Đây là mẫu.

MainActivity.java

public class MainActivity extends ActionBarActivity { 

    private RecyclerView mRecyclerView; 

    private MyAdapter mAdapter; 

    @Override protected void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    setContentView(R.layout.activity_main); 

    mRecyclerView = (RecyclerView) findViewById(R.id.list); 
    LinearLayoutManager llm = new LinearLayoutManager(this); 
    llm.setSmoothScrollbarEnabled(true); 
    mRecyclerView.setLayoutManager(llm); 
    mRecyclerView.setAdapter(mAdapter = new MyAdapter(this, genItemList())); 
    } 

    @Override public boolean onCreateOptionsMenu(Menu menu) { 
    // Inflate the menu; this adds items to the action bar if it is present. 
    getMenuInflater().inflate(R.menu.menu_main, menu); 
    return true; 
    } 

    @Override public boolean onOptionsItemSelected(MenuItem item) { 
    // Handle action bar item clicks here. The action bar will 
    // automatically handle clicks on the Home/Up button, so long 
    // as you specify a parent activity in AndroidManifest.xml. 
    int id = item.getItemId(); 

    //noinspection SimplifiableIfStatement 
    if (id == R.id.action_settings) { 
     return true; 
    } 

    return super.onOptionsItemSelected(item); 
    } 

    public void addItems(View view) { 
    mAdapter.addItemList(genItemList()); 
    } 

    private List<Item> genItemList() { 
    List<Item> list = new ArrayList<>(50); 
    for (int i = 0; i < 50; i++) { 
     Item item = new Item(); 
     item.text1 = "AAAAAAAAAAAAAAAAAAAAAAAA"; 
     item.text2 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; 
     list.add(item); 
    } 
    return list; 
    } 

    public void updateHeader(View view) { 
    mAdapter.updateHeader("Updated header"); 
    } 
} 

MyAdapter.java

public class MyAdapter extends RecyclerView.Adapter { 

    private static final String TAG = "MyAdapter"; 

    private static final int TYPE_HEADER = 0; 
    private static final int TYPE_ITEM = 1; 

    private LayoutInflater mInflater; 

    private List<Item> mItemList; 

    private Header mHeader; 

    public MyAdapter(Context context, List<Item> items) { 
    mInflater = LayoutInflater.from(context); 
    mItemList = items != null ? items : new ArrayList<Item>(); 
    mHeader = new Header(); 
    mHeader.text = "header"; 
    } 

    @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int type) { 
    switch (type) { 
    case TYPE_HEADER: 
     Log.d(TAG, "create header view holder"); 
     View headerView = mInflater.inflate(android.R.layout.simple_list_item_1, viewGroup, false); 
     return new HeaderViewHolder(headerView); 
    case TYPE_ITEM: 
     View itemView = mInflater.inflate(R.layout.layout_item, viewGroup, false); 
     return new MyViewHolder(itemView); 
    } 
    return null; 
    } 

    @Override public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) { 
    if (viewHolder instanceof HeaderViewHolder) { 
     Log.d(TAG, "bind header view holder"); 
     TextView textView = (TextView) viewHolder.itemView.findViewById(android.R.id.text1); 
     textView.setText(mHeader.text); 
     Log.d(TAG, "position: " + position + " holder: " + viewHolder + " text: " + mHeader.text); 
    } else if (viewHolder instanceof MyViewHolder) { 
     Item item = mItemList.get(position - 1) 
     ((MyViewHolder) viewHolder).setText1(item.text1); 
     ((MyViewHolder) viewHolder).setText2(item.text2); 
    } 
    } 

    @Override public int getItemCount() { 
    return mItemList == null ? 0 : mItemList.size() + 1; // plus header 
    } 

    @Override public int getItemViewType(int position) { 
    return position == 0 ? TYPE_HEADER : TYPE_ITEM; 
    } 

    public void addItemList(List<Item> list) { 
    if (list != null) { 
     mItemList.addAll(list); 
     notifyDataSetChanged(); 
    } 
    } 

    public void updateHeader(String text) { 
    mHeader.text = text; 
    notifyItemChanged(0); 
    // notifyDataSetChanged(); 
    } 

    static class HeaderViewHolder extends RecyclerView.ViewHolder { 

    public HeaderViewHolder(View itemView) { 
     super(itemView); 
    } 
    } 

    static class MyViewHolder extends RecyclerView.ViewHolder { 

    TextView mTextView1; 
    TextView mTextView2; 

    public MyViewHolder(View itemView) { 
     super(itemView); 

     mTextView1 = (TextView) itemView.findViewById(R.id.text1); 
     mTextView2 = (TextView) itemView.findViewById(R.id.text2); 
    } 

    public void setText1(String text) { 
     mTextView1.setText(text); 
    } 

    public void setText2(String text) { 
     mTextView2.setText(text); 
    } 
    } 
} 

Header.java

public class Header { 

    public String text; 
} 

Item.java

public class Item { 

    public String text1; 
    public String text2; 
} 

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
       xmlns:tools="http://schemas.android.com/tools" 
       android:layout_width="match_parent" 
       android:layout_height="match_parent" 
       android:orientation="vertical" 
       tools:context=".MainActivity"> 

    <android.support.v7.widget.RecyclerView 
     android:id="@+id/list" 
     android:layout_width="match_parent" 
     android:layout_height="0dp" 
     android:layout_weight="1" /> 

    <LinearLayout 
     style="?android:buttonBarStyle" 
     android:layout_width="match_parent" 
     android:layout_height="56dp" 
     android:orientation="horizontal"> 

    <Button 
     android:id="@+id/add" 
     style="?android:buttonBarButtonStyle" 
     android:layout_width="0dp" 
     android:layout_height="match_parent" 
     android:layout_weight="1" 
     android:onClick="addItems" 
     android:text="Add items" /> 

    <Button 
     android:id="@+id/update" 
     style="?android:buttonBarButtonStyle" 
     android:layout_width="0dp" 
     android:layout_height="match_parent" 
     android:layout_weight="1" 
     android:onClick="updateHeader" 
     android:text="update header" /> 
    </LinearLayout> 

</LinearLayout> 

layout_item.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
       android:layout_width="match_parent" 
       android:layout_height="match_parent" 
       android:orientation="vertical"> 

    <TextView 
     android:id="@+id/text1" 
     android:layout_width="match_parent" 
     android:layout_height="wrap_content" 
     android:ellipsize="marquee" 
     android:maxLines="1" 
     android:textAppearance="?android:textAppearanceLarge" 
     android:textColor="@android:color/black" /> 

    <TextView 
     android:id="@+id/text2" 
     android:layout_width="match_parent" 
     android:layout_height="wrap_content" 
     android:ellipsize="marquee" 
     android:maxLines="1" 
     android:textAppearance="?android:textAppearanceSmall" 
     android:textColor="@android:color/black" /> 

</LinearLayout> 

Và sau đó, đây là đầu ra logcat khi tôi nhấp vào 3 lần "cập nhật tiêu đề ":

06-05 19:57:50.368 20400-20400/com.imaygou.recyclerupdateitemdemo D/MyAdapter﹕ create header view holder 
06-05 19:57:50.369 20400-20400/com.imaygou.recyclerupdateitemdemo D/MyAdapter﹕ bind header view holder 
06-05 19:57:50.370 20400-20400/com.imaygou.recyclerupdateitemdemo D/MyAdapter﹕ position: 0 holder: ViewHolder{3f742717 position=0 id=-1, oldPos=-1, pLpos:-1 no parent} text: header 
06-05 19:57:54.030 20400-20400/com.imaygou.recyclerupdateitemdemo D/MyAdapter﹕ create header view holder 
06-05 19:57:54.031 20400-20400/com.imaygou.recyclerupdateitemdemo D/MyAdapter﹕ bind header view holder 
06-05 19:57:54.031 20400-20400/com.imaygou.recyclerupdateitemdemo D/MyAdapter﹕ position: 0 holder: ViewHolder{3ac01621 position=0 id=-1, oldPos=-1, pLpos:-1 no parent} text: Updated header 
06-05 19:57:56.938 20400-20400/com.imaygou.recyclerupdateitemdemo D/MyAdapter﹕ bind header view holder 
06-05 19:57:56.938 20400-20400/com.imaygou.recyclerupdateitemdemo D/MyAdapter﹕ position: 0 holder: ViewHolder{3f742717 position=0 id=-1, oldPos=-1, pLpos:-1 no parent} text: Updated header 
06-05 19:57:59.613 20400-20400/com.imaygou.recyclerupdateitemdemo D/MyAdapter﹕ bind header view holder 
06-05 19:57:59.613 20400-20400/com.imaygou.recyclerupdateitemdemo D/MyAdapter﹕ position: 0 holder: ViewHolder{3ac01621 position=0 id=-1, oldPos=-1, pLpos:-1 no parent} text: Updated header 

Nếu tôi sử dụng notifyDataSetChanged() thay vì notifyItemChanged(0), mọi thứ đều hoạt động tốt. Không còn một ViewHolder nữa. Nhưng tại sao?

Tại sao nó sẽ tạo Chế độ xem mới và sử dụng cả hai chế độ xem này?

Thực tiễn tốt nhất về cách sử dụng notifyItemChanged(int) là gì?

+0

Xin chào, tôi đang gặp sự cố tương tự. Bạn đã tìm thấy một sửa chữa/workaround để gọi notifyItemChanged() mà không cần tạo một ViewHolder mới? – robocab

Trả lời

40

RecyclerView sử dụng cả ViewHolder cho hoạt ảnh mượt mà từ trạng thái cũ sang trạng thái mới. Đây là hành vi mặc định của RecyclerView.ItemAnimator.

Bạn có thể vô hiệu hóa hình ảnh động bằng cách đi qua một phim hoạt hình mục trống để RecyclerView:

listView.setItemAnimator(null); 
4

Dưới đây là một vài vấn đề với thực hiện của bạn:

  • getItemCount hy vọng số lượng của tất cả các mục trong recyclerview bao gồm tiêu đề, do đó bạn nên quay lại mItemList.size() + 1

  • lĩnh vực vị trí trong onBindViewHolder() đề cập đến vị trí của một phần tử trong toàn bộ recyclerview bao gồm tiêu đề. như vậy để ràng buộc một mục phi tiêu đề, bạn sẽ làm điều gì đó như - điều này sẽ không thất bại vì getItemViewType trả về một số lớn hơn 0 cho TYPE_ITEMs

Bằng cách đó, notifyItemChanged nên cư xử như mong đợi

+0

Bạn nói đúng về vị trí tiêu đề. Nhưng vẫn còn, cùng một đầu ra logcat, hai HeaderViewHolders. –

0

Nhiều giải pháp sạch hơn (không phải là lỗi trong phim hoạt hình, nhưng đây là một tính năng của người quản lý bố trí):

mRecyclerView.setLayoutManager(new GridLayoutManager(this, 5, LinearLayoutManager.VERTICAL, false){ 
     @Override 
     public boolean supportsPredictiveItemAnimations() { 
      return false;//super.supportsPredictiveItemAnimations(); 
     } 
    }); 
4
((SimpleItemAnimator) myRecyclerView.getItemAnimator()).setSupportsChangeAnimations(false); 
Các vấn đề liên quan