Android RecyclerView prevent recycling all items - java

I have a RecyclerView in which the user can drag and drop an item to a different position in the RecyclerView.
So far everything works fine.
My problem begins when the RecyclerView has more items than it can display. So when it recycle his content. When the user drag and drop an item to a different position and than scroll away from this part the position changes are not saved. The user just see the old positions for the items.
You can see this issue in the .gif below.
I already tried several things out like:
recyclerViewItem.getRecycledViewPool().setMaxRecycledViews(0,0);
recyclerViewItem.setItemViewCacheSize(30);
and
holder.setIsRecyclable(false);
inside of my onBindViewHolder.
But nothing seems to work for me.
Does anyone know a solution for this problem?
My Fragment class:
public class ShoppinglistFragment extends Fragment {
private ViewModel viewModel;
private ShoppinglistAdapter adapterShoppingItem = new ShoppinglistAdapter();
private RecyclerView recyclerViewItem;
private Boolean fragmentStarted = false;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.shoppinglist_layout, container, false);
recyclerViewItem = view.findViewById(R.id.recyclerViewShoppinglist);
return view;
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
viewModel = ViewModelProviders.of(getActivity()).get(ViewModel.class);
fragmentStarted = true;
recyclerViewItem.setLayoutManager(new LinearLayoutManager(getContext()));
recyclerViewItem.setHasFixedSize(false);
recyclerViewItem.getRecycledViewPool().setMaxRecycledViews(0,0);
recyclerViewItem.setItemViewCacheSize(30);
recyclerViewItem.setAdapter(adapterShoppingItem);
ItemTouchHelper.Callback callback =
new ItemMoveCallback(adapterShoppingItem);
ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
touchHelper.attachToRecyclerView(recyclerViewItem);
//fetch all Shoppinglist Items from local storage
viewModel.getAllShoppinglistItems().observe((LifecycleOwner) this, new Observer <List<Shoppinglist>>() {
#Override
public void onChanged(#Nullable List<Shoppinglist> items) {
if (fragmentStarted){
adapterShoppingItem.submitList(items);
fragmentStarted = false;
}
}
});
adapterShoppingItem.setOnPositionChanged(new ShoppinglistAdapter.onPositionChanged() {
#Override
public void onPositionChanged(Shoppinglist fromItem, Shoppinglist toItem, int fromPosition, int toPosition) {
int fromPositionServer = fromItem.getPosition();
int toPositionServer = toItem.getPosition();
fromItem.setPosition(toPositionServer);
toItem.setPosition(fromPositionServer);
//updating Shoppinglist Item locally
viewModel.updateItem(fromItem);
viewModel.updateItem(toItem);
}
});
}
}}
and my Adapter class:
public class ShoppinglistAdapter extends ListAdapter<Shoppinglist, ShoppinglistAdapter.NoteHolder> implements ItemMoveCallback.ItemTouchHelperContract {
public Integer ressourceType;
private onPositionChanged onPositionChanged;
private ArrayList<Shoppinglist> globalItemList = new ArrayList<Shoppinglist>();
public ShoppinglistAdapter() {
super(DIFF_CALLBACK);
Integer valueOf = Integer.valueOf(0);
this.ressourceType = valueOf;
}
private static final DiffUtil.ItemCallback<Shoppinglist> DIFF_CALLBACK = new DiffUtil.ItemCallback<Shoppinglist>() {
#Override
public boolean areItemsTheSame(Shoppinglist oldItem, Shoppinglist newItem) {
return oldItem.getSqlid() == newItem.getSqlid();
}
#Override
public boolean areContentsTheSame(Shoppinglist oldItem, Shoppinglist newItem) {
return oldItem.getTitle().equals(newItem.getTitle());
}
};
#NonNull
#Override
public NoteHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.shoppinglist_item, parent, false);
return new NoteHolder(itemView);
}
#Override
public void onBindViewHolder(#NonNull NoteHolder holder, int position) {
Shoppinglist currentItem = getItem(position);
holder.setIsRecyclable(false);
holder.tv.setText(currentItem.getTitle());
...
}
public Shoppinglist getNoteAt(int position) {
return getItem(position);
}
public class NoteHolder extends RecyclerView.ViewHolder {
private TextView tv;
public NoteHolder(View itemView) {
super(itemView);
tv= itemView.findViewById(R.id.tv);
globalItemList .add(currentNote);
}
}
public interface onPositionChanged {
void onPositionChanged(Shoppinglist fromItem, Shoppinglist toItem, int fromPosition, int toPosition);
}
public void setOnPositionChanged(ShoppinglistAdapter.onPositionChanged listener) {
this.onPositionChanged = listener;
}
#Override
public void onRowMoved(int fromPosition, int toPosition) {
//send switched Items to Fragment
onPositionChanged.onPositionChanged(globalItemList.get(fromPosition), globalItemList.get(toPosition), fromPosition, toPosition);
//switch Items inside of the global Item List
Collections.swap(globalItemList, fromPosition, toPosition);
notifyItemMoved(fromPosition, toPosition);
}
#Override
public void onRowSelected(NoteHolder myViewHolder) {}
#Override
public void onRowClear(NoteHolder myViewHolder) {}
#Override
public int getItemViewType(int position) {
return super.getItemViewType(position);
}
}

The problem is that you should swap items in ListAdapter.mDiffer which holds the current showing list. So, globalItemList in your code is unused. Replace onRowMoved of your adapter with the following one:
#Override
public void onRowMoved(int fromPosition, int toPosition) {
ArrayList<Shoppinglist> list = new ArrayList(getCurrentList());
Collections.swap(list, fromPosition, toPosition);
submitList(list);
}

Related

how to remove one adapter from inner recycle view android java

i have nested recycle view and i want to remove one item (inner recycle view) from the outer recycle view but my code clear all adapters in the inner recycle view so how can i delete or clear only one list adapter from inner recycle view and is their is a better way to make nested recycle view .....?
public class MainWorkingTimeAdapter extends RecyclerView.Adapter<MainWorkingTimeAdapter.MainWorkingTimeViewHolder> {
private ArrayList<String> list = new ArrayList();
OnItemListener pOnItemListener;
String DAY_TAG;
Context context ;
ArrayList<WorkingTime>workingTimes;
String TAG ="MainWorkingTimeAdapter";
#NonNull
#Override
public MainWorkingTimeViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
return new MainWorkingTimeViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.main_working_time_layout, parent, false), pOnItemListener);
}
#Override
public void onBindViewHolder(#NonNull MainWorkingTimeViewHolder holder, #SuppressLint("RecyclerView") int position) {
holder.adapter = new WorkingTimePreviewAdapter();
holder.adapter.setList(workingTimes,list.get(position).toLowerCase(Locale.ROOT));
}
holder.recyclerView.setAdapter(holder.adapter);
holder.recyclerView.setLayoutManager(new LinearLayoutManager(context));
holder.switch_.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (pOnItemListener != null) {
pOnItemListener.switchClicked(isChecked,position);
}
if(!isChecked){
holder.adapter.clearList();
}
}
});
}
#Override
public int getItemCount() {
return list.size();
}
public void setList(ArrayList<String> list,String dayTag,Context context, OnItemListener onItemListener) {
this.list = list;
this.context =context;
this.DAY_TAG =dayTag;
this.pOnItemListener = onItemListener;
notifyDataSetChanged();
}
public class MainWorkingTimeViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
OnItemListener onItemListener;
RecyclerView recyclerView ;
WorkingTimePreviewAdapter adapter ;
Button button;
Switch switch_ ;
TextView title ;
public MainWorkingTimeViewHolder(#NonNull View v, OnItemListener onItemListener) {
super(v);
recyclerView = v.findViewById(R.id.recycler_main_work_time);
button = v.findViewById(R.id.button_right_main_work_time);
title = v.findViewById(R.id.tv_title_main_work_time);
switch_ = v.findViewById(R.id.switch_day_preview);
adapter = new WorkingTimePreviewAdapter();
this.onItemListener = onItemListener;
button.setOnClickListener(this);
}
#Override
public void onClick(View v) {
onItemListener.rightButtonClicked(getAdapterPosition(),list.get(getAdapterPosition()).toLowerCase(Locale.ROOT));
}
}
public void updateRecycler(ArrayList<WorkingTime> workingTimes,int pos){
this.workingTimes = workingTimes;
notifyItemChanged(pos);
}
public interface OnItemListener {
void rightButtonClicked(int pos,String day);
void switchClicked(boolean isChecked,int pos);
}

Disable buttons in recycler view after clicking

I have a recycler view, which consists of edittext and buttons for each view. I would like to have functionality in such a way that if I enter some text or attach an image for a view, then some of the buttons' functionality needs to be disabled in that view as soon as I click the button.
With the below code, I can attach images when ProofClick function is called via the interface, and then I set the button as disabled for the same position, but the button is not disabled, and I can attach images to the same view twice, which I do not want.
Adapter class:
// Check if the button is clicked and disable
private boolean clicked = false;
public InsStepsContentAdapter(List<InspectonScroll> scrollList,
ClickOnRecycle clickOnRecycle, String[] options) {
this.scrollList = scrollList;
this.clickOnRecycle = clickOnRecycle;
this.options = options;
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.activity_recycler_steps, parent, false));
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.setIsRecyclable(false);
holder.progressbar.setMax(scrollList.size());
holder.title.setText(scrollList.get(position).getSteps().get(position).getTitle());
holder.btnAttach.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(clicked){
clicked = true;
clickOnRecycle.ProofClick(holder.getAdapterPosition());
holder.btnAttach.setEnabled(true);
} else{
holder.btnAttach.setEnabled(false);
}
}
});
}
public int getItemCount() {
return scrollList.size();
}
#Override
public int getItemViewType(int position) {
return position;
}
public void updateList(List<ScrollList> scrollList){
this.scrollList = scrollList;
notifyDataSetChanged();
}
Model class:
public class InspectonScroll {
private List<Steps> Steps = new ArrayList<>();
public List<Steps> getSteps() {
return steps;
}
public void setSteps(List<Steps> steps) {
this.steps = steps;
}
}
ButtonAdapter
public class ButtonAdapter extends RecyclerView.Adapter<ButtonAdapter.ViewHolder> {
Activity activity;
ArrayList<ButtonModel> arrayList;
public ButtonAdapter(Activity activity, ArrayList<ButtonModel> arrayList) {
this.activity = activity;
this.arrayList = arrayList;
}
#NonNull
#Override
public ButtonAdapter.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
return new ViewHolder(activity.getLayoutInflater().inflate(R.layout.row_item,parent,false));
}
#Override
public void onBindViewHolder(#NonNull ButtonAdapter.ViewHolder holder, int position) {
holder.button.setText("item"+position);
ButtonModel item = arrayList.get(position);
holder.button.setEnabled(item.isEnabled);
holder.button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if(item.isEnabled){
item.isEnabled = false;
notifyDataSetChanged();
}
Toast.makeText(activity, "Clicked", Toast.LENGTH_SHORT).show();
}
});
}
#Override
public int getItemCount() {
return arrayList.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
Button button;
public ViewHolder(#NonNull View itemView) {
super(itemView);
button = itemView.findViewById(R.id.button);
}
}
}
ButtonModel
public class ButtonModel {
boolean isEnabled = true;
}
one thing can be done here is maintaining the state of the button in the model as shown in the example and by using that state we can enable or disable the button in the onBindViewHolder method .

RecylerView does not remove last item

I have a list adapter connected to a recycler view. I use SwipToDelete which calls deleteItem().
If I remove an item, the list entry in the recycler view gets removed.
The problem: If there is only one item in the recycler view and I try to remove it, the list entry is still visible in the recycler view. But it is gone as soon as a restart the app.
I think notifyItemRemoved() should take care of it, like it does if there is more than one item.
RepoListAdapter:
public class RepoListAdapter extends ListAdapter<Repo, RepoViewHolder> {
private RecyclerView recyclerView;
public RepoListAdapter(#NonNull DiffUtil.ItemCallback<Repo> diffCallback) {
super(diffCallback);
}
#NonNull #Override
public RepoViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int viewType) {
return RepoViewHolder.create(viewGroup);
}
#Override
public void onBindViewHolder(RepoViewHolder holder, int position) {
Repo current = getItem(position);
holder.bind(current.getName());
}
public static class RepoDiff extends DiffUtil.ItemCallback<Repo> {
#Override
public boolean areItemsTheSame(#NonNull Repo oldItem, #NonNull Repo newItem) {
return oldItem == newItem;
}
#Override
public boolean areContentsTheSame(#NonNull Repo oldItem, #NonNull Repo newItem) {
return oldItem.getName().equals(newItem.getName());
}
}
#Override
public void onAttachedToRecyclerView(#NonNull RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
this.recyclerView = recyclerView;
}
public Context getContext() {
return recyclerView.getContext();
}
public void deleteItem(int position) {
Repo recentlyDeletedRepo = getItem(position);
RepoViewModel repoViewModel = new ViewModelProvider((ViewModelStoreOwner) getContext()).get(RepoViewModel.class);
repoViewModel.delete(recentlyDeletedRepo);
notifyItemRemoved(position);
notifyItemRangeRemoved(position, 1);
Snackbar snackbar = Snackbar.make(recyclerView, R.string.snackbar_undo, Snackbar.LENGTH_LONG);
snackbar.setAction(R.string.snackbar_undo, v -> undoDelete(recentlyDeletedRepo, position));
snackbar.show();
}
private void undoDelete(Repo recentlyDeletedRepo, int position) {
RepoViewModel repoViewModel = new ViewModelProvider((ViewModelStoreOwner) getContext()).get(RepoViewModel.class);
repoViewModel.insert(recentlyDeletedRepo);
notifyItemInserted(position);
}
}
RepoViewModel:
public class RepoViewModel extends AndroidViewModel {
private final RepoRepository repoRepository;
private final LiveData<List<Repo>> allRepos;
private Repo currentRepo;
public RepoViewModel(Application application) {
super(application);
repoRepository = new RepoRepository(application);
allRepos = repoRepository.getAllRepos();
}
public Repo getCurrentRepo() {
return currentRepo;
}
public void setCurrentRepo(Repo currentRepo) {
this.currentRepo = currentRepo;
}
public LiveData<Repo> getRepoById(int id) {
return repoRepository.getRepoById(id);
}
public LiveData<List<Repo>> getAllRepos() {
return allRepos;
}
public long insert(Repo repo) {
return repoRepository.insert(repo);
}
public void update(Repo repo) {
repoRepository.update(repo);
}
public void delete(Repo repo) {
repoRepository.delete(repo);
}
}
ProjectsFragment:
public class ProjectsFragment extends Fragment {
private RecyclerView recyclerView;
private TextView textview_no_project;
private RepoListAdapter repoListAdapter;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
FloatingActionButton floatingActionButton = ((MainActivity) requireActivity()).getFloatingActionButton();
floatingActionButton.setVisibility(View.VISIBLE);
return inflater.inflate(R.layout.fragment_projects, container, false);
}
public void onViewCreated(#NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
textview_no_project = view.findViewById(R.id.textview_no_project);
repoListAdapter = new RepoListAdapter(new RepoListAdapter.RepoDiff());
recyclerView = view.findViewById(R.id.recyclerview);
recyclerView.setLayoutManager(new LinearLayoutManager(requireActivity()));
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(), DividerItemDecoration.VERTICAL);
recyclerView.addItemDecoration(dividerItemDecoration);
recyclerView.setAdapter(repoListAdapter);
ItemTouchHelper itemTouchHelper = new
ItemTouchHelper(new SwipeToDeleteCallback(repoListAdapter));
itemTouchHelper.attachToRecyclerView(recyclerView);
RepoViewModel repoViewModel = new ViewModelProvider(requireActivity()).get(RepoViewModel.class);
repoViewModel.getAllRepos().observe(getViewLifecycleOwner(), this::onLoaded);
}
private void onLoaded(List<Repo> list){
if (list.size() == 0) {
textview_no_project.setVisibility(View.VISIBLE);
}
else {
textview_no_project.setVisibility(View.INVISIBLE);
repoListAdapter.submitList(list);
}
}
}
just try notifyDataSetChanged(); in your adapter

Two recyclerView and one onClick showing the content of the clicked element of the respective recyclerView

Problem
In my acitvity i have two recyclerViews showing popular movies and top rated movies respectively.
When I click on a movie in a recyclerView, that movie is sent to an activity showing its details.
I go back to the home and if I click on a movie of another recyclerView, the previously clicked movie is shown.
I need to show the details of any clicked movie in any recyclerView.
I have a single viewHolder (MovieViewHolder)
public class MovieViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
//Widgets
TextView title, rating;
ImageView imageView;
//Click listener
OnMovieListener onMovieListener ;
public MovieViewHolder(#NonNull View itemView, OnMovieListener onMovieListener) {
super(itemView);
this.onMovieListener = onMovieListener;
title = itemView.findViewById(R.id.movie_title);
imageView = itemView.findViewById(R.id.movie_img);
rating = itemView.findViewById(R.id.rating_bar);
itemView.setOnClickListener(this);
}
#Override
public void onClick(View v) {
onMovieListener.onMovieClick(getAdapterPosition());
}
An interface
public interface OnMovieListener {
void onMovieClick(int position);
void onCategoryClick(String category);
}
This two Adapter:
This for popular movies
public class MovieRecyclerView extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<MovieModel> mMovies;
private final OnMovieListener onMovieListener;
public MovieRecyclerView(OnMovieListener onMovieListener) {
this.onMovieListener = onMovieListener;
}
#NonNull
#Override
public RecyclerView.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.movie_item,
parent, false);
return new MovieViewHolder(view, onMovieListener);
}
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder holder, int position) {
((MovieViewHolder) holder).title.setText(mMovies.get(position).getTitle());
((MovieViewHolder) holder).rating.setText(String.valueOf((float) (mMovies.get(position).getVote_average())));
//ImageView: Using Glide Library
Glide.with(holder.itemView.getContext())
.load("https://image.tmdb.org/t/p/w500/"
+ mMovies.get(position).getPoster_path())
.into(((MovieViewHolder) holder).imageView);
}
#Override
public int getItemCount() {
if (mMovies != null) {
return mMovies.size();
}
return 0;
}
public void setmMovies(List<MovieModel> mMovies) {
this.mMovies = mMovies;
notifyDataSetChanged();
}
//Getting the id of the movie clicked
public MovieModel getSelectedMovie(int position) {
if (mMovies != null) {
if (mMovies.size() > 0) {
return mMovies.get(position);
}
}
return null;
}
This for top_rated
public class MovieLatestRecyclerView extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<MovieModel> mMoviesLatest;
private OnMovieListener onMovieListener;
public MovieLatestRecyclerView(OnMovieListener onMovieListener) {
this.onMovieListener = onMovieListener;
}
#NonNull
#Override
public RecyclerView.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.movie_item,
parent, false);
return new MovieViewHolder(view, onMovieListener);
}
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder holder, int position) {
((MovieViewHolder)holder).title.setText(mMoviesLatest.get(position).getTitle());
((MovieViewHolder)holder).rating.setText(String.valueOf((float) (mMoviesLatest.get(position).getVote_average())));
//ImageView: Using Glide Library
Glide.with(holder.itemView.getContext())
.load("https://image.tmdb.org/t/p/w500/"
+ mMoviesLatest.get(position).getPoster_path())
.into(((MovieViewHolder)holder).imageView);
}
#Override
public int getItemCount() {
if(mMoviesLatest != null) {
return mMoviesLatest.size();
}
return 0;
}
public void setmMoviesLatest(List<MovieModel> mMoviesLatest) {
this.mMoviesLatest = mMoviesLatest;
notifyDataSetChanged();
}
//Getting the id of the movie clicked
public MovieModel getSelectedMovie(int position){
if(mMoviesLatest != null){
if(mMoviesLatest.size() > 0){
return mMoviesLatest.get(position);
}
}
return null;
}
In MainActivity i configure the recyclerViews with this method
private void ConfigureRecyclerView() {
movieRecyclerViewAdapter = new MovieRecyclerView(this);
movieLatestRecyclerViewAdapter = new MovieLatestRecyclerView(this);
recyclerView_popular.setAdapter(movieRecyclerViewAdapter);
recyclerView_popular.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
recyclerView_latest.setAdapter(movieLatestRecyclerViewAdapter);
recyclerView_latest.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
//RecyclerView Pagination
//Loading next page of api response
recyclerView_popular.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(#NonNull RecyclerView recyclerView, int newState) {
if (!recyclerView.canScrollVertically(1)) {
//Here we need to display the next search result
movieListViewModel.searchNextPage();
}
}
});
recyclerView_latest.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(#NonNull RecyclerView recyclerView, int newState) {
if (!recyclerView.canScrollVertically(1)) {
//Here we need to display the next search result
movieListViewModel.searchNextPage();
}
}
});
}
And i use this for passing the selected movie to another activity
#Override
public void onMovieClick(int position) {
//WE don't need position of the movie in recyclerView
// We need the ID of the movie in order to get all it's details
Intent intent = new Intent(this, MovieDetails.class);
intent.putExtra("movie", movieRecyclerViewAdapter.getSelectedMovie(position));
startActivity(intent);
}
Whoever you are reading my question I thank you for your time and I apologize for the certain banality and length.
At this line:
intent.putExtra("movie", movieRecyclerViewAdapter.getSelectedMovie(position));
You are getting a movie from movieRecyclerViewAdapter. This adapter is only used with the 'Popular' movie recycler view.
onMovieClick is used for both the 'Popular' and the 'Top Rated' recycler views. So, clicking an item from 'Top Rated' will attempt to use an item from 'Popular'.
To fix this, a click from 'Top Rated' should not attempt to get an item from movieRecyclerViewAdapter.
You could do something like change onMovieClick(int position) to onMovieClick(MovieModel movie), so you don't have to worry about knowing which adapter to get the movie from.

RecyclerView drag and drop not working with several view types

I am trying to use a RecyclerView with a vertical LinearLayoutManager in order to display a list items. This list can contain several different item types (differents layouts), and it can be reordered by the user using drag and drop.
For the item types, as documented in Android documentation, I have overridden the getItemType method in order to handle different types of views in the recycler and handle it in the onCreateViewHolder and onBindViewHolder. This works like a charm.
For the drag and drop reorder, I have used a ItemTouchHelper.Callback (inspired by this sample project). This also works well.
The problem happen when I try to use different items types AND the drag and drop behaviour. As long as the drag occurs between items of the same type, this works well, but when i'm draggin a view of type A over a view of type B, the drag stop and the view returned to it's original position.
Here is my code:
MyFragment.java
public class MyFragment extends Fragment implements MyAdapter.Listener {
private MyViewModel mViewModel;
private RecyclerView mRecyclerView;
private MyAdapter mAdapter;
private ItemTouchHelper mItemTouchHelper;
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment, container, false);
mRecyclerView = root.findViewById(R.id.recyclerView);
mAdapter = new MyAdapter(this);
mRecyclerView.setAdapter(mAdapter);
mItemTouchHelper = new ItemTouchHelper(new MyDragHelperCallback(mAdapter));
mItemTouchHelper.attachToRecyclerView(mRecyclerView);
mViewModel = ViewModelProviders.of(getActivity()).get(MyViewModel.class);
mViewModel.addObserver(this, new Observer<List<Item>>() {
#Override
public void onChanged(#Nullable List<Item> items) {
mAdapter.updateList(items);
}
});
mAdapter.updateList(mViewModel.getList());
return root;
}
#Override
public void onStartDragRequest(#NonNull RecyclerView.ViewHolder viewHolder) {
mItemTouchHelper.startDrag(viewHolder);
}
}
MyDragHelperCallback.java
public class MyDragHelperCallback extends ItemTouchHelper.Callback {
private static final int DRAG_MOVEMENT_FLAGS = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
#NonNull
private MyDragListener mListener;
public MyDragHelperCallback(#NonNull MyDragListener listener) {
mListener = listener;
}
#Override
public boolean isLongPressDragEnabled() {
return true;
}
#Override
public boolean isItemViewSwipeEnabled() {
return false;
}
#Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
if (!(recyclerView.getLayoutManager() instanceof LinearLayoutManager)) {
throw new IllegalArgumentException("Should only be used with a LinearLayoutManager");
}
return makeMovementFlags(DRAG_MOVEMENT_FLAGS, 0);
}
#Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
mListener.onItemMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition());
return true;
}
#Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
// Nothing to do
}
}
MyDragListener.java
public interface MyDragListener {
void onItemMoved(int from, int to);
}
MyAdapter.java
public class MyAdapter extends RecyclerView.Adapter<MyHolder> implements MyDragListener {
#Nullable
private List<Item> mList;
public void updateList(#Nullable List<Item> list) {
mList = list;
notifyDataSetChanged();
}
#Override
public MyHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == 0) {
return new MyHolderBis(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_bis, parent, false));
}
return new MyHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false));
}
#Override
public void onBindViewHolder(final MyHolder holder, int position) {
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View view) {
mListener.onStartDragRequest(holder);
return true;
}
});
// bind views to data
}
#Override
public int getItemCount() {
return mList != null ? mList.size() : 0;
}
#Override
public int getItemViewType(int position) {
return mList.get(position).getType();
}
#Override
public void onItemMoved(int from, int to) {
notifyItemMoved(from, to);
}
}
Is there something I missing ?
Or is this simply to possible to achieve ?
Thanks for all your answers !

Categories