Встроиваем RecyclerView в CardView

от автора

Прочитав пост на хабре о новых виджетах RecyclerView и CardView. Новые виджеты в Android L решил попробовать использовать
В сети много примеров где CardView встраивается в RecyclerView. Интересовало наоборот встроить RecyclerView в CardView. Чтобы еще эта конструкция была фрагментом

Скачал пример из статьи. Сразу возникла проблема при удалении нескольких элементов. Посмотрев код поставил проверку

    private void delete(Record record) {         int position = records.indexOf(record);         Log.i(">" , "position=" + position);         if( position != -1 ) {             records.remove(position);             notifyItemRemoved(position);         }     } 

Это было только начало… После добавления фрагмента, вылезла другая проблема. CardView не может корректно «обернуть» список из RecyclerView по размеру по высоте. wrap_content не помогает. Оказалось, многие уже сталкивались и есть решения: Nested Recycler view height doesn’t wrap its content

Сначала посмотрев A First Glance at Android’s RecyclerView думал использовать методы layoutManager.getDecoratedMeasuredHeight()… но это не помогло. Размеры возращались 0
Пришлось переписать onMeasure в LinearLayoutManager. Взято со stackoverflow:

MyLinearLayoutManager

    public class MyLinearLayoutManager extends LinearLayoutManager {          public MyLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {             super(context, orientation, reverseLayout);         }          public MyLinearLayoutManager(Context context) {             super(context);         }          private int[] mMeasuredDimension = new int[2];         @Override         public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,                               int widthSpec, int heightSpec) {             final int widthMode = View.MeasureSpec.getMode(widthSpec);             final int heightMode = View.MeasureSpec.getMode(heightSpec);             final int widthSize = View.MeasureSpec.getSize(widthSpec);             final int heightSize = View.MeasureSpec.getSize(heightSpec);             int width = 0;             int height = 0;             for (int i = 0; i < getItemCount(); i++) {                  if (getOrientation() == HORIZONTAL) {                      measureScrapChild(recycler, i,                             View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),                             heightSpec,                             mMeasuredDimension);                      width = width + mMeasuredDimension[0];                     if (i == 0) {                         height = mMeasuredDimension[1];                     }                 } else {                     measureScrapChild(recycler, i,                             widthSpec,                             View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),                             mMeasuredDimension);                     height = height + mMeasuredDimension[1];                     if (i == 0) {                         width = mMeasuredDimension[0];                     }                 }             }             switch (widthMode) {                 case View.MeasureSpec.EXACTLY:                     width = widthSize;                 case View.MeasureSpec.AT_MOST:                 case View.MeasureSpec.UNSPECIFIED:             }              switch (heightMode) {                 case View.MeasureSpec.EXACTLY:                     height = heightSize;                 case View.MeasureSpec.AT_MOST:                 case View.MeasureSpec.UNSPECIFIED:             }              setMeasuredDimension(width, height);         }          private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,                                        int heightSpec, int[] measuredDimension) {             View view = recycler.getViewForPosition(position);             recycler.bindViewToPosition(view, position);             if (view != null) {                 RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();                 int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,                         getPaddingLeft() + getPaddingRight(), p.width);                 int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,                         getPaddingTop() + getPaddingBottom(), p.height);                 view.measure(childWidthSpec, childHeightSpec);                 measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;                 measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;                 recycler.recycleView(view);             }         }     } 

Заработало. Удаление стало возможно только с конца. Удалении из середины или с начала списка приводило к исключению:

java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 3(offset:-1).state:5 

Странно. Погуглив еще немного, наткнулся на аналогичные проблемы у народа IndexOutOfBoundsException Invalid item position XX(XX). Item count:XX #134. Посмотрев весь топик прочитал:

It is indeed a RecyclerView bug and is yet to be fixed.

For more information check:
code.google.com/p/android/issues/detail?id=77846
code.google.com/p/android/issues/detail?id=77232

А строчкой выше как раз было решение:

Now I am doing some dirty workaround like
if (index == 0) {notifydatasetchange();} else {notifyItemRemoved(index)}

Точнее посмотрев как удаляются элементы я понял, что надо заменить notifyItemRemoved(index) на notifydatasetchange(). С добавлением аналогично. Решение не оптимальное, но рабочее и в текущей реализации виджета наверное единственное

Такой PopUp виджет может быть полезен для отображения сообщений программы. Вместо окон прогресса. По таймеру можно удалять верхнее сообщение через определенное время или при клике на него сразу.

Fragment

public class PlusOneFragment extends Fragment { ...     @Override     public View onCreateView(LayoutInflater inflater, ViewGroup container,                              Bundle savedInstanceState) {         // Inflate the layout for this fragment         View view = inflater.inflate(R.layout.fragment_plus_one, container, false);          List<Record> records = new ArrayList<Record>();         populateRecords(records);          recyclerView = (RecyclerView)view.findViewById(R.id.recyclerView);         //recyclerView.setHasFixedSize(false);         adapter = new RecyclerViewAdapter(records);         LinearLayoutManager layoutManager = new MyLinearLayoutManager(getActivity());         RecyclerView.ItemAnimator itemAnimator = new DefaultItemAnimator();          recyclerView.setAdapter(adapter);         recyclerView.setLayoutManager(layoutManager);         recyclerView.setItemAnimator(itemAnimator);          cardView = (CardView)view.findViewById(R.id.cardView);          return view;     } 

Layout

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"     xmlns:tools="http://schemas.android.com/tools"     xmlns:card_view="http://schemas.android.com/apk/res-auto"     android:layout_width="match_parent"     android:layout_height="wrap_content"     tools:context=".PlusOneFragment">      <android.support.v7.widget.CardView         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:id="@+id/cardView"         card_view:cardCornerRadius="6dp"         card_view:cardBackgroundColor="#84ffff">              <android.support.v7.widget.RecyclerView                 android:layout_width="400dp"                 android:layout_height="wrap_content"                 android:id="@+id/recyclerView" />     </android.support.v7.widget.CardView> </FrameLayout> 

Activity

        Fragment newFragment = PlusOneFragment.newInstance();         getSupportFragmentManager()                 .beginTransaction()                 .replace(R.id.container, newFragment)                 .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)                 .addToBackStack(null)                 .commit(); 

RecyclerViewAdapter

package com.renal128.demo.recyclerviewdemo.adapter;  import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.ImageView; import android.widget.TextView;  import com.renal128.demo.recyclerviewdemo.R; import com.renal128.demo.recyclerviewdemo.model.Record;  import java.util.List;  public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> {      private List<Record> records;      public RecyclerViewAdapter(List<Record> records) {         this.records = records;     }      public List<Record> getRecords() {         return records;     }      @Override     public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {         View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.recyclerview_item, viewGroup, false);         return new ViewHolder(v);     }      @Override     public void onBindViewHolder(ViewHolder viewHolder, int i) {         Record record = records.get(i);         int iconResourceId = 0;         switch (record.getType()) {             case GREEN:                 iconResourceId = R.drawable.green_circle;                 break;             case RED:                 iconResourceId = R.drawable.red_circle;                 break;             case YELLOW:                 iconResourceId = R.drawable.yellow_circle;                 break;         }         viewHolder.icon.setImageResource(iconResourceId);         viewHolder.name.setText(record.getName());         viewHolder.deleteButtonListener.setRecord(record);         viewHolder.copyButtonListener.setRecord(record);     }      @Override     public int getItemCount() {         return records.size();     }      private void copy(Record record) {         int position = records.indexOf(record);         Record copy = record.copy();         records.add(position + 1, copy);         //notifyItemInserted(position + 1);         notifyDataSetChanged();     }      private void delete(Record record) {         int position = records.indexOf(record);         Log.i(">" , "position=" + position);         if( position != -1 ) {             records.remove(position);             //notifyItemRemoved(position);             notifyDataSetChanged();         }     }      class ViewHolder extends RecyclerView.ViewHolder {          private TextView name;         private ImageView icon;         private Button deleteButton;         private Button copyButton;         private DeleteButtonListener deleteButtonListener;         private CopyButtonListener copyButtonListener;          public ViewHolder(View itemView) {             super(itemView);             name = (TextView) itemView.findViewById(R.id.recyclerViewItemName);             icon = (ImageView) itemView.findViewById(R.id.recyclerViewItemIcon);             deleteButton = (Button) itemView.findViewById(R.id.recyclerViewItemDeleteButton);             copyButton = (Button) itemView.findViewById(R.id.recyclerViewItemCopyButton);             deleteButtonListener = new DeleteButtonListener();             copyButtonListener = new CopyButtonListener();             deleteButton.setOnClickListener(deleteButtonListener);             copyButton.setOnClickListener(copyButtonListener);         }     }      private class CopyButtonListener implements View.OnClickListener {         private Record record;          @Override         public void onClick(View v) {             copy(record);         }          public void setRecord(Record record) {             this.record = record;         }     }      private class DeleteButtonListener implements View.OnClickListener {         private Record record;          @Override         public void onClick(View v) {             delete(record);         }          public void setRecord(Record record) {             this.record = record;         }     } } 

Изначальный код был взят здесь: github.com/renal128/RecyclerViewDemo

PlusOneFragment — Это фрагмент-шаблон, который любезно сгенерировала мне Android Studio
@App-z.net

ссылка на оригинал статьи http://habrahabr.ru/post/255397/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *