Android Custom Spinner With Image And Text

Last Updated on February 6, 2018

Hi and welcome to another tutorial from Codingdemos, in this tutorial you will learn how create android custom spinner with images and text. The spinner will have a list of country names and flags, when you tap on any of the item an Android toast message will appear on the screen.

Android Custom Spinner With Images And Text

By the end of this article, we will have an app that looks like this. (Large preview)

In this tutorial we will be using the following:

    – Android studio version 2.3.3
    – Android emulator Nexus 5X with API 24
    – Minimum SDK API 16

1- Open up Android Studio and create a new project and give it a name, in our case we’ve named it (SpinnerImages), choose API 16 as the minimum SDK, then choose a blank activity, click “Finish” and wait for Android Studio to build your project.

2- Let’s create a new xml file and name it custom_spinner_row.xml, this file will have all the views that will be shown for each row inside Android spinner.

3- Inside custom_spinner_row.xml will have 2 Android textviews and 1 Android imageview, the 2 textviews will be used for country name and population while the imageview will be used for the country flag.


< ?xml version="1.0" encoding="utf-8"?>
< RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    < ImageView
        android:id="@+id/ivFlag"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="5dp"
        app:srcCompat="@mipmap/ic_launcher" />

    < TextView
        android:id="@+id/tvPopulation"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/tvName"
        android:layout_toEndOf="@+id/ivFlag"
        android:layout_toRightOf="@+id/ivFlag"
        android:padding="5dp"
        android:text="TextView"
        android:textStyle="bold" />

    < TextView
        android:id="@+id/tvName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toEndOf="@+id/ivFlag"
        android:layout_toRightOf="@+id/ivFlag"
        android:padding="5dp"
        android:text="TextView"
        android:textColor="@color/colorAccent"
        android:textSize="20sp"
        android:textStyle="italic" />
< /RelativeLayout>

4- Open activity_main.xml file, here we will add an Android spinner.


<?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"
    android:orientation="vertical"
    tools:context="com.codingdemos.spinnerimages.MainActivity">

    < Spinner
        android:id="@+id/spinner"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="40dp" />
< /LinearLayout>

5- Next we need to create a custom adapter, this adapter will be used to initialize the views inside custom_spinner_row.xml so that we can use it later with android spinner to show the data. Right click on the project package name com.codingdemos.spinnerimagesNewJava Class

6- I’ve named the custom adapter class as CustomAdapter.java. To be able to access the functions of ArrayAdapter we need to use extends with ArrayAdapter<String>. Once you do that you will see a red line under the class name, hover your mouse over that error and Android Studio will warn you that you need to create a constructor.

android custom adapter constructor

Android custom adapter constructor. (Large preview)

android custom adapter constructor

How to create Android custom adapter constructor. (Large preview)

7- From Choose Super Class Constructor dialog box, choose the first option for the type of constructor ArrayAdapter(context:Context, resources:int)


public CustomAdapter(@NonNull Context context, @LayoutRes int resource) {
        super(context, resource);
    }

8- Let’s modify the constructor by replacing resource parameter with 3 more parameters: String[]titles , int[]images and String[]population


    String[] spinnerTitles;
    int[] spinnerImages;
    String[] spinnerPopulation;
    Context mContext;

    public CustomAdapter(@NonNull Context context, String[] titles, int[] images, String[] population) {
        super(context, R.layout.custom_spinner_row);
        this.spinnerTitles = titles;
        this.spinnerImages = images;
        this.spinnerPopulation = population;
        this.mContext = context;
    }

9- We will need to override few methods: getCount , getView and getDropDownView

    getCount : Return the number of items in the list. If you don’t override this method, the spinner list will be empty.
    getView : This is where we work with initializing the views that we added them in the custom layout.
    getDropDownView: This will show the data when you tap on Android spinner, if you don’t override this method your app will crash when you try to tap on the spinner.

@Override
    public int getCount() {
        return super.getCount();
    }

@NonNull
    @Override
    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
        return super.getView(position, convertView, parent);
    }

@Override
    public View getDropDownView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
        return super.getDropDownView(position, convertView, parent);
    }

10- Declare an inner class and name it ViewHolder, inside this class we will declare the views inside custom_spinner_row.xml


private static class ViewHolder {
        ImageView mFlag;
        TextView mName;
        TextView mPopulation;
    }

11- Inside getCount method we will return the number of titles inside the spinner


@Override
    public int getCount() {
        return spinnerTitles.length;
    }

12- Let’s initialize the views inside getView method


@NonNull
    @Override
    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
        ViewHolder mViewHolder = new ViewHolder();
        if (convertView == null) {
            LayoutInflater mInflater = (LayoutInflater) mContext.
                    getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = mInflater.inflate(R.layout.custom_spinner_row, parent, false);
            mViewHolder.mFlag = (ImageView) convertView.findViewById(R.id.ivFlag);
            mViewHolder.mName = (TextView) convertView.findViewById(R.id.tvName);
            mViewHolder.mPopulation = (TextView) convertView.findViewById(R.id.tvPopulation);
            convertView.setTag(mViewHolder);
        } else {
            mViewHolder = (ViewHolder) convertView.getTag();
        }
        mViewHolder.mFlag.setImageResource(spinnerImages[position]);
        mViewHolder.mName.setText(spinnerTitles[position]);
        mViewHolder.mPopulation.setText(spinnerPopulation[position]);

        return convertView;
    }

13- Inside getDropDownView method we will return getView by passing the position, the view and the parent.


@Override
    public View getDropDownView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
        return getView(position, convertView, parent);
    }

14- Now we are done with CustomAdapter.java class, next we will work with MainActivity.java class to initialize the spinner, spinner titles, images and population.

15- Declare and initialize the titles, images and population.


    String[] spinnerTitles;
    String[] spinnerPopulation;
    int[] spinnerImages;

spinnerTitles = new String[]{"Australia", "Brazil", "China", "France", "Germany", "India", "Ireland", "Italy", "Mexico", "Poland"};
        spinnerPopulation = new String[]{"24.13 Million", "207.7 Million", "1.379 Billion", "66.9 Million", "82.67 Million", "1.324 Billion", "4.773 Million", "60.6 Million", "127.5 Million", "37.95 Million"};
        spinnerImages = new int[]{R.drawable.flag_australia
                , R.drawable.flag_brazil
                , R.drawable.flag_china
                , R.drawable.flag_france
                , R.drawable.flag_germany
                , R.drawable.flag_india
                , R.drawable.flag_ireland
                , R.drawable.flag_italy
                , R.drawable.flag_maxico
                , R.drawable.flag_poland};

16- Initialize the spinner and the custom adapter


CustomAdapter mCustomAdapter = new CustomAdapter(MainActivity.this, spinnerTitles, spinnerImages, spinnerPopulation);
mSpinner.setAdapter(mCustomAdapter);

17- Allow the user to tap on the items in the spinner by using Spinner.setOnItemSelectedListener and show the selected item in an Android toast message.


mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
                Toast.makeText(MainActivity.this, spinnerTitles[i], Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onNothingSelected(AdapterView<?> adapterView) {

            }
        });

18- Compile and run the app, you will see an android spinner being populated by country name, flag and it’s population.

19- One thing you will notice when you run the app, you will immediately see Android toast message containing spinner item appears on the screen without making any selection! To prevent that from happening, we need to override a method called <a href="https://developer.android.com/reference/android/app/Activity.html#onUserInteraction()">onUserInteraction</a>. This method is used to detect that the user have interacted with the device while the activity is still running.

20- Let’s override onUserInteraction


@Override
    public void onUserInteraction() {
        super.onUserInteraction();
        isUserInteracting = true;
    }

21- We will use the boolean variable isUserInteracting inside android spinner onItemSelected method to show the toast message.


mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
                if (isUserInteracting) {
                    Toast.makeText(MainActivity.this, spinnerTitles[i], Toast.LENGTH_SHORT).show();
                }
            }
            @Override
            public void onNothingSelected(AdapterView<?> adapterView) {

            }
        });

22- Compile and run the app again, this time you will not see the spinner item shows up immediately on the screen thanks to onUserInteraction . The source code for the android custom spinner tutorial is available on Github. I hope you find this tutorial helpful and if you have any question please post them in the comment below.

4 Comments

    1. Hi, you can change the width of the spinner by changing android:layout_width="match_parent" to android:layout_width="wrap_content" and you can also reduce the text and image size.

  1. Very useful.
    Not sure why you need the ViewHolder?
    Surely if yo uhave the View then you can just get the child text and images and set them on thw view…

  2. First thanks,
    I have problems with getDropDownView I am getting convertView in null and this exception
    E/AndroidRuntime: FATAL EXCEPTION: main Process: pe.com.startapps.implementos, PID: 8770 java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.view.View.toString()' on a null object reference at pe.com.startapps.implementos.adapters.CustomSpinnerAdapter.getDropDownView(CustomSpinnerAdapter.java:61) at android.widget.Spinner$DropDownAdapter.getDropDownView(Spinner.java:994) at android.widget.Spinner$DropDownAdapter.getView(Spinner.java:990) at android.widget.AbsListView.obtainView(AbsListView.java:2366) at android.widget.ListView.measureHeightOfChildren(ListView.java:1408) at android.widget.ListView.onMeasure(ListView.java:1315) at android.view.View.measure(View.java:23169) at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6749) at android.widget.FrameLayout.onMeasure(FrameLayout.java:185) at android.view.View.measure(View.java:23169) at com.android.internal.widget.AlertDialogLayout.tryOnMeasure(AlertDialogLayout.java:144) at com.android.internal.widget.AlertDialogLayout.onMeasure(AlertDialogLayout.java:69) at android.view.View.measure(View.java:23169) at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6749) at android.widget.FrameLayout.onMeasure(FrameLayout.java:185) at android.view.View.measure(View.java:23169) at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6749) at android.widget.FrameLayout.onMeasure(FrameLayout.java:185) at android.view.View.measure(View.java:23169) at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6749) at android.widget.FrameLayout.onMeasure(FrameLayout.java:185) at com.android.internal.policy.DecorView.onMeasure(DecorView.java:716) at android.view.View.measure(View.java:23169) at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:2718) at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1545) at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1855) at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1460) at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7183) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:949) at android.view.Choreographer.doCallbacks(Choreographer.java:761) at android.view.Choreographer.doFrame(Choreographer.java:696) at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:935) at android.os.Handler.handleCallback(Handler.java:873) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:193) at android.app.ActivityThread.main(ActivityThread.java:6669) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>