2015-06-05 12 views
21

Ostatnio używam RecyclerView i dodam niestandardowy widok nagłówka (inny typ widoku elementów) i próbuję zaktualizować go po zmianie danych. Dzieje się coś dziwnego. Adapter tworzy nowy obiekt HeaderViewHolder i używa zarówno nowego nagłówka HeaderViewHolder, jak i starego.Dlaczego RecyclerView.notifyItemChanged() utworzy nowy ViewHolder i użyje zarówno starego ViewHolder, jak i nowego?

Oto próbka.

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> 

A potem, tutaj jest wyjście logcat kiedy kliknąłem 3 razy „update nagłówek ":

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 

Jeśli używam notifyDataSetChanged() zamiast notifyItemChanged(0), wszystko działa poprawnie. Nikt nie ma więcej ViewHolder. Ale dlaczego?

Dlaczego utworzy on nowy ViewHolder i użyje obu z nich?

Jaka jest najlepsza praktyka dotycząca korzystania z notifyItemChanged(int)?

+0

Witam, mam ten sam problem. Czy znalazłeś poprawkę/obejście problemu do wywołania notifyItemChanged() bez tworzenia nowego obiektu ViewHolder? – robocab

Odpowiedz

40

RecyclerView używa obu ViewHolder do płynnej animacji od starego stanu do nowego. Jest to domyślne zachowanie RecyclerView.ItemAnimator.

Możesz wyłączyć animację przekazując pustą pozycję animatora do RecyclerView:

listView.setItemAnimator(null); 
4

Oto kilka problemów z realizacji:

  • getItemCount spodziewa się, że liczba wszystkich elementów w recyclerview tym nagłówku należy więc powrócić mItemList.size() + 1

  • polu Pozycja w onBindViewHolder() odnosi się do pozycji elementu w całym widoku recyklingu, w tym nagłówku. tak aby związać wyrobom nagłówka można zrobić coś takiego - nie zawiedzie, ponieważ getItemViewType zwraca liczbę większą niż 0 TYPE_ITEMs

Dzięki temu notifyItemChanged powinien zachowywać się zgodnie z oczekiwaniami

+0

Masz rację co do pozycji nagłówka. Ale nadal, ten sam wynik logcat, dwa HeaderViewHolders. –

0

Więcej roztwór czyszczący (nie jest to błąd w animatora, ale jest to cecha managera Układ):

mRecyclerView.setLayoutManager(new GridLayoutManager(this, 5, LinearLayoutManager.VERTICAL, false){ 
     @Override 
     public boolean supportsPredictiveItemAnimations() { 
      return false;//super.supportsPredictiveItemAnimations(); 
     } 
    }); 
4
((SimpleItemAnimator) myRecyclerView.getItemAnimator()).setSupportsChangeAnimations(false); 
Powiązane problemy