0

Im trying to understand this code:

private class ViewHolder {
    TextView txtName, txtSinger;
    ImageView playB, stopB;
}

@Override
public View getView(int position, View view, ViewGroup parent) {
    final ViewHolder viewHolder;
    if (view == null) {
        viewHolder = new ViewHolder();

        // inflate (create) another copy of our custom layout
        LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        view = layoutInflater.inflate(layout, null);


        viewHolder.txtName = (TextView) view.findViewById(R.id.songName_text);
        viewHolder.txtSinger = (TextView) view.findViewById(R.id.singer_text);
        viewHolder.playB = (ImageView) view.findViewById(R.id.play_png);
        viewHolder.stopB = (ImageView) view.findViewById(R.id.stop_png);
        view.setTag(viewHolder);
    } else {
        viewHolder = (ViewHolder) view.getTag();
    }
    final Song song = arrayList.get(position);

    // make changes to our custom layout and its subviews
    viewHolder.txtName.setText(song.getName());
    viewHolder.txtSinger.setText(song.getSinger());

    // play music
    viewHolder.playB.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // MediaPlayer has not been initialized OR clicked song is not the currently loaded song
            if (currentSong == null || song != currentSong) {

                // Sets the currently loaded song
                currentSong = song;
                mediaPlayer = MediaPlayer.create(context, song.getSong());

                Toast.makeText(context, "Playing: "+ song.getSinger() + " " + song.getName(), Toast.LENGTH_LONG).show();
            }

            if (mediaPlayer.isPlaying()) {
                mediaPlayer.pause();
                viewHolder.playB.setImageResource(R.drawable.play);
            } else {
                mediaPlayer.start();
                viewHolder.playB.setImageResource(R.drawable.pause);
            }
        }
    });

    // stop
    viewHolder.stopB.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            // If currently loaded song is set the MediaPlayer must be initialized
            if (currentSong != null) {
                mediaPlayer.stop();
                mediaPlayer.release();
                currentSong = null; // Set back currently loaded song
            }
            viewHolder.playB.setImageResource(R.drawable.play);
        }
    });
    return view;
}

But not the whole code! The part that is confusing me is the ViewHolder part.

My questions:

  • Why do i have to create a private class called ViewHolder instead of just creating a public method to store all my views (txtName, txtSinger, playB, stopB) and use that in my inflater?
  • what does view.setTag(viewHolder) means?
  • What is exactly setTag and getTag in this context?

If anyone can break this down for me it would be very helpful to progress my understanding of code.

Thank you.

Delice
  • 743
  • 9
  • 20

2 Answers2

1

What is exactly setTag and getTag in this context?

Android views support "tags", which are arbitrary objects you can attach to them. There's no real definition for tags, because they're whatever you want them to be. All of these are equally valid:

  • view.setTag(2)
  • view.setTag("Hello world")
  • view.setTag(new Object())

what does view.setTag(viewHolder) means?

You're attaching the viewHolder object to view as its tag. This doesn't do anything by itself, but it lets you retrieve the viewHolder later on by calling (ViewHolder) view.getTag().

Why do i have to create a private class called ViewHolder [...]

When you're working with ListView adapters, there are two things that can slow the performance of your app way down: calling inflate() and calling findViewById().

You get around the first by using the passed-in view argument when it's not null, and only calling inflate() when the passed in view argument is null. That's this bit of your code:

if (view == null) {
    ...
    LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    view = layoutInflater.inflate(layout, null);
    ...
} else {
    ...
}

You get around the second by using this "ViewHolder pattern". You create an object (the view holder) to "hold" the views after you look them up. That's this bit of your code:

final ViewHolder viewHolder;
if (view == null) {
    viewHolder = new ViewHolder();
    ...
    viewHolder.txtName = (TextView) view.findViewById(R.id.songName_text);
    viewHolder.txtSinger = (TextView) view.findViewById(R.id.singer_text);
    viewHolder.playB = (ImageView) view.findViewById(R.id.play_png);
    viewHolder.stopB = (ImageView) view.findViewById(R.id.stop_png);
    view.setTag(viewHolder);
} else {
    viewHolder = (ViewHolder) view.getTag();
}

Once that block is done, you'll have a ViewHolder instance named viewHolder that you can use to access views directly. When the passed-in view argument is null, you create the view holder, populate it by calling findViewById(), and save it by calling setTag(). When the passed-in view argument is not null, you can simply retrieve the view holder by calling getTag().

Put that all together, and that means you can write code like this:

viewHolder.txtName.setText(song.getName());

Instead of this slower code:

TextView txtName = view.findViewById(R.id.songName_text);
txtName.setText(song.getName());
Ben P.
  • 52,661
  • 6
  • 95
  • 123
  • I have a question regarding the 3th question you answered "Why do i have to create a private class called ViewHolder [...]" --> Why does it have to be a `class`? is it possible to make it a `public method` ? – Delice Nov 09 '18 at 23:35
  • Why do you want it to be a method? But, short answer, no. You can only pass objects to `setTag()`, so you have to define a class for your view holder. – Ben P. Nov 09 '18 at 23:42
  • I've had an older project where i made a public method called `myViews()` that i used in my `onCreate` method in an `if-statement`.. so i just wondered why i had to use `class` this time. – Delice Nov 09 '18 at 23:54
  • 1
    I think the major difference here is that, when you have just one Activity, you can use a method to organize all of your `findViewById()` calls... but in this case you have a _different_ view holder for _every_ row, so you need _many_ different "groups" of `findViewById()` calls. A view holder object/class is the best way to do that. – Ben P. Nov 10 '18 at 03:24
1

A viewholder is a design pattern used in android apps in order to replace the more expensive findviewbyid calls.

Quoting docs

Your code might call findViewById() frequently during the scrolling of ListView, which can slow down performance. Even when the Adapter returns an inflated view for recycling, you still need to look up the elements and update them. A way around repeated use of findViewById() is to use the "view holder" design pattern.

Regarding the second and third question the settag and gettag is basically a way for your view to have "memories" you can refer here for further information and a more detailed explanation!

Hope this helps!

Georgios S.
  • 229
  • 1
  • 10
  • Thank you for your input! I think i almost understood everything. I just need to verify one last question and i put that into Ben P. answer. – Delice Nov 09 '18 at 23:36