Search code examples
androidtextandroid-recyclerviewtext-sizecardview

Issues with RecyclerView only updating random cardview text size


Im trying to implement a dynamic text size option within my app. For some reason the recycler is only randomly changing text size within my cardviews instead of setting all the text to the desired size. As I scroll the list, the top cardview text will change correctly but the next 3-4 will stay default and randomly down the list another cardview text will display correctly. when i scroll back up the list, the cardview that displays correctly will change at random.

Main Activity....

// Dark Mode Menu
@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case android.R.id.home:
            mDrawer.openDrawer(GravityCompat.START);
            return true;
        case R.id.menu_night_mode_day:
            setNightMode(AppCompatDelegate.MODE_NIGHT_NO);
            break;
        case R.id.menu_night_mode_night:
            setNightMode(AppCompatDelegate.MODE_NIGHT_YES);
            break;
        case R.id.menu_night_mode_auto:
            setNightMode(AppCompatDelegate.MODE_NIGHT_AUTO);
            break;
        // Text Size Options
        case R.id.menu_text_size_small:
            setTextSize(18);
            break;
        case R.id.menu_text_size_medium:
            setTextSize(20);
            break;
        case R.id.menu_text_size_large:
            setTextSize(22);
            break;
    }
    return super.onOptionsItemSelected(item);
}

// Dark Mode Menu
private void setNightMode(@AppCompatDelegate.NightMode int nightMode) {
    AppCompatDelegate.setDefaultNightMode(nightMode);

    if (Build.VERSION.SDK_INT >= 11) {
        recreate();
    }
}

// Dynamic text size
private void setTextSize(int textSize) {
    TextView description = (TextView) findViewById(R.id.cardview_description);
    description.setTextSize(textSize);
    saveToPreferences(this, "THE_TEXT_SIZE", "" + textSize);
}

My Adapter....

public class MyPageAdapter extends Adapter<MyPageHolder> {

public List<MenuPageItems> datas;
private Activity activity;
public String dynamicTextSize;

public MyPageAdapter(Activity activity){
    datas = new ArrayList<>();
    this.activity = activity;
}

public void add(MenuPageItems dataModel){
    datas.add(dataModel);
}

public void add(MenuPageItems dataModel, int position){
    datas.add(position, dataModel);
}

public void addAll(List<MenuPageItems> menuPageItems){
    datas.addAll(menuPageItems);
}

@Override
public MyPageHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View v = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
    return createViewHolder(v, viewType);
}

@Override
public void onBindViewHolder(MyPageHolder holder, int position) {
    holder.bind(datas.get(position), activity, position);
    dynamicTextSize = "20";
}

@Override
public int getItemCount() {
    return datas.size();
}

@Override
public int getItemViewType(int position){
    return datas.get(position).getViewResId();
}

public int searchViewTypePosition(int viewType){
    int i = 0;
    boolean found = false;
    while(i < datas.size() && !found){
        if(datas.get(i).getViewResId() == viewType){
            found = true;
            i--;
        }
        i++;
    }
    return i;
}

public MyPageHolder createViewHolder(View v, int viewType){
    return datas.get(searchViewTypePosition(viewType)).createViewHolder(v, activity, this);
}
}

Holder....

public abstract class MyPageHolder extends RecyclerView.ViewHolder{

protected final Activity activity;
protected MyPageAdapter adapter;
public TextView txtTitle, txtDescription, txtTheContent;
public ImageView imgImage;
public View view;

public MyPageHolder(View v, Activity activity, MyPageAdapter adapter) {
    super(v);
    this.activity = activity;
    this.adapter = adapter;

    imgImage = (ImageView) v.findViewById(R.id.cardview_image);
    txtTitle = (TextView) v.findViewById(R.id.cardview_title);
    txtDescription = (TextView) v.findViewById(R.id.cardview_description);
    view = (CardView) v.findViewById(R.id.card_view);

    view.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            /*/ this is where the magic happens when clicked /*/
        }
    });
}

public void bind(MenuPageItems dataModel, Activity activity, final int position) {
    final MenuPageItems m = (MenuPageItems)dataModel;
    imgImage.setImageResource(m.image);
    txtTitle.setText(m.title);
    txtDescription.setText(m.description);
    //txtTheContent.setText(m.theContent);

    view.setOnClickListener(new View.OnClickListener() {
        @Override public void onClick(View v){

            Intent cvIntent = new Intent(view.getContext(), EndpageActivity.class);

            // header image to pass to endpage activity
            cvIntent.putExtra("endpageHeader", m.image);

            // text to pass to endpage activity
            cvIntent.putExtra("endpageTitle", m.title);
            cvIntent.putExtra("endpageTheContent", m.theContent);
            view.getContext().startActivity(cvIntent);
        }
    });
}
}

Do I need to add something to my adapter or viewholder to update all the text properly?


Solution

  • I think I get it, but I don't see where you are setting the text size at all, you said it changes in some cards randomly.

    As I see it, what needs to be done is to set the size in the Holder's bind method. This gets executed every time the card needs to be redrawn. You can read the shared preferences inside the bind(), but that is terribly inefficient since the holder's bind method will be called many times over when scrolling. You wan to avoid any excess work inside the Holders bind()

    Add a dynamicTextSize member variable to the adapter and set the value with either:

    1. Add a setText/getText size to the adapter and the activity can set this when needed.
    2. Retrieve the text size inside the adapter's constructor and then override the notifyDataSetChanged() method and pull the value again each time that is called. Then call super.notifyDataSetChanged()

    Example:

    @Override
    public void notifyDataSetChanged() {
       this.dynamicTextSize = // Pull value from shared preferences
       super.notifiyDataSetChanged();
    }
    

    What I also don't see is the dynamicTextSize value being passed into the holder. Since the holder has a reference to the adapter, you can add a getTextSize() method to the adapter, then the holder can call into the adapter to get it.

    public MyPageHolder(View v, Activity activity, MyPageAdapter adapter) {
       ...
       this.dynamicTextSize = adapter.getTextSize()
    }
    

    Finally, in the setTextSize() method you'll need to call the adapter.notifyDataSetChanged() to update the adapter.

    Update 10/17

    I've attempted to add some detail to by previous post.

    Main Activity

    // Dynamic text size
    private void setTextSize(int textSize) {
    
        //  Add a call to set the text to the adapter's member variable:
        mAdapter.setTextSize(textSize);
    
        // I'm not sure what description is here...  I don't see what type the member is
        description.setTextSize(textSize);
        saveToPreferences(this, "THE_TEXT_SIZE", "" + textSize);
    }
    

    In your adapter, add a method to set and get the text size. The set will be called by the main activity, when the text size changes, and the get is called by the holder each time it needs to set the size of the TextView.

    public class MyPageAdapter extends Adapter<MyPageHolder> {
    
        ...
        public String dynamicTextSize;
    
        public void setTextSize(int textSize) {
           dynamicTextSize = textSize;
        }
        // This will be called by the holder
        public int getTextSize() {
           return dynamicTextSize;
        } 
        ...
    }
    

    In your holder:

    public abstract class MyPageHolder extends RecyclerView.ViewHolder{
        public void bind(MenuPageItems dataModel, Activity activity, final int position) {
            ...
    
            // Call into the adapter to get the text size.
            int textSize = adapter.getTextSize();
            txtDescription.setTextSize(textSize);
        }
    }
    

    Update 10/19

    I was able to get it to work with just a small change.

    1. Add a getDynamicTextSize in your MainActivity
    2. Add a call to the get from within the MyPageAdapter constructor.

      public MyPageAdapter(Activity activity){
          datas = new ArrayList<>();
          this.activity = activity;
          dynamicTextSize = ((MainActivity)activity).getDynamicTextSize();
      }
      

    While this does work, there is are few things it will not do for you.

    1. Ties your fragments to always being a child of the MainActivity activity, you can get around this with an interface, but still not pretty.

    2. Will not update the current activity as soon as the user chooses the new text size. Since the mainActivity takes the menu event, you will need to inform whatever Fragment(s) is/are active, that the text setting has changed and then call notifiyDataSetChanged on the adapter.

    3. Does not set the size of the text outside of the custom RecyclerView. I see you have a few fragments that do not use you recycler view. These will not take the setting. The setting in the menu would make you think all the text in the app should change.

    The accepted reply in this post seems to be a good way to adjust the text size for the entire app. Some changes in your app almost show that you've seen it.