Nathan Case
Nathan Case

Reputation: 695

Recycler View with Header and Edit Text

I have a recyclerview with a header achieved by using two different element types. In my header there is an edit text which I want to use for filtering the nonheader elements of the list. Below is my current implementation, I have one concern and one problem with it.

My concern is that what I am doing in publishResults with the notifyItemRangeRemoved and notifyItemInserted is the wrong way to update the recycler view. I originally was doing notifyDatasetChanged but his would cause the header row to be refreshed too and the edit text to lose focus. What I really want is a way to refresh only the item rows and leave the header row untouched.

My current problem is that with the existing code if I scroll down too much the edit text looses focus. I want the edit text to keep focus even if I scroll to the bottom of the list.

The code used to use a ListView with setHeaderView and that worked somehow so there must be someway of achieving the goal just not sure what the trick with a recycler view is. Any help is much appreciated.

public class SideListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements Filterable {
    private static final int TYPE_HEADER = 0;
    private static final int TYPE_ITEM = 1;

    private final List<String> data;
    public List<String> filteredData;

    private HeaderActionListener headerActionListener;

    public SideListAdapter(Context context, ArrayList<String> data, HeaderActionListener headerActionListener) {
        this.data = data;
        filteredData = new ArrayList<>(data);
        this.context = context;
        this.headerActionListener = headerActionListener;
    }

    @Override
    public Filter getFilter() {
        return new TestFilter();
    }

    static class SideListItem extends RecyclerView.ViewHolder {
        LinearLayout baseLayout;
        public SideListItem(View itemView) {
            super(itemView);
            baseLayout = (LinearLayout) itemView.findViewById(R.id.settings_defaultcolor);
        }
    }

    class SideListHeader extends SideListHeader {
        EditText sort;

        public SideListHeaderLoggedIn(View itemView) {
            super(itemView);
            sort = (EditText) itemView.findViewById(R.id.sort);
        }
    }


    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == TYPE_ITEM) {
            View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item, parent, false);
            return new SideListItem(v);
        } else if (viewType == SideListHeader) {
            View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.header, parent, false);
            return new SideListHeader(v);
        }

        throw new RuntimeException("there is no type that matches the type " + viewType + " + make sure your using types correctly");
    }

    public interface HeaderActionListener {
        boolean onSortEditorAction(TextView arg0, int arg1, KeyEvent arg2);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {

        if (holder instanceof SideListHeader) {
            final SideListHeader sideListHeader = (SideListHeader) holder;

            sideListHeader.sort.setOnEditorActionListener(new TextView.OnEditorActionListener() {
                @Override
                public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {

                }
            });

            sideListHeader.sort.addTextChangedListener(new TextWatcher() {
                @Override
                public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {

                }

                @Override
                public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {

                }

                @Override
                public void afterTextChanged(Editable editable) {
                    String result = sideListHeader.sort.getText().toString().replaceAll(" ", "");
                    getFilter().filter(result);
                }
            });
        }

        if (holder instanceof SideListItem) {            
            // Inflate normal item //
        }
    }

    //    need to override this method
    @Override
    public int getItemViewType(int position) {
        if (isPositionHeader(position)) {
            return  TYPE_HEADER;
        }
        return TYPE_ITEM;
    }

    private boolean isPositionHeader(int position) {
        return position == 0;
    }

    //increasing getItemcount to 1. This will be the row of header.
    @Override
    public int getItemCount() {
        return filteredData.size() + 1;
    }

    private class TestFilter extends Filter {
        @Override
        protected FilterResults performFiltering(CharSequence constraint) {
            FilterResults results = new FilterResults();
            String prefix = constraint.toString().toLowerCase();
            if (prefix.isEmpty()) {
                ArrayList<String> list = new ArrayList<>(data);
                results.values = list;
                results.count = list.size();
            } else {
                final ArrayList<String> list = new ArrayList<>(data);
                final ArrayList<String> nlist = new ArrayList<>();

                for (int i = 0 ; i < list.size(); i++) {
                    String item = list.get(i);
                    if (item.contains(prefix)) {
                        nlist.add(item);
                    }               
                }

                results.values = nlist;
                results.count = nlist.size();
            }
            return results;
        }

        @SuppressWarnings("unchecked")
        @Override
        protected void publishResults(CharSequence constraint, FilterResults results) {
            notifyItemRangeRemoved(1, getItemCount()-1);
            filteredData.clear();
            filteredData.addAll((List<String>)results.values);
            for(int i = 1; i < getItemCount() - 1; i++){
                 notifyItemInserted(i);
            }
        }
    }
}

Upvotes: 2

Views: 789

Answers (1)

Baudi Yusupov
Baudi Yusupov

Reputation: 11

I'm not sure how correct this way is, but in my code I implemented it like that

private var headerList: List<HeaderItem> = listOf(HeaderItem("Title"))

private fun searchItem(items: List<Items>, query: String) {
    items.filterIsInstance<MainItem>().filter { filteredItems ->
        filteredItems.header.lowercase().contains(query.lowercase())
    }.let { searchedItems ->
        rvAdapter.submitList(headerList + searchedItems)
    }
}

This way I was able to preserve header element when I did my search

Upvotes: 0

Related Questions