CheckedTextView not drawing checkmark when programmatically selected
Asked Answered
W

4

6

I'm writing an Android app that has a ListView with CheckedTextView items in it. It's basically a question with a variable number of answers. If you select one answer you can press "next" and see the next question.

I've written all the code to save the answers and go to the next questions, and I've also provided the user with a "previous" button to go back to the previous question. If the previous question already has an answer, I want that answer to be selected. And that's where the problems start.

Right now, I can get the position of the selected answer and I call listView.setItemChecked(pos, true) but the radio button is not selected.

if(selectedAnswer != null) {
    int pos = mAnswerAdapter.getPosition(selectedAnswer);
    if(pos != -1) {
        listAnswers.setItemChecked(pos, true);
    }
}

Only when I do something else, like drag down the statusbar does the view seem to refresh and draw the selected state of the radiobutton.

I fill the list with answers like this:

mAnswerAdapter = new AnswerArrayAdapter(getContext(), R.layout.listitem_answer, currentQuestion.getAnswers(), user.getLanguage());
listAnswers.setAdapter(mAnswerAdapter);
mAnswerAdapter.notifyDataSetChanged();

For reference:

The list_item layout:

<?xml version="1.0" encoding="utf-8"?>
<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:minHeight="?attr/listPreferredItemHeightSmall"
    android:drawableStart="@drawable/questionnaire_answer_radio"
    android:drawablePadding="10dp"
    android:gravity="center_vertical"
    android:paddingStart="5dp"
    android:paddingEnd="5dp"
    android:textColor="@android:color/white"
    android:id="@+id/questionnaire_answer_checkbox"/>

The adapter:

package be.iminds.mresist;

import android.content.Context;
import android.support.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.CheckedTextView;

import java.util.List;

import be.iminds.mresist.models.QuestionnaireDefinition;
import butterknife.BindView;
import butterknife.ButterKnife;

public class AnswerArrayAdapter extends ArrayAdapter<QuestionnaireDefinition.Answer> {

    private String mLang;
    private Context mContext;

    public AnswerArrayAdapter(Context context, int textViewResourceId, List<QuestionnaireDefinition.Answer> answers, final String lang) {
        super(context, textViewResourceId, answers);
        this.mContext = context;
        this.mLang = lang;
    }

    @NonNull
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        AnswerArrayAdapter.ViewHolder holder = null;
        QuestionnaireDefinition.Answer item = getItem(position);

        if (convertView == null) {
            convertView = inflater.inflate(R.layout.listitem_answer, parent, false);
            holder = new AnswerArrayAdapter.ViewHolder(convertView);
            convertView.setTag(holder);
        } else
            holder = (AnswerArrayAdapter.ViewHolder) convertView.getTag();

        holder.answerText.setText(item.getAnswer(mLang));

        return convertView;
    }


    static class ViewHolder {
        @BindView(R.id.questionnaire_answer_checkbox) CheckedTextView answerText;

        public ViewHolder(View view) {
            ButterKnife.bind(this, view);
        }
    }
}

The relevant fragment code (first call to openQuestion happens onStart):

/**
     * Opens the question with the corresponding index
     * @param questionIdx
     */
    private void openQuestion(int questionIdx)
    {
        currentQuestionIdx = questionIdx;
        final Question currentQuestion = getCurrentQuestion();

        txtQuestionTitleCounter.setText(String.format(getString(R.string.questionnaire_counter), questionIdx + 1, qAssignment.getDefinition().getQuestions().size()));
        txtQuestion.setText(currentQuestion.getQuestion(user.getLanguage()));
        mAnswerAdapter = new AnswerArrayAdapter(getContext(), R.layout.listitem_answer, currentQuestion.getAnswers(), user.getLanguage());
        listAnswers.setAdapter(mAnswerAdapter);
        mAnswerAdapter.notifyDataSetChanged();


        //If it's not the last question and there are more questions than one, make the next button visible
        if(!isLastQuestion(currentQuestionIdx) && qAssignment.getDefinition().getQuestions().size() > 1)
            mNextButton.setVisibility(View.VISIBLE);
        else
            mNextButton.setVisibility(View.GONE);

        //If it's not the first question and there are more questions than one, make the previous button visible
        if(!isFirstQuestion(currentQuestionIdx) && qAssignment.getDefinition().getQuestions().size() > 1)
            mPreviousButton.setVisibility(View.VISIBLE);
        else
            mPreviousButton.setVisibility(View.GONE);

        //If the question's already been answered, fill in the answer
        markPreviousAnswer(currentQuestion);

    }


/**
     * Marks a previously selected answer
     * @param currentQuestion
     */
    private void markPreviousAnswer(Question currentQuestion) {
        if(qAssignment.getAnswerValues() != null && qAssignment.getAnswerValues().containsKey(currentQuestion.getQuestionKey())) {

            //Value of answer
            Integer value = qAssignment.getAnswerValues().get(currentQuestion.getQuestionKey());
            QuestionnaireDefinition.Answer selectedAnswer = null;
            for(QuestionnaireDefinition.Answer answer: currentQuestion.getAnswers()) {
                if(answer.getValue() == value) {
                    selectedAnswer = answer;
                    break;
                }
            }
            if(selectedAnswer != null) {
                int pos = mAnswerAdapter.getPosition(selectedAnswer);
                if(pos != -1) {
                    listAnswers.setItemChecked(pos, true);
                }
            }
        }
    }

CheckedTextView selector drawable:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:state_pressed="true">
        <shape android:shape="oval">
            <size android:width="16dp" android:height="16dp"/>
            <solid android:color="@color/colorPrimaryDarker"/>
            <stroke android:color="@android:color/white" android:width="2dp"/>
        </shape>
    </item><!-- pressed -->

    <item android:state_checked="true">
        <shape android:shape="oval">
            <size android:width="16dp" android:height="16dp"/>
            <solid android:color="@color/colorPrimaryDarker"/>
            <stroke android:color="@android:color/white" android:width="2dp"/>
        </shape>
    </item> <!-- checked -->

    <item>
        <shape android:shape="oval" android:innerRadius="10dp">
            <size android:width="16dp" android:height="16dp"/>
            <stroke android:color="@android:color/white" android:width="1dp"/>
        </shape>
    </item> <!-- default -->

</selector>
Windfall answered 24/1, 2017 at 15:1 Comment(19)
No one? This is a pretty blocking issue. I've tried it with custom checkmark drawables but no luck.Windfall
Check out this post setItemChecked not working? , see if it helps.Fairy
The thing is, it does work. However it doesn't draw the checkmark until the view is "refreshed" by something like dragging the status bar down over the app.Windfall
The listview's choicemode is also "singleChoice"Windfall
Update your post with entire code for adapter, list_item_layout and the acitivity code to further analyze the issue.Fairy
Done. I added everything relevant. The view is actually a fragment, not an activity.Windfall
this will help https://mcmap.net/q/1779351/-checkedtextview-not-checkedForeshow
@Windfall Try by adding android:checkMark="?android:attr/listChoiceIndicatorSingle" attribute in the CheckedTextView and also setting listview android:choiceMode="singleChoice" and check.Fairy
@Windfall Had you checked the above post? Did it work?Fairy
@Fairy Yes, in fact it was like that before I used my own drawable to draw the radio button. Listview always was in singleChoice mode. Still doesn't make a difference. :(Windfall
@Windfall I'd tried your code with builtin checkbox and it worked fine. "I used my own drawable"- could you provide code for that part too?Fairy
@Fairy it's the checkedtextview selector at the bottom of my post. So your radio button is selected after loading the data in the adapter and executing markPreviousAnswer?Windfall
@Windfall Yep. Remove the custom drawable and check.Fairy
@Fairy That works, but now it's just the standard android radiobutton which doesn't fit my theme. Is there no way to do this with a custom drawable? Thx!Windfall
@Fairy On further testing, it doesn't seem to be a problem with the drawable but with the drawableStart property of CheckedTextView. If I use android:drawableStart="?android:attr/listChoiceIndicatorSingle" instead of android:checkMark it also doesn't redraw the selected state when reloading data.Windfall
@Windfall I'd tried the exact code you had provided it works. Could you debug by setting breakpoint and check the setChecked(pos,true) is called correctly?Fairy
can you post complete code?Encipher
I think you should call setChecked() in getView() method where you have assigned text.Sewoll
Have you set the list's choice mode? listAnswers.setChoiceMode(ListView.CHOICE_MODE_SINGLE);Anemophilous
M
3

I think you should call notifyDataSetChanged() after setting the answer to true so that the ListView can refresh its views immediatly

int pos = mAnswerAdapter.getPosition(selectedAnswer);
if(pos != -1) {
    listAnswers.setItemChecked(pos, true);
    mAnswerAdapter.notifyDataSetChanged()
}
Moonmoonbeam answered 31/1, 2017 at 8:35 Comment(2)
Doesn't make a difference. Still the same behaviour.Windfall
sorry I misread the question, I thought that the view was being checked in the adapter and listAnswers was the datasetMoonmoonbeam
E
2

For the simple checkbox, the attribute button will be used:

android:button="@drawable/questionnaire_answer_radio"

And for the CheckedTextView use the attribute checkMark:

android:checkMark="@drawable/questionnaire_answer_radio"
Ectomorph answered 6/2, 2017 at 15:0 Comment(0)
T
0

I've had similar problems facing the interface while using things such as radio buttons. Pass along a boolean value to the getView and then inside the getView state that:

boolean checked; //The variable being passed
.
.
.

if(item.getBoolean){   
 radioButton.setChecked(true);
}
else
{
radioButton.setChecked(false);
}

Then when anyone clicks to change the button implement the following in the onClick function of the radioButton:

if(item.getBoolean){   
 item.setBoolean(false)
 mAnswerAdapter.notifyDatasetChanged();
}
else
{
item.setBoolean(true)
 mAnswerAdapter.notifyDatasetChanged();
}

I hope this helps

Telesis answered 31/1, 2017 at 14:27 Comment(1)
That doesn't really work. Setting the CheckedTextView to checked in the getView still doesn't render it when you just update the adapter. Drawing the radiobutton onClick works fine, but drawing it when you select an item programmatically doesn't.Windfall
R
0

I can't remember, since i have not used ListView for lists for awhile. If you use RecyclerView, u bind that with onBindViewHolder to adapter, the equivalent method of the ListView should do the trick. But RecyclerView is superior to Listview.

Creata field, named selectedPosition for instance, inside the Adapter class. When you invoke notifyDatasetChanged, onBindView method of RecyclerView.Adapter is invoked. Check position that you want to be selected with the selectedPosition field of the adapter and change the view with that position as you like.

public void onBindViewHolder(MyViewHolder holder, final int position) {
    holder.tvCamMenu.setText(data.get(position));
    if (selectedPosition == position) {
        holder.tvCamMenu.setTextColor(Color.YELLOW);
    } else {
        holder.tvCamMenu.setTextColor(Color.WHITE);
    }
}

This is the snippet i use to set color of the selected column inside the horizontal RecyclerView. Finally call notifyDatasetChanged and the view will change as you modify it to be when selected.

Rhebarhee answered 5/2, 2017 at 22:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.