androidlistviewandroid-arrayadaptersectionheader

How to make Section Header in ListView, and Swipe to delete a Item with Undo option


Required:

1.I want to Make a Sectioned Header Listview in Android.

2.That should be Swipe to delete a item, with Undo option like Gmail App.but Gmail App not contains Section Header.

3.My Application should be have Section Header.

i tried below mentioned link for Swipe to delete along with Undo button. its worked perfectly.

Problem:

EDIT :1(ListView)

1.i found the code for Swipe to Delete a item with Undo in Listview Link-Swipe to delete a listview item and Section Header Using Listview link-Section header in listview.

2.both are having two different Base Adapters Iam getting some Error,Please help me to merge that adapters or suggest me any new way to add section Header in Swipe to Delete a Listview items.

CODE: Class for Swipe to Delete and Section Header in Listview

ListViewActivity.class

package com.data.swipetodeletesimplelistview;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.app.Activity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;

import static android.widget.Toast.LENGTH_SHORT;
public class ListViewActivity extends AppCompatActivity {

    private static final int TIME_TO_AUTOMATICALLY_DISMISS_ITEM = 3000;

/*For Section header*/
    ListView mListView;
    ArrayList<String> mArrayList = new ArrayList<String>();
    SectionedAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_list_view);
        init((ListView) findViewById(R.id.list_view));


/*For Section Header Starts Here*/
        mListView = (ListView) findViewById(R.id.list_view);

        adapter = new SectionedAdapter() {

            @Override
            protected View getHeaderView(String caption, int index, View convertView, ViewGroup parent) {
                convertView = getLayoutInflater().inflate(R.layout.section_header, null);
                TextView header = (TextView) convertView.findViewById(R.id.header);
                header.setText(caption);
                return convertView;
            }
        };

        for (int i = 0; i < 5; i++)
        {
            mArrayList.add("Item " + i);
            MyAdapter myAdapter = new MyAdapter();
            adapter.addSection("Header " + i, myAdapter);
        }
        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {

            public void onItemClick(AdapterView<?> arg0, View arg1, int position, long arg3) {
                Toast.makeText(getApplicationContext(), arg0.getAdapter().getItem(position).toString(), Toast.LENGTH_LONG).show();
            }
        });
        mListView.setAdapter(adapter);

/*For Section Header Ends Here*/
    }
/*FOr Swipe to Delete a item Starts Here*/   
    private void init(ListView listView)
    {
        final MyBaseAdapter adapter = new MyBaseAdapter();
        listView.setAdapter(adapter);
        final SwipeToDismissTouchListener<ListViewAdapter> touchListener =
                new SwipeToDismissTouchListener<>(
                        new ListViewAdapter(listView),
                        new SwipeToDismissTouchListener.DismissCallbacks<ListViewAdapter>() {
                            @Override
                            public boolean canDismiss(int position) {
                                return true;
                            }

                            @Override
                            public void onPendingDismiss(ListViewAdapter recyclerView, int position) {

                            }

                            @Override
                            public void onDismiss(ListViewAdapter view, int position) {
                                adapter.remove(position);
                            }
                        });

        touchListener.setDismissDelay(TIME_TO_AUTOMATICALLY_DISMISS_ITEM);
        listView.setOnTouchListener(touchListener);
        // Setting this scroll listener is required to ensure that during ListView scrolling,
        // we don't look for swipes.
        listView.setOnScrollListener((AbsListView.OnScrollListener) touchListener.makeScrollListener());
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                if (touchListener.existPendingDismisses()) {
                    touchListener.undoPendingDismiss();
                } else {
                    Toast.makeText(ListViewActivity.this, "Position " + position, LENGTH_SHORT).show();
                }
            }
        });
    }

    /*FOr Swipe to Delete*/
    static class MyBaseAdapter extends BaseAdapter
    {

        private static final int SIZE = 100;

        private final List<String> mDataSet = new ArrayList<>();

        MyBaseAdapter() {
            for (int i = 0; i < SIZE; i++)
                mDataSet.add(i, "This is row number " + i);
        }

        @Override
        public int getCount() {
            return mDataSet.size();
        }

        @Override
        public String getItem(int position) {
            return mDataSet.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        public void remove(int position) {
            mDataSet.remove(position);
            notifyDataSetChanged();
        }

        static class ViewHolder {
            TextView dataTextView;
            ViewHolder(View view) {
                dataTextView = (TextView) view.findViewById(R.id.txt_data);
                view.setTag(this);
            }
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {

            ViewHolder viewHolder = convertView == null
                    ? new ViewHolder(convertView = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.list_item, parent, false)) : (ViewHolder) convertView.getTag();

            viewHolder.dataTextView.setText(mDataSet.get(position));
            return convertView;
        }
    }

/*FOr Swipe to Delete a item Ends Here*/

    /*For adding Section header*/
    class MyAdapter extends BaseAdapter
{

        public int getCount()
        {
            return mArrayList.size();
        }

        public Object getItem(int position)
        {
            return mArrayList.get(position);
        }

        public long getItemId(int position) {
            return position;
        }

        public View getView(int position, View convertView, ViewGroup parent) {
            convertView = (TextView) getLayoutInflater().inflate(R.layout.section_item, null);
            TextView item = (TextView) convertView.findViewById(R.id.item);
            item.setText(mArrayList.get(position));
            return convertView;
        }
    }
}

Solution

  • You basically wanted to have two type of rows in the list.

    1. Item which will have swipe to delete functionality.
    2. Header which will not have swipe functionality.

    Create a recyclerView or listView by inflating two different type of layout. Check How to create RecyclerView with multiple view type?

    Edit: Swipe to delete, Undo and Sectioned list Adapter issue

    As mentioned by @MadScientist, there should be only one Adapter for the list or recyclerView. Follow following steps to achieve your requirements:

    1. Create a Recycler View to display sectioned header and items.
    2. Implement Swipe to delte functionality to your existing list.
    3. Implement undo functionality to your existing list.

    See below sample code of RecyclerAdapter for sectioned header and undo functionality:

    public class SectionedRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
        private static final int TYPE_HEADER = 0;
        private static final int TYPE_ITEM = 1;
        private List<SectionedItem> sectionedItemList;
        private List<SectionedItem> itemsPendingRemoval;
        private Context context;
    
        private static final int PENDING_REMOVAL_TIMEOUT = 3000;
        private Handler handler = new Handler();
        private HashMap<SectionedItem, Runnable> pendingRunnables = new HashMap<>();
    
        public SectionedRecyclerAdapter(List<SectionedItem> itemList, Context context) {
            this.sectionedItemList = itemList;
            this.context = context;
            itemsPendingRemoval = new ArrayList<>();
        }
    
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            if (viewType == TYPE_ITEM) {
                View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_item, parent, false);
                return new SectionedItemViewHolder(view);
            } else if (viewType == TYPE_HEADER) {
                View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_header, parent, false);
                return new SectionedHeaderViewHolder(view);
            }
            throw new RuntimeException("there is no type that matches the type " + viewType + " + make sure your using types correctly");
        }
    
        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, final int position) {
            if (viewHolder instanceof SectionedItemViewHolder) {
                final SectionedItem data = sectionedItemList.get(position);
    
                if (itemsPendingRemoval.contains(data)) {
                    ((SectionedItemViewHolder) viewHolder).itemLayout.setVisibility(View.GONE);
                    ((SectionedItemViewHolder) viewHolder).undoLayout.setVisibility(View.VISIBLE);
                    ((SectionedItemViewHolder) viewHolder).undobutton.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View view) {
                            undoOpt(data);
                        }
                    });
                } else {
                    ((SectionedItemViewHolder) viewHolder).itemLayout.setVisibility(View.VISIBLE);
                    ((SectionedItemViewHolder) viewHolder).undoLayout.setVisibility(View.GONE);
                    ((SectionedItemViewHolder) viewHolder).itemName.setText(sectionedItemList.get(position).itemName);
                }
            }
    
            if (viewHolder instanceof SectionedHeaderViewHolder) {
                ((SectionedHeaderViewHolder) viewHolder).headerTitle.setText(sectionedItemList.get(position).itemName);
            }
        }
    
        @Override
        public int getItemCount() {
            return sectionedItemList.size();
        }
    
        @Override
        public int getItemViewType(int position) {
            if (isPositionHeader(position)) {
                return TYPE_HEADER;
            }
            return TYPE_ITEM;
        }
    
        private boolean isPositionHeader(int position) {
    
            return sectionedItemList.get(position).isHeader;
        }
    
        private void undoOpt(SectionedItem customer) {
            Runnable pendingRemovalRunnable = pendingRunnables.get(customer);
            pendingRunnables.remove(customer);
            if (pendingRemovalRunnable != null)
                handler.removeCallbacks(pendingRemovalRunnable);
            itemsPendingRemoval.remove(customer);
            // this will rebind the row in "normal" state
            notifyItemChanged(sectionedItemList.indexOf(customer));
        }
    
        public void pendingRemoval(int position) {
    
            final SectionedItem data = sectionedItemList.get(position);
            if (!itemsPendingRemoval.contains(data) && !data.isHeader) {
                itemsPendingRemoval.add(data);
                // this will redraw row in "undo" state
                notifyItemChanged(position);
                // let's create, store and post a runnable to remove the data
                Runnable pendingRemovalRunnable = new Runnable() {
                    @Override
                    public void run() {
                        remove(sectionedItemList.indexOf(data));
                    }
                };
                handler.postDelayed(pendingRemovalRunnable, PENDING_REMOVAL_TIMEOUT);
                pendingRunnables.put(data, pendingRemovalRunnable);
            }
        }
    
        public void remove(int position) {
            SectionedItem data = sectionedItemList.get(position);
            if (itemsPendingRemoval.contains(data)) {
                itemsPendingRemoval.remove(data);
            }
            if (sectionedItemList.contains(data)) {
                sectionedItemList.remove(position);
                notifyItemRemoved(position);
            }
        }
    
        private void removeItemPermanently(int position) {
            sectionedItemList.get(position).isSoftDeleted = false;
            sectionedItemList.remove(position);
            notifyItemRemoved(position);
        }
    
        public boolean isPendingRemoval(int position) {
            SectionedItem data = sectionedItemList.get(position);
            return (itemsPendingRemoval.contains(data) || data.isHeader);
        }
    }
    

    I have used link as reference to implement undo bar

    Add SwipeUtil.java class as it is from the above link and update your Activity class like below: Add this function to your Activity class

    private void setSwipeForRecyclerView() {
    
            SwipeUtils swipeHelper = new SwipeUtils(0, ItemTouchHelper.LEFT, ActivityB.this) {
                @Override
                public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
                    int swipedPosition = viewHolder.getAdapterPosition();
                    SectionedRecyclerAdapter adapter = (SectionedRecyclerAdapter) sectionedList.getAdapter();
                    adapter.pendingRemoval(swipedPosition);
                }
    
                @Override
                public int getSwipeDirs(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
                    int position = viewHolder.getAdapterPosition();
                    SectionedRecyclerAdapter adapter = (SectionedRecyclerAdapter) sectionedList.getAdapter();
                    if (adapter.isPendingRemoval(position)) {
                        return 0;
                    }
                    return super.getSwipeDirs(recyclerView, viewHolder);
                }
            };
    
            ItemTouchHelper mItemTouchHelper = new ItemTouchHelper(swipeHelper);
            mItemTouchHelper.attachToRecyclerView(sectionedList);
    
            //set swipe label
            swipeHelper.setLeftSwipeLable("Deleted");
            //set swipe background-Color
            //swipeHelper.setLeftcolorCode(ContextCompat.getColor((), R.color.swipebg));
    
        }
    

    And use below code to create the list:

     sectionedList = (RecyclerView)findViewById(R.id.sectioned_list);
     RecyclerView.LayoutManager layoutManager= new LinearLayoutManager(this);
     sectionedList.setLayoutManager(layoutManager);
     final SectionedRecyclerAdapter sectionedRecyclerAdapter = new SectionedRecyclerAdapter(itemList, this);
    
     sectionedList.setAdapter(sectionedRecyclerAdapter);
     setSwipeForRecyclerView();
    

    Edit2: ViewHolders

    public class SectionedItemViewHolder extends RecyclerView.ViewHolder{
        public TextView itemName;
        public TextView undobutton;
        public View itemLayout;
        public View undoLayout;
    
        public SectionedItemViewHolder(View itemView) {
            super(itemView);
            itemName = (TextView)itemView.findViewById(R.id.item_title);
            undobutton = (TextView) itemView.findViewById(R.id.txt_undo);
            itemLayout = itemView.findViewById(R.id.item_layout);
            undoLayout = itemView.findViewById(R.id.undo_layout);
        }
    }
    
    public class SectionedHeaderViewHolder extends RecyclerView.ViewHolder{
        public TextView headerTitle;
    
        public SectionedHeaderViewHolder(View itemView) {
            super(itemView);
            headerTitle = (TextView)itemView.findViewById(R.id.header_title);
        }
    }