简体   繁体   中英

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

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

please use this link to implement section recycle view in android

http://android-pratap.blogspot.in/2015/12/sectioned-recyclerview-in-android_1.html

Didnt get the question much, but if what is needed is this

Header view not swipe-able but content view swipe-able and an undo bar there then,

You do not need 2 adapters, Just one RecyclerView adapter will do, with two different view types, as answered by: @nnn
But in your SimpleItemCallback implementation, modify the RecyclerView position you do not need the callback to work on by doing the following:

ItemTouchHelper.SimpleCallback simpleCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.RIGHT | ItemTouchHelper.LEFT) {
            @Override
            public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
                return false;
            }

            @Override
            public int getSwipeDirs(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
// check for the position you do not want the ItemTouchHelper to work 
//and make it return 0;
                if (viewHolder.getAdapterPosition() == 0)
                    return 0;
                else
                    return super.getSwipeDirs(recyclerView, viewHolder);
            }

            @Override
            public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
// perform delete operation and undo bar operation here.                 

}
            };
            ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleCallback);
            itemTouchHelper.attachToRecyclerView(recyclerView);

FYI: A good library for undobar: com.cocosw:undobar:1.+@aar

RecyclerView Adapter code skeleton:

public class Adapter_HeaderView extends RecyclerView.Adapter<Adapter_HeaderView.ViewHolder> {
    private final int VIEW_TYPE_HEADER = 1, VIEW_TYPE_CONTENT = 2;
    private Context context;
    private List<Object> list;

    //    private int meanPercentage;
    public Adapter_HeaderView(Context context, List<Object> list) {
        this.context = context;
        this.list = list;
//        this.meanPercentage =meanPercentage;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        int layoutID;
        switch (viewType) {
            case VIEW_TYPE_CONTENT:
                // content row layout
                layoutID = R.layout.row_list;
                break;
            case VIEW_TYPE_HEADER:
                // header row layout
                layoutID = R.layout.row_list_header;
                break;
            default:
                layoutID = R.layout.row_list;
                break;

        }
        return new ViewHolder(LayoutInflater.from(context).inflate(layoutID, parent, false));
    }
// override the getItemViewType to return position based on position
    @Override
    public int getItemViewType(int position) {
        if (position == 0) {
            return VIEW_TYPE_HEADER;
        } else
            return VIEW_TYPE_CONTENT;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, final int position) {
        if (getItemViewType(position) != VIEW_TYPE_HEADER) {
          // load content layout here
          // access all items of list as list.get(position-1); as dummy item added as the first element
        } else {
        // load header layout components here. 
          }
    }

    @Override
    public int getItemCount() {
        // add a dummy item in item count which will be the recycler view header. 
        return list.size() + 1;
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        public ViewHolder(View itemView) {
            super(itemView);
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM