Click here to Skip to main content
15,881,740 members
Articles / All Topics

Remember Drag and Drop Position with RecyclerView

Rate me:
Please Sign up or sign in to vote.
5.00/5 (4 votes)
19 Oct 2015CPOL7 min read 47.5K   3   7
Tutorial on how to remember the drag and drop position of your RecyclerView list items in Android

In this post, I will provide a tutorial on how to remember the drag and drop position of your RecyclerView list items in Android. There are few third party libraries that exist to help implement drag and drop in RecyclerView and Paul Burke provided an excellent three part tutorial on how to use native components to implement drag and drop. I will build upon this tutorial to show how your app can remember the new re-ordered position after the list has been re-ordered by drag and dropping. See this in action below.

https://gyazo.com/eb35ae18d5fbe0f4a3bd3d73a85a24eb

Whichever way you implement RecyclerView drag and drop, the challenge is when you exit the app and come back, how does your app remember the last positions of the list items after they were re-organized. You may think that there is a new Intent (REMEMBER_LIST_POSITION) you can call but there is none, at-least I have not seen any so you will have to implement this manually if you desire such functionality. Here is how I have chosen to implement this functionality.

  1. Create a standard RecyclerView
  2. Implement drag and drop on the RecyclerView
  3. Attach a listener to the RecyclerView Adapter to listen for when the list item changes
  4. When the list item changes, grab all the Ids on the items in the collections
  5. Collapse this list into a JSON string and save to the SharedPreference
  6. When the RecyclerView is shown again, fetch the JSON string from SharedPreference and inflate back into a List<Long>
  7. Use this list to sort the items and display, reflecting their last positions the last time they were changed.

If you find the tutorial helpful at anytime, please pause and use the social media buttons to share it in case someone else will benefit from it. Let us go ahead and see this in action. As with most things in programming, your approach may be different from mine and I will be glad to hear how you solved this problem if it is something that you have had to deal with. The source code for this tutorial can be found here.

Create New Android Studio Project

In this section of the post, we want to create a new Android Studio project for our drag and drop demo app. I am using Android Studio 1.4 and if you have created a new Android project lately you will realize that a few things have changed especially with the new project template. Follow the steps below to create new project for this demo app.

Step 1: Create New Project

Follow the standard new project wizard to create a new Android Studio project. Give the project any name, select Phone and Tablet form factor. Select the default API level 16 and Blank Activity template.

Step 2: Cleanup

The new template comes with some boiler plate code that we do not need for this demo app. In the activity_main.xml, remove the Floating Action Button component. In MainActivity.java file, also remove the Floating Action Button code block. In content_main.xml, remove the Hello World TextView.

Step 3: Add RecyclerView

In the place where you remove the Hello World TextView in content_main.xml, add RecyclerView like this:

XML
<android.support.v7.widget.RecyclerView
        android:id="@+id/note_recycler_view"
        android:scrollbars="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

Step 4: Add Model Class

At the root of your project, add a model class Customer.java. This demo app is showing a list of fictitious customers. The names and images of the people used in this demo app are not real. The images are purchased from Dollar Photo Club. Here is the content of the Customer.java, use the generate functional to add the boiler plate getter and setter code.

Java
public class Customer {
    private Long id;
    private String name;
    private String emailAddress;
    private int imageId;
    private String imagePath;
}

Step 5: Add Drag Image

Add this image to your res/drawable folder, we will use this as the handle for our drag and drop.

Step 6: Add Dependencies

Add the following dependencies for the external libraries we will use for this app; many thanks to the authors of those libraries.

compile 'de.hdodenhof:circleimageview:2.0.0'
    compile 'com.squareup.picasso:picasso:2.5.2'
    compile 'com.yqritc:recyclerview-flexibledivider:1.2.6'
    compile 'com.google.code.gson:gson:2.3.1'

Also add this line to your Manifest:

XML
<uses-permission android:name="android.permission.INTERNET" />

Step 7: Add Sample Data

We are using sample in memory data to generate the demo data displayed in this app. Optionally, create a package called utilities and in this package, add a class called SampleData.java. In this class, add a method called:

Java
public static List<Customer> addSampleCustomers()

and here is the content of that method.

Implement RecyclerView with Drag and Drop

In this section, we will go ahead and implement the RecyclerView with drag and drop functionality. Follow the steps below to implement RecyclerView.

Step 1: Add Adapter

At the root of the project, add a class file named CustomerListAdapter.java, leave the file alone and letès come back to it in a minute.

Step 2: Implement ItemTouchHelper

To implement ItemTouchHelper, we actually need to add four files. To understand the purpose of each of these files, read these post series. Add the following files to your project.

  1. In your utilities package, add ItemTouchHelperAdapter.java and below is the content:
    Java
    public interface ItemTouchHelperAdapter {
        /**
         * Called when an item has been dragged far enough to trigger a move. This is called every time
         * an item is shifted, and not at the end of a "drop" event.
         *
         * @param fromPosition The start position of the moved item.
         * @param toPosition   Then end position of the moved item.
    
         */
        void onItemMove(int fromPosition, int toPosition);
    
    
        /**
         * Called when an item has been dismissed by a swipe.
         *
         * @param position The position of the item dismissed.
    
         */
        void onItemDismiss(int position);
    }
  2. In your utilities package, add ItemTouchHelperViewHolder.java and below is the content:
    Java
    public interface ItemTouchHelperViewHolder {
        /**
          * Implementations should update the item view to indicate it's active state.
         */
        void onItemSelected();
    
    
        /**
         * state should be cleared.
         */
        void onItemClear();
    }
  3. In your utilities package, add SimpleItemTouchHelperCallback.java and here is the content:
    Java
    public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
    
        private final ItemTouchHelperAdapter mAdapter;
    
        public SimpleItemTouchHelperCallback(ItemTouchHelperAdapter adapter) {
            mAdapter = adapter;
        }
    
        @Override
        public boolean isLongPressDragEnabled() {
            return true;
        }
    
        @Override
        public boolean isItemViewSwipeEnabled() {
            return false;
        }
    
        @Override
        public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
            final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
            final int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
            return makeMovementFlags(dragFlags, swipeFlags);
        }
    
        @Override
        public boolean onMove(RecyclerView recyclerView, 
        RecyclerView.ViewHolder source, RecyclerView.ViewHolder target) {
            mAdapter.onItemMove(source.getAdapterPosition(), target.getAdapterPosition());
            return true;
        }
    
        @Override
        public void onSwiped(RecyclerView.ViewHolder viewHolder, int i) {
            mAdapter.onItemDismiss(viewHolder.getAdapterPosition());
        }
    
        @Override
        public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
            if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
                ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder;
                itemViewHolder.onItemSelected();
            }
    
            super.onSelectedChanged(viewHolder, actionState);
        }
    
        @Override
        public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
            super.clearView(recyclerView, viewHolder);
    
            ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder;
            itemViewHolder.onItemClear();
        }
    }
  4. Add a package called listener and add an interface called OnStartDragListener.java and here is the content:
    Java
    public interface OnStartDragListener {
        /**
         * Called when a view is requesting a start of a drag.
         *
         * @param viewHolder The holder of the view to drag.
         */
        void onStartDrag(RecyclerView.ViewHolder viewHolder);
    }
  5. While you are at the listener package, add another interface called OnCustomerListChangedListener.java and here is the content:
    Java
    public interface OnCustomerListChangedListener {
        void onNoteListChanged(List<Customer> customers);
    }

Step 3: Implement Custom Row

Now, we need to add the custom row that lays out the views item in each row of our list. In your res/layout folder, add the layout file row_customer_list.xml and here is the content of that file.

XML
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="wrap_content">

    <de.hdodenhof.circleimageview.CircleImageView
        android:layout_width="70dp"
        android:layout_height="70dp"
        android:layout_margin="10dp"
        android:layout_centerVertical="true"
        android:id="@+id/image_view_customer_head_shot" />

    <LinearLayout
        android:id="@+id/linear_layout_customer_name"
        android:layout_width="0dp"
        android:layout_weight="3"
        android:layout_margin="10dp"
        android:paddingTop="@dimen/margin_padding_normal"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:orientation="vertical">

        <TextView
            android:id="@+id/text_view_customer_name"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            />
        <TextView
            android:id="@+id/text_view_customer_email"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"  />
    </LinearLayout>

    <ImageView
        android:id="@+id/handle"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical|right"
        android:scaleType="center"
        android:src="@drawable/ic_reorder_grey" />

</LinearLayout>

Step 4: Implement the Adapter

We now have all the components needed to implement the adapter. Now update the adapter with the following code. I am including the import statements so you can see which library is being referenced from this file. A few things are happening in the code below, for the most part it is a standard implementation of RecyclerView Adapter.

The drag and drop is simply implemented with the Java Collections framework. And after the swap, we are attaching our own home made listener which lets us know that a swap has been made and returns the new collections of items back to us. This is the list we will now work on.

Java
package com.okason.draganddrop;

import android.content.Context;
import android.graphics.Color;
import android.support.v4.view.MotionEventCompat;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import com.okason.draganddrop.listeners.OnCustomerListChangedListener;
import com.okason.draganddrop.listeners.OnStartDragListener;
import com.okason.draganddrop.utilities.ItemTouchHelperAdapter;
import com.okason.draganddrop.utilities.ItemTouchHelperViewHolder;
import com.squareup.picasso.Picasso;

import java.util.Collections;
import java.util.List;

/**
 * Created by Valentine on 10/18/2015.
 */
public class CustomerListAdapter extends
        RecyclerView.Adapter<CustomerListAdapter.ItemViewHolder>
        implements ItemTouchHelperAdapter {

    private List<Customer> mCustomers;
    private Context mContext;
    private OnStartDragListener mDragStartListener;
    private OnCustomerListChangedListener mListChangedListener;

    public CustomerListAdapter(List<Customer> customers, Context context,
                               OnStartDragListener dragLlistener,
                               OnCustomerListChangedListener listChangedListener){
        mCustomers = customers;
        mContext = context;
        mDragStartListener = dragLlistener;
        mListChangedListener = listChangedListener;
    }


    @Override
    public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View rowView = LayoutInflater.from
        (parent.getContext()).inflate(R.layout.row_customer_list, parent, false);
        ItemViewHolder viewHolder = new ItemViewHolder(rowView);
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(final ItemViewHolder holder, int position) {

        final Customer selectedCustomer = mCustomers.get(position);

        holder.customerName.setText(selectedCustomer.getName());
        holder.customerEmail.setText(selectedCustomer.getEmailAddress());
        Picasso.with(mContext)
                .load(selectedCustomer.getImagePath())
                .placeholder(R.drawable.profile_icon)
                .into(holder.profileImage);



        holder.handleView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN) {
                    mDragStartListener.onStartDrag(holder);
                }
                return false;
            }
        });
    }

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

    @Override
    public void onItemMove(int fromPosition, int toPosition) {
        Collections.swap(mCustomers, fromPosition, toPosition);
        mListChangedListener.onNoteListChanged(mCustomers);
        notifyItemMoved(fromPosition, toPosition);
    }

    @Override
    public void onItemDismiss(int position) {

    }

    public static class ItemViewHolder extends RecyclerView.ViewHolder implements
            ItemTouchHelperViewHolder {
        public final TextView customerName, customerEmail;
        public final ImageView handleView, profileImage;


        public ItemViewHolder(View itemView) {
            super(itemView);
            customerName = (TextView)itemView.findViewById(R.id.text_view_customer_name);
            customerEmail = (TextView)itemView.findViewById(R.id.text_view_customer_email);
            handleView = (ImageView)itemView.findViewById(R.id.handle);
            profileImage = (ImageView)itemView.findViewById(R.id.image_view_customer_head_shot);
        }

        @Override
        public void onItemSelected() {
            itemView.setBahttp://valokafor.com/wp-admin/post.php?post=1804&action=edit#ckgroundColor(Color.LTGRAY);
        }

        @Override
        public void onItemClear() {
            itemView.setBackgroundColor(0);
        }
    }
}

Step 5: Implement RecyclerView Java Code

Open your MainActivity.java and add the following instance variables near the top of the file.

Java
private RecyclerView mRecyclerView;
    private CustomerListAdapter mAdapter;
    private RecyclerView.LayoutManager mLayoutManager;
    private ItemTouchHelper mItemTouchHelper;
    private List<Customer> mCustomers;

After the onCreate method, add this method. And then call this method from the onCreate() method probably after the call to set Toolbar. Ignore the error warning for a minute.

Java
private void setupRecyclerView(){
        mRecyclerView = (RecyclerView) findViewById(R.id.note_recycler_view);
        mRecyclerView.setHasFixedSize(true);
        mLayoutManager = new LinearLayoutManager(this);
        mRecyclerView.setLayoutManager(mLayoutManager);
        mCustomers = SampleData.addSampleCustomers();

        //setup the adapter with empty list
        mAdapter = new CustomerListAdapter(mCustomers, this, this, this);
        ItemTouchHelper.Callback callback = new SimpleItemTouchHelperCallback(mAdapter);
        mItemTouchHelper = new ItemTouchHelper(callback);
        mItemTouchHelper.attachToRecyclerView(mRecyclerView);
        mRecyclerView.addItemDecoration(new HorizontalDividerItemDecoration.Builder(this)
                .colorResId(R.color.colorPrimaryDark)
                .size(2)
                .build());
        mRecyclerView.setAdapter(mAdapter);
    }

Update the signature of your MainActivity to implement the two listeners that we added like below and use Android Studio quick fix to implement the methods.

Java
public class MainActivity extends AppCompatActivity
    implements OnCustomerListChangedListener,
        OnStartDragListener{

Here is the implementation of one of the methods, and we will implement the other one in the next section.

Java
@Override
    public void onStartDrag(RecyclerView.ViewHolder viewHolder) {
        mItemTouchHelper.startDrag(viewHolder);

    }

At this point, you should be able to run your app. Everything should be working except that when you close the app and open, the position of the drag and drop will not be saved. We will implement that next. But make sure that you get the app working before you proceed.

Implement Save Drag and Drop Position

At this point, your drag and drop list should be working and we now want to remember the position of the list items after they have been re-organized. Like I mentioned at the beginning of the post, this is accomplished by saving the ids of the list items to SharedPreference so go ahead and add the following class members to the top of the file.

Java
private SharedPreferences mSharedPreferences;
    private SharedPreferences.Editor mEditor;
    public static final String LIST_OF_SORTED_DATA_ID = "json_list_sorted_data_id";
    public final static String PREFERENCE_FILE = "preference_file";

And in the onCreate() instantiate the SharedPreference like so:

Java
mSharedPreferences = this.getApplicationContext()
                .getSharedPreferences(PREFERENCE_FILE, Context.MODE_PRIVATE);
        mEditor = mSharedPreferences.edit();

Then go ahead and implement the other method that listens for when the list changes and here is the implementation of that method:

Java
@Override
    public void onNoteListChanged(List<Customer> customers) {
        //after drag and drop operation, the new list of Customers is passed in here

        //create a List of Long to hold the Ids of the
        //Customers in the List
        List<Long> listOfSortedCustomerId = new ArrayList<Long>();

        for (Customer customer: customers){
            listOfSortedCustomerId.add(customer.getId());
        }

        //convert the List of Longs to a JSON string
        Gson gson = new Gson();
        String jsonListOfSortedCustomerIds = gson.toJson(listOfSortedCustomerId);


        //save to SharedPreference
        mEditor.putString(LIST_OF_SORTED_DATA_ID, jsonListOfSortedCustomerIds).commit();
        mEditor.commit();
    }

Then, add this method to your MainActivity.java:

Java
private List<Customer> getSampleData(){

        //Get the sample data
        List<Customer> customerList = SampleData.addSampleCustomers();

        //create an empty array to hold the list of sorted Customers
        List<Customer> sortedCustomers = new ArrayList<Customer>();

        //get the JSON array of the ordered of sorted customers
        String jsonListOfSortedCustomerId = mSharedPreferences.getString(LIST_OF_SORTED_DATA_ID, "");

        //check for null
        if (!jsonListOfSortedCustomerId.isEmpty()){

            //convert JSON array into a List<Long>
            Gson gson = new Gson();
            List<Long> listOfSortedCustomersId = gson.fromJson
            (jsonListOfSortedCustomerId, new TypeToken<List<Long>>(){}.getType());

            //build sorted list
            if (listOfSortedCustomersId != null && listOfSortedCustomersId.size() > 0){
                for (Long id: listOfSortedCustomersId){
                    for (Customer customer: customerList){
                        if (customer.getId().equals(id)){
                            sortedCustomers.add(customer);
                            customerList.remove(customer);
                            break;
                        }
                    }
                }
            }

            //if there are still customers that were not in the sorted list
            //maybe they were added after the last drag and drop
            //add them to the sorted list
            if (customerList.size() > 0){
                sortedCustomers.addAll(customerList);
            }

            return sortedCustomers;
        }else {
           return customerList;
        }
    }

Now update the line in setupRecyclerView() that you get the data from:

Java
mCustomers = SampleData.addSampleCustomers();

to:

Java
mCustomers = getSampleData();

This is the method that does the little magic of re-displaying items using their last sorted position. All on the fly without re-saving the items back to the database. Obviously, if the data is coming from a database, the above method must run in a background thread to avoid blocking the main thread. I have used variations of this approach for a few recent projects. I would like to know if you have had to solve a similar problem and what approach you ended up using.

If you think anyone in your social media list can benefit from the list, please feel free to share the post.

Keep coding!

The post Remember Drag and Drop Position with RecyclerView appeared first on Val Okafor.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) ValOkafor.com
United States United States
My name is Val Okafor, I am a Senior Software Engineer with specialization in Android Development. Learning and problem solving is my passion and I share my Android development knowledge through my blog ValOkafor.com.

My Android courses are the courses I wish I had when I started. I teach Android development in the context of a fully developed Android app. I believe that new Android concepts are better understood if they are presented in the context of creating an app from scratch to finish.

I focus on creating Productivity Android apps and besides Android development I have 7 years’ experience as System Administrator supporting Enterprise applications and 2 years’ experience as a Web Developer building websites using PHP and ASP.Net.

I have worked for corporations such as The Home Depot, American Council on Exercise, Legend3D and HD Supply, Inc. I have a bachelor's degree in Information Technology from National University San Diego, California and a master's degree in Software Engineering from Regis University Denver, Colorado.

I enjoy sharing my extensive work experience through my blog, social media.

Comments and Discussions

 
Questiondrag and drop position Pin
Member 1561942829-Apr-22 1:41
Member 1561942829-Apr-22 1:41 
Questionchanging the background of the items in real time Pin
unyck shakya12-Oct-17 0:36
unyck shakya12-Oct-17 0:36 
QuestionNicely written Pin
DenP24-Mar-17 10:11
DenP24-Mar-17 10:11 
QuestionDrag, Drop and Swipe to Refresh Pin
Member 1030574417-Mar-16 18:19
Member 1030574417-Mar-16 18:19 
QuestionCompile error using AIDE Pin
pkfox21-Oct-15 22:29
professionalpkfox21-Oct-15 22:29 
AnswerRe: Compile error using AIDE Pin
Val Okafor21-Oct-15 22:37
professionalVal Okafor21-Oct-15 22:37 
GeneralRe: Compile error using AIDE Pin
pkfox22-Oct-15 1:13
professionalpkfox22-Oct-15 1:13 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.