Прочитав пост на хабре о новых виджетах 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:
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 виджет может быть полезен для отображения сообщений программы. Вместо окон прогресса. По таймеру можно удалять верхнее сообщение через определенное время или при клике на него сразу.
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; }
<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>
Fragment newFragment = PlusOneFragment.newInstance(); getSupportFragmentManager() .beginTransaction() .replace(R.id.container, newFragment) .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) .addToBackStack(null) .commit();
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/
Добавить комментарий