MvxSmartRecyclerView 1.0.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package MvxSmartRecyclerView --version 1.0.0                
NuGet\Install-Package MvxSmartRecyclerView -Version 1.0.0                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="MvxSmartRecyclerView" Version="1.0.0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add MvxSmartRecyclerView --version 1.0.0                
#r "nuget: MvxSmartRecyclerView, 1.0.0"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install MvxSmartRecyclerView as a Cake Addin
#addin nuget:?package=MvxSmartRecyclerView&version=1.0.0

// Install MvxSmartRecyclerView as a Cake Tool
#tool nuget:?package=MvxSmartRecyclerView&version=1.0.0                

Android MvxSmartRecyclerView

This is an unofficial package that contains an expandable AndroidX RecyclerView supported for MvvmCross. This view allows us to bind a collection of items (objects, ViewModels, etc) to the ItemsSource property. It works similarly to a RecyclerView. However, this comes with out-of-the-box functionality such as dragging items up and down and swiping them by setting a boolean property to EnableDrag, EnableSwipeRight and/or EnableSwipeLeft attributes.

All original functionality of MvxRecyclerView is also available and it is highly encouraged that you read the documentation before proceeding.

Getting Started

You will need to ensure that you have the MvxSmartRecyclerView NuGet package installed in your .Droid project.

  1. We want to create an app with a list of students. Firstly, in our .Core project, we will need to create our classes: Student.cs and Lesson.cs.
public class Student : MvxNotifyPropertyChanged
{
    private string firstName;
    private string lastName;
    private Lesson lesson;
    private int sequence;

    public Student(string firstName, string lastName, Lesson lesson)
    {
        FirstName = firstName;
        LastName = lastName;
        Lesson = lesson;
    }

    public string FirstName { get => firstName; set => SetProperty(ref firstName, value); }
    public string LastName { get => lastName; set => SetProperty(ref lastName, value); }
    public Lesson Lesson { get => lesson; set => SetProperty(ref lesson, value); }
    public int Sequence { get => sequence; set => SetProperty(ref sequence, value); }
}
public class Lesson : MvxNotifyPropertyChanged
{
    public static readonly Lesson Empty = new Lesson(Subject.None, DateTime.MinValue);

    private int sequence;

    public Lesson(Subject subject, DateTime dateTime)
    {
        Subject = subject;
        DateTime = dateTime;
    }

    public DateTime DateTime { get; }
    public int Sequence { get => sequence; set => SetProperty(ref sequence, value); }
    public Subject Subject { get; }
}

public enum Subject
{
    None = 0,
    English = 1,
    Math = 2,
}
  1. In our ViewModel we will initialise a list of Students for binding.
public MvxObservableCollection<Student> Students { get; set; }
  1. For the rest of the steps, everything will be done in our .Droid project. We will create a layout to display our Student.cs entity by creating StudentView.xml.

StudentView.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   android:layout_width="match_parent"
   android:layout_height="30dp"
   android:orientation="horizontal"
   android:gravity="center">
   <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      app:MvxBind="Text Format('{0} {1} - {2} on {3:d}', FirstName, LastName, Lesson.Subject, Lesson.DateTime);"/>
   <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_marginHorizontal="4sp"
      android:textColor="?android:attr/colorAccent"
      app:MvxBind="Text Sequence;"/>
</LinearLayout>
  1. Finally, adding MvxSmartRecyclerView to one of your View.xml is very simple.
<MvvmCross.SmartRecyclerView.MvxSmartRecyclerView
    android:id="@+id/smart_appointment_recyclerview"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:MvxItemTemplate="@layout/studentview"
    app:MvxBind="ItemsSource Students;"/>

An alternative to using the MvxItemTemplate attribute would be to create a custom Item Template Selector if you want each item to show a different view.

Custom Adapter

If you want to implement a custom adapter for MvxSmartRecyclerView, you can either set it inside your View.cs, or the recommended way: inside your View.xml.

For example:

namespace AppointmentPlanner.DroidX.Components
{
    public class AppointmentSmartRecyclerAdapter : MvxSmartRecyclerAdapter
    {
        public AppointmentSmartRecyclerAdapter()
        {
        }

        public AppointmentSmartRecyclerAdapter(IMvxAndroidBindingContext bindingContext) : base(bindingContext)
        {
        }

        // Custom code...
    }
}

To set this adapter via xml you will need to provide it in the MvxAdapter attribute on the MvxSmartRecyclerView. It must be of the format: Fully.Qualified.ClassName, Assembly.Name. Hence, for the example above: let us say the assembly will be AppointmentPlanner.DroidX and as you see the namespace is AppointmentPlanner.DroidX.Components then the string will be: AppointmentPlanner.DroidX.Components.AppointmentSmartRecyclerAdapter, AppointmentPlanner.DroidX.

<MvvmCross.SmartRecyclerView.MvxSmartRecyclerView
    app:MvxAdapter="AppointmentPlanner.DroidX.Components.AppointmentSmartRecyclerAdapter, AppointmentPlanner.DroidX"/>

Note: if you do not provide a MvxAdapter, the MvxSmartRecyclerView will fallback to use MvxSmartRecyclerAdapter.

Dragging Items

To enable the dragging feature, we need to modify our xml and set the EnableDrag attribute to true.

<MvvmCross.SmartRecyclerView.MvxSmartRecyclerView
    app:EnableItemDrag="true"/>

Prevent Dragging Items

If you want to prevent certain items in the list from being dragged, you can create a custom adapter that inherits MvxSmartRecyclerAdapter and override the ShouldDragItem(object item) method. This allows you to specify conditions on which items are allowed to be dragged.

public class AppointmentSmartRecyclerAdapter : MvxSmartRecyclerAdapter
{
    // Constructors...

    public override bool ShouldDragItem(object item)
    {
        if (item is something...)
        {
            // Allow dragging
            return true;
        }
        else
        {
            // Prevent dragging
            return false;
        }
    }
}

Swiping Items

To enable the swiping feature, we need to modify our xml and set EnableSwipeRight and/or EnableSwipeLeft attributes to true.

<MvvmCross.SmartRecyclerView.MvxSmartRecyclerView
    app:EnableSwipeRight="true"
    app:EnableSwipeLeft="true"
    app:MvxBind="ItemSwipeRight SwipeRightCommand;
            ItemSwipeLeft SwipeLeftCommand;"/>

Swipe actions are bindable and can have 2 different actions depending on the direction of the swipe. ItemSwipeLeft and ItemSwipeRight are bindable and are done in the same way as MvxRecyclerView's ItemClickCommand and ItemLongClickCommand.

Prevent Swiping Items

If you want to prevent certain items in the list from being swiped left or right, you can create a custom adapter that inherits MvxSmartRecyclerAdapter and override either the ShouldSwipeLeft(object item) and/or ShouldSwipeRight(object item) methods. This allows you to specify conditions on which items are allowed to be swiped left or right.

public class AppointmentSmartRecyclerAdapter : MvxSmartRecyclerAdapter
{
    // Constructors...

    public override bool ShouldSwipeLeft(object item)
    {
        if (item is something...)
        {
            // Allow left swipe
            return true;
        }
        else
        {
            // Prevent left swipe
            return false;
        }
    }

    public override bool ShouldSwipeRight(object item)
    {
        if (item is something...)
        {
            // Allow right swipe
            return true;
        }
        else
        {
            // Prevent right swipe
            return false;
        }
    }
}

Swipe Backgrounds

We can show different backgrounds for an item depending on the swipe direction. In this example, we create 2 new layout files:

UnplanItemBackgroundView.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   android:id="@id/smart_recycler_item_left_background_view"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:orientation="horizontal"
   android:gravity="start"
   android:background="@android:color/holo_blue_light">
    <ImageView
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_centerVertical="true"
        android:layout_marginRight="10sp"
        android:background="@drawable/abc_ic_ab_back_material"
        android:contentDescription="Unplan Item" />
</LinearLayout>

RemoveItemBackgroundView.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   android:id="@id/smart_recycler_item_right_background_view"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:orientation="horizontal"
   android:gravity="end"
   android:background="@android:color/holo_red_light">
    <ImageView
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_centerVertical="true"
        android:layout_marginLeft="10sp"
        android:background="@drawable/abc_ic_clear_material"
        android:contentDescription="Remove Item" />
</LinearLayout>

Important: the important thing to notice in these files is that each layout has an android:id attribute. This is important for the control because it identifies which layout to show when swiping left or right, or not swiping at all. The android:ids needed for the control are:

  • android:id="@id/smart_recycler_item_left_background_view" (show layout when swiping right)
  • android:id="@id/smart_recycler_item_right_background_view" (show layout when swiping left)
  • android:id="@id/smart_recycler_item_foreground_view" (show layout for item when user is not swiping).

We then modify our StudentView.xml to include these layouts and wrap everything in a FrameLayout, making sure the background layouts are added first. We also need to add android:id="@id/smart_recycler_item_foreground_view" to the nested LinearLayout holding the default view to show when the user isn't swiping.

Note: by default, the LinearLayout's background is transparent, resulting in the background views to show. The LinearLayout's background attribute is set to @android:color/white to ensure the background views are hidden.

StudentView.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <include
        layout="@layout/unplanitembackgroundview"/>
    <include
        layout="@layout/removeitembackgroundview"/>
    <LinearLayout
        android:id="@id/smart_recycler_item_foreground_view"
        android:layout_width="match_parent"
        android:layout_height="30dp"
        android:orientation="horizontal"
        android:gravity="center"
        android:background="@android:color/white">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:MvxBind="Text Format('{0} {1} - {2} on {3:d}', FirstName, LastName, Lesson.Subject, Lesson.DateTime);"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginHorizontal="4sp"
            android:textColor="?android:attr/colorAccent"
            app:MvxBind="Text Sequence;"/>
    </LinearLayout>
</FrameLayout>

Dynamic Swipe Backgrounds

If you want to show different swipe backgrounds depending on certain conditions relating to the item, you can create a custom adapter that inherits MvxSmartRecyclerAdapter and override either the GetLeftBackgroundResourceId(object item) and/or GetRightBackgroundResourceId(object item) methods. This allows you to specify conditions on what backgrounds to show when swiping left or right. You will also need to override the GetBackgroundResourceIds() method to return a list of Resource Ids that will be used for the background views to help prevent visual bugs.

For example, continuing on from the Swipe Backgrounds section above, we will create 2 more layouts:

PlanItemBackgroundView.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/plan_item_background_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    android:gravity="start"
    android:background="@android:color/holo_green_light">
    <ImageView
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_centerVertical="true"
        android:layout_marginRight="10sp"
        android:background="@drawable/abc_ic_ab_back_material"
        android:contentDescription="Plan Item" />
</LinearLayout>

ResetItemBackgroundView.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/reset_item_background_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    android:gravity="end"
    android:background="@android:color/holo_orange_light">
    <ImageView
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_centerVertical="true"
        android:layout_marginLeft="10sp"
        android:background="@drawable/abc_ic_clear_material"
        android:contentDescription="Reset Item" />
</LinearLayout>

Important: make sure each layout has an android:id attribute. This is important for the control because it will help identify which layout to show when swiping left or right. In this example, they are:

  • android:id="@+id/plan_item_background_view"
  • android:id="@+id/reset_item_background_view"

In our custom adapter we make sure to override the required methods: GetLeftBackgroundResourceId(object item), GetRightBackgroundResourceId(object item) and GetBackgroundResourceIds():

public class AppointmentSmartRecyclerAdapter : MvxSmartRecyclerAdapter
{
    // Constructors...

    public override IEnumerable<int> GetBackgroundResourceIds() => new List<int>(1)
    {
        Resource.Id.reset_item_background_view,
        Resource.Id.plan_item_background_view,
    };

    public override int GetLeftBackgroundResourceId(object item)
    {
        if (item is something...)
        {
            return Resource.Id.plan_item_background_view;
        }

        return base.GetLeftBackgroundResourceId(item);
    }

    public override int GetRightBackgroundResourceId(object item)
    {
        if (item is something...)
        {
            return Resource.Id.reset_item_background_view;
        }

        return base.GetRightBackgroundResourceId(item);
    }
}

Note: the base implementation for:

  • GetLeftBackgroundResourceId(object item) returns Resource.Id.smart_recycler_item_left_background_view
  • GetRightBackgroundResourceId(object item) returns Resource.Id.smart_recycler_item_right_background_view

We then modify our StudentView.xml to include these new layouts and wrap everything in a FrameLayout, making sure the background layouts are added first.

StudentView.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <include
        layout="@layout/unplanitembackgroundview"/>
    <include
        layout="@layout/removeitembackgroundview"/>
		
    <include
        layout="@layout/planitembackgroundview"/>
    <include
        layout="@layout/resetitembackgroundview"/>
		
    <LinearLayout
        android:id="@id/smart_recycler_item_foreground_view"
        android:layout_width="match_parent"
        android:layout_height="30dp"
        android:orientation="horizontal"
        android:gravity="center"
        android:background="@android:color/white">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:MvxBind="Text Format('{0} {1} - {2} on {3:d}', FirstName, LastName, Lesson.Subject, Lesson.DateTime);"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginHorizontal="4sp"
            android:textColor="?android:attr/colorAccent"
            app:MvxBind="Text Sequence;"/>
    </LinearLayout>
</FrameLayout>

Note: for the actual logic that gets executed when swiping left or right, the ItemSwipeLeft and ItemSwipeRight will need to have logic that accounts for this.

Performance Optimisation

If you want to fine-tune when the RecyclerView should update its views, you can create a custom adapter that inherits MvxSmartRecyclerAdapter and override the CreateDiffUtilHelper(IList oldList, IList newList) method. This method allows you to return a DiffUtil.Callback object which helps the RecyclerView determine what views to update when performing an operation (add, remove, update). By default, this method returns a MvxDefaultSmartDiffUtilHelper which handles any object. However, you can override the method and return your own custom class that inherits MvxSmartDiffUtilHelper<T> to fine-tune when the RecyclerView should update its views.

Using the list of students as an example:

public class AppoinmentDiffUtilHelper : MvxSmartDiffUtilHelper<Student>
{
    public AppoinmentDiffUtilHelper(IList oldList, IList newList) 
        : base(oldList, newList)
    {
    }

    protected override bool AreContentsTheSame(Student oldItem, Student newItem)
    {
        return oldItem.FirstName == newItem.FirstName 
            && oldItem.LastName == newItem.LastName 
            && oldItem.Lesson.Subject == newItem.Lesson.Subject 
            && oldItem.Lesson.DateTime == newItem.Lesson.DateTime;
    }

    protected override bool AreItemsTheSame(Student oldItem, Student newItem)
    {
        return oldItem.FirstName == oldItem.FirstName
            && oldItem.LastName == newItem.LastName;
    }
}

Note: the AreItemsTheSame(Student oldItem, Student newItem) method checks if the two items represent the same item. If true: the AreContentsTheSame(Student oldItem, Student newItem) method is called and checks if the item's content has been changed. If true: the RecyclerView updates the corresponding view, otherwise it doesn't.

In our custom adapter we make sure to override the CreateDiffUtilHelper(IList oldList, IList newList) method and return a new instance of our custom DiffUtil.Callback class: AppoinmentDiffUtilHelper.

public class AppointmentSmartRecyclerAdapter : MvxSmartRecyclerAdapter
{
    // Constructors...

    protected override DiffUtil.Callback CreateDiffUtilHelper(IList oldList, IList newList) => new AppoinmentDiffUtilHelper(oldList, newList);
}
Product Compatible and additional computed target framework versions.
MonoAndroid monoandroid10.0 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on MvxSmartRecyclerView:

Package Downloads
MvxSectionedRecyclerView

MvvmCross is the .NET MVVM framework for cross-platform solutions, including Xamarin iOS, Xamarin Android, Xamarin Forms, Windows and Mac. This is an unofficial package that contains a sectioned AndroidX RecyclerView with out-of-the-box functionality such as dragging items up and down and swiping them, supported for MvvmCross.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.0.1 147 2/10/2024
1.0.0 126 2/1/2024