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
Related
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);
}
sorry for my bad english
My problem: when I rotate my application, all data is lost, I am using a ViewModel, my data loads when I push the button, this is my code for test .
https://github.com/elviss116/androidMvvm-master
This is the Fragment :
public class MovieListFragment extends Fragment {
private MovieListViewModel mViewModel;
private List<MovieModel.DataModel> movieList = new ArrayList<>();
private RecyclerView recyclerView;
private MoviesAdapter mAdapter;
private Button cargar;
public static MovieListFragment newInstance() {
return new MovieListFragment();
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container,
#Nullable Bundle savedInstanceState) {
View view= inflater.inflate(R.layout.movie_list_fragment, container, false);
recyclerView = (RecyclerView)view.findViewById(R.id.recycler_view);
cargar = view.findViewById(R.id.btnCargar);
mAdapter = new MoviesAdapter(movieList);
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getContext());
recyclerView.setLayoutManager(mLayoutManager);
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.setAdapter(mAdapter);
return view;
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
cargar.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mViewModel = ViewModelProviders.of(MovieListFragment.this).get(MovieListViewModel.class);
mViewModel.init();
mViewModel.getMovies().observe(MovieListFragment.this, new Observer<MovieModel>() {
#Override
public void onChanged(#Nullable MovieModel movieModels) {
movieList.addAll(movieModels.getData());
mAdapter.notifyDataSetChanged();
}
});
}
});
}
}
this is the ViewModel :
public class MovieModel {
#SerializedName("data")
List<DataModel> data;
public List<DataModel> getData() {
return data;
}
public void setData(List<DataModel> data) {
this.data = data;
}
public class DataModel {
#SerializedName("name")
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
this is the adapter :
public class MoviesAdapter extends RecyclerView.Adapter<MoviesAdapter.MyViewHolder> {
private List<MovieModel.DataModel> moviesList;
public class MyViewHolder extends RecyclerView.ViewHolder {
public TextView name;
public MyViewHolder(View view) {
super(view);
name = (TextView) view.findViewById(R.id.name);
}
}
public MoviesAdapter(List<MovieModel.DataModel> moviesList) {
this.moviesList = moviesList;
}
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.movie_list_row, parent, false);
return new MyViewHolder(itemView);
}
#Override
public void onBindViewHolder(MyViewHolder holder, int position) {
MovieModel.DataModel movie = moviesList.get(position);
holder.name.setText(movie.getName());
}
#Override
public int getItemCount() {
return moviesList.size();
}
}
When you rotate your phone the activity gets destroyed and re-created, so that is why you're seeing your data dissapear.
What I would recommend is grabbing and observing the view model when the activity is created, outside of the click listener. This way the fragment will immediately start observing the data when it is rotated, but will only be initialized when the button is clicked.
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mViewModel = ViewModelProviders.of(MovieListFragment.this).get(MovieListViewModel.class);
mViewModel.getMovies().observe(MovieListFragment.this, new Observer<MovieModel>() {
#Override
public void onChanged(#Nullable MovieModel movieModels) {
movieList.addAll(movieModels.getData());
mAdapter.notifyDataSetChanged();
}
});
cargar.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mViewModel.init();
}
});
}
how can i filter Recycler View using SearchView
i try all the video tuturial but all of them get thier item Recycler View from a list
but i get my item from db and dont know how should i do this
this is my adapter Code
public class AthleteAdapter extends RecyclerView.Adapter<AthleteAdapter.AthleteHolder> {
private List<Athlete> athletes = new ArrayList<>();
private OnItemClickListener listener;
#NonNull
#Override
public AthleteHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View itemview = LayoutInflater.from(parent.getContext()).inflate(R.layout.athlete_item, parent, false);
return new AthleteHolder(itemview);
}
#Override
public void onBindViewHolder(#NonNull AthleteHolder holder, int position) {
Athlete currnetAthlete = athletes.get(position);
holder.textViewName.setText(currnetAthlete.getAthlete_name());
holder.textViewLastname.setText(currnetAthlete.getAthlete_lastname());
holder.textView_phonenumber.setText(currnetAthlete.getAthlete_phonenumber());
holder.textViewTime1.setText(String.valueOf(currnetAthlete.getSign_in_time()));
holder.textViewTime2.setText(String.valueOf(currnetAthlete.getCheck_tution_time()));
}
public void setAthletes(List<Athlete> athletes) {
this.athletes = athletes;
notifyDataSetChanged();
}
public Athlete getposition(int position) {
return athletes.get(position);
}
#Override
public int getItemCount() {
return athletes.size();
}
public void setOnItemClickListener(OnItemClickListener listener) {
this.listener = listener;
}
public interface OnItemClickListener {
void onItemClick(Athlete athlete);
}
class AthleteHolder extends RecyclerView.ViewHolder {
private TextView textViewName, textViewLastname, textView_phonenumber, textViewTime1, textViewTime2;
public AthleteHolder(#NonNull View itemView) {
super(itemView);
textViewName = itemView.findViewById(R.id.name_main);
textViewLastname = itemView.findViewById(R.id.lastname_main);
textView_phonenumber = itemView.findViewById(R.id.textView_number);
textViewTime1 = itemView.findViewById(R.id.time1);
textViewTime2 = itemView.findViewById(R.id.time2);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int position = getAdapterPosition();
if (listener != null && position != RecyclerView.NO_POSITION) {
listener.onItemClick(athletes.get(position));
}
}
});
}
}
}
Step 1 : Get all data from db and give that list to Adapter
usersList = db.getAll();
adapter = new dataAdapter(getApplicationContext(), usersList);
adapter.setdatainterfacemethod(this);
Step 2 : get Text from Searchview
sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
#Override
public boolean onQueryTextSubmit(String query) {
return false;
}
#Override
public boolean onQueryTextChange(String newText) {
filter(s.toString());
}
return false;
}
});
Step 3 : compare searchview text and list text if exits then give list to adapter
public void filter(String text) {
List<Users> name = new ArrayList<>();
for (Users u : usersList) {
if (u.getNAME().toLowerCase().contains(text.toLowerCase())) {
name.add(u);
}
}
adapter.filterlist(name);
}
Step 4 :- change list and notify adapter (in adapter class)
public void filterlist(List<Users> name) {
this.usersList = name;
notifyDataSetChanged();
}
--------------------- Adapter class -----------------
public class dataAdapter extends RecyclerView.Adapter<dataAdapter.viewholder> {
public List<Users> usersList;
Context context;
dataInterface dataInterface;
public dataAdapter(Context context, List<Users> usersList) {
this.usersList = usersList;
this.context = context;
}
public void setdatainterfacemethod(dataInterface dataInterface) {
this.dataInterface = dataInterface;
}
#NonNull
#Override
public viewholder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.include_recycle_demo, viewGroup, false);
return new viewholder(view);
}
#Override
public void onBindViewHolder(#NonNull final viewholder viewholder, final int i) {
}
#Override
public int getItemCount() {
return usersList.size();
}
public void filterlist(List<Users> name) {
this.usersList = name;
notifyDataSetChanged();
}
public class viewholder extends RecyclerView.ViewHolder {
public viewholder(#NonNull View itemView) {
super(itemView);
}
}
}
I have a RecyclerView Fragment to which I added a Filter Search in an edit text. It works, but when I click in a Card of the Recycler, it goes to the wrong detail. My best guess is that it's getting the wrong position from the getAdapterPosition since let's say I have this list {a,b,c,d,e,f,g,h}. when I filter and get two itmes left like {d,g}. If I click d it redirects to a, if I click g it redirects to b.
This is my RecyclerView
public class RecyclerProfile extends Fragment implements
Adapter.AdapterListener, com.example.cake.profiling.Adapter.SearchListener
{
private RecyclerListener recyclerListener;
private List<Profile> profiles = (new DAOProfile()).getProfile();
private Adapter recyclerAdapter= new Adapter(profiles, this);
public RecyclerProfile() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view= inflater.inflate(R.layout.fragment_recycler_profile,
container, false);
RecyclerView recyclerView = view.findViewById(R.id.recyclerProfile);
EditText editText = view.findViewById(R.id.editText);
editText.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int
count) {
}
#Override
public void afterTextChanged(Editable s) {
filter(s.toString());
}
});
recyclerView.setHasFixedSize(true);
RecyclerView.LayoutManager layoutManager = new
LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(recyclerAdapter);
setHasOptionsMenu(true);
return view;
}
private void filter(String text){
ArrayList<Profile> filteredList = new ArrayList<>();
for (Profile profile: profiles){
if (profile.getName().toLowerCase().contains(text.toLowerCase())){
filteredList.add(profile);
}
}
recyclerAdapter.filterList(filteredList);
profiles = new ArrayList<>(filteredList);
}
#Override
public void listen(Profile profile, Integer position) {
recyclerListener.send(profile, position);
}
#Override
public void profileSelected(Profile profile) {
}
//INTERFACE
public interface RecyclerListener {
void send(Profile profile, Integer position);
}
//ON ATTACH
#Override
public void onAttach(Context context) {
super.onAttach(context);
this.recyclerListener = (RecyclerListener) context;
}
}
This is my Adapter
public class Adapter extends RecyclerView.Adapter<Adapter.ProfileViewHolder> {
private List<Profile> profiles;
private AdapterListener adapterListener;
//CONSTRUCTOR
public Adapter(List<Profile> profiles,AdapterListener adapterListener) {
this.profiles = profiles;
this.adapterListener = adapterListener;
}
#NonNull
#Override
public Adapter.ProfileViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
Context context = parent.getContext();
LayoutInflater layoutInflater = LayoutInflater.from(context);
View view = layoutInflater.inflate(R.layout.card_profile, parent, false);
ProfileViewHolder profileViewHolder = new ProfileViewHolder(view);
return profileViewHolder;
}
#Override
public void onBindViewHolder(#NonNull Adapter.ProfileViewHolder holder, int position) {
Profile profile = profiles.get(position);
holder.setter(profile);
}
#Override
public int getItemCount() {
return profiles.size();
}
public void filterList (ArrayList<Profile> filteredList){
profiles = filteredList;
notifyDataSetChanged();
}
//VIEWHOLDER
class ProfileViewHolder extends RecyclerView.ViewHolder{
private ImageView image;
private TextView name;
public ProfileViewHolder(View itemView) {
super(itemView);
name = itemView.findViewById(R.id.nameProfile);
image = itemView.findViewById(R.id.imageProfile);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Profile profile = profile.get(getAdapterPosition());
adapterListener.receive(profile, getAdapterPosition());
}
});
}
public void setter (Profile profile){
name.setText(profile.getName());
image.setImageResource(profile.getImage());
}
}
public interface AdapterListener {
void receive(Profile profile, Integer position);
}
}
getAdapterPosition()
this will return the position of the item of current data set. This is what I'd do
class ProfileViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private ImageView image;
private TextView name;
Profile mProfile;
public ProfileViewHolder(View itemView) {
super(itemView);
name = itemView.findViewById(R.id.nameProfile);
image = itemView.findViewById(R.id.imageProfile);
name.setOnClickListener(this);
image.setOnClickListener(this);
}
public void setter (Profile profile){
if(profile != null) {
mProfile = profile;
name.setText(mProfile.getName());
image.setImageResource(mProfile.getImage());
}
}
}
#Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.item_nameProfile:
case R.id.item_imageProfile:
for(int i = 0 ; i < profiles.size() ; i++) {
if(profiles.get(i).getName().equalsIgnoreCase(mProfile.getName()) {
adapterListener.receive(profiles.get(i), i);
break;
}
}
break;
}
}
public interface AdapterListener {
void receive(Profile profile, Integer position);
}
}
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 !