Search code examples
javaandroidandroid-studiolayout-inflaterandroid-viewholder

Android Studio - Help me to understand this code


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.


Solution

  • 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());