tisdag 27 mars 2012

Android : Custom spinner with custom object!

Lets spin forward by implementing a custom spinner with a custom drop down view!
Using style= tag on the spinner didnt work for me to override the styles, so i just changed the background.
First a custom drawable selector with the appropiate images.

drawable/btn_dropdown.xml
<?xml version="1.0" encoding="utf-8" ?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_window_focused="false" android:state_enabled="true" android:drawable="@drawable/btn_dropdown_normal" />
    <item android:state_pressed="true" android:drawable="@drawable/btn_dropdown_pressed" />
    <item android:state_focused="true" android:state_enabled="true" android:drawable="@drawable/btn_dropdown_pressed" />
    <item android:state_enabled="true" android:drawable="@drawable/btn_dropdown_normal" />
    <item android:drawable="@drawable/btn_dropdown_normal" />
</selector>

Next, i just reworked the btn_dropdown_* images that ships with the Android SDK. They should be located in ~androidsdkroot/platforms/android-(yourversion)/data/res/drawable-hdpi. There's 5 different states that can be used but i just made images for two. Normal & pressed.

Okay, the images done! Just need to change the spinner background to @drawable/btn_dropdown.xml to get our own look! I still use the android.R.layout.simple_spinner_item to display the objects in my spinner.
Lets get going, the main layout:

layout/main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <Button
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Custom Spinner with custom data" />
    <Spinner  android:id="@+id/SpinnerOrginal"
              android:layout_width="fill_parent"
              android:layout_height="wrap_content"
              />
    <Spinner android:id="@+id/SpinnerCustom"
             android:layout_width="fill_parent"
             android:layout_height="wrap_content"
             android:background="@drawable/btn_dropdown"
             />
    <Button android:id="@+id/buttonUseItem"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="Use Selection"/>
    <TextView android:id="@+id/myTextView"
              android:layout_height="wrap_content"
              android:layout_width="wrap_content"/>
</LinearLayout>
Next up our helper class from the previous entry with a little modification.
src/CountryInfo.java
public class CountryInfo {
    private String countryName;
    private long countryPopulation;
    private int countryFlag; // Populate it with our resource ID for the correct image.
    
    public CountryInfo(String cName, long cPopulation, int flagImage)
    {
        countryName = cName;
        countryPopulation = cPopulation;
        countryFlag = flagImage;
    }
    public String getCountryName()
    {
        return countryName;
    }
    public long getCountryPopulation()
    {
        return countryPopulation;
    }
    public int getCountryFlag()
    {
        return countryFlag;
    }
    public String toString()
    {
        return countryName;
    }
}
And lastly our main activity with our CountryAdapter class that will implement the view.
src/SpinnerTest.java (First part)

import android.app.Activity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.*;
import java.util.ArrayList;

public class SpinnerTest extends Activity
{
    
    Button button_UseSelectedItem;
    Spinner mySpinner;
    TextView myTextView;
    ArrayList<CountryInfo> myCountries;

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        myCountries = populateList();
        setContentView(R.layout.main);
        mySpinner = (Spinner) findViewById(R.id.SpinnerCustom);
        Spinner OrginalSpinner = (Spinner) findViewById(R.id.SpinnerOrginal);
        button_UseSelectedItem = (Button) findViewById(R.id.buttonUseItem);
        myTextView = (TextView) findViewById(R.id.myTextView);

        CountryAdapter myAdapter = new CountryAdapter(this, android.R.layout.simple_spinner_item, myCountries);

        mySpinner.setAdapter(myAdapter);
        OrginalSpinner.setAdapter(myAdapter);
        
        button_UseSelectedItem.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // Can also use mySpinner.setOnItemClickListener(......) 
                // Using a separate button here as there's often other data to select
                // or if you choose the wrong item.
                CountryInfo myCountry;
                if(mySpinner.getSelectedItem() != null)
                {
                    myCountry = (CountryInfo) mySpinner.getSelectedItem();
                    myTextView.setText(String.format("Country: " + myCountry.getCountryName() + "\t Population: " + myCountry.getCountryPopulation()));
                }
            }
        });
    }

    public ArrayList<CountryInfo> populateList()
    {
        ArrayList<CountryInfo> myCountries = new ArrayList<CountryInfo>();
        myCountries.add(new CountryInfo("USA", 308745538, R.drawable.usa)); // Image stored in /drawable
        myCountries.add(new CountryInfo("Sweden", 9482855, R.drawable.sweden));
        myCountries.add(new CountryInfo("Canada", 34018000, R.drawable.canada));
        return myCountries;
    }


As you see it looks like the previous one except we have created a custom adapter for our new spinner. populateList() method also gets the ints from the icons i have in the drawable/ directory.
Okay lets continue with the adapter!
src/SpinnerTest.java (2nd part)
public class CountryAdapter extends ArrayAdapter<CountryInfo>
    {
        private Activity context;
        ArrayList<CountryInfo> data = null;

        public CountryAdapter(Activity context, int resource, ArrayList<CountryInfo> data)
        {
            super(context, resource, data);
            this.context = context;
            this.data = data;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) 
        {   // Ordinary view in Spinner, we use android.R.layout.simple_spinner_item
            return super.getView(position, convertView, parent);   
        }

        @Override
        public View getDropDownView(int position, View convertView, ViewGroup parent)
        {   // This view starts when we click the spinner.
            View row = convertView;
            if(row == null)
            {
                LayoutInflater inflater = context.getLayoutInflater();
                row = inflater.inflate(R.layout.spinner_layout, parent, false);
            }

            CountryInfo item = data.get(position);

            if(item != null)
            {   // Parse the data from each object and set it.
                ImageView myFlag = (ImageView) row.findViewById(R.id.imageIcon);
                TextView myCountry = (TextView) row.findViewById(R.id.countryName);
                if(myFlag != null)
                {
                    myFlag.setBackgroundDrawable(getResources().getDrawable(item.getCountryFlag()));
                }
                if(myCountry != null)
                    myCountry.setText(item.getCountryName());

            }

            return row;
        }
    }
}



getView() method could be skipped, typed it along for clarity. getDropDownView() inflates my own layout for the drop down list. I just inflate it and set the corresponding ImageView & TextView from each object in the ArrayList. Plenty of information if you google ListView and custom adapters.

layout/spinner_layout.xml ( LinearLayout with a ImageView and TextView )

<?xml version="1.0" encoding="utf-8" ?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="fill_parent"
              android:layout_height="wrap_content"
              android:orientation="horizontal"
              android:background="@drawable/bluegradient">
<ImageView android:id="@+id/imageIcon"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:background="@drawable/canada"/>
<TextView android:id="@+id/countryName"
          android:singleLine="true"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:gravity="center"
          android:ellipsize="marquee"
          style="@style/SpinnerText"/>
</LinearLayout>

drawable/bluegradient.xml (Our background in the dropdown view, changes when item is pressed)
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true" >
        <shape>
            <solid
                android:color="#2183b0" />
            <stroke
                android:width="1dp"
                android:color="#adc6e8" />
           <corners
                android:radius="4dp" />
            <padding
                android:left="4dp"
                android:top="4dp"
                android:right="4dp"
                android:bottom="4dp" />
        </shape>
    </item>
    <item>
        <shape>
            <gradient
                android:startColor="#2183b0"
                android:endColor="#7cbfde"
                android:angle="270" 
                android:type="linear"
                />
            <stroke
                android:width="1dp"
                android:color="#2183b0" />
            <corners
                android:radius="4dp" />
            <padding
                android:left="0dp"
                android:top="4dp"
                android:right="0dp"
                android:bottom="4dp" />
        </shape>
    </item>
</selector>


That's it! Final result, spinner closed, and open!


12 kommentarer:

  1. Den här kommentaren har tagits bort av skribenten.

    SvaraRadera
  2. style="@style/SpinnerText" = ?

    SvaraRadera
    Svar
    1. Custom style for the text, let's see if i can find it.
      Here you go, http://pastebin.com/Mfrqxjgf
      Need to update this for the Holo look i think. <.<

      Radera
  3. Hey! Very nice tutorial...Can I use your image in my project ?

    SvaraRadera
  4. Where do I put the images to get them to override the gray ones that keep showing up no mater where I paste your blue images?

    SvaraRadera
    Svar
    1. Hi, have you specified the spinner background drawable ?
      drawable/btn_dropdown.xml which links to images based on the state.

      Radera
    2. I still had "android:drawable/btn_dropdown" in my activity. I removed the android keyword and it works now. Thanks.

      Radera
  5. Will you please upload whole application as project. I am getting error

    SvaraRadera
    Svar
    1. Sorry, project is lost locally. Whats the error ?

      Radera
  6. I get error with android 4.1.2 (API 16) in line:
    LayoutInflater inflater = context.getLayoutInflater();

    I fixed:
    LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);

    SvaraRadera