Some Kotlin Problem with solutions Every developer need to know

Multi-Selection Animation in Android

Multi selection can be tricky on mobile. Default solutions, where you have a list with check boxes at the right behave bad when the list is getting real big and gets more functionality like filtering.

Suppose when creating your own playlists it can be confusing to understand which songs have already been added without switching between several screens or endlessly scrolling through a list of selected songs.
And the situation may get even worse if we decide to apply filters. In this case, the list of compositions may change once we’ve applied a filter, but the compositions you’ve already selected may not be displayed at all. Vitally decided to solve this problem with yalantis.com concept for multiselection.


In this little experiment, the screen is divided into two parts, so user can always see and manage what he selected without leaving current view. As for filtering or else, it applies only for main left list.

Implementing the component

The animation’s logic seems straightforward, yet it has a few catches.
The component has a ViewPager with two RecyclerViews. We can make a ViewPager page narrower than the screen by overriding the getPageWidth method in the ViewPager adapter and returning a floating number between 0 and 1.
A ViewPager has two pages, each with the RecyclerView. Unselected items are on the left list. Selected items are on the right one.
For instance, if you click an unselected item, a few things will happen:
  1. The clicked item is deleted from the unselected items list and added to the container that holds both lists.
  2. Item position in the selected items list is determined. (The unselected list always has its items sorted alphabetically. The selected list has its items in the order they were selected)
  3. A hidden item is added to the selected items list.
  4. The translation animation is run for the clicked item.
  5. The clicked item is deleted and the hidden item in the selected list is shown.

How to use MultiSelect

Here are 5 simple steps to follow if you want to use this multiselect component in your project.

1. First, add this to your root build.gradle:

allprojects {
  repositories {
   ...
   maven { url "https://jitpack.io" }
  }
}
And then add this to your module build.gradle:
 dependencies {
   compile 'com.github.yalantis:multi-selection:v0.1' }

2. Then create a ViewHolder:

class ViewHolder extends RecyclerView.ViewHolder {
   TextView name;
   TextView comment;
   ImageView avatar;

   public ViewHolder(View view) {
       super(view);
       name = (TextView) view.findViewById(R.id.name);
       comment = (TextView) view.findViewById(R.id.comment);
       avatar = (ImageView) view.findViewById(R.id.yal_ms_avatar);
   }

   public static void bind(ViewHolder viewHolder, Contact contact) {
       viewHolder.name.setText(contact.getName());
       viewHolder.avatar.setImageURI(contact.getPhotoUri());
       viewHolder.comment.setText(String.valueOf(contact.getTimesContacted()));
   }
}

Take note of the static bind method. It’s useful to have it in here because that way you can use the same viewholder in both adapters.

Download Source Code


3. Next, create two adapters for unselected and selected items. 

The first one should extend BaseLeftAdapter; the second, BaseRightAdapter:
public class LeftAdapter extends BaseLeftAdapter<Contact, ViewHolder>{
   private final Callback callback;

   public LeftAdapter(Callback callback) {
       super(Contact.class);
       this.callback = callback;
   }

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

   @Override
   public void onBindViewHolder(@NonNull final ViewHolder holder, int position) {
       super.onBindViewHolder(holder, position);

       ViewHolder.bind(holder, getItemAt(position));

       holder.itemView.setOnClickListener(view -> {
           // ...
           callback.onClick(holder.getAdapterPosition());
           // ...
       });

   }

}
Notice that you should call super constructor with the model class you use in the adapters.
The adapter for selected items is very similar:
public class RightAdapter extends BaseRightAdapter<Contact, ViewHolder> {

   private final Callback callback;

   public RightAdapter(Callback callback) {
       this.callback = callback;
   }

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

   @Override
   public void onBindViewHolder(@NotNull final ViewHolder holder, int position) {
       super.onBindViewHolder(holder, position);

       ViewHolder.bind(holder, getItemAt(position));

       holder.itemView.setOnClickListener(view -> {
           // ...
           callback.onClick(holder.getAdapterPosition());
           // ...
       });
   }
}
Adapters need to extend different base classes because unselected items are sorted, but selected items stay in the order they were previously selected.

4. Finally, call the builder:

MultiSelectBuilder<Contact> builder = new MultiSelectBuilder<>(Contac
       .withContext(this)
       .mountOn((ViewGroup) findViewById(R.id.mount_point))
       .withSidebarWidth(46 + 8 * 2); // ImageView width with paddings
You’ll then need to:
  • Pass the context.
  • Pass the view (usually FrameLayout) that you want the component to be mounted on.
  • Specify the sidebar width in dp (shown in the image below).

how-we-build-a-multiselection-component-for-android-application

5. Last but not least, set the adapters:

LeftAdapter leftAdapter = new LeftAdapter(position -> mMultiSelect.select(position));
RightAdapter rightAdapter = new RightAdapter(position -> mMultiSelect.deselect(position));

leftAdapter.addAll(contacts);

builder.withLeftAdapter(leftAdapter)
       .withRightAdapter(rightAdapter);
Now all you need to do is call builder.build(), which returns the MultiSelect<T> instance.

Thats it.

Comments