androidonclicklistenerandroid-imagebutton

Button needs to get clicked twice


I want to get familiar with Android and started taking some Udacity courses. Currently, we have to make an ImageButton play some audio on click for language-learning purposes. I thought it would be nice if the button toggled as well, for example, in a paused state, it should display a "play" icon and in a playing state, a "pause" icon. This is very simple to achieve using an OnClickListener and it also works properly, up to 1 small detail: the user has to click the button twice.

After some searching, I found out that the first click is used to focus the view in that position and the second view is when the fired OnClickEvent is actually handled. I tried to change the focusable and focusableInTouchModeattributes to all possible combinations in all possible places, but it didn't work.

Here is my main Activity:

public class MainActivity extends AppCompatActivity {

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

    TextView numbersView = findViewById(R.id.numbers);
    numbersView.setOnClickListener(v -> {
        Intent numbersIntent = new Intent(MainActivity.this, NumbersActivity.class);
        startActivity(numbersIntent);
    });

    TextView familyView = findViewById(R.id.family);
    familyView.setOnClickListener(v -> {
        Intent familyIntent = new Intent(MainActivity.this, FamilyActivity.class);
        startActivity(familyIntent);
    });

    TextView colorView = findViewById(R.id.colors);
    colorView.setOnClickListener(v -> {
        Intent colorsIntent = new Intent(MainActivity.this, ColorActivity.class);
        startActivity(colorsIntent);
    });

    TextView phrasesView = findViewById(R.id.phrases);
    phrasesView.setOnClickListener(v -> {
        Intent phrasesIntent = new Intent(MainActivity.this, PhrasesActivity.class);
        startActivity(phrasesIntent);
    });
}

}

Here is the corresponding xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical"
android:divider="?android:dividerHorizontal"
android:showDividers="middle"
android:background="#212121">

<TextView
    android:id="@+id/numbers"
    style="@style/CategoryStyle"
    android:layout_height="wrap_content"
    android:text="@string/numbers"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    android:textColor="#b794f6"/>

<TextView
    android:id="@+id/family"
    style="@style/CategoryStyle"
    android:layout_height="wrap_content"
    android:text="@string/family"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    android:textColor="#90CAF9"/>

<TextView
    android:id="@+id/colors"
    style="@style/CategoryStyle"
    android:layout_height="wrap_content"
    android:text="@string/colors"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    android:textColor="#EEEEEE"/>

<TextView
    android:id="@+id/phrases"
    style="@style/CategoryStyle"
    android:layout_height="wrap_content"
    android:text="@string/phrases"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    android:textColor="#FFE082"/>

Here is my NumbersActivity (the other activities are very much alike):

public class NumbersActivity extends AppCompatActivity {

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

    ListView view = findViewById(R.id.coolList);

    List<Word> words = new ArrayList<>(Arrays.asList(
            new Word("lutti", "one", R.drawable.number_one, R.raw.test_audio),
            new Word("otiiko", "two", R.drawable.number_two, R.raw.test_audio),
            new Word("tolookosu", "three", R.drawable.number_three, R.raw.test_audio),
            new Word("oyyisa", "four", R.drawable.number_four, R.raw.test_audio),
            new Word("massokka", "five", R.drawable.number_five, R.raw.test_audio),
            new Word("temmokka", "six", R.drawable.number_six, R.raw.test_audio),
            new Word("kenekaku", "seven", R.drawable.number_seven, R.raw.test_audio),
            new Word("kawinta", "eight", R.drawable.number_eight, R.raw.test_audio),
            new Word("wo'e", "nine", R.drawable.number_nine, R.raw.test_audio),
            new Word("na'aacha", "ten", R.drawable.number_ten, R.raw.test_audio)));

    WordAdapter itemsAdapter = new WordAdapter(this, words, getColor(R.color.numbers_category));

    view.setAdapter(itemsAdapter);
}

}

//I know that doing it like this isn't that great, I just needed to get something up and running and worry about these changes later.

Here is the xml to that:

<?xml version="1.0" encoding="utf-8"?>
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="@+id/coolList"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:divider="#B0BEC5"
android:dividerHeight="2dp"
android:background="#212121"
android:focusable="false"
android:focusableInTouchMode="false"></ListView>

Here is the Word Class:

public class Word {

private String miwokTranslation;
private String defaultTranslation;
private int ressourceID = -1;  //just init, in order to check for it later
private int audioID;

public Word(String miwokTranslation, String defaultTranslation, int ressourceID, int audioID) {
    this.miwokTranslation = miwokTranslation;
    this.defaultTranslation = defaultTranslation;
    this.ressourceID = ressourceID;
    this.audioID = audioID;
}

public Word(String miwokTranslation, String defaultTranslation, int audioID) {
    this.miwokTranslation = miwokTranslation;
    this.defaultTranslation = defaultTranslation;
    this.audioID = audioID;
}

public String getMiwokTranslation() {
    return miwokTranslation;
}

public String getDefaultTranslation() {
    return defaultTranslation;
}

public int getResourceID() {
    return ressourceID;
}

public int getAudioID() {
    return audioID;
}

@Override
public String toString() {
    return miwokTranslation + "\n" + defaultTranslation;
}
}

Here is the corresponding Adapter:

public class WordAdapter extends ArrayAdapter<Word> {

private final int color;
Context context;

public WordAdapter(@NonNull Context context, @NonNull List<Word> objects, int color) {
    super(context, 0, objects);
    this.color = color;
    this.context = context;
}

@RequiresApi(api = Build.VERSION_CODES.O)
@SuppressLint("ClickableViewAccessibility")
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {

    if (convertView == null) {
        convertView = LayoutInflater.from(getContext()).inflate(R.layout.list_item, parent, false);
    }

    Word item = getItem(position);

    ImageView image = convertView.findViewById(R.id.list_item_icon);

    if(item.getResourceID() == -1) {
        image.setVisibility(View.GONE);
    } else {
        image.setImageResource(item.getResourceID());
    }

    TextView miwokWord = convertView.findViewById(R.id.miwok_word);
    miwokWord.setText(item.getMiwokTranslation());
    miwokWord.setTextColor(color);

    TextView defaultWord = convertView.findViewById(R.id.default_word);
    defaultWord.setText(item.getDefaultTranslation());
    defaultWord.setTextColor(color);

    ImageButton button = convertView.findViewById(R.id.imageButton);
    MediaPlayer mediaPlayer = MediaPlayer.create(context, item.getAudioID());

    button.setOnClickListener(v -> {
        if (!mediaPlayer.isPlaying()) {
            button.setImageResource(R.drawable.play_icon);
            mediaPlayer.start();
        } else {
            button.setImageResource(R.drawable.pause_icon);
            mediaPlayer.pause();
        }
    });
    return convertView;
}
}

The Annotations are these due to previous efforts and I didn't remove them, in case i'd need them again.

Last but not least, here is the xml of the adapter:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:minHeight="?android:attr/listPreferredItemHeight"
android:padding="16dp"
android:focusable="true"
android:focusableInTouchMode="false">

<ImageView
    android:id="@+id/list_item_icon"
    android:layout_width="88dp"
    android:layout_height="88dp"
    android:contentDescription="@string/magic"
    android:background="#424242"/>

<LinearLayout
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_weight="1"
    android:orientation="vertical"
    android:paddingStart="16dp"
    tools:ignore="RtlSymmetry">

    <TextView
        android:id="@+id/miwok_word"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:textStyle="bold"/>

    <TextView
        android:id="@+id/default_word"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>

</LinearLayout>

<ImageButton
    android:id="@+id/imageButton"
    android:layout_width="73dp"
    android:layout_height="match_parent"
    android:contentDescription="@string/playsound"
    android:src="@drawable/play_icon"/></LinearLayout>

I have been sitting on this for some good couple of hours by now without any significant progress and at this Point i don't have any clues about what to do next. Any Help would be appreciated.

Thanks in Advance


Solution

  • Not sure why you concluded that focus has anything to do with your problem.

    If the default beginning state of the icon is android:src="@drawable/play_icon" and the default beginning state of the mediaPlayer is "not playing", wouldn't the first click result in button.setImageResource(R.drawable.play_icon); which just...sets the play icon again? So the first click changes the icon from play to play, and the second sets it from play to pause.

    Have you tried debugging your code with a breakpoint? Set one in your click listener and see what happens.