Never code any boilerplate RecyclerAdapter again!
This library will make it easy and painless to map your data item with a target ViewHolder.
- Smart OnClick / OnLongClickListener SimpleItemOnClickOnLongClickActivity
- State holding with OnItemSelectedListener MultipleViewTypesResolverActivity
- Custom View Events CustomViewEventActivity
- Drag & drop DragAndDropItemActivity
- Drag & drop with handle DragAndDropHandleItemActivity
- Swipe to remove item SwipeRemoveItemActivity
- Drag & drop, Swipe, View Events MultipleEventsAndExtensionsActivity
- Grid + Drag & drop GridActivity
- Multiple ViewHolder types resolver MultipleViewTypesResolverActivity
- Multiple items select MultiSelectItemsActivity
- Single RadioButton select SingleSelectRadioButtonItemActivity
- Multiple CheckBox select MultiSelectCheckBoxItemsActivity
- Multiple Switch select MultiSelectSwitchItemsActivity
- Multiple Expandable items MultipleExpandableItemActivity
- Single Expandable item SingleExpandableItemActivity
- Nested SmartRecyclerAdapter NestedSmartRecyclerAdaptersActivity
- Endless scroll EndlessScrollActivity
- Endless scroll with load more button EndlessScrollLoadMoreButtonActivity
- Diff Util extension DiffUtilActivity
- Kotlin + AndroidX Coming soon
- Java + AndroidX v3.0.0
- Java + AppCompat v2.2.0
Add jcenter() or maven { url "https://dl.bintray.com/manneohlund/maven" } to your build.gradle under repositories
dependencies {
implementation 'io.github.manneohlund:smart-recycler-adapter:3.0.0'
}SmartRecyclerAdapter
.items(items)
.map(MoviePosterModel.class, PosterViewHolder.class)
.map(MovieBannerModel.class, BannerViewHolder.class)
.map(MovieModel.class, MovieViewHolder.class)
.map(TopNewsModel.class, TopNewsViewHolder.class)
.into(recyclerView);Just extend your ViewHolder class with SmartViewHolder and pass in the target type ex SmartViewHolder<Mail>.
Note that the constructor can both take View or ViewGroup as parameter, in this case PosterViewHolder(ViewGroup parentView) to avoid casting to ViewGroup while inflating.
The parentView is the recyclerView.
The method unbind has an default implementation and is optional.
public class PosterViewHolder extends SmartViewHolder<MoviePosterModel> {
public PostViewHolder(ViewGroup parentView) {
super(LayoutInflater.from(parentView.getContext()).inflate(R.layout.poster_view, parentView, false));
}
@Override
public void bind(MoviePosterModel model) {
Glide.with(imageView)
.load(model.posterUrl)
.into(imageView);
}
@Override
public void unbind() {
Glide.with(imageView).clear(imageView);
}
} You can easily assign events to views and add an OnViewEventListener to the SmartRecyclerAdapter for easy event handling.
SmartRecyclerAdapter
.items(items)
.map(MovieModel.class, MovieViewHolder.class)
// Adds a basic `OnViewEventListener` to any `SmartViewHolder` extension that implements `ViewEventListenerHolder`
.addViewEventListener((view, eventId, position) -> handleItemEvent())
.into(recyclerView);In your view holder, add eg OnClickListener to a view and call onViewEvent on the OnViewEventListener.
Your ViewHolder must implements ViewEventListenerHolder to recieve the OnViewEventListener.
class MovieViewHolder
extends SmartViewHolder<MovieModel>
implements SmartViewEventListenerHolder {
private OnViewEventListener viewEventListener;
@Override
public void setOnViewEventListener(@NonNull OnViewEventListener viewEventListener) {
this.viewEventListener = viewEventListener;
}
@Override
public void bind(MovieModel movieModel) {
imageView.setOnClickListener(view ->
viewEventListener.onViewEvent(view, R.id.action_play_movie, getAdapterPosition()));
}
}If you are lazy and want to auto assign a predefined onClickListener and onLongClickListener with eventIds R.id.event_on_click and R.id.event_on_long_click,
Default implemented view event id for OnItemClickListener is R.id.event_on_click.
Default implemented view id for OnItemClickListener is R.id.undefined.
R.id.undefined targets root view of the ViewHolder (ViewHolder.itemView).
interface OnMovieItemClickListener extends OnItemClickListener {
@NonNull
@Override
default Class<? extends SmartViewHolder> getViewHolderType() {
return MovieViewHolder.class;
}
}SmartRecyclerAdapter will automatically bind an View.OnClickListener to a view with id R.id.movie_info_button.
interface OnMovieInfoButtonClickListener extends OnItemClickListener {
@NonNull
@Override
default Class<? extends SmartViewHolder> getViewHolderType() {
return MovieViewHolder.class;
}
@Override
default int getViewId() {
return R.id.movie_info_button;
}
}And add event listener to SmartRecyclerAdapter builder.
SmartRecyclerAdapter
.items(items)
.map(HeaderModel.class, HeaderViewHolder.class)
.map(MovieModel.class, MovieViewHolder.class)
.map(MovieTrailerModel.class, MovieTrailerViewHolder.class)
// Adds `OnItemClickListener` and auto binds `View.OnClickListener` on all ViewHolders.
.addViewEventListener((OnItemClickListener) (view, eventId, position) -> handleEvent(eventId))
// Adds event listener for MovieViewHolder only and overrides any generic `OnItemClickListener`
.addViewEventListener((OnMovieItemClickListener) (view, eventId, position) -> playMovie())
// Adds event listener for MovieViewHolder only and auto binds `View.OnClickListener` on view with id `R.id.movie_info_button`
.addViewEventListener((OnMovieInfoButtonClickListener) (view, eventId, position) -> showMovieInfo(position))
.into(recyclerView);If you want to bind one data type with different view holders depending on some attribute you can set a ViewTypeResolver.
Note .map() call not needed in this case but you can combine if you want to.
SmartRecyclerAdapter
.items(items)
.setViewTypeResolver((item, position) -> {
if (item instanceof MovieTrailerModel) {
return MovieTrailerViewHolder.class;
} else if (item instanceof MovieModel && ((MovieModel)item).isRatedR()) {
return RMovieViewHolder.class;
} return MovieViewHolder.class; // Add default view if needed, else SmartRecyclerAdapter will look at the base `.map` mapping
})
.into(recyclerView);New in SmartRecyclerAdapter v2.0.0 is support for nested recycler adapter.
Now you can easily build complex nested adapters without hustle and have full control of the adapter in your view controlling Fragment or Activity.
Use the new create() method instead of the into(recyclerView) to create just the SmartRecyclerAdapter then set the adapter to the recycler view in your ViewHolder.
Just implement the SmartAdapterHolder interface in your ViewHolder and SmartRecyclerAdapter will handle the mapping.
SmartRecyclerAdapter myWatchListSmartMovieAdapter = SmartRecyclerAdapter
.items(myWatchListItems)
.map(MovieModel.class, ThumbViewHolder.class)
.addViewEventListener((OnItemClickListener) (view, eventId, position) -> playMovie())
.create();SmartRecyclerAdapter
.items(items)
.map(MoviePosterModel.class, PosterViewHolder.class)
.map(MyWatchListModel.class, MyWatchListViewHolder.class)
.map(MyWatchListViewHolder.class, myWatchListSmartMovieAdapter)
.into(recyclerView);class MyWatchListViewHolder
extends SmartViewHolder<MyWatchListModel>
implements SmartAdapterHolder {
// Constructor here
@Override
public void setSmartRecyclerAdapter(SmartRecyclerAdapter smartRecyclerAdapter) {
recyclerView.setLayoutManager(new LinearLayoutManager(context, HORIZONTAL, false));
recyclerView.setAdapter(smartRecyclerAdapter);
}
public void bind(MyWatchListModel myWatchListModel) {
// bind model data to views
}
public void unbind() {
// optional unbinding of view data model
}
}A popular feature in apps is to have endless scrolling with pagination, in other words load more items when user has scrolled to bottom. With SmartEndlessScrollRecyclerAdapter you can achieve this.
SmartEndlessScrollRecyclerAdapter endlessScrollAdapter = SmartEndlessScrollRecyclerAdapter
.items(items)
.map(MovieModel.class, MovieViewHolder.class)
.into(recyclerView);Called when scrolled to the last item and loading view is visible.
endlessScrollAdapter.setOnLoadMoreListener(() -> {
endlessScrollAdapter.addItems(moreItems);
});Enable/Disable endless scrolling and thus removing the loading view.
endlessScrollAdapter.setEndlessScrollEnabled(false);
You can also set your custom loading/loadmore view.
endlessScrollAdapter.setCustomLoadMoreLayoutResource(R.layout.your_custom_loadmore_view);
For more samples test out the sample app and see the source code.
Sometimes a ViewHolder created by the Adapter cannot be recycled due to its transient state.
In order to fix this is to implement Re in your SmartViewHolder extension so that upon receiving this callback,
Adapter can clear the animation(s) that effect the View's transient state and return true so that the View can be recycled.
class MovieViewHolder
extends SmartViewHolder
implements RecyclableViewHolder {
@Override
public boolean onFailedToRecycleView() {
// Clear animations or other stuff
return true;
}
}If you want to catch when the view is attached and detached from the window in your ViewHolder you can implement OnViewAttachedToWindowListener and OnViewDetachedFromWindowListener in your SmartViewHolder extension.
Becoming detached from the window is not necessarily a permanent condition the consumer of an Adapter's views may choose to cache views offscreen while they are not visible, attaching and detaching them as appropriate.
public class MovieViewHolder
extends SmartViewHolder
implements OnViewAttachedToWindowListener,
OnViewDetachedFromWindowListener {
@Override
public void onViewAttachedToWindow() {
// Restore
}
@Override
public void onViewDetachedFromWindow() {
// Cache
}
}More guides coming to the Wiki Page
Variable parameter overloading with many different addViewEventListener calls.
.addViewEventListener(
MovieViewHolder.class,
R.id.event_on_click,
(view, eventId, position) -> playMovie())class MovieViewHolder
extends SmartAutoEventViewHolder<MyWatchListModel>
implements SmartAdapterHolder {}Create an OnItemClickListener for MovieViewHolder.
SmartAutoEvent implementations has been removed so no need for ex MovieViewHolder to extend SmartAutoEventViewHolder.
interface OnMovieItemClickListener extends OnItemClickListener {
@NonNull
@Override
default Class<? extends SmartViewHolder> getViewHolderType() {
return MovieViewHolder.class;
}
}Add listener to the SmartAdapterBuilder.
.addViewEventListener((OnMovieItemClickListener) (view, eventId, position) -> playMovie())SmartRecyclerAdapter adapter = SmartRecyclerAdapter
.items(items)
.map(MovieModel.class, MovieViewHolder.class)
.into(recyclerView);
// We can add more data
adapter.addItems(items);
// Add data at index with animation
adapter.addItem(0, item);
// Add data at index without animation
adapter.addItem(0, item, false);
// Remove item at index with animation
adapter.removeItem(0);
// Remove item at index without animation
adapter.removeItem(0, false);
// Replace item at index with animation
adapter.replaceItem(0, item);
// Replace item at index without animation
adapter.replaceItem(0, item, false);
// Get items by type
adapter.getItems(MovieModel.class);
// Delete all items in the list
adapter.clear();