RecyclerView Drawer - Set Selection

I am trying to code my own recyclerview slector algorithm. So far, I successfully managed to code that partially except handling the first selection. Basically I want to set background as blue for selected item, white elsewhere (same as ListView's setSelection).

So, the idea is:

  1. Set Blue background for the first element in Adapter's onCreateViewHolder method.
  2. In ActivityMain, define an intance View variable navMenuSelection to store current selection
  3. In recyclerView's onclick listener, set the background of clicked view to blue, background of navMenuSelection as white, and update navMenuSelection to clicked view

All is working except: 1. Can't initialize navMenuSelection with the first view. Tried to use mDrawerLayout.getChildAt(0) at onPostCreate method, but returns null

  1. How to pass the view navMenuSelection in savedInstanceState bundle?

Any idea will be highly appreciated.

public class ActivityMain extends AppCompatActivity {
    private View navMenuSelection = null; // Select current view

    protected void onCreate(Bundle savedInstanceState) {
    // ALL THE CODES TO DEFINE RECYCLERVIEW

        mRecyclerView.addOnItemTouchListener(new RecycleTouchListener(this, new ClickListner() {
            @Override
            public void onClick(View view, int position) {
                if(view != navMenuSelection){
                    setNavItemSelected(view);
                    removeNavItemSelected(navMenuSelection);
                    navMenuSelection = view;
                    mDrawerLayout.closeDrawers();
                }
            }
        }));
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        mDrawerToggle.syncState();
        navMenuSelection = mDrawerLayout.getChildAt(0);
    }
    @Override
    public void onSaveInstanceState(Bundle savedInstanceState){
        //savedInstanceState.put???("currselection", navMenuSelection); // HOW TO DO THAT
        super.onSaveInstanceState(savedInstanceState);
    }

    public void setNavItemSelected(View v){
        if(v!= null) {
            v.setBackgroundColor(R.color.BLUE));
        }
    }

    public void removeNavItemSelected(View v){
        if(v!= null) {
            v.setBackgroundColor(R.color.WHITE));
        }
    }
}

Adapter Class (after moving onClick event to adapter)

public class NavDrawerAdapter extends RecyclerView.Adapter<NavDrawerAdapter.ViewHolder> {

    private String[] mNavTitles; // stores title
    private int[] mIcons; // stores icon

    private Context context;
    private int oldpostion = 0;

    public NavDrawerAdapter(Context context, String Titles[], int[] Icons){
        this.context = context;
        mNavTitles = Titles;
        mIcons = Icons;
    }

    public class ViewHolder extends RecyclerView.ViewHolder {

        TextView textView;
        ImageView imageView;

        public ViewHolder (View itemView) {
            super(itemView);
            textView = (TextView) itemView.findViewById(R.id.title);
            imageView = (ImageView) itemView.findViewById(R.id.icon);
        }
    }

    @Override
    public NavDrawerAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.navdrawer_item,parent,false);
        return new ViewHolder(v,viewType);
    }

    @Override
    public void onBindViewHolder(NavDrawerAdapter.ViewHolder holder, final int position) {
        holder.textView.setText(mNavTitles[position]);
        holder.imageView.setImageResource(mIcons[position]);
        if(position == 0) {
            setNavItemSelected(holder.itemView);
        }
        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(position != oldpostion){
                    setNavItemSelected(v);
                    //removeNavItemSelected(OLD VIEW);
                    oldpostion = position;
                }
            }
        });
    }

    @Override
    public int getItemCount() {
        return mNavTitles.length;
    }

    @Override
    public int getItemViewType(int position) {
        return 1;
    }

    public void setNavItemSelected(View v){
        if(v!= null) {
            v.setBackgroundColor(context.getResources().getColor(R.color.navdrawer_item_selected_bg));
            TextView tview = (TextView) v.findViewById(R.id.title);
            tview.setTextColor(context.getResources().getColor(R.color.navdrawer_item_selected_text));
        }
    }

    public void removeNavItemSelected(View v){
        if(v!= null) {
            v.setBackgroundResource(R.drawable.list_selector_nav_drawer);
            TextView tview = (TextView) v.findViewById(R.id.title);
            tview.setTextColor(context.getResources().getColorStateList(R.color.list_selector_nav_drawer_text));
        }
    }
}

Answers


Since you are already using array to pass your icon and title, let's add another array to save your current selection

 private boolean isSelected[] = {true, false, false, false, false};   //make sure that the length of this array matches the length of navtitles and navicon. set the first element to true since you want the first item to be selected by default. you can declare this directly on the adapter if this is static

or you can pass it as a parameter

private boolean isSelected[];
public NavDrawerAdapter(Context context, String Titles[], int[] Icons, boolean[] isSelected){
    this.context = context;
    mNavTitles = Titles;
    mIcons = Icons;
    this.isSelected = isSelected;
}

....


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

    holder.itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            for(int i=0; i < isSelected.length(); i++){
                if (i==position){
                    isSelected[position]=true;
                else
                    isSelected[position]=false;
            }
            notifyDataSetChanged();
        }
    });

    if (isSelected[position]==true){
        //execute your codes here
    }else{
        //execute your codes here

    }
}

just try to debug if there's any error or typo. i did not use any code editor for this so there may be some error


As for your questions:

  1. To get a view you need in the RecyclerView you can use mRecyclerView.findViewHolderForAdapterPosition(position).
  2. I may be wrong but I think you only need to save the position of the view, not the view itself, which is just int.

Another pattern, I used before, is adding a boolean array to your RecyclerView.Adapter that saves the info about what view(s) (associated with a position in your data collection) should be activated in the moment. Based on this array I was changing background of views in onBindViewHolder().

Your current solution is ok if the recycler view is short enough to be seen on a single screen. But as soon as it starts recycling its old viewholders you'll get "selected" background reused as well on not selected views - all because you remembered views, rather than data positions connected to them.

EDIT

I'll put here some relevant code from the adapter to illustrate my idea on implementation. In my approach I used a state list drawable with "selected" background for the activated state, but you can do all the same by manually setting the background.

public class RecyclerAdapter
        extends RecyclerView.Adapter<RecyclerAdapter.YourViewHolder> {

    private final List<...> data;
    private final RecyclerAdapterCallbacks listener; //e.g. for activity callbacks
    private final List<Boolean> activation;

    public RecyclerAdapter(List<...> data, RecyclerAdapterCallbacks listener) {
        this.data = data;
        this.listener = listener;
        this.activation = new ArrayList<>(data.size());
        fillActivationList();
    }

    private void fillActivationList() {
        int size = data.size();
        for (int i = 0; i < size; i++) activation.add(false);
    }

    //------YourViewHolder implementation here------

    public interface RecyclerAdapterCallbacks { //Callbacks interface if needed
        void onRowSelected(... dataPiece);

        void onRowDeselected();
    }

    @Override
    public WordsViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        //your code
    }

    @Override
    public void onBindViewHolder(YourViewHolder holder, final int position) {
        holder.field.setText(data.get(position).getString());

        holder.itemView.setActivated(activation.get(position));
        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Boolean previousState = activation.get(position);
                deactivateAll();
                activation.set(position, !previousState);
                notifyDataSetChanged();
                if (!previousState) {
                    listener.onRowSelected(data.get(position));
                } else {
                    listener.onRowDeselected();
                }
            }
        });
    }

    private void deactivateAll() {
        Collections.fill(activation, false);
    }

    @Override
    public int getItemCount() {
        return data.size();
    }

    public void clearResults() {
        notifyItemRangeRemoved(0, data.size());
        data.clear();
        activation.clear();
    }

    public void add(Word item) {
        data.add(item);
        notifyItemInserted(data.size() - 1);
        activation.add(false);
    }

To make it easier for you, I suggest just adding the layout in your viewholder so you can just change the background of the layout. not the cleanest method but this one should do the trick for you

public class ViewHolder extends RecyclerView.ViewHolder {

    TextView textView;
    ImageView imageView;
    LinearLayout layout; //whatever layout your using, just an example

    public ViewHolder (View itemView) {
        super(itemView);
        textView = (TextView) itemView.findViewById(R.id.title);
        imageView = (ImageView) itemView.findViewById(R.id.icon);
        layout = (LinearLayout) itemView.findViewById(R.id.layout);
    }
}

...

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

holder.itemView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        for(int i=0; i < isSelected.length(); i++){
            if (i==position){
                isSelected[position]=true;
            else
                isSelected[position]=false;
        }
        notifyDataSetChanged();
    }
});

if (isSelected[position]==true){
    //execute your codes here
     holder.layout.setBackgroundResource(R.drawable.list_selector_nav_drawer);          
     holder.textView.setTextColor(context.getResources().getColorStateList(R.color.list_selector_nav_drawer_text));

}else{
    //execute your codes here
     holder.layout.setBackgroundResource(R.drawable.list_selector_nav_drawer);
     holder.textView.setTextColor(context.getResources().getColorStateList(R.color.list_selector_nav_drawer_text));

}
}

Need Your Help

C# Extension Method to Calculate Position Between a Range

c# extension-methods mathematical-optimization

I am trying to write an extension method off a 'decimal' that calculates the value of a given number in relation to a given lower and upper range. I know that sounds a bit strange so here is a conc...

About UNIX Resources Network

Original, collect and organize Developers related documents, information and materials, contains jQuery, Html, CSS, MySQL, .NET, ASP.NET, SQL, objective-c, iPhone, Ruby on Rails, C, SQL Server, Ruby, Arrays, Regex, ASP.NET MVC, WPF, XML, Ajax, DataBase, and so on.