There are posts on how to click on a certain item in a RecyclerView at whatever position you input, but is there a way to click on all the views, especially given the fact that I may not know how many views there will be in the Recycler View.
I thought of getting the number of items in the RecyclerView like in this link but it involves getting the actual RecyclerView from the activity which sometimes is not possible.
I've also seen a block of code like:
public static class RecyclerViewItemCountAssertion implements ViewAssertion { private final int expectedCount;
public RecyclerViewItemCountAssertion(int expectedCount) {
this.expectedCount = expectedCount;
}
#Override
public void check(View view, NoMatchingViewException noViewFoundException) {
if (noViewFoundException != null) {
throw noViewFoundException;
}
RecyclerView recyclerView = (RecyclerView) view;
RecyclerView.Adapter adapter = recyclerView.getAdapter();
assertThat(adapter.getItemCount(), is(expectedCount));
}
}
But i'm unsure of how to manipulate to get the count
So simply in your test:
final View viewById = activityTestRule.getActivity().findViewById(R.id.your_recycler_view_id);
final RecyclerView recyclerView = (RecyclerView) viewById;
final RecyclerView.Adapter adapter = recyclerView.getAdapter();
final int itemsCount = adapter.getItemCount();
So you can iterate through from 0 to itemsCount-1
You can use this class to get recycler view element at position (in your case 0 to itemsCount-1)
public class RecyclerViewMatcher {
private final int recyclerViewId;
public RecyclerViewMatcher(int recyclerViewId) {
this.recyclerViewId = recyclerViewId;
}
public Matcher<View> atPosition(final int position) {
return atPositionOnView(position, -1);
}
public Matcher<View> atPositionOnView(final int position, final int targetViewId) {
return new TypeSafeMatcher<View>() {
Resources resources = null;
View childView;
public void describeTo(Description description) {
String idDescription = Integer.toString(recyclerViewId);
if (this.resources != null) {
try {
idDescription = this.resources.getResourceName(recyclerViewId);
} catch (Resources.NotFoundException var4) {
idDescription = String.format("%s (resource name not found)", recyclerViewId);
}
}
description.appendText("with id: " + idDescription);
}
public boolean matchesSafely(View view) {
this.resources = view.getResources();
if (childView == null) {
RecyclerView recyclerView =
(RecyclerView) view.getRootView().findViewById(recyclerViewId);
if (recyclerView != null && recyclerView.getId() == recyclerViewId) {
childView = recyclerView.findViewHolderForAdapterPosition(position).itemView;
} else {
return false;
}
}
if (targetViewId == -1) {
return view == childView;
} else {
View targetView = childView.findViewById(targetViewId);
return view == targetView;
}
}
};
}}
and use this class like this:
onView(new RecyclerViewMatcher(R.id.your_recycler_view_id)
.atPositionOnView(0, R.id.your_item_body))
.perform(click());
So finally you are able to click all items in your recyclerview. I hope it would help. Cheers.
I'm thinking out loud here, but a couple of things I would try are:
a- Check to see if there is any way to put this in the ViewHolder/RecyclerView Adapter. That really depends on when you want to trigger the click on all items so I'm guessing it won't work for you.
b- Try a while loop with a try/catch for getting the item. This doesn't sound like a good idea from a software engineering best practices perspective, but it should work if your goal is just to get something working.
c- If you can't get the recycler view itself, is there some way you can access the arraylist (or whatever) you used to populate the recyclerView itself?
Some questions:
1- In what cases is it not possible for you to get the actual recycler view? And if you can't get it, then how will you trigger its onclick anyway?
2- Could you maybe share your code and where you need to do this?
disclaimer: I'm a bit new to android development, but I hope this helps.
To be thread safe you should use custom view action something like
#RunWith(AndroidJUnit4.class)
public class XXXXActivityTest {
int count=0;
#Test
public void xxxxxxx() throws Exception {
onView(allOf(withId(R.id.drawer_list))).perform(new ViewAction() {
#Override
public Matcher<View> getConstraints() {
return null;
}
#Override
public String getDescription() {
return null;
}
#Override
public void perform(UiController uiController, View view) {
count=((ListView)view).getAdapter().getCount();
}
});
}
}
then you can iterate with
onView(new RecyclerViewMatcher(R.id.drawer_list)
.atPositionOnView(0, R.id.your_item_body))
.perform(click());
assertThat(.......) for all items
Related
I have a recycleview showing a list of audio files fetched from my audios.json file hosted on my server. i have a model class with a getter method getLanguage() to see the audio language. I would like to show only audio files of users preference in recycle view. Say for example, if user wants only english and russian i would like to show only list of russian and english. How can we achieve this? Right now the entire list is displayed.
public class AudioAdapter extends RecyclerView.Adapter<AudioAdapter.HomeDataHolder> {
int currentPlayingPosition = -1;
Context context;
ItemClickListener itemClickListener;
List<Output> wikiList;
public AudioAdapter(List<Output> wikiList, Context context) {
this.wikiList = wikiList;
this.context = context;
}
#NonNull
#Override
public HomeDataHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(context).inflate(R.layout.audio_row_layout,viewGroup,false);
HomeDataHolder mh = new HomeDataHolder(view);
return mh;
}
#Override
public void onBindViewHolder(#NonNull final HomeDataHolder homeDataHolder, int i) {
String desc = wikiList.get(i).getLanguage() + " • " + wikiList.get(i).getType();
homeDataHolder.tvTitle.setText(wikiList.get(i).getTitle());
homeDataHolder.tvotherinfo.setText(desc);
homeDataHolder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (itemClickListener != null)
itemClickListener.onClick(view,homeDataHolder.getAdapterPosition());
}
});
homeDataHolder.rippleLayout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (itemClickListener != null)
itemClickListener.onClick(view,homeDataHolder.getAdapterPosition());
}
});
}
#Override
public int getItemCount() {
return wikiList.size();
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getItemViewType(int position) {
return position;
}
public void setClickListener(ItemClickListener itemClickListener) { // Method for setting clicklistner interface
this.itemClickListener = itemClickListener;
}
public class HomeDataHolder extends RecyclerView.ViewHolder {
TextView tvTitle,tvotherinfo;
MaterialRippleLayout rippleLayout;
public HomeDataHolder(View v) {
super(v);
this.tvTitle = v.findViewById(R.id.title);
this.tvotherinfo = v.findViewById(R.id.audioDesc);
this.rippleLayout = v.findViewById(R.id.ripple);
}
}
}
The general idea for this should be:
you have one list with all items
you have filter rules selected by the user
You filter items from number 1, to see which ones match the constraints and store this in another list.
Then the recycler view only shows the items of the list from number 3.
This means that recycler view's getItemCount would return the size of the filtered list, not the whole list.
Instead of passing the wikiList as it is, filter it then send it:
Lets say that you filled up the wikiList, before passing it to the adapter, filter it like this:
In the activity that you initialize the adapter in:
public class YourActivity extends ............{
........
........
//your filled list
private List<Output> wikiList;
//filtered list
private List<Output> filteredList= new ArrayList<Output>();
//filters
private List<String> filters = new ArrayList<String>();
//lets say the user chooses the languages "english" and "russian" after a button click or anything (you can add as many as you want)
filters.add("english");
filters.add("russian");
//now filter the original list
for(int i = 0 ; i<wikiList.size() ; i++){
Output item = wikiList.get(i);
if(filters.contains(item.getLanguage())){
filteredList.add(item);
}
}
//now create your adapter and pass the filteredList instead of the wikiList
AudioAdapter adapter = new AudioAdapter(filteredList , this);
//set the adapter to your recyclerview........
......
.....
......
}
I use above "english" and "russian" for language. I don't know how they are set in your response, maybe you use "en" for "english" so be careful.
I have a recycler and inside of it there are cardviews where I fetch information from a REST service, I'm trying to implement an endless scroll, It's supposed that user will see 10 cardviews every time he scrolls down until there are no more cardviews to show, How can I achieve that?
I've seen a few examples but none of them really helped me about how to do it. I don't even know what I need to put in adapter.class or in my Fragment.class because I don't understand how to implement that, it would be great if someone could tell me the correct way to implement the infinite scroll in my code...
Thanks in advance.
MainAdapter.class
public class MainAdapter extends RecyclerView.Adapter<MainAdapter.ViewHolder> implements View.OnClickListener
{
private ArrayList<Business> businessList;
private Activity activity;
private int layoutMolde,idb;
public MainAdapter(Activity activity, ArrayList<Business> list, int layout)
{
this.activity = activity;
this.businessList = list;
layoutMolde = layout;
}
#Override
public MainAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.main_row, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(ViewHolder holder, int position)
{
holder.mTitle.setText(businessList.get(position).getBusiness_name());
holder.number_rating.setText(businessList.get(position).getRating().toString());
Glide.with(activity).load(businessList.get(position).getLogo_url_string()).into(holder.mImg);
}
#Override
public int getItemCount() {
return businessList.size();
}
#Override
public void onClick(View v)
{
}
public class ViewHolder extends RecyclerView.ViewHolder {
public TextView mTitle;
public ImageView mImg;
public ImageView logo;
public RatingBar main_rating;
public TextView number_rating;
public ViewHolder( View itemView)
{
super(itemView);
mTitle = (TextView) itemView.findViewById(R.id.nom_business_main);
number_rating = (TextView) itemView.findViewById(R.id.number_rating);
mImg = (ImageView) itemView.findViewById(R.id.img_main);
main_rating=(RatingBar) itemView.findViewById(R.id.rating_main);
main_rating.setRating((float)1);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v)
{
Intent in = new Intent(v.getContext(), BusinessPremium.class);
int position = getAdapterPosition();
idb = businessList.get(position).getId();
in.putExtra("no", idb);
v.getContext().startActivity(in);
}
});
}
}
}
FeedsFragment.class
public class FeedsFragment extends Fragment
{
private ArrayList<Business> arrayBusiness,arrayBasics;
private Gson gson;
private static final Type BUSINESS_TYPE = new TypeToken<ArrayList<Business>>() {}.getType();
private RecyclerView.LayoutManager mLayoutManager;
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View android = inflater.inflate(R.layout.fragment_feeds, container, false);
if (!internetConnectionCheck(FeedsFragment.this.getActivity()))
{
Toast.makeText(getApplicationContext(), "Error de Conexión", Toast.LENGTH_LONG).show();
}
new RequestBase(getActivity()) {
#Override
public JsonObject onHttpOk(JsonObject response) throws JSONException {
JsonObject objeto, pagination_details = null, details, premium_img;
JsonArray data;
if (getActivity() == null)
return response;
if (response.get("pagination") == null)
{
objeto = response;
} else {
objeto = response;
pagination_details = response.get("pagination").getAsJsonObject();
data = objeto.get("data").getAsJsonArray();
gson = new Gson();
arrayBusiness = gson.fromJson(data, BUSINESS_TYPE);
Log.d("size", String.valueOf(arrayBusiness.size()));
FeedsFragment.this.getActivity().runOnUiThread(new Runnable()
{
#Override
public void run()
{
RecyclerView recycler = (RecyclerView) FeedsFragment.this.getActivity().findViewById(R.id.recycler_main);
MainAdapter adapter = new MainAdapter(getActivity(), arrayBusiness, R.layout.main_row);
recycler.setNestedScrollingEnabled(false);
mLayoutManager = new GridLayoutManager(FeedsFragment.this.getActivity(), 2);
recycler.setLayoutManager(mLayoutManager);
recycler.setAdapter(adapter);
GifTextView loading = (GifTextView)FeedsFragment.this.getActivity().findViewById(R.id.loading);
TextView loadingText = (TextView)FeedsFragment.this.getActivity().findViewById(R.id.loadingText);
loading.setVisibility(View.GONE);
loadingText.setVisibility(View.GONE);
}
});
}
if (pagination_details.isJsonNull()) {
Log.d("Paginacion", pagination_details.toString());
}
return objeto;
}
#Override
public void onHttpCreate(JsonObject response) throws JSONException
{
}
#Override
public void onHttpUnprocessableEntity(JsonObject response) throws JSONException
{
this.cancel(true);
final String error = response.get("errors").toString();
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
Toast.makeText(getActivity().getApplicationContext(), error, Toast.LENGTH_LONG).show();
}
});
}
}.execute("businesses/premiums", "GET");
return android;
}
}
you can refresh using SwipeRefreshLayout in android to refresh and in the on refresh override method call your api
note:put your API call request in a method and call that method inyour onRefresh method of SwipeRefreshLayout
When writing RecyclerView.Adapter, you anyway need to provide the getItemCount method that returns the correct number of items (may be large). RecyclerView will call on its own initiative the onBindViewHolder(holder, position) method of this adapter. All you need is to provide functionality of retrieving data, relevant to this position. There is no difference at all, if your list is smaller than screen, slightly larger than screen or Integer.MAX_VALUE size. RecyclerView will take care not to fetch/allocate too much extra items.
You do not need to implement scroll listeners or otherwise explicitly handle the scrolling.
The only tricky part is that you may need to take a long action like server call to get some items. Then just return uninitialized holder (empty view) on the first invocation and start fetching the needed row in the background thread. When you have it, call notifyDataSetChanged or notifyItemRangeChanged, and RecyclerView will take care to update itself.
For performance reasons I would strongly recommend to update content in chunks of the fixed size rather than sending individual server request per every row displayed. For some public servers like Google Books this is clearly a requirement, as they have quota limits per request.
If you need to view the full source code on how this possibly could be implemented, there is an open source project here in GitHub.
Make a static boolean variable named "ready" and initialize it to false.
Add the if ready condition in the onLoadMore method as below.
public boolean onLoadMore(int page, int totalItemsCount) {
if (ready) {
//load more from API
}
return false;
}
set ready to true in onBindViewHolder when the position of item is last.
Here is a way that a colleague of mine introduced. we worked in it together and i implemented it successfully with no issues. I wanted to give back to anyone having this issue.
in your adapter you need to set the count to be infinite size and then when you want the position of an item you should use val loopPos = position % dataSource.size anytime you need the position. lets take a look how this can be done in a recyclerView adapter but could also be applied to FragmentStatePagerAdapter.
class InfiniteLoopingHorizontalRecyclerViewAdapter(var dataSource: ArrayList<String>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflatedView: View = LayoutInflater.from(parent.context)
.inflate(R.layout.your_finite_layout, parent, false)
return ItemHolder(inflatedView)
}
override fun getItemCount(): Int {
return Integer.MAX_VALUE //***** this should be high enough - wink wink ******
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
//****** this is critical here when you need the position use the loopPos ****/
val loopPos = position % dataSource.size
(holder as? ItemHolder)?.bind(dataSource[loopPos], loopPos)
}
inner class ItemHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bind(myString: String, position: Int) = with(itemView) {
myTextView.setText(myString)
}
}
}
how it works:
lets say your dataSource size is 50 but your position is at 51 that means the following: 51%50 . which gives you position 1. and lets say again your position is 57 and again your dataSource size is still 50. that means your position is 7. so to be clear, anytime you need a infinite affect you can use the modules of the position and the dataSource size.
ps:
lets go crazy and say we scrolled to position 11323232323214 then that means 11323232323214%50 = 14 so its position 14 in your datasource that will be used. you can then polish off the affect with wrapping your recyclerview in a SnapHelper class
You can add a scrollListener to your recyclerview.
Check a similar answer here
And the main SO post here
Where, the scrollListener will check where exactly are you in the recyclerview and based on some logic (which you can flexibly write) make a second call!
I have a weird problem. I switched to RecyclerView from ListView and I can't refresh or notify of change in my ListView. I tried calling Item.this.notifyDataSetChanged();
and other methods to refresh View but it doesn't work.
Instead RecyclerView is refreshed when I scroll(regardless of direction). How can I notify my RecyclerView when there is a change?
CODE:
#Override
public void onBindViewHolder(Ids holder, final int position) {
rowItemClass = (ListViewRow) rowItems.get(position);
Log.e("swag", "OYOYOYOYOYO");
if (Globals.isPlaying && Globals.pos == position) {
if (pausedSamePos == true) {
holder.pauseed_play.setVisibility(View.VISIBLE);
holder.playing_pause.setVisibility(View.GONE);
} else {
holder.pauseed_play.setVisibility(View.GONE);
holder.playing_pause.setVisibility(View.VISIBLE);
}
holder.song_currenttime_sb.setActive();
holder.song_duration.setVisibility(View.INVISIBLE);
holder.song_duration_sb.setVisibility(View.VISIBLE);
holder.seekbar.setActive();
} else {
holder.seekbar.setInactive();
holder.song_currenttime_sb.setInactive();
holder.song_icon.setImageResource(rowItemClass.getImageId());
holder.song_duration_sb.setVisibility(View.INVISIBLE);
holder.song_duration.setVisibility(View.VISIBLE);
holder.pauseed_play.setVisibility(View.GONE);
holder.playing_pause.setVisibility(View.GONE);
}
sharedPreference = new SharedPreference();
holder.song_duration.setTypeface(Globals
.getTypefaceSecondary(context));
holder.song_duration_sb.setTypeface(Globals
.getTypefaceSecondary(context));
holder.song_name.setTypeface(Globals.getTypefacePrimary(context));
holder.song_currenttime_sb.setTypeface(Globals
.getTypefaceSecondary(context));
holder.song_name.setText(rowItemClass.getTitle());
holder.song_duration.setText(rowItemClass.getDesc());
holder.song_duration_sb.setText(rowItemClass.getDesc());
holder.favorite.setTag(position);
holder.song_currenttime_sb.setTag(position);
holder.seekbar.setTag(position);
holder.clickRegister.setTag(position);
holder.song_icon.setTag(position);
holder.song_name.setTag(position);
holder.song_duration.setTag(position);
holder.song_duration_sb.setTag(position);
holder.more_options.setTag(position);
// int task_id = (Integer) holder.seekbar.getTag();
final Ids finalHolder = holder;
holder.clickRegister.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
try {
if ((Globals.isPlaying.booleanValue())
&& (Globals.pos == position)) {
pausePlaying();
} else {
Globals.stopPlaying();
pausedSamePos = false;
Globals.pos = position;
Globals.isPlaying = true;
Item.this.notifyDataSetChanged();
Globals.mp = MediaPlayer.create(context, Integer
.valueOf(Item.this.songPos[position])
.intValue());
Globals.mp.start();
Globals.pos = position;
Globals.isPlaying = Boolean.valueOf(true);
Item.this.notifyDataSetChanged();
Globals.mp
.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
#Override
public void onCompletion(
MediaPlayer mpOnComplete) {
mpOnComplete.release();
Globals.isPlaying = false;
pausedSamePos = false;
Globals.isPlaying = Boolean
.valueOf(false);
finalHolder.menu_options
.startAnimation(new ViewExpandAnimation(
finalHolder.menu_options));
Item.this.notifyDataSetChanged();
}
});
}
} catch (Exception localException) {
}
}
});
holder.clickRegister
.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
Globals.stopPlaying();
Item.this.notifyDataSetChanged();
return true;
}
});
}
I've been struggling hard with a similar issue, trying to handle a use-case where all of the adapter's content has to be replaced and the recycler-view should start from scratch: calling notifyDataSetChanged(), swapAdapter() with numerous combinations of subsequent calls to view/layout-manager invalidation requests resulted in nothing but a (seemingly) empty recycler-view. The view didn't even try to rebind the view holders.
What seemed to have worked it out is this hack-ish fix:
view.swapAdapter(sameAdapter, true);
view.scrollBy(0, 0);
As it turns out, scrollBy (even with 0 offsets) drives the recycler-view into laying out its views and executing the pending view-holders rebinding.
if you want notify your recycleListView just simple call notifyDataSetChanged(); in your class adapter. this is my method in my adapter class :
public class ListAdapter extends RecyclerView.Adapter<Listdapter.ViewHolder> {
....
private List<DesaObject> desaObjects= new ArrayList<DesaObject>();
public void setListObjects(List<DesaObject> desaObjects){
if(this.desaObjects.size()>0)
this.desaObjects.clear();
this.desaObjects = desaObjects;
notifyDataSetChanged();
}
....
public static class ViewHolder extends RecyclerView.ViewHolder {
public final View mView;
public final AppCompatTextView desa;
public ViewHolder(View view) {
super(view);
mView = view;
desa = (AppCompatTextView) view.findViewById(R.id.desa);
}
}
}
in your code you dont actually set or change your listObjects in the OnClickListener and please try notifyDataSetChanged(); instead of Item.this.notifyDataSetChanged();
I am not sure why is this, but notifyDataSetChanged(); didn't work. So I tried keeping track of changed items and refreshing them manually with notifyItemChanged(int); and so far it seems to be working. I am still not sure why refreshing whole RecyclerView didn't work.
Initialize your Adapter Again with changed data and use method 'swapadapter'. hope it helps.
I have a RecyclerView that can show items as list, small grids or large grid and this can be change at runtime. Depending on what style user chooses i inflate different layout in onCreateViewHolder.
I also use layoutManger.setSpanSizeLookUp() to switch between styles. My code looks like this
layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override
public int getSpanSize(int position) {
if(showType == ProductAdapter.SHOW_TYPE_SMALL_GRID)
return 1;
else
return columnCount; //show one item per row
}
});
#Override
public void onClick(View v) {
if(showType == ProductAdapter.SHOW_TYPE_SMALL_GRID)
showType = ProductAdapter.SHOW_TYPE_LARGE_GRID;
else
showType = ProductAdapter.SHOW_TYPE_SMALL_GRID;
int firstVisibleItem = layoutManager.findFirstVisibleItemPosition();
adapter = new ProductAdapter(getActivity(), productList, showType);
recyclerView.setAdapter(adapter);
layoutManager.scrollToPosition(firstVisibleItem);
}
The problem is to force onCreateViewHolder to be called I'm creating a new object every time user changes the style. Is there any other way?! to force onBindViewHolder() to be recalled. I simply use adapter.notifyDataSetChanged() How can i get something similar for onCreateViewHolder?
Any solution that doesn't uses multiple adapters is good enough!
What you need to do is:
Modify your Adapter:
Specify two types of Views that your Adapter can inflate:
private static final int LARGE_GRID_ITEM = -1;
private static final int SMALL_GRID_ITEM = -2;
Create a field that can store current type mCurrentType
Use your Adapter's getItemViewType. For example like this:
#Override
public int getItemViewType (int position) {
return mCurrentType;
}
In your createViewHolder use the viewType to decide what type of ViewHolder you need to create.
public final RecyclerView.ViewHolder createViewHolder (ViewGroup parent, int viewType){
if (viewType == LARGE_GRID_ITEM) {
//return large grid view holder
} else {
//return small grid view holder
}
}
Additionally you can create methods:
public void toggleItemViewType () {
if (mCurrentType == LARGE_GRID_ITEM){
mCurrentType = SMALL_GRID_ITEM;
} else {
mCurrentType = LARGE_GRID_ITEM;
}
}
public boolean displaysLargeGrid(){
return mCurrentType == LARGE_GRID_ITEM;
}
Modify the code you posted:
layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override
public int getSpanSize(int position) {
if (adapter.displaysLargeGrid()) {
return 1;
} else {
return columnCount; //show one item per row
}
}
});
#Override
public void onClick(View v) {
adapter.toggleItemViewType();
adapter.notifyDataSetChanged();
}
Its not the optimal choice but it's better to create a new Adapter, which will call onCreateViewHolder(). This way you can avoid your troubles, by the cost of very tiny performance issues.
I have a RecyclerView. Each row has a play button, textview and Progressbar. when click on the play button have to play audio from my sdcard and have to progress Progressbar
The problem is when i scroll down the recyclerview change the Progressbar in next row.means I can fit 5 items on the screen at once. When I scroll to the 6th, 6th row seekbar changes suddenly.
public class ListAdapter extends RecyclerView.Adapter {
private List<Historyitem> stethitems;
public Context mContext;
public Activity activity;
public Handler mHandler;
static MediaPlayer mPlayer;
static Timer mTimer;
public ListAdapter(Activity activity,Context mContext,List<Historyitem> stethitems) {
this.stethitems = stethitems;
this.mContext = mContext;
this.activity = activity;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
View rootView = LayoutInflater.
from(mContext).inflate(R.layout.stethoscopeadapteritem, null, false);
RecyclerView.LayoutParams lp = new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
rootView.setLayoutParams(lp);
mHandler = new Handler();
return new MyViewHolder(rootView);
}
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder viewHolder, int position) {
final Historyitem dataItem = stethitems.get(position);
final MyViewHolder myViewHolder = (MyViewHolder) viewHolder;
myViewHolder.progressplay.setProgress(0);
myViewHolder.stethdatetime.setText(dataItem.getReported_Time());
myViewHolder.stethhosname.setText(dataItem.getdiv());
if(dataItem.getPatient_Attribute().replaceAll(" ","").equals("")){
myViewHolder.stethdoctorname.setText(dataItem.getunit());
} else {
myViewHolder.stethdoctorname.setText(dataItem.getPatient_Attribute());
}
myViewHolder.stethstreamplay.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
FileDownload(dataItem.getmsg(),
myViewHolder.progressplay);
}
});
}
#Override
public int getItemCount() {
return stethitems.size();
}
public class MyViewHolder extends RecyclerView.ViewHolder {
final CustomTextRegular stethdatetime;
final CustomTextView stethhosname;
final CustomTextBold stethdoctorname;
final ImageButton stethstreamplay;
final NumberProgressBar progressplay;
public MyViewHolder(View itemView) {
super(itemView);
stethdatetime = (CustomTextRegular)
itemView.findViewById(R.id.stethdatetime);
stethhosname = (CustomTextView)
itemView.findViewById(R.id.stethhosname);
stethdoctorname = (CustomTextBold)
itemView.findViewById(R.id.stethdoctorname);
stethstreamplay = (ImageButton)
itemView.findViewById(R.id.stethstreamplay);
progressplay= (NumberProgressBar)
itemView.findViewById(R.id.progressplay);
}
}
public void FileDownload(final String downloadpath,
final NumberProgressBar progressplay) {
new AsyncTask<NumberProgressBar, Integer, NumberProgressBar>() {
NumberProgressBar progress;
#Override
protected void onPreExecute() {
super.onPreExecute();
try {
if(mPlayer!=null){
mPlayer.stop();
}
}catch (Exception e){
}
try {
if(mTimer != null){
mTimer.purge();
mTimer.cancel();
}
}catch (Exception e){
}
}
#Override
protected NumberProgressBar doInBackground(NumberProgressBar... params) {
int count;
progress = progressplay;
try {
final List<NameValuePair> list = new ArrayList<NameValuePair>();
list.add(new BasicNameValuePair("pid",id));
URL url = new URL(Config.requestfiledownload + "?path=" +
downloadpath);
URLConnection connection = url.openConnection();
connection.connect();
int lenghtOfFile = connection.getContentLength();
// download the file
InputStream input = new BufferedInputStream(url.openStream());
OutputStream output = new FileOutputStream(Environment.getExternalStorageDirectory() +
"record.wav");
byte data[] = new byte[1024];
long total = 0;
while ((count = input.read(data)) != -1) {
total += count;
// publishing the progress....
publishProgress((int) (total * 100 / lenghtOfFile));
output.write(data, 0, count);
}
output.flush();
output.close();
input.close();
} catch (Exception e) {
}
return progress;
}
#Override
protected void onPostExecute(final NumberProgressBar numberProgressBar) {
super.onPostExecute(numberProgressBar);
try {
StartMediaPlayer(numberProgressBar);
} catch (Exception e){
e.printStackTrace();
}
}
}.execute();
}
public void StartMediaPlayer(final NumberProgressBar progressbar){
Uri playuri = Uri.parse("file:///sdcard/record.wav");
mPlayer = new MediaPlayer();
mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mPlayer.reset();
try {
mPlayer.setDataSource(mContext, playuri);
} catch (IllegalArgumentException e) {
} catch (SecurityException e) {
} catch (IllegalStateException e) {
} catch (Exception e) {
}
try {
mPlayer.prepare();
} catch (Exception e) {
}
mPlayer.start();
progressbar.setMax(mPlayer.getDuration());
mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mp) {
if(mPlayer!=null) {
mPlayer.release();
progressbar.setProgress(0);
}
if(mTimer != null){
mTimer.purge();
mTimer.cancel();
}
}
});
mTimer = new Timer();
mTimer.schedule(new TimerTask() {
#Override
public void run() {
mHandler.post(new Runnable() {
#Override
public void run() {
progressbar.setProgress(mPlayer.getCurrentPosition());
}
});
}
},0,500);
}}
Please try this
If you are using ListView - override the following methods.
#Override
public int getViewTypeCount() {
return getCount();
}
#Override
public int getItemViewType(int position) {
return position;
}
If you are using RecyclerView - override only getItemViewType() method.
#Override
public int getItemViewType(int position) {
return position;
}
Add setHasStableIds(true); in your adapter constructor and Override these two methods in adapter. It also worked if anyone using a RecyclerView inside a ViewPager which is also inside a NestedScrollView.
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getItemViewType(int position) {
return position;
}
As the name implies, the views in a RecyclerView are recycled as you scroll down. This means that you need to keep the state of each item in your backing model, which in this case would be a Historyitem, and restore it in your onBindViewHolder.
1) Create position, max, and whatever other variables you need to save the state of the ProgressBar in your model.
2) Set the state of your ProgressBar based on the data in your backing model; on click, pass the position of the item to your FileDownload/StartMediaPlayer methods.
public void onBindViewHolder(final RecyclerView.ViewHolder viewHolder, int position) {
final Historyitem dataItem = stethitems.get(position);
final MyViewHolder myViewHolder = (MyViewHolder) viewHolder;
myViewHolder.progressplay.setMax(dataItem.getMax());
myViewHolder.progressplay.setProgress(dataItem.getPosition());
...
myViewHolder.stethstreamplay.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
FileDownload(dataItem.getmsg(), position);
}
});
3) Update the progress bar by updating the backing model and notifying that it was changed.
stethitems.get(position).setPosition(mPlayer.getCurrentPosition());
notifyItemChanged(position);
I have faced the same problem while I was trying to implement a recyclerview that contains a edittex and a checkbox as a row elements. I solved the scrolling value changing problem just by adding the following two lines in the adapter class.
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getItemViewType(int position) {
return position;
}
I hope it will be a possible solution. Thanks
recyclerview.setItemViewCacheSize(YourList.size());
If your recyclerview ViewHolder has more logic or has a different different view then you should try:
**order_recyclerView.setItemViewCacheSize(x);**
where x is the size of the list. The above works for me, I hope it works for you too.
When we are changing RecyclerView items dynamically (i.e. when changing background color of a specific RecyclerView item), it could change appearance of the items in unexpected ways when scrolling due to the nature of how RecyclerView reuse its items.
However to avoid that it is possible to use android.support.v4.widget.NestedScrollView wrapped around the RecyclerView and letting the NestedScrollView handle the scrolling.
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
And then in the code you can disable nested scrolling for the RecyclerView to smooth out scrolling by letting only the NestedScrollView to handle scrolling.
ViewCompat.setNestedScrollingEnabled(recyclerView, false);
Just put you recylerView in a NestedScroll View in your xml and add the property nestedScrollingEnabled = false.
And on your adapter onBindViewHolder add this line
final MyViewHolder viewHolder = (MyViewHolder)holder;
Use this viewHolder object with your views to setText or do any kind of Click events.
e.g viewHolder.txtSubject.setText("Example");
Override the method getItemViewType in adapter. in kotlin use
override fun getItemViewType(position: Int): Int {
return position
}
I had the same problem while handle a lot of data , it works with 5 because it renders the five elements that are visible on the screen but that gives prob with more elements. The thing is ..
Sometimes RecyclerView and listView just skips Populating Data. In case of RecyclerView binding function is skipped while scrolling but when you try and debug the recyclerView adapter it will work fine as it will call onBind every time , you can also see the official google developer's view The World of listView. Around 20 min -30 min they will explain that you can never assume the getView by position will be called every time.
so, I will suggest to use
RecyclerView DataBinder created by satorufujiwara.
or
RecyclerView MultipleViewTypes Binder created by yqritc.
These are other Binders available if you find those easy to work around .
This is the way to deal with MultipleView Types or if you are using large amount of data . These binders can help you
just read the documentation carefully that will fix it, peace!!
Why don't you try like this,
HashMap<String, Integer> progressHashMap = new HashMap<>();
//...
if(!progressHashMap.containsKey(downloadpath)){
progressHashMap.put(downloadpath, mPlayer.getCurrentPosition());
}
progressbar.setProgress(progressHashMap.get(downloadpath));
try this
#Override public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
int position) { LinearSmoothScroller linearSmoothScroller =
new LinearSmoothScroller(recyclerView.getContext()) {
#Override
public PointF computeScrollVectorForPosition(int targetPosition) {
return LinearLayoutManager.this
.computeScrollVectorForPosition(targetPosition);
}
}; linearSmoothScroller.setTargetPosition(position); startSmoothScroll(linearSmoothScroller); }
see this also
This line changes progress to 0 on each bind
myViewHolder.progressplay.setProgress(0);
Save its state somewhere then load it in this same line.
I had the similar issue and searched alot for the right answer. Basically it is more of a design of recycler view that it updates the view on the scroll because it refreshes the view.
So all you need to do is at the bind time tell it not to refresh it.
This is how your onBindViewHolder should look like
#Override
#SuppressWarnings("unchecked")
public void onBindViewHolder(final BaseViewHolder holder, final int position) {
holder.bind(mList.get(position));
// This is the mighty fix of the issue i was having
// where recycler view was updating the items on scroll.
holder.setIsRecyclable(false);
}
This is the expected behaviour of recyclerView. Since the view is recycled your items may get into random views. To overcome this you have to specify which item is put into which kind of view by yourself. This information can be kept in a SparseBooleanArray. what you can do is create a SparseBooleanArray in your adapter like this
SparseBooleanArray selectedItems = new SparseBooleanArray();
whenever your view changes, do:
selectedItems.put(viewItemIndex,true);
Now in your onBindViewHolder do
if(selectedItems.get(position, false)){
//set progress bar of related to the view to desired position
}
else {
//do the default
}
This is the basic to solve your problem. You can adjust this logic to any kind of similar problem in recyclerView.