Pull to refresh

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

Reading time 14 min
Views 18K


Прочитав пост на хабре о новых виджетах «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(). С добавлением аналогично. Решение не оптимальное, но рабочее и в текущей реализации виджета, наверное, единственное.

Такое решение напрочь убило анимацию. Тут бы и закончить исследования…

В результате выяснилось место падения в переопределенном onMeasure()
IndexOutOfBoundsException = java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 0(offset:-1).state:7

Дальнейшее исследование кода RecyclerView на предмет как-то перехватить ситуацию или запросить заранее offset не увенчались успехом. Сделал жесткий хак! Не судите строго)
View view = null;
try {
      view = recycler.getViewForPosition(position);
     }catch (IndexOutOfBoundsException ex){
        Log.i(">", "IndexOutOfBoundsException = " + ex + "position : " + position);
}


Теперь анимация появилась, но после первого добавления (инициализации) список не появлялся. Хотя элементы добавлялись и все появлялось, после следующей операции. Сделал еще один хак в метод добавления элементов. Надеюсь понятно что он делает
if ( adapter.getItemCount() == 1 ) {
     adapter.notifyDataSetChanged();
}

В статье Building a RecyclerView LayoutManager – Part 1 есть решение казалось бы всей проблемы, но у меня не заработало. Может версия support library надо было обновить или SDK. Не знаю.
This is actually the only required override to get your LayoutManager to compile. The implementation is pretty straightforward, just return a new instance of the RecyclerView.LayoutParams that you want applied by default to all the child views returned from the Recycler. These parameters will be applied to each child before they return from getViewForPosition()

@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
    return new RecyclerView.LayoutParams(
        RecyclerView.LayoutParams.WRAP_CONTENT,
        RecyclerView.LayoutParams.WRAP_CONTENT);
} 


В результате имеем «хаченый» подход, который стоит ли применять…

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

Получился округленный с тенью и анимацией список-фрагмент. Корректно обрабатывается поворот экрана. Легко встраивается в приложение. Единственная мелочь. После переупорядочивания стека фрагментов, когда пользователь поработал с приложением, окно не всегда появлялось. Возможно какой-то callback не в UI Thread… Решение, обращаться к фрагменту через Handler.

        new Handler(Looper.getMainLooper()).post(new Runnable() {
            @Override
            public void run() {
               mOverlayMessageFragment.addMessage(text);
            }
        });


Communicating with the UI Thread
Every app has its own special thread that runs UI objects such as View objects; this thread is called the UI thread. Only objects running on the UI thread have access to other objects on that thread. Because tasks that you run on a thread from a thread pool aren't running on your UI thread, they don't have access to UI objects. To move data from a background thread to the UI thread, use a Handler that's running on the UI thread.
developer.android.com/training/multiple-threads/communicate-ui.html


Communicating with Other Fragments
Often you will want one Fragment to communicate with another, for example to change the content based on a user event. All Fragment-to-Fragment communication is done through the associated Activity. Two Fragments should never communicate directly.
developer.android.com/training/basics/fragments/communicating.html


И еще одна полезная фишка для фрагмента:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // keep the fragment and all its data across screen rotation
    setRetainInstance(true);
}

Изменения кода приводятся ниже:

PopUpFragment.java
package net.appz.iconfounder.popupwidget.fragment;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v4.app.Fragment;
import android.support.v7.widget.CardView;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import net.appz.iconfounder.R;
import net.appz.iconfounder.popupwidget.adapter.RecyclerViewAdapter;
import net.appz.iconfounder.popupwidget.model.Record;

import java.util.ArrayList;
import java.util.List;


public class PopUpFragment extends Fragment{

    private static final String ARG_TIMER_INTERVAL = "timer_interval";
    private OnFragmentInteractionListener mListener;
    private HandlerPopUpMessages messageHandler;

    private int TIMER_INTERVAL_DEFAULT = 2000;

    private int timer_interval;


    private RecyclerViewAdapter adapter;
    private CardView cardView;
    private RecyclerView recyclerView;
    private List<Record> records = new ArrayList<Record>();

    /**
     * Use this factory method to create a new instance of
     * this fragment using the provided parameters.
     *
     * @param timer_interval .
     * @return A new instance of fragment PopUpFragment.
     */
    public static PopUpFragment newInstance(int timer_interval) {
        PopUpFragment fragment = new PopUpFragment();
        Bundle args = new Bundle();
        args.putInt(ARG_TIMER_INTERVAL, timer_interval);
        fragment.setArguments(args);
        return fragment;
    }

    public static PopUpFragment newInstance() {
        PopUpFragment fragment = new PopUpFragment();
        return fragment;
    }

    public PopUpFragment() {
        // Required empty public constructor
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            timer_interval = getArguments().getInt(ARG_TIMER_INTERVAL);
        } else {
            timer_interval = TIMER_INTERVAL_DEFAULT;
        }

        // keep the fragment and all its data across screen rotation
        //setRetainInstance(true);

        messageHandler = new HandlerPopUpMessages(this);

        if (savedInstanceState != null) {
            records = savedInstanceState.getParcelableArrayList(PopUpFragment.class.getSimpleName());
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
         outState.putParcelableArrayList(
                    PopUpFragment.class.getSimpleName(),
                    (java.util.ArrayList<? extends android.os.Parcelable>) records);
        super.onSaveInstanceState(outState);
    }

    Handler timerHandler = new Handler();
    Runnable timerRunnable = new Runnable() {
        @Override
        public void run() {
            if (adapter.getItemCount() > 0) {
                Record record = adapter.getRecords().get(0);
                long ts = record.getTimestamp();
                if (ts < System.currentTimeMillis() - timer_interval){
                    if (adapter.getItemCount() > 1){
                        record = adapter.getRecords().get(1);
                        record.setTimestamp(System.currentTimeMillis());
                    }
                    removeMessagePopUp();
                }
            }
            timerHandler.postDelayed(this, timer_interval);
        }
    };

    @Override
    public void onPause() {
        super.onPause();
        timerHandler.removeCallbacks(timerRunnable);
    }

    @Override
    public void onResume() {
        super.onResume();
        timerHandler.postDelayed(timerRunnable, timer_interval);
        if ( adapter.getItemCount() == 0 ) {
            cardView.setVisibility(View.GONE);
            mListener.onHidePopUpFrugment();
        }
    }

    @Override
    public void onStart() {
        super.onStart();
        mListener.onPopUpFragmentStart();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_popup, container, false);
        recyclerView = (RecyclerView)view.findViewById(R.id.recyclerView);
        // recyclerView.setHasFixedSize(true);
        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;
    }


    public void addMessage0ToPopUp(int type, String text){
        Bundle msgBundle = new Bundle();
        msgBundle.putInt(HandlerPopUpMessages.ICON_ARG, type);
        msgBundle.putString(HandlerPopUpMessages.TEXT_ARG, text);
        Message msg = new Message();
        msg.what = HandlerPopUpMessages.ADD_MESSAGE;
        msg.setData(msgBundle);
        messageHandler.sendMessage(msg);
    }

    public void removeMessagePopUp() {
        Bundle msgBundle = new Bundle();
        Message msg = new Message();
        msg.what = HandlerPopUpMessages.REMOVE_MESSAGE_0;
        msg.setData(msgBundle);
        messageHandler.sendMessage(msg);
    }


    private void addMessageInternal(int type, String text) {
        Record record = new Record();
        record.setName(text);
        record.setType(Record.Type.values()[type]);
        record.setTimestamp(System.currentTimeMillis());
        adapter.getRecords().add(record);
        adapter.notifyItemInserted(adapter.getItemCount()-1);
        //adapter.notifyDataSetChanged();

        // Bellow there is hack. First show RecyclerView
        if ( adapter.getItemCount() == 1 ) {
            adapter.notifyDataSetChanged();
        }

        if ( adapter.getItemCount() > 0 ) {
            cardView.setVisibility(View.VISIBLE);
            mListener.onShowPopUpFrugment();
        }
    }


    private void removeMessage0Internal(){
        if ( adapter.getItemCount() > 0 ) {
            adapter.getRecords().remove(0);
            adapter.notifyItemRemoved(0);
            //adapter.notifyDataSetChanged();
        }
        if ( adapter.getItemCount() == 0 ) {
            cardView.setVisibility(View.GONE);
            mListener.onHidePopUpFrugment();
        }
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            mListener = (OnFragmentInteractionListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString()
                    + " must implement OnFragmentInteractionListener");
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mListener = null;
        messageHandler.removeCallbacksAndMessages(null);
    }


    /**
     * This interface must be implemented by activities that contain this
     * fragment to allow an interaction in this fragment to be communicated
     * to the activity and potentially other fragments contained in that
     * activity.
     * <p/>
     * See the Android Training lesson <a href=
     * "http://developer.android.com/training/basics/fragments/communicating.html"
     * >Communicating with Other Fragments</a> for more information.
     */
    public interface OnFragmentInteractionListener {
        void onPopUpFragmentStart();
        void onHidePopUpFrugment();
        void onShowPopUpFrugment();
    }


    public class MyLinearLayoutManager extends LinearLayoutManager {

        public MyLinearLayoutManager(Context context)    {
            super(context);
        }

        // Not worked
        @Override
        public RecyclerView.LayoutParams generateDefaultLayoutParams() {
            return new RecyclerView.LayoutParams(
                    RecyclerView.LayoutParams.WRAP_CONTENT,
                    RecyclerView.LayoutParams.WRAP_CONTENT);
        }

        // Not worked
        @Override
        public boolean canScrollVertically() {
            //We do allow scrolling
            return true;
        }

        private int[] mMeasuredDimension = new int[2];

        @Override
        public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
                              int widthSpec, int heightSpec) {

            Log.i(">", "state " + state.toString());

            //if ( state.isPreLayout() ) {
            //    super.onMeasure(recycler, state, widthSpec, heightSpec);
            //} else
            {

                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 = null;
            // Bellow there is strong hack!
            try {
                view = recycler.getViewForPosition(position);
            }catch (IndexOutOfBoundsException ex){
                Log.i(">", "IndexOutOfBoundsException = " + ex + "position : " + position);
            }


            if (view != null) {

                // For adding Item Decor Insets to view
                //super.measureChildWithMargins(view, 0, 0);

                //recycler.bindViewToPosition(view, position);

                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);
            }
        }
    }

    private class HandlerPopUpMessages <T> extends Handler {

        public static final int ADD_MESSAGE = 100;
        public static final int REMOVE_MESSAGE_0 = 101;
        public static final String TEXT_ARG = "text";
        public static final String ICON_ARG = "icon";

        private final T fragment;

        public HandlerPopUpMessages(T fragment ){
            this.fragment = fragment;
        }

        @Override
        public void handleMessage(Message message){
            if (this.fragment != null){

                Bundle b = message.getData();

                switch (message.what){
                    case ADD_MESSAGE:
                        if(b == null)
                            new IllegalArgumentException("Message should be have params !");

                        String text = b.getString(TEXT_ARG);
                        int type = b.getInt(ICON_ARG);
                        ((PopUpFragment)fragment).addMessageInternal(type, text);
                        break;
                    case REMOVE_MESSAGE_0:
                        ((PopUpFragment)fragment).removeMessage0Internal();
                        break;
                }
            }
        }
    }
}



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=".PopUpFragment">

    <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>



MainActivity.java
...
            mPopupWidget = (PopUpFragment)
                    getSupportFragmentManager().findFragmentById(R.id.popup);
            if (DEBUG) Log.d(TAG, "onCreate() : mPopupWidget = " + mPopupWidget);
            if( mPopupWidget == null ){
                getSupportFragmentManager().beginTransaction()
                        .replace(R.id.popup,
                                PopUpFragment.newInstance(),
                                PopUpFragment.class.getSimpleName())
                        .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

Реализация с Handler's и Timer's: github.com/app-z/PopUpWidget

Посмотреть как это работает можно из Google Play: play.google.com/store/apps/details?id=net.appz.iconfounder
Tags:
Hubs:
+12
Comments 4
Comments Comments 4

Articles