Listview loses state of lines during scroll

Asked

Viewed 147 times

1

I own a ListView and I use the following layout for Adapter:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/llContainer"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="5dp"
    android:layout_marginTop="5dp"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/tvLocal"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="question"
        android:textColor="@color/app_gray"
        android:textSize="12sp" >
    </TextView>

    <TextView
        android:id="@+id/tvQuestion"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="question"
        android:textColor="@color/app_gray"
        android:textSize="12sp"
        android:textStyle="bold" >
    </TextView>

    <LinearLayout
        android:id="@+id/llContainerLikeDislike"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="5dp"
        android:layout_marginTop="5dp"
        android:orientation="vertical"
        android:visibility="gone" >

        <TextView
            android:id="@+id/tvAnswer"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="5dp"
            android:layout_marginBottom="5dp"
            android:textColor="@color/app_blue"
            android:textSize="12sp" />

        <LinearLayout
            android:id="@+id/llContainerButtons"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="5dp"
            android:layout_marginTop="5dp"
            android:orientation="horizontal" >

            <LinearLayout
                android:id="@+id/llLike"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="5dp"
                android:padding="5dp"
                android:clickable="true"
                android:gravity="center_horizontal"
                android:orientation="vertical" >

                <ImageView
                    android:id="@+id/ivLike"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:contentDescription="@null"
                    android:layout_margin="5dp"
                    android:src="@drawable/background_btn_like" />

                <TextView
                    android:id="@+id/tvHelped"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Helped"
                    android:textColor="@color/app_gray"
                    android:textSize="12sp" >
                </TextView>

                <TextView
                    android:id="@+id/tvNumLike"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textColor="@color/app_gray"
                    android:textSize="12sp" >
                </TextView>
            </LinearLayout>

            <LinearLayout
                android:id="@+id/llDisLike"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="5dp"
                android:padding="5dp"
                android:clickable="true"
                android:gravity="center_horizontal"
                android:orientation="vertical" >

                <ImageView
                    android:id="@+id/ivDisLike"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:contentDescription="@null"
                    android:layout_margin="5dp"
                    android:src="@drawable/background_btn_dislike" />

                <TextView
                    android:id="@+id/tvNotHelp"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Not Help"
                    android:textColor="@color/app_gray"
                    android:textSize="12sp" >
                </TextView>

                <TextView
                    android:id="@+id/tvNumDisLike"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textColor="@color/app_gray"
                    android:textSize="12sp" >
                </TextView>
            </LinearLayout>

        </LinearLayout>

    </LinearLayout>

</LinearLayout>

QA class:

public class QA {

    private int id;
    private String question, answer, answer_at, state, city, name;
    public enum state { like, dislike };
    private long up_votes_count, down_votes_count;
    private boolean visible, like, wasVoted;

    // get and setters

}

In the case llContainer (LinearLayout) must receive a click event and present the LinearLayout llContainerLikeDislike, as an expandable list. Control whether llContainerLikeDislike this with your Visibility Gone passes to Visible and vice versa. I use this method for this:

public static void expand(final View v, final ListView lv, final int position) {
    v.measure(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
    final int targetHeight = v.getMeasuredHeight();

    v.getLayoutParams().height = 0;
    v.setVisibility(View.VISIBLE);
    Animation a = new Animation()
    {
        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            v.getLayoutParams().height = interpolatedTime == 1
                    ? LayoutParams.WRAP_CONTENT
                    : (int)(targetHeight * interpolatedTime);
            v.requestLayout();

            //Moves the listview scroll so that the expanding area is visible.
            lv.setSelectionFromTop(position, v.getLayoutParams().height);
        }

        @Override
        public boolean willChangeBounds() {
            return true;
        }
    };

    // 1dp/ms
    a.setDuration((int)(targetHeight / v.getContext().getResources().getDisplayMetrics().density));
    v.startAnimation(a);
}

public static void collapse(final View v) {
    final int initialHeight = v.getMeasuredHeight();

    Animation a = new Animation()
    {
        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            if(interpolatedTime == 1){
                v.setVisibility(View.GONE);
            }else{
                v.getLayoutParams().height = initialHeight - (int)(initialHeight * interpolatedTime);
                v.requestLayout();
            }
        }

        @Override
        public boolean willChangeBounds() {
            return true;
        }
    };

    // 1dp/ms
    a.setDuration((int)(initialHeight / v.getContext().getResources().getDisplayMetrics().density));
    v.startAnimation(a);
}

In the Adapter use a ViewHolder to control the reference of each line with the setTag() and getTag(). In case each line is correctly keeping the qa object data during the scroll. But the scroll is changing the behavior of the lines that control the expansion and retraction of the lines and, enabling and disabling a line that had its ImageView selected. I use the ImageView's as if it were buttons to give a like or dislike on a line, when clicked it is disabled and changes the appearance. In short, the data of each line is correct for qa objects but that of the components as line Visibility Gone/Visible and, color change, enable and disable during scroll ListView are of wrong behavior. If a line for example of position 0 this Visible, disabled, and changed the color, while moving the scroll of ListView a further Random line below shows the same state as line 0. I used the debug and the methods are receiving the reference correctly and are being called only once, there is no repetition for the Random lines.

Follows getView() Adapter:

public class QAAdapter extends ArrayAdapter<QA> implements Filterable {

    private List<QA>filteredData;

    private ArrayList<QA> arrayQA;
    private ViewHolder holder;
    private final LayoutInflater inflater;
    private Activity activity;

    public QAAdapter(Activity activity,
            ArrayList<QA> arrayQA) {
        super(activity, 0);

        this.inflater = (LayoutInflater) activity
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        this.arrayQA = arrayQA;
        this.filteredData = arrayQA; 
        this.activity = activity;
    }

    public View getView(final int position, View convertView,
        final ViewGroup parent) {

    holder = new ViewHolder();

    if (convertView == null) {
        convertView = inflater.inflate(R.layout.item_pergunta, parent,
                false);

        Typeface tfLight = Typeface.createFromAsset(activity.getAssets(),
                "fonts/Oswald-Light.ttf");

        Typeface tfBold = Typeface.createFromAsset(activity.getAssets(),
                "fonts/Oswald-Bold.ttf");

        holder.tvAjudou = (TextView) convertView
                .findViewById(R.id.tvAjudou);
        holder.tvNaoAjudou = (TextView) convertView
                .findViewById(R.id.tvNaoAjudou);

        holder.tvLocal = (TextView) convertView.findViewById(R.id.tvLocal);
        holder.tvLocal.setTypeface(tfLight);

        holder.tvQuestion = (TextView) convertView
                .findViewById(R.id.tvQuestion);
        holder.tvQuestion.setTypeface(tfBold);

        holder.tvAnswer = (TextView) convertView
                .findViewById(R.id.tvAnswer);
        holder.tvAnswer.setTypeface(tfLight);

        holder.tvNumLike = (TextView) convertView
                .findViewById(R.id.tvNumLike);
        holder.tvNumLike.setTypeface(tfLight);

        holder.tvNumDisLike = (TextView) convertView
                .findViewById(R.id.tvNumDisLike);
        holder.tvNumDisLike.setTypeface(tfLight);

        holder.ivLike = (ImageView) convertView.findViewById(R.id.ivLike);

        holder.ivDisLike = (ImageView) convertView
                .findViewById(R.id.ivDisLike);

        holder.llContainerButtons = (LinearLayout) convertView
                .findViewById(R.id.llContainerButtons);

        holder.llContainer = (LinearLayout) convertView
                .findViewById(R.id.llContainer);

        holder.llContainerLikeDislike = (LinearLayout) convertView
                .findViewById(R.id.llContainerLikeDislike);
        holder.llContainerLikeDislike.setVisibility(View.GONE);

        holder.llLike = (LinearLayout) convertView
                .findViewById(R.id.llLike);

        holder.llDisLike = (LinearLayout) convertView
                .findViewById(R.id.llDisLike);

        holder.llContainer.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {

                final int position2 = ((ListView) parent)
                        .getPositionForView(v);

                QA qa = getItemQa(position2);

                boolean visibility = qa.isVisible();

                if (!visibility) {

                    qa.setVisible(true);

                    UtilsLab.expand(
                            v.findViewById(R.id.llContainerLikeDislike),
                            ((ListView) parent), position2);
                } else {
                    qa.setVisible(false);

                    UtilsLab.collapse(v
                            .findViewById(R.id.llContainerLikeDislike));
                }

                QASelected selectQASelectedById = QASelectedModel
                        .selectQASelectedById(activity, qa.getId());

                if (selectQASelectedById != null) {
                    v.findViewById(R.id.llLike).setEnabled(false);
                    v.findViewById(R.id.llDisLike).setEnabled(false);

                    boolean isLike = selectQASelectedById.isLike();

                    isLike = qa.isLike();
                    qa.setWasVoted(true);

                    v.findViewById(R.id.llLike).setSelected(isLike);
                    v.findViewById(R.id.llDisLike).setSelected(!isLike);

                    ImageView ivLike = (ImageView) v
                            .findViewById(R.id.ivLike);
                    ivLike.setSelected(isLike);

                    ImageView ivDisLike = (ImageView) v
                            .findViewById(R.id.ivDisLike);
                    ivDisLike.setSelected(!isLike);

                    if (isLike) {
                        ((TextView) v.findViewById(R.id.tvAjudou))
                                .setTextColor(activity.getResources()
                                        .getColor(R.color.app_azul_claro));

                        ((TextView) v.findViewById(R.id.tvNumLike))
                                .setTextColor(activity.getResources()
                                        .getColor(R.color.app_azul_claro));

                        ((TextView) v.findViewById(R.id.tvNaoAjudou))
                                .setTextColor(activity.getResources()
                                        .getColor(R.color.app_cinza_claro));

                        ((TextView) v.findViewById(R.id.tvNumDisLike))
                                .setTextColor(activity.getResources()
                                        .getColor(R.color.app_cinza_claro));
                    } else {
                        ((TextView) v.findViewById(R.id.tvAjudou))
                                .setTextColor(activity.getResources()
                                        .getColor(R.color.app_cinza_claro));

                        ((TextView) v.findViewById(R.id.tvNumLike))
                                .setTextColor(activity.getResources()
                                        .getColor(R.color.app_cinza_claro));

                        ((TextView) v.findViewById(R.id.tvNaoAjudou))
                                .setTextColor(activity.getResources()
                                        .getColor(R.color.app_vermelho));

                        ((TextView) v.findViewById(R.id.tvNumDisLike))
                                .setTextColor(activity.getResources()
                                        .getColor(R.color.app_vermelho));
                    }
                }

                notifyDataSetChanged();
            }
        });

        holder.llLike.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {

                QA qa = getItemQa(position);

                int value = (int) (qa.getUp_count() + 1);
                qa.setUp_count(value);

                qa.setLike(true);
                qa.setWasVoted(true);

                ((LinearLayout) v).setEnabled(false);
                ((LinearLayout) ((LinearLayout) v.getParent())
                        .findViewById(R.id.llDisLike)).setEnabled(false);
                ((ImageView) v.findViewById(R.id.ivLike)).setSelected(true);
                ((TextView) v.findViewById(R.id.tvAjudou))
                        .setTextColor(activity.getResources().getColor(
                                R.color.app_azul_claro));
                ((TextView) v.findViewById(R.id.tvNumLike))
                        .setTextColor(activity.getResources().getColor(
                                R.color.app_azul_claro));

                notifyDataSetChanged();

                TrackingManager.trackEvent(ConstantsLab.VOTACAO,
                        ConstantsLab.EVENTO_RESPOSTA_POSITIVA,
                        qa.getQuestion());

                UpDownVoteWsAsyncTask asyncTask = new UpDownVoteWsAsyncTask();
                asyncTask.flagUPDown = 1;
                asyncTask.id = qa.getId();
                asyncTask.execute(ConstantsLab.WS_BASE_URL
                        + ConstantsLab.WS_UP_DOWN_VOTE);
            }
        });

        holder.llDisLike.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {

                QA qa = getItemQa(position);

                int value = (int) (qa.getDown_count() + 1);
                qa.setDown_count(value);

                qa.setLike(false);
                qa.setWasVoted(true);

                ((LinearLayout) v).setEnabled(false);
                ((LinearLayout) ((LinearLayout) v.getParent())
                        .findViewById(R.id.llLike)).setEnabled(false);
                ((ImageView) v.findViewById(R.id.ivDisLike))
                        .setSelected(true);
                ((TextView) v.findViewById(R.id.tvNaoAjudou))
                        .setTextColor(activity.getResources().getColor(
                                R.color.app_vermelho));
                ((TextView) v.findViewById(R.id.tvNumDisLike))
                        .setTextColor(activity.getResources().getColor(
                                R.color.app_vermelho));

                notifyDataSetChanged();

                TrackingManager.trackEvent(ConstantsLab.VOTACAO,
                        ConstantsLab.EVENTO_RESPOSTA_NEGATIVA,
                        qa.getQuestion());

                UpDownVoteWsAsyncTask asyncTask = new UpDownVoteWsAsyncTask();
                asyncTask.flagUPDown = 0;
                asyncTask.id = qa.getId();
                asyncTask.execute(ConstantsLab.WS_BASE_URL
                        + ConstantsLab.WS_UP_DOWN_VOTE);
            }
        });

        convertView.setTag(holder);

    } else {
        holder = (ViewHolder) convertView.getTag();
    }

    final QA qa = getItemQa(position);

    if (qa != null) {

        String name = qa.getName();
        String city = qa.getCity();
        String state = qa.getState();

        if (qa.getName().equals(null) || qa.getName().equals("null")) {
            name = " ";
        }

        if (qa.getCity().equals(null) || qa.getCity().equals("null")) {
            city = " ";
        }

        if (qa.getState().equals(null) || qa.getState().equals("null")) {
            state = " ";
        }

        holder.tvLocal.setText(activity.getString(
                R.string.local_item_pergunta, name, city, state));

        holder.tvQuestion.setText(qa.getQuestion());
        holder.tvAnswer.setText(qa.getAnswer());

        holder.tvNumLike.setText(String.valueOf(qa.getUp_count()));
        holder.tvNumDisLike.setText(String.valueOf(qa.getDown_count()));

        if (qa.isVisible()) {
            holder.llContainerLikeDislike.setVisibility(View.VISIBLE);
            holder.llContainerLikeDislike.requestLayout();
        } else {
            holder.llContainerLikeDislike.setVisibility(View.GONE);
            holder.llContainerLikeDislike.requestLayout();
        }


        if (qa.isWasVoted()) {
            holder.llLike.setEnabled(false);
            holder.llDisLike.setEnabled(false);
            verifyStatusButtonLikeDisLike(qa);
        } else {
            holder.llLike.setEnabled(true);
            holder.llDisLike.setEnabled(true);
            holder.ivLike.setSelected(false);
            holder.tvAjudou.setTextColor(activity.getResources().getColor(
                    R.color.app_cinza_claro));
            holder.tvNumLike.setTextColor(activity.getResources().getColor(
                    R.color.app_cinza_claro));
            holder.ivDisLike.setSelected(false);
            holder.tvNaoAjudou.setTextColor(activity.getResources()
                    .getColor(R.color.app_cinza_claro));
            holder.tvNumDisLike.setTextColor(activity.getResources()
                    .getColor(R.color.app_cinza_claro));
        }

    }
    return convertView;
}

private void verifyStatusButtonLikeDisLike(final QA qa) {
    if (!qa.isLike()) {
        holder.ivDisLike.setSelected(true);
        holder.tvNaoAjudou.setTextColor(activity.getResources().getColor(
                R.color.app_vermelho));
        holder.tvNumDisLike.setTextColor(activity.getResources().getColor(
                R.color.app_vermelho));
        holder.ivLike.setSelected(false);
        holder.tvAjudou.setTextColor(activity.getResources().getColor(
                R.color.app_cinza_claro));
        holder.tvNumLike.setTextColor(activity.getResources().getColor(
                R.color.app_cinza_claro));
    } else {
        holder.ivLike.setSelected(true);
        holder.tvAjudou.setTextColor(activity.getResources().getColor(
                R.color.app_azul_claro));
        holder.tvNumLike.setTextColor(activity.getResources().getColor(
                R.color.app_azul_claro));
        holder.ivDisLike.setSelected(false);
        holder.tvNaoAjudou.setTextColor(activity.getResources().getColor(
                R.color.app_cinza_claro));
        holder.tvNumDisLike.setTextColor(activity.getResources().getColor(
                R.color.app_cinza_claro));
    }
}

//another methods
//...

    /**
     * {@link AsyncTask} for the send data like or dislike.
     */
    public class MyAsyncTask extends AsyncTask<String, Integer, Boolean>{...}

}
  • 1

    Look at this reply where it is explained why situations like this happen.

  • Sorry @ramaral, I saw the answer and could not understand where I am missing or letting slip the answer.

  • 1

    The important part is to understand how the mechanism of creating lines and the use of Viewholder. You must take into account that the information on a line is destroyed when it is no longer visible. When it is made visible again, it must be reconstructed in the method getView(). Any change that has been made to it, while visible, will have to be restored at the time of its reconstruction.

  • @Thank you for your reply. I believe I understand. I made changes to the QA class so that it keeps whether it is visible or invisible (llContainerLikeDislike line) and save the voting status (like and dislike) and whether it has already been voted on (enable Imageviews like and dislike). Apparently worked the problem of scroll :) But when doing tests to open more than one line and close them, the position is coming with wrong value and with this occurring wrong behavior in events.

  • 1

    Declare the setOnClickListener() outside the if(convertView == null) after else { holder = (ViewHolder) convertView.getTag();}

  • @ramaral, it worked! Thank you very much!

Show 1 more comment
No answers

Browser other questions tagged

You are not signed in. Login or sign up in order to post.