wbk727
wbk727

Reputation: 8408

CardView inside RecyclerView not resizing to correct height when expanded

After creating a RecyclerView, I've noticed that my CardView does not resize to the correct height when I expand it (Item A, Item B and Item C). It should be the same height as the txtSubtitle TextView. I think that txtSubtitleHeight could be the culprit but does anyone know what is causing this problem and how to resolve it?

enter image description here

public class MyFragmentRV extends android.support.v4.app.Fragment {

    public int mGridViewHeight;
//    public int txtSubtitleHeight;
    private static final int ITEM_TYPE = 100;
    private static final int HEADER_TYPE = 101;
    private static final int HEADER_TYPE_2 = 102;
    private static final int GRID_TYPE = 103;
//    GridView mGridViewA;
    ValueAnimator mAnimatorGV, mAnimatorTV;
    TextView txtArrowGV, txtTitle;

    //
    static final String[] frenchVowels = new String[]{
            "a", "e", "i", "o", "u", "y"
    };

    public MyFragmentRV.MyAdapter adapterGV;

    public MyFragmentRV() {}

    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

        return inflater.inflate(R.layout.fragment_rv, container, false);
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        View v = getView();
        assert v != null;

        recyclerView = v.findViewById(R.id.my_recyclerview);

        // set the linear layout manager
        recyclerView.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));

        // SpannableStrings
        // dynamically change SpannableString colour using defined attribute
        int[] attrS = {R.attr.spannablestringtextColor};
        TypedArray ta = getActivity().getTheme().obtainStyledAttributes(attrS);
        int colorSS = ta.getColor(0, Color.BLACK); //Color.BLACK - default value (colour will change automatically depending on chosen theme)
        Log.d(TAG, "clickMethod 1) " + Integer.toHexString(colorSS));
        ta.recycle();


        // SpannableString (start)
        SpannableStringBuilder ssb = new SpannableStringBuilder();

        SpannableString str1 = new SpannableString(" Item A ");
        str1.setSpan(new BackgroundColorSpan(Color.BLACK), 0, str1.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        str1.setSpan(new ForegroundColorSpan(ContextCompat.getColor(getContext(), R.color.yellow)), 0, str1.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        ssb.append(str1);

        SpannableString str2 = new SpannableString(" Hello World");
        str2.setSpan(new ForegroundColorSpan(colorSS), 0, str2.length(), 0);
        ssb.append(str2);
        // SpannableString (end)

        // init data
        data = new ArrayList<>();
        data.add(ssb);
        data.add("Item B");
        data.add("Item C");

        subdata = new ArrayList<>();
        subdata.add("\u2022 a");
        subdata.add("\u2022 b\n\u2022 bb");
        subdata.add("\u2022 c\n\u2022 cc\n\u2022 ccc");

        adapter = createAdapter();

        recyclerView.setAdapter(adapter);

        super.onActivityCreated(savedInstanceState);
    }

    RecyclerView recyclerView;
    ArrayList<CharSequence> data;
    ArrayList<String> subdata;
    RecyclerView.Adapter<ViewHolder> adapter;


    // creates the adapter
    private RecyclerView.Adapter<ViewHolder> createAdapter() {
        return new RecyclerView.Adapter<ViewHolder>() {
            @NonNull
            @Override
            public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int type) {
                switch (type) {
                    case HEADER_TYPE:
                        return new ViewHolder(inflateHelper(R.layout.header, parent));

                    case HEADER_TYPE_2:
                        return new ViewHolder(inflateHelper(R.layout.header, parent));

                    case ITEM_TYPE:
                        return new ViewHolder(inflateHelper(R.layout.recyclerview_item_tv, parent));

                    case GRID_TYPE:
                        return new ViewHolder(inflateHelper(R.layout.recyclerview_item_gv, parent));

                    default:
                        return new ViewHolder(inflateHelper(R.layout.recyclerview_item_tv, parent));
                }
            }

            @Override
            public void onBindViewHolder(@NonNull ViewHolder viewHolder, int position) {
                final Typeface iconFont = FontManager.getTypeface(getContext(), FontManager.FONTAWESOME); // FontManager class must be accessed first before text views can be set as image views

                switch (getItemViewType(position)) {
                    case HEADER_TYPE:
                        Button expandButton = viewHolder.itemView.findViewById(R.id.button);
                        expandButton.setText("Expand all");

                        expandButton.setOnClickListener(new View.OnClickListener() {
                            @Override
                            public void onClick(View v) {
                            }
                        });
                    break;
                    case HEADER_TYPE_2:
                        Button collapseButton = viewHolder.itemView.findViewById(R.id.button);
                        collapseButton.setText("Collapse all");

                        collapseButton.setOnClickListener(new View.OnClickListener() {
                            @Override
                            public void onClick(View v) {
                            }
                        });
                    break;
                    case ITEM_TYPE:
                        // get the current item
                        CharSequence itemA = data.get(position - 3);
                        String itemB = subdata.get(position - 3);


                        txtTitle = viewHolder.itemView.findViewById(R.id.tv_tv_A);
                        txtTitle.setText(itemA);

                        final TextView txtSubtitle = viewHolder.itemView.findViewById(R.id.tv_tv_B);
                        txtSubtitle.setText(itemB);
                        txtSubtitle.setVisibility(View.GONE);

                        //Add onPreDrawListener
                        txtSubtitle.getViewTreeObserver().addOnPreDrawListener(
                        new ViewTreeObserver.OnPreDrawListener() {

                            @Override
                            public boolean onPreDraw() {
                                txtSubtitle.getViewTreeObserver().removeOnPreDrawListener(this);
                                txtSubtitle.setVisibility(View.GONE);

                                final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
                                final int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
                                txtSubtitle.measure(widthSpec, heightSpec);

                                txtSubtitleHeight = txtSubtitle.getMeasuredHeight();

                                return true;
                            }
                        });

                        final TextView txtArrowTV = viewHolder.itemView.findViewById(R.id.tv_tv_expandcollapse);
                        txtArrowTV.setText(R.string.fa_icon_chevron_down);
                        txtArrowTV.setTypeface(iconFont);

                        //
                        CardView cardView = viewHolder.itemView.findViewById(R.id.cv_tv);
                        LinearLayout mLinearLayoutTV = viewHolder.itemView.findViewById(R.id.cardview_tv_titlerow);

                        cardView.setOnClickListener(new View.OnClickListener() {
                            @Override
                            public void onClick(View v) {
//                                Toast.makeText(getActivity(),"CardView clicked", Toast.LENGTH_SHORT).show();
                                if(txtSubtitle.getVisibility() == View.GONE){
                                    expandTV(txtSubtitle, txtArrowTV);
                                } else {
                                    collapseTV(txtSubtitle, txtArrowTV);
                                }
                            }
                        });

                        mLinearLayoutTV.setOnClickListener(new View.OnClickListener() {
                            @Override
                            public void onClick(View v) {
                                if(txtSubtitle.getVisibility() == View.GONE){
                                    expandTV(txtSubtitle, txtArrowTV);
                                } else {
                                    collapseTV(txtSubtitle, txtArrowTV);
                                }
                            }
                        });

                        txtArrowTV.setOnClickListener(new View.OnClickListener() {
                            @Override
                            public void onClick(View v) {
                                if(txtSubtitle.getVisibility() == View.GONE){
                                    expandTV(txtSubtitle, txtArrowTV);
                                } else {
                                    collapseTV(txtSubtitle, txtArrowTV);
                                }
                            }
                        });
                    break;
                    case GRID_TYPE:
                        TextView titleG = viewHolder.itemView.findViewById(R.id.tv_gv_A);

                        titleG.setText("French vowels");

                        final TextView txtArrowGV = viewHolder.itemView.findViewById(R.id.tv_gv_expandcollapse);
                        txtArrowGV.setText(R.string.fa_icon_chevron_down);
                        txtArrowGV.setTypeface(iconFont);

                        final GridView mGridViewA = viewHolder.itemView.findViewById(R.id.gv);
                        mGridViewA.setVisibility(View.GONE);
                        mGridViewA.setEnabled(false);
                        mGridViewA.setVerticalScrollBarEnabled(false);
                        adapterGV = new MyFragmentRV.MyAdapter(getActivity().getApplicationContext(), 0);
                        mGridViewA.setAdapter(adapterGV);
                        for (String frenchVowel : frenchVowels) {
                            adapterGV.addAdapterItem(new MyFragmentRV.AdapterItem(frenchVowel));
                        }


                        //Add onPreDrawListener
                        mGridViewA.getViewTreeObserver().addOnPreDrawListener(
                        new ViewTreeObserver.OnPreDrawListener() {

                            @Override
                            public boolean onPreDraw() {
                                mGridViewA.getViewTreeObserver().removeOnPreDrawListener(this);
                                mGridViewA.setVisibility(View.GONE);

                                final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
                                final int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
                                mGridViewA.measure(widthSpec, heightSpec);

                                mGridViewHeight = mGridViewA.getMeasuredHeight();

                                return true;
                            }
                        });


                        CardView cardViewG = viewHolder.itemView.findViewById(R.id.cv_gv);
                        LinearLayout mLinearLayoutGV = viewHolder.itemView.findViewById(R.id.cardview_gv_titlerow);

                        cardViewG.setOnClickListener(new View.OnClickListener() {
                            @Override
                            public void onClick(View v) {
                                if(mGridViewA.getVisibility() == View.GONE){
                                    expandGV(mGridViewA);
                                } else {
                                    collapseGV(mGridViewA);
                                }
                            }
                        });

                        mLinearLayoutGV.setOnClickListener(new View.OnClickListener() {
                            @Override
                            public void onClick(View v) {
                                if(mGridViewA.getVisibility() == View.GONE){
                                    expandGV(mGridViewA);
                                } else {
                                    collapseGV(mGridViewA);
                                }
                            }
                        });

                        txtArrowGV.setOnClickListener(new View.OnClickListener() {
                            @Override
                            public void onClick(View v) {
                                if(mGridViewA.getVisibility() == View.GONE){
                                    expandGV(mGridViewA);
                                } else {
                                    collapseGV(mGridViewA);
                                }
                            }
                        });
                    break;
                }
            }

            @Override
            public int getItemCount() {
                return data.size() + 3;
            }

            @Override
            public int getItemViewType(int position) {
                switch (position) {
                    case 0:
                        return HEADER_TYPE;
                    case 1:
                        return HEADER_TYPE_2;
                    case 2:
                        return GRID_TYPE;
                    default: return ITEM_TYPE;
                }
            }
        };
    }

    private View inflateHelper(int resId, ViewGroup parent) {
        return LayoutInflater.from(getActivity()).inflate(resId, parent, false);
    }

    // inner class for viewholder to use,
    class ViewHolder extends RecyclerView.ViewHolder {
        public ViewHolder(@NonNull View itemView) {
            super(itemView);
        }
    }

    public void expandGV(final GridView mGridViewA) {
        mGridViewA.setVisibility(View.VISIBLE);
        txtArrowGV.setText(R.string.fa_icon_chevron_up);

       ValueAnimator mAnimatorGV = slideAnimator(0, mGridViewHeight, mGridViewA);
        mAnimatorGV.start();
    }
    public void collapseGV(final GridView mGridViewA) {
        txtArrowGV.setText(R.string.fa_icon_chevron_down);

        int finalGVHeight = mGridViewA.getHeight();

        mAnimatorGV = slideAnimator(finalGVHeight, 0, mGridViewA);

        mAnimatorGV.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationEnd(Animator animator) {
                mGridViewA.setVisibility(View.GONE);
            }

            @Override
            public void onAnimationStart(Animator animator) {
            }

            @Override
            public void onAnimationCancel(Animator animator) {
            }

            @Override
            public void onAnimationRepeat(Animator animator) {
            }
        });
        mAnimatorGV.start();
    }
    public void expandTV(final TextView txtSubtitle, final TextView txtArrowTV) {
        txtSubtitle.setVisibility(View.VISIBLE);

        txtArrowTV.setText(R.string.fa_icon_chevron_up);

        mAnimatorTV = slideAnimator(0, txtSubtitleHeight, txtSubtitle);

        mAnimatorTV.start();
    }

    public void collapseTV(final TextView txtSubtitle, final TextView txtArrowTV) {
        txtArrowTV.setText(R.string.fa_icon_chevron_down);

        int finalTVHeight = txtSubtitle.getHeight();

        mAnimatorTV = slideAnimator(finalTVHeight, 0, txtSubtitle);

        mAnimatorTV.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationEnd(Animator animator) {
                txtSubtitle.setVisibility(View.GONE);
            }

            @Override
            public void onAnimationStart(Animator animator) {
            }

            @Override
            public void onAnimationCancel(Animator animator) {
            }

            @Override
            public void onAnimationRepeat(Animator animator) {
            }
        });
        mAnimatorTV.start();
    }


    public ValueAnimator slideAnimator(int start, int end, final View txtSubtitle) {

        final ValueAnimator animator = ValueAnimator.ofInt(start, end);

        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                // update height
                int value = (Integer) valueAnimator.getAnimatedValue();

                ViewGroup.LayoutParams layoutParamsTV = txtSubtitle.getLayoutParams();
                layoutParamsTV.height = value;
                txtSubtitle.setLayoutParams(layoutParamsTV);

//                ViewGroup.LayoutParams layoutParamsGV = mGridViewA.getLayoutParams();
//                layoutParamsGV.height = value;
//                mGridViewA.setLayoutParams(layoutParamsGV);
            }
        });
        return animator;
    }
}

XML

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:clickable="true"
    android:focusable="true"
    android:id="@+id/cv_tv"
    android:layout_marginBottom="20dp"
    >

    <LinearLayout
        android:id="@+id/cardview_main"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="10dp"
        android:animateLayoutChanges="true">

        <LinearLayout
            android:id="@+id/cardview_tv_titlerow"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:layout_marginBottom="2dp"
            android:weightSum="100">

            <TextView
                android:id="@+id/tv_tv_A"
                android:layout_weight="90"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:textColor="?android:attr/textColorPrimary"
                style="@android:style/TextAppearance.Medium" />

            <TextView
                android:id="@+id/tv_tv_expandcollapse"
                android:clickable="true"
                android:focusable="true"
                android:layout_weight="10"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginStart="10dp"
                android:textColor="?android:attr/textColorPrimary"
                style="@android:style/TextAppearance.Large" />
        </LinearLayout>

        <RelativeLayout
            android:id="@+id/relativelayout_tv"
            android:animateLayoutChanges="true"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <TextView
                android:id="@+id/tv_tv_B"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textColor="?android:attr/textColorPrimary"
                style="@android:style/TextAppearance.Large" />
        </RelativeLayout>
    </LinearLayout>
</android.support.v7.widget.CardView>

Macmist's suggestion

enter image description here

Upvotes: 1

Views: 1885

Answers (1)

Macmist
Macmist

Reputation: 723

Ok so it seems that it's your function to get the height that is not correct. Actually By rereading your code, I think I might have found an idea of what is going on. With the tests you have done we've determined that the issue was on the side of the function that retrieve the size. Now, on your demonstrating gif, all the expanded views have the same size, and for the last card, it seems to have the size you want, which is not having any space after the text.

So based on those information, my guess is the following:

  • your function to determine the height actually works
  • BUT you have only one variable defining that size (txtSubtitleHeight)
  • So for each card, the height is calculated and txtSubtitleHeight is replaced by the height of that card
  • And in the end, txtSubtitleHeight has the height of the last card seen. And as you are using this variable for expanding each card, each card will have the height from that last card seen.

So, based on that, if I'm not mistaken, a simple fix would be to have one height variable for each card, so one for each viewholder from your adapter.

You could do like:

   // inner class for viewholder to use,
    class ViewHolder extends RecyclerView.ViewHolder {
        public ViewHolder(@NonNull View itemView) {
            super(itemView);
        }
        int height;
    }

then you can update your onPreDraw function like this:

@Override
public boolean onPreDraw() {
    txtSubtitle.getViewTreeObserver().removeOnPreDrawListener(this);
                                txtSubtitle.setVisibility(View.GONE);
    final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    final int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
                                txtSubtitle.measure(widthSpec, heightSpec);
    viewHolder.height = txtSubtitle.getMeasuredHeight();
    return true;
}

And finally in your expandTV

public void expandTV(final TextView txtSubtitle, final TextView txtArrowTV, final ViewHolder holder) {
    txtSubtitle.setVisibility(View.VISIBLE);

    txtArrowTV.setText(R.string.fa_icon_chevron_up);

    mAnimatorTV = slideAnimator(0, holder.height, txtSubtitle);

    mAnimatorTV.start();
}

And I believe it should solve your issue :)

Upvotes: 3

Related Questions