I have a ViewPager2 contains ScrollView.
What the problem is, when I try to get ScrollView of current page in onPageSelected(), it doesn't work.
Here I'd like to set previous scrollY to the ScrollView when user back to see selected page. (because scrollY is reset for some reason before that)
My code is below.
ViewPagerAdapter.java (edited)
public class ViewPagerAdapter extends RecyclerView.Adapter<ViewPagerAdapter.ViewHolder> {
private LayoutInflater mInflater;
private List<String> mText;
private ViewPager2 pager2;
MainActivity main;
public ViewPagerAdapter(Context context, List<String> data, ViewPager2 pager2, MainActivity main){
this.mInflater = LayoutInflater.from(context);
this.mText = data;
this.pager2 = pager2;
this.main = main;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType){
View view = mInflater.inflate(R.layout.tab_scroll_item, parent, false);
return new ViewPagerAdapter.ViewHolder(view);
}
#Override
public void onBindViewHolder(final ViewPagerAdapter.ViewHolder holder, int position){
holder.scrollView.setTag("scv_tab" + position);
holder.scrollView.setOnScrollChangeListener(new View.OnScrollChangeListener() {
#Override
public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
if(scrollY != 0) {
main.setScrollY(scrollY, getPosition());
}
System.out.println("onScrollChanged : " + scrollY);
}
});
holder.textView.setEnabled(false);
holder.textView.setEnabled(true);
holder.textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, applyTextSize());
holder.textView.setText(mText.get(position));
holder.textView.setTag("tv_tab" + position);
}
#Override
public int getItemCount(){
return mText.size();
}
protected int getPosition(){
return pager2.getCurrentItem();
}
public class ViewHolder extends RecyclerView.ViewHolder{
ScrollView scrollView;
TextView textView;
public ViewHolder(View itemView){
super(itemView);
scrollView = itemView.findViewById(R.id.tab_scroll);
textView = itemView.findViewById(R.id.tab_textview);
}
}
[REVICED]onBindViewHolder
#Override
public void onBindViewHolder(final ViewPagerAdapter.ViewHolder holder, final int position){
holder.scrollView.setTag("scv_tab" + position);
holder.scrollView.setOnScrollChangeListener(new View.OnScrollChangeListener() {
#Override
public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
if(scrollY != 0) {
main.storeScrollY(scrollY, position);
}
System.out.println("onScrollChanged : " + scrollY);
}
});
int y = main.retrieveScrollY(position);
holder.scrollView.setScrollY(y);
holder.textView.setEnabled(false);
holder.textView.setEnabled(true);
holder.textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, applyTextSize());
holder.textView.setText(mText.get(position));
holder.textView.setTag("tv_tab" + position);
}
MainActivity.java
#Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(R.style.AppTheme);
super.onCreate(savedInstanceState);
...
mPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
#Override
public void onPageSelected(int position) {
super.onPageSelected(position);
if(!searchView.isIconified()){
searchView.setIconified(true);
}
if(highlightModel.getHighlitedOrNot(position)){
searchText.deleteTextHighlight(position);
highlightModel.setHighlitedOrNot(position, false);
}
int positionY[] = getScrollFromViewModel();
ScrollView sv = findScrollView(); // HERE IS THE PROBLEM
if(sv != null) {
sv.setScrollY(positionY[position]);
}else{
System.out.println("sv is null"); // ALWAYS SHOWS NULL
}
}
});
...
searchView = (SearchView) findViewById(R.id.searchview);
searchView.setOnSearchClickListener(new View.OnClickListener(){
#Override
public void onClick(View v){
fab.setVisibility(View.VISIBLE);
fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int actualResult = searchText.scrollToHighlightedWord(findTextView()
, findScrollView() // here findScrollView() works perfectly.
, searchResultIndex);
if(actualResult == (searchResultIndex + 1)) {
++searchResultIndex;
}else if(actualResult == searchResultIndex){
showToastAtMain("last word");
}else{
forOnClose();
}
}
});
}
});
private ScrollView findScrollView(){
ScrollView sv = mPager2.findViewWithTag("scv_tab" + mPager2.getCurrentItem());
return sv;
}
tab_scroll_item.xml (edited)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:id="#+id/tab1_layout">
<ScrollView
android:id="#+id/tab_scroll"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/tab_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20sp"
android:lineSpacingExtra="7dp"
android:textIsSelectable="true"
android:paddingStart="9dp"
android:paddingTop="9dp"
android:paddingEnd="9dp"/>
</ScrollView>
</LinearLayout>
Any advice is truly appreciated. Thank you for reading this long question.
onPageSelected will be called way earlier than selected page drawing mechanism, before any onCreateViewHolder and onBindViewHolder. I would advise you to "incject" somehow your data into your "page" (is it View or whole Fragment? that makes big difference) and in your case set this scroll position inside onBindViewHolder. This will make rendering of RecyclerView draw even first frame in correct scrolled position. Your way, even when you will wait some time for RecyclerView drawing, will make that onBindViewHolder will draw first frame on 0 scroll and your method will scroll a bit in next frames - this will be visible like some blink or fast-autoscroll (I think this behavior isn't intended)
edit - add proper method for setting scroll for your Adapter even before super call
#Override
public void onPageSelected(int position) {
int positionY[] = getScrollFromViewModel();
adapter.setScrollForPosition(positionY[position]);
super.onPageSelected(position);
...
for adapter
private final HashMap<Integer, Integer> scrollYHistory = new HashMap<>();
public void setScrollForPosition(int position, int scrollY){
scrollYHistory.put(position, scrollY);
}
use stored scrollY value in onBindViewHolder and clean entry in your HashMap
#Override
public void onBindViewHolder(final ViewPagerAdapter.ViewHolder holder, final int position){
Integer scrollY = scrollYHistory.remove(position);
if (scrollY == null) scrollYa = 0; // may be null if not stored yet!
holder.scrollView.setScrollY(scrollY);
...
also remove unnecessary reference to MainActivity main in adapter's constructor and variable inside, it already gets Context reference and doesn't (shouldn't) need to know which Activity is creating it
edit - expanding comment:
instead of calling scrollView.setScrollY straight inside onBindViewHolder you should set up your TextView at first, then wait for rendering it and then scrolling to proper position
#Override
public void onBindViewHolder(final ViewPagerAdapter.ViewHolder holder, final int position){
...
holder.textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, applyTextSize());
holder.textView.setText(mText.get(position));
...
holder.textView.post(new Runnable(){
// this will be called after nearest drawing
#Override
public void run() {
Integer scrollY = scrollYHistory.remove(position);
if (scrollY == null) scrollYa = 0; // may be null if not stored yet!
holder.scrollView.setScrollY(scrollY);
}
});
}
Thanks to snachmsm, I got to know that onPageSelected will be called way earlier than ViewPager2 drawing. And for some reason I found my solution using mPager2.postDelayed().
After adding these, I got NO null ScrollView here.
mPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
...
//Here I added onPageScrollStateChanged() and postDelayed() in it.
#Override
public void onPageScrollStateChanged(int state){
if(state == ViewPager2.SCROLL_STATE_SETTLING){
mPager2.postDelayed(new Runnable() {
#Override
public void run() {
int position = mPager2.getCurrentItem();
int y = scrollModel.getPreviousScrollY(position);
ScrollView sv = findScrollView();
sv.scrollTo(0, y);
}
}, 50);
}
}
});
}
Related
Hi everyone i have created project for survey in area. i have loaded the question in the recycler view where edittext,radiobutton,checkbox,spinner are generated dynamically(there no separate layout files for each) i have used new edittext and used textwatcher to keep the answers dynamically updated in the list so that it can return to mainactivity i.e from adapterclass to activity class. my problem is that the edittext, radiobuttons,etc are getting duplicated but not the question in the textview on scroll up or down or keyboard down after the if there operator performed like enter input inside edittext or change value in spinner then scroll up
note:- the textview is static component the dynamic input are add to linearlayout inside the row layout file that is textview and edittext are both childcomponents of linearlayout based on if case the input type is added as 2nd child of linear layout
Activity.class
mlist=new ArrayList<>();
mlist.clear();
mlist=db.populatequestion();
Log.d("size", String.valueOf(mlist.size()));
Log.d("Question",mlist.get(0).getQuestionType());
mRecyclerView=findViewById(R.id.rvForm);
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
formsadapter =new formsadapter(mlist,this,mRecyclerView);
mRecyclerView.setAdapter(formsadapter);
formsadapter.class
public class formsadapter extends RecyclerView.Adapter<formsadapter.ViewHolder> {
private List<FormQData> mlists;
private List<formanswer> formanswers;
private Context mContext1;
private RecyclerView mRecyclerV1;
public class ViewHolder extends RecyclerView.ViewHolder{
public LinearLayout linearLayout;
public TextView Question;
public View layout;
public ViewHolder(View v) {
super(v);
layout=v;
Question=v.findViewById(R.id.tvQuestion);
linearLayout=v.findViewById(R.id.llform);
formanswers = new ArrayList<>();
}
}
public formsadapter(List<FormQData> myDataset, Context context, RecyclerView recyclerView) {
mlists = myDataset;
mContext1 = context;
mRecyclerV1 = recyclerView;
}
public formsadapter.ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType){
LayoutInflater inflater = LayoutInflater.from(
parent.getContext());
View v =inflater.inflate(R.layout.row_form, parent, false);
ViewHolder vh = new ViewHolder(v);
return vh;
}
public void onBindViewHolder(formsadapter.ViewHolder holder, final int position){
final FormQData fb=mlists.get(position);
formanswer fa=new formanswer();
holder.Question.setText(fb.getQuestionContent());
LinearLayout.LayoutParams mainparams1 = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.MATCH_PARENT,1.0f);
mainparams1.gravity= Gravity.RIGHT;
if(fb.getQuestionType().equals("textbox")){
EditText et=new EditText(mContext1);
et.setBackgroundResource(R.drawable.boxwithcircularcoloredborders);
et.setTextColor(mContext1.getResources().getColor(R.color.colorPrimary));
et.setPadding(15,15,15,15);
et.addTextChangedListener(new TextWatcher() {
#Override public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
#Override public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
#Override
public void afterTextChanged(Editable editable) {
fa.setQuestionid(fb.getQuestionid());
fa.setAnswer(et.getText().toString());
if(formanswers.contains(fa)){
formanswers.set(formanswers.indexOf(fa),fa);
}
else
formanswers.add(fa);
}
});
holder.linearLayout.addView(et,1);
}
else if(fb.getQuestionType().equals("radiobox")){
RadioGroup rg=new RadioGroup(mContext1);
rg.setOrientation(RadioGroup.HORIZONTAL);
String[] arrOfStr = fb.getQuestionOptions().split(":");
RadioButton[] radiobutton = new RadioButton[arrOfStr.length];
for(int i=0;i<arrOfStr.length;i++){
radiobutton[i]=new RadioButton(mContext1);
radiobutton[i].setText(arrOfStr[i]);
radiobutton[i].setLayoutParams(mainparams1);
radiobutton[i].setBackgroundResource(R.drawable.boxwithcircularcoloredborders);
radiobutton[i].setTextColor(mContext1.getResources().getColor(R.color.colorPrimary));
radiobutton[i].setTypeface(Typeface.DEFAULT_BOLD);
radiobutton[i].setPadding(15,15,15,15);
int finalI = i;
radiobutton[i].setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
fa.setQuestionid(fb.getQuestionid());
fa.setAnswer(radiobutton[finalI].getText().toString());
if(formanswers.contains(fa)){
formanswers.set(formanswers.indexOf(fa),fa);
}
else
formanswers.add(fa);
}
});
rg.addView(radiobutton[i],i);
}
holder.linearLayout.addView(rg,1);
}
else if(fb.getQuestionType().equals("checkbox")){
List<String> answer=new ArrayList<>();
RadioGroup rg=new RadioGroup(mContext1);
rg.setOrientation(RadioGroup.HORIZONTAL);
String[] arrOfStr = fb.getQuestionOptions().split(":");
CheckBox[] checkBoxes = new CheckBox[arrOfStr.length];
for(int i=0;i<arrOfStr.length;i++){
checkBoxes[i]=new CheckBox(mContext1);
checkBoxes[i].setText(arrOfStr[i]);
checkBoxes[i].setLayoutParams(mainparams1);
checkBoxes[i].setBackgroundResource(R.drawable.boxwithcircularcoloredborders);
checkBoxes[i].setTextColor(mContext1.getResources().getColor(R.color.colorPrimary));
checkBoxes[i].setTypeface(Typeface.DEFAULT_BOLD);
checkBoxes[i].setPadding(15,15,15,15);
int finalI = i;
checkBoxes[i].setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
fa.setQuestionid(fb.getQuestionid());
if(checkBoxes[finalI].isChecked()){
String temp=checkBoxes[finalI].getText().toString();
if(answer.contains(temp)){
answer.set(answer.indexOf(temp),temp);
}else {
answer.add(checkBoxes[finalI].getText().toString());
}
}else{
answer.remove(checkBoxes[finalI].getText().toString());
}
fa.setAnswer(answer.toString());
if(formanswers.contains(fa)){
formanswers.set(formanswers.indexOf(fa),fa);
}
else
formanswers.add(fa);
}
});
rg.addView(checkBoxes[i],i);
}
holder.linearLayout.addView(rg,1);
}
else if(fb.getQuestionType().equals("dropdown")){
Spinner spinner=new Spinner(mContext1);
fa.setQuestionid(fb.getQuestionid());
List<String> arrOfStr=new ArrayList<>(Arrays.asList(fb.getQuestionOptions().split(":")));
arrOfStr.add(0,"Select A Option");
ArrayAdapter<String> spinnerAdapter=new ArrayAdapter<>(mContext1,R.layout.row_dropdownsinglecelltv,R.id.tvsinglecell2,arrOfStr);
spinnerAdapter.setDropDownViewResource(R.layout.row_dropdownsinglecelltv);
spinner.setPopupBackgroundDrawable(mContext1.getResources().getDrawable(R.drawable.boxcircularcorner));
spinner.setAdapter(spinnerAdapter);
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
String spinnerValue=String.valueOf(adapterView.getSelectedItem());
if (!spinnerValue.equals("Select A Option")){
//Log.d("value","upload");
fa.setAnswer(spinnerValue);
if(formanswers.contains(fa))
formanswers.set(formanswers.indexOf(fa),fa);
else
formanswers.add(fa);
}else{
if(formanswers.contains(fa))
formanswers.remove(fa);
}
}
#Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
});
holder.linearLayout.addView(spinner,1);
}
else {
TextView et=new TextView(mContext1);
et.setText("invalid question type");
holder.linearLayout.addView(et,1);
}
}
public int getItemCount(){
return mlists.size();
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getItemViewType(int position) {
return position;
}
public List<formanswer> formanswers(){
return formanswers;
}
}
row_form.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:padding="10dp"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:id="#+id/llform"
android:background="#drawable/boxwithcoloredborder"
android:layout_width="match_parent"
android:padding="15dp"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="#+id/tvQuestion"
android:padding="5dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#color/colorPrimary"
android:textStyle="bold"
android:text="TextView" />
</LinearLayout>
</LinearLayout>
here is the sample video
https://imgur.com/a/hi3Vb5a
i have found the solution it seems the problem is entirely different. The problem is that the recycler view is not getting recycled properly its has viewCachesize which needs to be increased to list size
you can check if your recyclerview is getting recycled or not using
#Override
public void onViewRecycled(#NonNull formsadapter.ViewHolder holder) {
Log.d("recyclerview","recyclerview is recycled");
}
if the log is being appeared then you solve this issue adding
holder.setIsRecyclable(false);
in the onbindviewholder or adding
mRecyclerView.setItemViewCacheSize(mlist.size()+1);
in the main activity itself
Evidently just use the listview itself instead of recyclerview then these lines dont need to be added
links:-
RecyclerView not recycling views if the view count is small
How can I detect recycling from within a recyclerView adapter, so I can perform an action before it gets recycled?
I have a EditText in RecyclerView. When I edit the content of a EditText and scroll up/down, that same content is displayed in another line.
Also, If I return to the same position, the EditText is empty.
Here is the layout for every item of the RecyclerView:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#F3F2F2"
android:orientation="vertical"
android:padding="5dp">
<TextView
android:id="#+id/txt_jantri_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="#font/quicksand_bold"
android:text="1"
android:textColor="#color/theme_color"
android:textSize="10sp" />
<EditText
android:id="#+id/ed_jantri_number"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:background="#drawable/shape_jantri_edittext"
android:fontFamily="#font/quicksand_bold"
android:gravity="center"
android:inputType="number"
android:maxLines="1"
android:minLines="1"
android:padding="5dp"
android:textSize="12dp" />
</LinearLayout>
</LinearLayout>
Here is the Adapter class where I am setting text to the box number:
public class AdapterJantri extends RecyclerView.Adapter<AdapterJantri.Myholder> {
#NonNull
#Override
public Myholder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
return new Myholder(LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.single_jantri_view, viewGroup, false));
}
#Override
public void onBindViewHolder(#NonNull Myholder myholder, int i) {
myholder.txtJantriNumber.setText(String.valueOf(i + 1));
}
#Override
public int getItemCount() {
return 99;
}
public class Myholder extends RecyclerView.ViewHolder {
EditText edJantriNumber;
TextView txtJantriNumber;
public Myholder(#NonNull View itemView) {
super(itemView);
edJantriNumber = itemView.findViewById(R.id.ed_jantri_number);
txtJantriNumber = itemView.findViewById(R.id.txt_jantri_number);
}
}
}
Here is the Activity
public class JantriActivity extends AppCompatActivity {
RecyclerView recyclerView;
SlideToActView slideToActView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_jantri);
initiliaization();
recyclerView.setAdapter(new AdapterJantri());
}
private void initiliaization() {
recyclerView = findViewById(R.id.recycler_jantri);
recyclerView.setLayoutManager(new GridLayoutManager(this, 4));
slideToActView = findViewById(R.id.swipe_save_transaction);
}
}
A ViewAdapter re-uses a View. So, when scrolling, instead of creating new views, an Adapter will re-use a view that is no longer visible.
So, that is the main reason you have the onBindViewHolder. With that method, Android gives you chance to update the view content before it becomes visible. If you won't update all contents of the (the TextView and the EditText), you observe issues like that one that you mentioned.
Note this code:
#Override
public void onBindViewHolder(#NonNull Myholder myholder, int i) {
myholder.txtJantriNumber.setText(String.valueOf(i + 1));
}
As you can see, you are only updating the TextView. So, that TextView (re-used or not) will always display proper text. However, you are not updating the content of the EditText. This way, if the View was just created, the EditText is empty. If the view is being re-used, you will see the old value (from a different line).
So, in order to keep update the EditText properly, you must store all texts in a separated list. For example:
public class AdapterJantri extends RecyclerView.Adapter<AdapterJantri.Myholder> {
private String[] mTextList;
public AdapterJantri() {
mTextList = new String[getItemCount()];
}
#Override
public void onBindViewHolder(#NonNull Myholder myholder, int i) {
myholder.txtJantriNumber.setText(String.valueOf(i + 1));
// Here, you update the EditText content
myholder.edJantriNumber.setText(mTextList[i]);
}
#Override
public void onViewRecycled(#NonNull final Myholder holder) {
// Here, you save the text before the view gets recycled
int index = holder.getAdapterPosition();
if (index >= 0) {
mTextList[index] = holder.edJantriNumber.getText().toString();
}
}
}
EDIT
If you have a TextWatcher and you need to disable it temporarily, I suggest to:
First
Don't create a TextWatcher inside the bindViewHolder. You don't need to create a new one everytime. Just add it and remove it. You can use your own MyHolder as a TextWatcher
public class Myholder extends RecyclerView.ViewHolder implements TextWatcher {
EditText edJantriNumber;
TextView txtJantriNumber;
public Myholder(#NonNull View itemView) {
super(itemView);
edJantriNumber = itemView.findViewById(R.id.ed_jantri_number);
txtJantriNumber = itemView.findViewById(R.id.txt_jantri_number);
}
#Override
public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {
// Add your code here
}
#Override
public void onTextChanged(final CharSequence s, final int start, final int before,
final int count) {
// Add your code here
}
#Override
public void afterTextChanged(final Editable s) {
// Add your code here
}
public void disableTextChangeListner() {
// This will remove the listener. So, it will stop to listen to new events
edJantriNumber.removeTextChangedListener(this);
}
public void addTextChangeListner() {
// This will add the listener. So, it will start to listen to new events
edJantriNumber.addTextChangedListener(this);
}
}
Then, during bindViewHolder, disable the listener, set the new text, and add the listener again as foolows:
#Override
public void onBindViewHolder(#NonNull Myholder myholder, int i) {
myholder.disableTextChangeListner();
myholder.txtJantriNumber.setText(String.valueOf(i + 1));
myholder.edJantriNumber.setText(mTextList[i]);
myholder.addTextChangeListner();
}
When button clicked, i must update a TextView in same position and I have done it, but 9th and 10th position of RecyclerView follow first position and second position. In other word, if I clicked first button position, First position of TextView is updated, but, 9th position of TextView also updated, It should be not updated. How to solve this?
I follow this link
here is my Adapter
class ProductsByStoreAdapter extends RecyclerView.Adapter<ProductsByStoreAdapter.ViewHolder> {
private ArrayList<Products> products;
ProductsByStoreAdapter(ArrayList<Products> productses) {
this.products = productses;
//products = CenterRepository.getCenterRepository()
//.getListOfProductsInShoppingList();
}
#Override
public ProductsByStoreAdapter.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.products_card_item, viewGroup, false);
return new ProductsByStoreAdapter.ViewHolder(view);
}
class ViewHolder extends RecyclerView.ViewHolder{
private TextView tv_product_name, tv_product_price, tv_product_quantity;
private ImageView im_product_image;
private ImageButton button_add_product, button_min_product;
private EditText e_note;
private LinearLayout layout_note;
ViewHolder(View view) {
super(view);
im_product_image = (ImageView)view.findViewById(R.id.product_image);
tv_product_name = (TextView)view.findViewById(R.id.product_name);
tv_product_price = (TextView)view.findViewById(R.id.product_price);
tv_product_quantity = (TextView)view.findViewById(R.id.product_quantity);
e_note = (EditText)view.findViewById(R.id.e_note);
layout_note = (LinearLayout)view.findViewById(R.id.layout_note);
this.button_add_product = (ImageButton)view.findViewById(R.id.button_add_product);
button_min_product = (ImageButton)view.findViewById(R.id.button_min_product);
}
}
#Override
public void onBindViewHolder(final ProductsByStoreAdapter.ViewHolder viewHolder, final int position) {
Glide.with(viewHolder.im_product_image.getContext())
.load(products.get(position).getImage_uri())
.centerCrop()
.crossFade()
//.placeholder(R.drawable.placeholder_main)
.into(viewHolder.im_product_image);
CurrencyFormats currencyFormat = new CurrencyFormats();
viewHolder.tv_product_name.setText(products.get(position).getName());
viewHolder.tv_product_price.setText(currencyFormat.toRupiah(products.get(position).getPrice()));
//viewHolder.tv_product_quantity.setText("0");
viewHolder.button_add_product.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//current object
Products tempObj = (products).get(position);
((ProductsByStoreActivity)view.getContext()).updateItemCount(true);
tempObj.setQuantity(String.valueOf(1));
viewHolder.tv_product_quantity.setText(tempObj.getQuantity());
}
});
viewHolder.button_min_product.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Products tempObj = (products).get(position);
viewHolder.tv_product_quantity.setText(CenterRepository
.getCenterRepository().getListOfProductsInShoppingList()
.get(indexOfTempInShopingList).getQuantity());
((ProductsByStoreActivity)view.getContext()).updateItemCount(false);
}
}
}else {
}
}
});
}
#Override
public int getItemCount() {
//return products.size();
return products == null ? 0 : products.size();
}}
You need to move your onclick listener into onCreateViewHolder.
final ProductsByStoreAdapter.ViewHolder viewHolder = new ProductsByStoreAdapter.ViewHolder(view);
button_add_product.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//current object
Products tempObj = products.get(viewHolder.getAdapterPosition(););
((ProductsByStoreActivity)view.getContext()).updateItemCount(true);
tempObj.setQuantity(String.valueOf(1));
viewHolder.tv_product_quantity.setText(tempObj.getQuantity());
}
});
return viewHolder;
You can do the same with the other onclicklistener
Update: You do not setText to your product_quantity textview in the BindView function, unless a button is clicked. this means its value will be recycled from other items. you should check with an if statement what is the quantity of the item and present it even without clicking.
Old and not correct answer:
I am not sure if this is the problem, but its an easy check, so try it out. There are 2 positions - the adapter position, and the layout position. I think maybe the position you are using (the one that came from the onBind function) is the latter. You want the adapter position, so try using getAdapterPosition() like this:
Products tempObj = (products).get(getAdapterPosition());
add below line to resolve the problem of 9th and 10th position of item
#Override
public int getItemViewType(int position)
{
return position;
}
I have one CardView with custom selector which include focus state to true
<android.support.v7.widget.CardView
android:id="#+id/cvColor"
android:layout_width="45dp"
android:layout_height="45dp"
android:layout_gravity="center"
android:layout_marginBottom="4dp"
android:layout_marginLeft="5dp"
android:layout_marginTop="2dp"
android:foreground="#drawable/card_color_size"
android:minHeight="50dp"
android:visibility="gone"
app:cardCornerRadius="3dp"
app:cardElevation="4dp"
app:cardPreventCornerOverlap="false"
app:cardUseCompatPadding="true" >
My need is when I click on card it should be clicked as well as focused too. so I did something like this.
#Override
public void onBindViewHolder(final ViewHolderCard1 viewHolder,
final int position) {
viewHolder.size.setVisibility(View.VISIBLE);
viewHolder.tvSize.setText(size.get(position).get("size"));
viewHolder.size.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
posSize = position;
Toast.makeText(getApplicationContext(), "" + posSize,
Toast.LENGTH_SHORT).show();
viewHolder.size.setFocusableInTouchMode(true);
}
});
}
now when I click once of the item of RecycleView it works fine, both Toast and focus happens same time.
But as soon as I click next item it just like on first click card perform click event and on second it gets focus.
anyone has idea what should I do?
Try this code.
#Override
public void onBindViewHolder(final ViewHolderCard1 viewHolder,
int position) {
viewHolder.size.setVisibility(View.VISIBLE);
viewHolder.tvSize.setText(size.get(position).get("size"));
viewHolder.size.setOnClickListener(new Listener(position, viewHolder.size));
}
class Listener implements OnClickListener {
private int position;
private ImageView imageView;
private CardView cardview;
Listener(int position, CardView cardview) {
this.position = position;
this.imageView = imageView;
this.data = data;
}
#SuppressLint("UseValueOf")
#Override
public void onClick(View v) {
posSize = position;
Toast.makeText(getApplicationContext(), "" + posSize,
Toast.LENGTH_SHORT).show();
CardView.setFocusableInTouchMode(true);
}
}
I solved this few days back do I am posting here my answer if any one faeces this problem.
SparseBooleanArray did the trick in my case.
First of all declare SparseBooleanArray in your Adapter Class. and Initialize it in your constructor of adapter class.
private ArrayList<HashMap<String, String>> size;
public final SparseBooleanArray selectedItems;
public Size(ArrayList<HashMap<String, String>> title) {
this.size = title;
selectedItems = new SparseBooleanArray(size.size());
}
Now I use setSelected for focusing selected card. inside onBindViewHolder (or getView in case you are using BaseAdapter).
viewHolder.size.setSelected(selectedItems.get(position));
viewHolder.size.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
posSize = position;
if (selectedItems.size() == 0) {
selectedItems.put(position, true);
} else if (selectedItems.size() > 0) {
selectedItems.clear();
selectedItems.put(position, true);
}
notifyDataSetChanged();
}
});
Happy Coding.
Update #1
Added hasStableIds(true) and updated Picasso to version 2.5.2.
It does not solve the issue.
Reproduction:
RecyclerView with GridLayoutManager (spanCount = 3).
List items are CardViews with ImageView inside.
When all the items does not fit the screen calling notifyItemChanged on one item causes more than one calls to onBindViewHolder().
One call is for position from notifyItemChanged others for items not visible on the screen.
Issue:
Sometimes the item at position passed to the notifyItemChanged is loaded with an image belonging to an item that is not on the screen (most likely due to recycling of the view holder - although I would assume that if the item remains in place then the passed viewholder would be the same).
I have found Jake's comment on other issue here about calling load() even if the file/uri is null. Image is loaded on every onBindViewHolder here.
Simple sample app:
git clone https://github.com/gswierczynski/recycler-view-grid-layout-with-picasso.git
Tap on an item calls notifyItemChanged with parameter equal to the position of that item.
Code:
public class MainActivity extends ActionBarActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.container, new PlaceholderFragment())
.commit();
}
}
public static class PlaceholderFragment extends Fragment {
public PlaceholderFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
RecyclerView rv = (RecyclerView) rootView.findViewById(R.id.rv);
rv.setLayoutManager(new GridLayoutManager(getActivity(), 3));
rv.setItemAnimator(new DefaultItemAnimator());
rv.setAdapter(new ImageAdapter());
return rootView;
}
}
private static class ImageAdapter extends RecyclerView.Adapter<ImageViewHolder> implements ClickableViewHolder.OnClickListener {
public static final String TAG = "ImageAdapter";
List<Integer> resourceIds = Arrays.asList(
R.drawable.a0,
R.drawable.a1,
R.drawable.a2,
R.drawable.a3,
R.drawable.a4,
R.drawable.a5,
R.drawable.a6,
R.drawable.a7,
R.drawable.a8,
R.drawable.a9,
R.drawable.a10,
R.drawable.a11,
R.drawable.a12,
R.drawable.a13,
R.drawable.a14,
R.drawable.a15,
R.drawable.a16,
R.drawable.a17,
R.drawable.a18,
R.drawable.a19,
R.drawable.a20);
#Override
public ImageViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item, parent, false);
return new ImageViewHolder(v, this);
}
#Override
public void onBindViewHolder(ImageViewHolder holder, int position) {
Log.d(TAG, "onBindViewHolder position: " + position + " | holder obj:" + holder.toString());
Picasso.with(holder.iv.getContext())
.load(resourceIds.get(position))
.fit()
.centerInside()
.into(holder.iv);
}
#Override
public int getItemCount() {
return resourceIds.size();
}
#Override
public void onClick(View view, int position) {
Log.d(TAG, "onClick position: " + position);
notifyItemChanged(position);
}
#Override
public boolean onLongClick(View view, int position) {
return false;
}
}
private static class ImageViewHolder extends ClickableViewHolder {
public ImageView iv;
public ImageViewHolder(View itemView, OnClickListener onClickListener) {
super(itemView, onClickListener);
iv = (ImageView) itemView.findViewById(R.id.iv);
}
}
}
public class ClickableViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {
OnClickListener onClickListener;
public ClickableViewHolder(View itemView, OnClickListener onClickListener) {
super(itemView);
this.onClickListener = onClickListener;
itemView.setOnClickListener(this);
itemView.setOnLongClickListener(this);
}
#Override
public void onClick(View view) {
onClickListener.onClick(view, getPosition());
}
#Override
public boolean onLongClick(View view) {
return onClickListener.onLongClick(view, getPosition());
}
public static interface OnClickListener {
void onClick(View view, int position);
boolean onLongClick(View view, int position);
}
}
I spent more time than I'd like to admit to work around oddities with RecyclerView and the new adapter that comes with it. The only thing that finally worked for me in terms of correct updates and making sure notifyDataSetChanges and all of its other siblings didn't cause odd behavior was this:
On my adapter, I set
setHasStableIds(true);
In the constructor. I then overrode this method:
#Override
public long getItemId(int position) {
// return a unique id here
}
And made sure that all my items returned a unique id.
How you achieve this is up to you. For me, the data was supplied from my web service in the form of a UUID and I cheated by converting parts of the UUID to long using this:
SomeContent content = _data.get(position);
Long code = Math.abs(content.getContentId().getLeastSignificantBits());
Obviously this is not a very safe approach but chances are it works for my lists which will contain < 1000 items. So far I haven't run into any trouble with it.
What I recommend is to try this approach and see if it works for you. Since you have an array, getting a unique number for you should be simple. Maybe try returning the position of the actual item (and not the position that is passed in the getItemId()) or create a unique long for each of your records and pass that in.
Have you tried calling the mutate() method on the Drawable? See here, for instance.
here a working solution but has graphics glitches when calling notifyDataSetChanged()
holder.iv.post(new Runnable() {
#Override
public void run() {
Picasso.with(holder.iv.getContext())
.load(resourceIds.get(position))
.resize(holder.iv.getWidth(), 0)
.into(holder.iv);
});
it works because at this point image has a width, unfortunately when I need to update all the checkboxes the in the viewholder (like a select all action), and I call notifyDataSetChanged() and the effect is very ugly
still searching for a better solution
edit:
this solution works for me:
holder.iv.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
holder.iv.getViewTreeObserver().removeOnGlobalLayoutListener(this);
else
holder.iv.getViewTreeObserver().removeGlobalOnLayoutListener(this);
Picasso.with(holder.iv.getContext())
.load(resourceIds.get(position))
.resize(holder.iv.getMeasuredWidth(), 0)
.into(holder.iv);
}
});