1

I'm very new to programming as well as Android deveopment so please pardon me if my basics aren't well versed yet. I have provided my code at the bottom.

As mentioned in the title, how can I reference each view among the views created by my custom arrayadapter? So that I could access variables of each view object in the arrayadapter.

Actually, I have 2 questions to what I need to achieve. So right now I am trying to create a simple app which consist of a science test. I have managed to display all the questions(TextViews) paired with a CheckBox view into a ListView layout. My idea is that when a user checks the CheckBox, it will store a 'true' Boolean value somewhere (probably in an ArrayList). Afterwards at the score summary activity, I will compare the user's answers with the original Boolean values (the question's correct answer) to check if the right answer was given for each question and return the score. Is it advisable to do this?

TestActivity.java

package com.example.android.scienceknowledgetest;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.webkit.WebView;
import android.widget.Button;
import android.widget.ListView;

import java.util.ArrayList;

public class TestActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);

        // ArrayList of all the questions and their boolean (answer) values
        ArrayList<Question> questions = new ArrayList<Question>();
        questions.add(new Question("Plants derive most of their dry mass from the air", true, ""));
        questions.add(new Question("Aluminium is the most common metal in the Earth's crust", true, ""));
        questions.add(new Question("Vitamin C has be shown to prevent colds", false, ""));
        questions.add(new Question("We lose most of our heat through our heads", false, ""));
        questions.add(new Question("Dogs are unable to digest chocolate", false, ""));
        questions.add(new Question("Apple pips contain cyanide", true, ""));
        questions.add(new Question("Cholesterol is a naturally-occurring toxic substance", false, ""));
        questions.add(new Question("When you're on a diet, you lose weight by oxidising fat to a gas and exhaling it", true, ""));
        questions.add(new Question("Human beings are unable to sense when the oxygen level of the air is low", true, ""));
        questions.add(new Question("Most of the Y chromosome is passed unchanged from father to son", true, ""));

        // Constructing a new TestAdapter instance
        TestAdapter adapter = new TestAdapter(this, questions);

        // Find the ListView to display the questions and check boxes
        ListView questions_checkbox_list = (ListView) findViewById(R.id.question_checkbox_list);
        // Display the questions and check boxes to the ListView questions_checkbox_list
        questions_checkbox_list.setAdapter(adapter);

        // Configuring the Button Submit
        Button submitAnswers = (Button) findViewById(R.id.submit_answers);
        submitAnswers.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent endTest = new Intent(TestActivity.this, TestSummary.class);
                startActivity(endTest);
            }
        });
    }
}

TestAdapter

package com.example.android.scienceknowledgetest;

import android.app.Activity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.TextView;
import java.util.ArrayList;

public class TestAdapter extends ArrayAdapter<Question> {

    // TestAdapter constructor
    public TestAdapter(Activity context, ArrayList<Question> questions) {
        super(context, 0, questions);
    }

    private View listItemsView;

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        // Check if the current view is recycled, otherwise inflate new view
        listItemsView = convertView;
        if (listItemsView == null) {
            listItemsView = LayoutInflater.from(getContext()).inflate(R.layout.list_item, parent, false);
        }

        // Find the current Question object and store in the variable currentQuestion
        Question currentQuestion = getItem(position);

        // Find the TextView texts of the Question and set the question
        TextView questionText = (TextView) listItemsView.findViewById(R.id.question_text);
        questionText.setText(currentQuestion.getmQuestion());

        // Find the CheckBox of the Question and set the boolean value
        CheckBox answerBool = (CheckBox) listItemsView.findViewById(R.id.answer_boolean);

        // Return the item as a view
        return listItemsView;
    }
}

Question.java

package com.example.android.scienceknowledgetest;

public class Question {

    // Declaring the question variable
    private String mQuestion;

    // Declaring the answer variable
    private boolean mAnswer;

    // Declaring the attempt variable
    private String mAttempt;

    // Getter method to request for the question
    public String getmQuestion() {
        return  mQuestion;
    }

    // Getter method to request for the boolean value of the answer
    public boolean getmAnswer() {
        return mAnswer;
    }

    // Question Constructor
    public Question(String question, boolean answer, String attempt) {
        mQuestion = question;
        mAnswer = answer;
        mAttempt = attempt;
    }
}

activity_test.xml

<?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:orientation="vertical">

    <ListView
        android:layout_weight="1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="8dp"
        android:id="@+id/question_checkbox_list" />

    <Button
        android:layout_weight="0"
        android:id="@+id/submit_answers"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/submit_answers"
        android:textSize="16sp"
        android:layout_margin="16dp"
        android:layout_marginLeft="48dp"/>

</LinearLayout>

list_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp">

    <CheckBox
        android:id="@+id/answer_boolean"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/question_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="8dp"
        tools:text="When you're on a diet, you lose weight by oxidising fat to a gas and exhaling it"/>

</LinearLayout>
L. KT
  • 37
  • 1
  • 9
  • regrading the _is this advisable_, how will what you propose cater for no answer given/selected (e.g. using a checkbox may answer some questions). – MikeT May 21 '17 at 06:02
  • @MikeT If no answer is given, it will mean the user's answer on that question will set the Boolean value of that question to 'false'. Then, I will compare the user's answer to the correct answer. – L. KT May 21 '17 at 06:07
  • Yep so unless you set the answers to all be wrong they may get a reasonable number correct by doing nothing. Of course if you set them all wrong it probably won't take a rocket scientist to suss out that simply changing will get 100%. That's why I suggest it may not be advisable and that perhaps you should consider not answered as a third state. – MikeT May 21 '17 at 06:11
  • Ignoring the third state, you'd be looking at using `setOnCheckedChangedListener` and then doing the appropriate stuff in the `onCheckChanged` method within the listener which you override. You might have to use `answerBool.setTag(position)` in the adpeter along with `view.getTag()` in the listener where the retrieved **position** could be used to determine the applicable array element. This may be of assistance http://stackoverflow.com/questions/11332111/how-to-do-something-when-a-checkbox-change-state (_noting that it's not within a ListView, so a little more complex_). – MikeT May 21 '17 at 06:29
  • @MikeT Thanks for that guidance, now the way I look at it, using check boxes might not be a good strategy to administer the test unless I complicate things with multiple checkboxes. – L. KT May 21 '17 at 16:14

1 Answers1

1

Here's something along the lines of what you want. It's all within the Adapter. Here's the code for TestAdpater :-

public class TestAdapter extends ArrayAdapter<Question> {

    Context passedcontext;

    // TestAdapter constructor
    public TestAdapter(Activity context, ArrayList<Question> questions) {
        super(context, 0, questions);
        passedcontext = context;
    }

    private View listItemsView;

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        // Check if the current view is recycled, otherwise inflate new view
        listItemsView = convertView;
        if (listItemsView == null) {
            listItemsView = LayoutInflater.from(getContext()).inflate(R.layout.list_item, parent, false);
        }

        // Find the current Question object and store in the variable currentQuestion
        Question currentQuestion = getItem(position);

        // Find the TextView texts of the Question and set the question
        TextView questionText = (TextView) listItemsView.findViewById(R.id.question_text);
        questionText.setText(currentQuestion.getmQuestion());

        // Find the CheckBox of the Question and set the boolean value
        CheckBox answerBool = (CheckBox) listItemsView.findViewById(R.id.answer_boolean);
        answerBool.setTag(position);
        answerBool.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                detectCheckBoxChanged(buttonView, isChecked);

            }
        });

        // Return the item as a view
        return listItemsView;
    }

    public void detectCheckBoxChanged(CompoundButton buttonview, boolean ischecked) {
        Toast.makeText(passedcontext,
                "CheckBox " +
                        buttonview.getTag().toString() +
                        " Changed to " + Boolean.toString(ischecked),
                Toast.LENGTH_SHORT).show();
    }
}

The first change is the addition of the line :-

Context passedcontext;

This will hold the context of the invoking activity (next new line of code).

The second change is the addition of a line to set passedcontext with the context from the invoking activity, this done in the TestAdapter's constructor :-

passedcontext = context;

The next change is to set the tag of the CheckBoxes with the position so that the you can determine which CheckBox has fired the Listener. This appears immediately (or soon after) after you have determined the id of the CheckBox within the getView method, which conveniently has the position passed to it.:-

answerBool.setTag(position);

To do things in order and perhaps simplify the code a new class method is added to the class to handle the CheckBox Changed event (in this case issue a Toast detailing the which CheckBox was clicked and it's state). Note it is to use this Toast why we got the invoking activity's context, as Toast needs a context in which to display :-

    public void detectCheckBoxChanged(CompoundButton buttonview, boolean ischecked) {
        Toast.makeText(passedcontext,
                "CheckBox " +
                        buttonview.getTag().toString() +
                        " Changed to " + Boolean.toString(ischecked),
                Toast.LENGTH_SHORT).show();
    }

The last piece of code sets and includes the listener for the CheckBoxs being changed. You could incorporate the code above within the listener rather than invoke the new detectCheckBoxChanged method. This code is added in the getView method and needs to be (at least the set) after you have ascertained the id of the CheckBox(s) :-

        answerBool.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                detectCheckBoxChanged(buttonView, isChecked);

            }
        });

You could change the detectCheckBoxChanged method to include something along the lines of :-

    int position = Integer.parseInt(buttonview.getTag().toString());
    TestActivity.handleCheckBoxChanged(position, isChecked);

In which case the handleCheckBoxChanged method in the TestActivity activity (the invoking activity) would be invoked e.g.

    public static void handleCheckBoxChanged(int position, boolean ischecked) {
        // handle the change here using the position
        Log.d("CHKBOXHIT",
                "Checkbox " +
                        Integer.toString(position) +
                        " was hit it is now "
                        + Boolean.toString(ischecked)
        );
    }

The following changes could be the basis of getting around the "not answered" issue I commented on.

1) Add an int array as a class variable to the TestActivity (i.e immediately after 'public class TestAdapter extends ArrayAdapter {')e.g.

    static int[] answers;

2) Setup the answers array and initialise the elements to the value that inidcates no answer, this code following the setup of the questions ArrayList. e.g.

    answers = new int[questions.size()];
    for (int i = 0; i < answers.length; i++ ) {
        answers[i] = -1;
    }

3) Add a new method that will be invoked when an answer is clicked that is passed the index and the state.

public static void setAnswer(int index, boolean answer) {
    answers[index] = 0;
    if (answer) {
        answers[index] = 1;
    }
}

4) Add the following line to the detectCheckBoxChanged method in the TestAdapter class :-

    TestActivity.setAnswer(position, ischecked);

5) To test it change the submitAnswers onClick listener in TestActivity to :-

    submitAnswers.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            //Intent endTest = new Intent(ScienceKnowledgeTest.this, ScienceKnowledgeTest.class);
            //startActivity(endTest);
            for (int i =0; i < answers.length; i++) {
                switch (answers[i]) {
                    case -1:
                        Log.d("RESULTS", "Question "
                                + Integer.toString(i + 1) +
                                " was not answered."
                        );
                        break;
                    case 0:
                        Log.d("RESULTS", "Question " +
                                Integer.toString(i + 1) +
                                " was answered as False"
                        );
                        break;
                    case 1:
                        Log.d("RESULTS", "Question " +
                                Integer.toString(i + 1) +
                                " was answered as True"
                        );
                        break;
                    default:
                        Log.d("RESULTS", "Well that's unusual something appears to be wrong!!!");
                        break;
                }

            }
        }
    });

However, this has the issue that to select a false answer requires 2 clicks to turn the check box to true and then to false. Two check boxes one for true and one for false would be clearer.

MikeT
  • 51,415
  • 16
  • 49
  • 68