Hello i`m trying to get recyclerview viewholder at particular position with this code:
val viewHolder = educationRv.findViewHolderForAdapterPosition(i) as SectionsRecyclerAdapter.ViewHolder
the problem is that recycler view recycle not visible items and i can't get item and position 0 if its not visible. I searched before ask i tried this solutions, but can't prevent recycling of the views (i know that this is not good solution but i`m stuck and can't figure it out better) :
educationRv.recycledViewPool.setMaxRecycledViews(1,0)
override fun getItemViewType(position: Int): Int {
return 1
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
if (holder != null) {
holder.setIsRecyclable(false)
}
Generalized -> i have recyclerview with 20 edittexts which can be added dynamically, when user click save i need to get all edittexts values and save it to array, the problem comes when button save is clicked only two or three views are visible when i try to get not visible return null
As i got your query this solution would definitely help you
In you viewHolder do like this
etText.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) {
yourEditTextList.set(getAdapterPosition(), s.toString());
}
#Override
public void afterTextChanged(Editable s) {
}
});
btnAddNewEditText.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
yourEditTextList.add(new String(""));
notifyDataSetChanged();
}
});
Here yourEditTextList is the list of String in of your editText in your recylerView
In your activity where you have recylerView
btnDone.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//Fetch you list that your have passed on recycler view you will get here
}
});
Related
My app is in the foreground. and there are 2 items into a recyclerview list. whenever I delete my 2nd item from the list and after that click on the 1st item my app gets crash. and in Logcat I get the error like - java.lang.IndexOutOfBoundsException: Index: 1, Size: 1. But when I delete 1st item from my list and after that click on the remaining 2nd item my app works fine.
public void onClick(View v) {
onClick.onItemCli(position, banners.get(position));
}
I tried out by doing position -1 in this method like the below code.
public void onClick(View v) {
onClick.onItemCli(position-1, banners.get(position-1));
}
but by this case my app is crashing whenever there are 2 items in list and i click on 2nd item.
The error I am getting is :
java.lang.ArrayIndexOutOfBoundsException: length=12; index=-1
public class HeaderSliderAdapter extends RecyclerView.Adapter<HeaderSliderAdapter.ViewHolder> {
public List<Banner> banners;
public Context context;
private OnItemClicked onClick;
public HeaderSliderAdapter(Context context, List<Banner> banners) {
this.banners = banners;
this.context = context;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.home_header, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(final HeaderSliderAdapter.ViewHolder holder, final int position) {
if (TextUtils.isEmpty(banners.get(position).getmSliderImage())) {
holder.mSliderImageView.setImageResource(R.drawable.no_img_ava);
holder.mBackgroundImage.setVisibility(View.GONE);
} else {
Picasso.with(context)
.load(banners.get(position).getmSliderImage())
.fit()
.into(holder.mSliderImageView, new Callback() {
#Override
public void onSuccess() {
holder.mBackgroundImage.setVisibility(View.GONE);
holder.tvWarningFailedtoLoad.setVisibility(View.GONE);
holder.tvTitle.setVisibility(View.VISIBLE);
holder.mSliderImageView.setClickable(true);
}
#Override
public void onError() {
holder.mBackgroundImage.setVisibility(View.GONE);
holder.tvWarningFailedtoLoad.setVisibility(View.VISIBLE);
holder.tvTitle.setVisibility(View.GONE);
holder.mSliderImageView.setClickable(false);
}
});
}
holder.mSliderImageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
onClick.onItemCli(position, banners.get(position));
}
});
}
#Override
public int getItemCount() {
return banners.size(); }
public void setOnClick(OnItemClicked onClick) {
this.onClick = onClick;
}
public interface OnItemClicked {
void onItemCli(int position, Banner passData);
}
public class ViewHolder extends RecyclerView.ViewHolder {
public ImageView mSliderImageView,mBackgroundImage;
public TextView tvWarningFailedtoLoad;
public TextView tvTitle;
View mView;
public ViewHolder(View itemView) {
super(itemView);
mView = itemView;
mSliderImageView = mView.findViewById(R.id.mSliderImage);
mBackgroundImage = mView.findViewById(R.id.backgroundImage);
tvWarningFailedtoLoad = mView.findViewById(R.id.tvFailedtoLoad);
tvTitle = mView.findViewById(R.id.mSliderImagetitle);
tvTitle.setSelected(true);
tvTitle.setSingleLine(true);
}
}
}
The error is getting in the below method.
holder.mSliderImageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
onClick.onItemCli(position, banners.get(position));
}
});
java.lang.IndexOutOfBoundsException: Index: 1, Size: 1
I gues that the clue to your issue is in the following part of the documentation:
Note that unlike ListView, RecyclerView will not call this method again if the position of the item changes in the data set unless the item itself is invalidated or the new position cannot be determined. For this reason, you should only use the position parameter while acquiring the related data item inside this method and should not keep a copy of it. If you need the position of an item later on (e.g. in a click listener), use getAdapterPosition() which will have the updated adapter position. Override onBindViewHolder(ViewHolder, int, List) instead if Adapter can handle efficient partial bind.
So change the implementation of your setOnClickListener() method so it does not use the parametere position, but calls getAdapterPosition() instead to acquire the fresh value of your position.
ok guys
i'm developing an android app which requires lots of edittext fields(so i put them into recyclerview for better ui performance)
firstly i encountered the input values on edittext fields are not saved so scrolling caused the lose of values in edittext field
i solved the problem by adding textwatcher to edittext fields and saving input data but now im encountering another problem:
just try the following steps in proper order:
1- tap to some edittext input field(soft keyboard open ups simultaneously)
2- now scroll the recyclerview to other edittext fields (so edittext which you entering value into it, disappears from screen)
3- and try to input some words into it by keyboard
after these steps
edittext which you edited doesnt contain any value.
instead, other randomly choosen edittext gains the value which you entered
probably recyclerview's reusing mechanism is causing it, not sure
i dont want to disable scrolling when soft keyboard opened to fix this possible behaviour/issue so is there another way to solve this problem?
EDIT:
public class SampleAdapter extends RecyclerView.Adapter{
private static List<String> mEditTextValues = new ArrayList<>();
public SampleAdapter(List<String> dataSet){
mEditTextValues = dataSet;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new CustomViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.edittext,parent,false));
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
CustomViewHolder viewHolder = ((CustomViewHolder)holder);
viewHolder.mEditText.setTag(position);
viewHolder.mEditText.setText(mEditTextValues.get(position));
}
#Override
public int getItemCount() {
return mEditTextValues.size();
}
public class CustomViewHolder extends RecyclerView.ViewHolder{
private EditText mEditText;
public CustomViewHolder(View itemView) {
super(itemView);
mEditText = (EditText)itemView.findViewById(R.id.editText);
mEditText.addTextChangedListener(new TextWatcher() {
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
public void afterTextChanged(Editable editable) {
if(mEditText.getTag()!=null){
mEditTextValues.set((int)mEditText.getTag(),editable.toString());
}
}
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
});
}
}
}
If you want to stop editing random place, you can hide the soft keypad when the current editing edit text starts non-focusing.
Adapter.bindView
viewHolder.mEditText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
#Override
public void onFocusChange(View view, boolean b) {
if (b == false) {
hideKeyboard(view.getContext(), view);
}
}
});
To hide the keypad
/**
* To hide a keypad.
*
* #param context
* #param view
*/
public static void hideKeyboard(Context context, View view) {
if ((context == null) || (view == null)) {
return;
}
InputMethodManager mgr =
(InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
mgr.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
It sounds like you need a ScrollView in your layout but there is no code posted
Try to put your TextWatcher in onBindViewHolder it will work
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
CustomViewHolder viewHolder = ((CustomViewHolder)holder);
viewHolder.mEditText.setText(mEditTextValues.get(position));
viewHolder.mEditText.addTextChangedListener(new TextWatcher() {
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
public void afterTextChanged(Editable editable) {
}
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
mEditTextValues.set(position,mEditText.getText().toString())
}
});
}
I'm trying to highlight my selected item in recyclerView when it's clicked.But it triggers two items instead. Please help me. Should i store clicked items as arraylist and clear them on new one clicked?
public class StationsAdapter extends RecyclerView.Adapter<StationsHolder> {
List<Station> stations;
public StationsAdapter(List<Station> stations){
this.stations = stations;
}
public void changeItemAtPosition(int position) {
notifyItemChanged(position);
}
#Override
public StationsHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new StationsHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.stations_item,parent,false));
}
#Override
public void onBindViewHolder(StationsHolder holder, int position) {
bind(holder);
}
private void bind(final StationsHolder holder) {
holder.tvTitle.setText(stations.get(holder.getAdapterPosition()).getName());
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
holder.tvTitle.setTextColor(ContextCompat.getColor(AppDelegate.getContext(),R.color.colorAccent));
}
});
}
#Override
public int getItemCount() {
return stations.size();
}
}
This is due to recycler reusing the same view when you scroll. In order to fix that, you will have to do the next:
Store the selected item when you click on it. In a variable or in an array if you want more than one item
Check the selected item variable/array in the bind method to know if you have to color it or not
That way it will work flawlessly
I'm trying to make two side by side ListViews act like a GridView to an extent. The reason I'm not using GridView is because there's no support for a Staggered Look. Anyways, I have the following code so far:
<- Old,now irrelevant code ->
EDIT:
I did as #Sam suggested and used the following code:
lv1.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (touchSource == null) {
touchSource = v;
}
if (v == touchSource) {
lv2.dispatchTouchEvent(event);
if (event.getAction() == MotionEvent.ACTION_UP) {
clickSource = v;
touchSource = null;
}
}
return false;
}
});
lv1.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
if (parent == clickSource) {
//my own code here
}
}
});
lv1.setOnScrollListener(new OnScrollListener() {
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (view == clickSource) {
}
boolean loadMore = /* maybe add a padding */
firstVisibleItem + visibleItemCount + 10 >= totalItemCount;
if (loadMore) {
//add items, load more
}
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
});
lv2.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (touchSource == null) {
touchSource = v;
}
if (v == touchSource) {
lv1.dispatchTouchEvent(event);
if (event.getAction() == MotionEvent.ACTION_UP) {
clickSource = v;
touchSource = null;
}
}
return false;
}
});
lv2.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
if (parent == clickSource) {
}
}
});
lv2.setOnScrollListener(new OnScrollListener() {
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (view == clickSource) {
}
boolean loadMore = /* maybe add a padding */
firstVisibleItem + visibleItemCount + 2 >= totalItemCount;
if (loadMore) {
}
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
});
I am also using a header on one of the lists to create a Staggered Effect and I need to keep this stagger at all costs. This is mostly working (the above code) but it unsyncs a lot of the time. I have figured out this is only when I have small short swipes. I'm not sure why, and I can't find a good solution. It seems to me that the above logic should work.
Also, just in case it matters, I am using UniversalImageLoader to load photos and a simple EndlessScroll logic to load more photos (directly calling adapter.notifyDataSetChanged()). This stuff doesn't seem too relevant though. Even when photos are loaded, I can still see it unsyncing.
I want to be clear: if I do short swipes (and sometimes just in general repeated scrolling) I can unsync the lists at will.
Any help is appreciated. Thanks.
EDIT 2:
Yet to find a good solution. Here are the solutions that I have found that don't quite work:
The above solution: unsyncs far too often
Pinterest List View: no OnScrollListener/onItemClick differentiation
StaggeredGridView: No onScrollListener
Stag Project: No onItemClick listener
Linear Layout Method: No onItemClick Listener
Any ideas? Thanks.
Okay, so I ended up using the Linear Layout Method(See the Question) and used setTag to get the onItemClickListener working, and a CustomScrollView implementation to get the infinite List working.
Basically, two linearLayouts stacked horizontally, inside a scrollview. It works rather well, and loads much faster.
Also, to load my images SmartImageView if that helped.
If someone wants the code, I might be able to post parts of it.
Instead of listening to onTouch why don't you scroll lists in the onScroll listener of ListViews. That's what I'm using in that library with sort of tracks the scroll of a ListView similarly and it works like a charm. Check out this file. https://github.com/JlUgia/list_moving_container/blob/master/src/com/ugia/listmovingcontainer/fragment/BottomListMovingContainerFragment.java
Pay attention specially to 81 (don't forget to add the global layout updater listener) and 89 (with the onScroll Listener).
That way you can forget about the click hacks as well.
UPDATE
I'd code it the following way.
lv1.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
updateListPosition(lv2, lv1);
}
});
lv1.setOnScrollListener(new AbsListView.OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
updateListPosition(lv2, lv1);
}
});
lv2.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
updateListPosition(lv1, lv2);
}
});
lv2.setOnScrollListener(new AbsListView.OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
updateListPosition(lv1, lv2);
}
});
private void updateListPosition(ListView updatedList, ListView scrolledList){
updatedList.setScrollY(scrolledList.getScrollY());
}
** setScrollY already invalidates your list.
** Note that I didn't test the code. Although it should be simple enough.
I have an activity that searches in a set of images. The images are displayed in a gallery. There is an EditText where the user can enter a search term. When the user enters text in the EditText the gallery is filtered according to the entered text.
I have made a SearchResultAdapter extends BaseAdapter implements Filterable which works basically fine. If the user enters a text the data in the adapter is filtered. I logged the results: It works so far. Then I call notifyAdapterSetChanged so the gallery shows the updated data set. notifyAdapterSetChanged is actually called and the request is received by the gallery as it calls my adapters getCount() (I logged all calls).
Setting the selection to 0 crashes the application due to IndexOutOfBoundsException as I expect it when the gallery has no items. Still the gallery shows all images that were in it before.
The problem is:
When the data set is empty (i.e. the user has entered a search term that does not fit to any image) then the gallery does not show the update. The gallery receives the notifyAdapterSetChanged, queries the adapter on the item count (which is 0) and updates the scroll limits. It does not update the images shown in the gallery. I can not scroll through the images anymore as there are basically no images to display, but the images are not removed from the gallery. It looks to the user as if the gallery froze.
When the invalid search term is deleted (i.e. the data set is not empty) then the gallery shows the correct images. Also under some conditions the gallery shows the empty list like when the application is minimized and then reopened. I can not reproduce the conditions however under which the gallery empties itself(apart from the minimizing/reopening of the application):
I have tried invalidating and requestLayout()ing the gallery, but it does not help. Does anyone know how I can make the gallery update the list of displayed images?
Here the log messages when the user enters a text that matches no image:
[SearchResultAdapter.SearchFilter] Results: 0
[SearchResultAdapter.SearchFilter] called notifyDataSetChanged()
[SearchResultAdapter.getCount()] getCount called: dateset size 0
[SearchResultAdapter.getCount()] getCount called: dateset size 0
Notice how the adapter is actually called by the gallery on the data set size
The initialising code in the activity:
final Gallery gallery = (Gallery) findViewById(R.id.search_results);
final SearchResultAdapter adapter = new SearchResultAdapter(this, gallery);
gallery.setAdapter(adapter);
EditText searchField = (EditText) findViewById(R.id.search_searchfield);
searchField.addTextChangedListener(new TextWatcher()
{
#Override
public void onTextChanged(CharSequence s, int start, int before,
int count)
{
// TODO unhardcode criterion
adapter.putFilterTerm(SearchCriteria.OFFERING, s.toString());
adapter.getFilter().filter(s);
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after)
{
// do nothing
}
#Override
public void afterTextChanged(Editable s)
{
// do nothing
}
});
And the SearchResultAdapter (lines that do not matter are left out):
public class SearchResultAdapter extends BaseAdapter implements Filterable
{
private Context context;
private Filter filter;
public void putFilterTerm(SearchCriteria key, String value)
{
filterTerms.put(key, value);
}
public boolean containsFilterTerm(SearchCriteria key)
{
return filterTerms.containsKey(key);
}
public String getFilterTerm(SearchCriteria key)
{
return filterTerms.get(key);
}
List<PageContainer> completeList = new ArrayList<PageContainer>();
List<PageContainer> currentList = new ArrayList<PageContainer>();
Map<SearchCriteria, String> filterTerms = new HashMap<SearchCriteria, String>();
View view;
public SearchResultAdapter(Context context, View gallery)
{
this.context = context;
this.view = gallery;
for (ContentPropertyContainer page : Model.getInstance()
.getRootContainer().getChildren())
{
completeList.add((PageContainer) page);
}
currentList.addAll(completeList);
}
#Override
public int getCount()
{
return currentList.size();
}
#Override
public Object getItem(int position)
{
return currentList.get(position);
}
#Override
public long getItemId(int position)
{
PageContainer item = currentList.get(position);
for (int i = 0; i < completeList.size(); ++i)
{
if (completeList.get(i) == item)
{
return i;
}
}
return -1;
}
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
View listview = convertView;
// Code left out here tries to reuse the convertView
// like Romain Guy said in a Google I/O Talk
return listview;
}
private class ResultList
{
ResultList(List<PageContainer> results)
{
this.results = results;
}
List<PageContainer> results;
}
#Override
public Filter getFilter()
{
// implements Filterable
if (filter == null)
{
filter = new SearchFilter();
}
return filter;
}
private class SearchFilter extends Filter
{
SearchFilter()
{
// do nothing, needed for visibility
}
#Override
protected FilterResults performFiltering(CharSequence constraint)
{
FilterResults results = new FilterResults();
// Code left out here filters the list
return results;
}
#Override
protected void publishResults(CharSequence constraint,
FilterResults results)
{
if (results == null)
{
return;
}
if (results.values instanceof ResultList)
{
currentList = ((ResultList) (results.values)).results;
notifyDataSetChanged();
}
}
}
}
EDIT:
Setting the visibility of the gallery to invisible and then to visible again solved the problem.
view.post(new Runnable()
{
#Override
public void run()
{
view.setVisibility(View.INVISIBLE);
view.setVisibility(View.VISIBLE);
}
});
Is there a better solution? This seems rather hackish to me.