Android 4
6. ListView, Spinner, GridView, and Gallery - Part B
This chapter
- 6.0 Selection Widgets
- 6.1 ListView
- 6.2 Spinner
- 6.3 AutocompleteTextView
- 6.4 GridView
- 6.5 Gallery
- 6.6 ImageView
- 6.7 Image Switcher View
A GridView displays items in a two-dimensional, scrolling grid. We have moderate control over the number and size of the columns and the number of rows is dynamically determined based on the number of items. The items are acquired from a ListAdapter.
Following properties determine the number of columns and their sizes:
- android:numColumns
Specifies how many columns there are, of if we supply a value of auto_fit, Android will compute the number of columns based on the available space and the following properties. - android:verticalSpacing and android:horizontalSpacing
Indicate how much whitespace there should be between items in the grid. - android:columnWidth
Indicates how many pixel wide each column should be. - android:stretchMode
Indicates, for grids with auto_fit for android:numColumns, what should happen for any available space not taken up by columns or spacing. This can be columnWidth, to have the columns taken up available space, or spacingWidth, to have the whitespace between columns absorb extra space.
Layout file:
<?xml version="1.0" encoding="utf-8"?> <GridView xmlns:android= "http://schemas.android.com/apk/res/android" android:id="@+id/gridview" android:layout_width="fill_parent" android:layout_height="fill_parent" android:numColumns="auto_fit" android:verticalSpacing="10dp" android:horizontalSpacing="10dp" android:columnWidth="90dp" android:stretchMode="columnWidth" android:gravity="center" />
Java code, GView.java:
package com.bogotobogo.GView; import com.bogotobogo.GView.ImageAdapter; import com.bogotobogo.GView.R; import android.app.Activity; import android.os.Bundle; import android.widget.GridView; public class GViewActivity extends Activity { @Override /** Called when the activity is first created. */ public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); GridView gridview = (GridView) findViewById(R.id.gridview); gridview.setAdapter(new ImageAdapter(this)); } }
Another class, ImageAdapter.java
package com.bogotobogo.GView; import android.content.Context; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.GridView; import android.widget.ImageView; public class ImageAdapter extends BaseAdapter { private Context mContext; public ImageAdapter(Context c) { mContext = c; } public int getCount() { return mThumbIds.length; } public Object getItem(int position) { return null; } public long getItemId(int position) { return 0; } // create a new ImageView for each item referenced by the Adapter public View getView(int position, View convertView, ViewGroup parent) { ImageView imageView; if (convertView == null) { // if it's not recycled, initialize some attributes imageView = new ImageView(mContext); imageView.setLayoutParams(new GridView.LayoutParams(85, 85)); imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); imageView.setPadding(8, 8, 8, 8); } else { imageView = (ImageView) convertView; } imageView.setImageResource(mThumbIds[position]); return imageView; } // references to our images private Integer[] mThumbIds = { R.drawable.aaa, R.drawable.bbb, R.drawable.ccc, R.drawable.ddd, R.drawable.eee, R.drawable.fff }; }
Image files: drawables.zip.
First we take care of some required methods inherited from BaseAdapter. The constructor and getCount() are self-explanatory. Normally, getItem() should return the actual object at the specified position in our Adapter, but for our case, we're not going to bother. Likewise, getItemId() should return the row id of the item, but right now we don't care.
However, getView() is the method we care about. This one creates a new View for each image that we put in our ImageAdapter. So we're going to create an ImageView each time. When this is called, we're going to receive a View, which is likely a recycled View object (at least after the first call), so we check for this, if it's null, we initialize the ImageView and setup all the properties we want.
if (convertView == null) { // if it's not recycled, initialize some attributes imageView = new ImageView(mContext);The LayoutParams() initialization sets the height and width of the View,this ensures that no matter the drawable size, each image is resized and cropped to fit in the ImageView (if necessary).
imageView.setLayoutParams(new GridView.LayoutParams(85, 85));
With setScaleType(), we say that images should be cropped toward the center (if necessary).
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
And finally, we set the padding within the ImageView. (Note that, if the images have various aspect-ratios, as they do in our case, then less padding will cause for more cropping of the image, if it does not match the dimensions given to the ImageView.)
imageView.setPadding(8, 8, 8, 8);
At the end of getView() we set the image resource and return the ImageView.
imageView.setImageResource(mThumbIds[position]);
Here is another example of GridView.
Java code:
package com.bogotobogo.GViewB; import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.GridView; import android.widget.TextView; public class GViewBActivity extends Activity implements AdapterView.OnItemSelectedListener { TextView selection; /** Called when the activity is first created. */ @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); selection=(TextView)findViewById(R.id.selection); GridView g=(GridView) findViewById(R.id.grid); g.setAdapter(new FunnyLookingAdapter(this, android.R.layout.simple_list_item_1, WORLDCUP2010)); g.setOnItemSelectedListener(this); } public void onItemSelected(AdapterView<?> parent, View v, int position, long id) { selection.setText(WORLDCUP2010[position]); } public void onNothingSelected(AdapterView<?> parent) { selection.setText(""); } private class FunnyLookingAdapter extends ArrayAdapter<String> { Context ctxt; FunnyLookingAdapter(Context ctxt, int resource, String[] WORLDCUP2010) { super(ctxt, resource, WORLDCUP2010); this.ctxt=ctxt; } public View getView(int position, View convertView, ViewGroup parent) { TextView label=(TextView)convertView; if (convertView==null) { convertView=new TextView(ctxt); label=(TextView)convertView; } label.setText(WORLDCUP2010[position]); return(convertView); } } static final String[] WORLDCUP2010 = new String[] { "Algeria", "Argentina", "Australia", "Brazil", "Cote d'Ivoire", "Cameroon", "Chile", "Costa Rica", "Denmark", "England", "France", "Germany", "Ghana", "Greece", "Honduras", "Italy", "Japan", "Netherlands", "New Zealand", "Nigeria", "North Korea", "Paraguay", "Portugal","Serbia", "Slovakia", "Slovenia", "South Africa", "South Korea", "Spain", "Switzerland", "United States", "Uruguay" }; }
For the grid cells, we create our own views, by subclassing ArrayAdapter and overriding getView(). In this case, we wrap the strings in our own TextView widgets, just to be different. If getView() receives a TextView, we just reset its text; otherwise, we create a new TextView instance and populate it.
With layout:
<?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"> <TextView android:id="@+id/selection" android:layout_width="fill_parent" android:layout_height="wrap_content"/> <GridView android:id="@+id/grid" android:layout_width="fill_parent" android:layout_height="fill_parent" android:verticalSpacing="35dp" android:horizontalSpacing="5dp" android:numColumns="auto_fit" android:columnWidth="100dp" android:stretchMode="columnWidth" android:gravity="center"/> </LinearLayout>
For this grid, we take up the entire screen except for what our selection label requires. The number of columns is computed by Android, android:numColumns ="auto_fit", based on 5-pixel horizontal spacing and 100-pixel columns, with the columns absorbing any width left over, android:stretchMode = "columnWidth".
With Added images to res/drawable/ directory, we get the result as below.
A Gallery is a View commonly used to display items in a horizontally scrolling list that locks the current selection at the center. When one is selected, a message will be shown.
Here is the Java code:
package com.bogotobogo.GalleryA; import android.app.Activity; import android.content.Context; import android.content.res.TypedArray; import android.os.Bundle; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.Gallery; import android.widget.ImageView; import android.widget.Toast; import android.widget.AdapterView.OnItemClickListener; public class GalleryAActivity extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Gallery g = (Gallery) findViewById(R.id.gallery); g.setAdapter(new ImageAdapter(this)); g.setOnItemClickListener(new OnItemClickListener() { public void onItemClick (AdapterView<?> parent, View v, int position, long id) { Toast.makeText(GalleryAActivity.this, "" + position, Toast.LENGTH_SHORT).show(); } }); } public class ImageAdapter extends BaseAdapter { int mGalleryItemBackground; private Context mContext; private Integer[] mImageIds = { R.drawable.aaa, R.drawable.bbb, R.drawable.ccc, R.drawable.ddd, R.drawable.eee, R.drawable.fff }; public ImageAdapter(Context c) { mContext = c; TypedArray a = c.obtainStyledAttributes(R.styleable.GalleryA); mGalleryItemBackground = a.getResourceId( R.styleable.GalleryA_android_galleryItemBackground, 0); a.recycle(); } public int getCount() { return mImageIds.length; } public Object getItem(int position) { return position; } public long getItemId(int position) { return position; } public View getView(int position, View convertView, ViewGroup parent) { ImageView i = new ImageView(mContext); i.setImageResource(mImageIds[position]); i.setLayoutParams(new Gallery.LayoutParams(150, 100)); i.setScaleType(ImageView.ScaleType.FIT_XY); i.setBackgroundResource(mGalleryItemBackground); return i; } } }
We start as usual: set the layout and capture the View we want (our Gallery).
setContentView(R.layout.main); Gallery g = (Gallery) findViewById(R.id.gallery);
We then set an Adapter, called ImageAdapter for the Gallery which is a new class that we'll create next.
g.setAdapter(new ImageAdapter(this));
Then we create an item click listener for the Gallery. This is like a normal on-click listener (which you might be familiar with for buttons), but it listens to each item that we've added to the Gallery.
The onItemClick() callback method receives the AdapterView where the click occurred, the specific View that received the click, the position of the View clicked (zero-based), and the row id of the item clicked (if applicable). All that we care about is the position, so that we can pop up a Toast message that tells us the index position of the item clicked. We do this with Toast.makeText().show().g.setOnItemClickListener(new OnItemClickListener() { public void onItemClick(AdapterView<?> parent, View v, int position, long id) { Toast.makeText(GalleryAActivity.this, "" + position, Toast.LENGTH_SHORT).show(); } });
Let's look at the ImageAdapter class.
First, there are a few member variables, including an array of IDs that reference the images we placed in our drawable resources directory.
Next is the constructor, where we define the member Context. The rest of the constructor sets up a reference for our Gallery them, which adds the nice framing for each Gallery item. Once we have our mGalleryItemBackground, it's important to recycle the StyledAttribute for later re-use.
public ImageAdapter(Context c) { mContext = c; TypedArray a = c.obtainStyledAttributes(R.styleable.GalleryA); mGalleryItemBackground = a.getResourceId(R.styleable.GalleryA_android_galleryItemBackground, 0); a.recycle(); }
The next three methods are required for basic member queries. But then we have the getView() method, which is called for each item read by our ImageAdapter, when the Gallery is being built. Here, we use our member Context to create a new ImageView. We then define the image resource with the current position of the Gallery items (corresponding to our array of drawables), set the dimensions for the ImageView, set the image scaling to fit the ImageView dimensions, then finally set the background theme for the ImageView.
Layout file:
<?xml version="1.0" encoding="utf-8"?> <Gallery xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/gallery" android:layout_width="fill_parent" android:layout_height="wrap_content" />
and with resource file in res/values/resource.xml to defines the styleable attribute as a resource of the app itself and gives it the necessary structure to work in the app.
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="GalleryA"> <attr name="android:galleryItemBackground" /> </declare-styleable> </resources>
If we run the application, we will get something like this:
This ImageView example is really simple.
Almost everything is done by our layout file, main.xml:
<?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent"> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:paddingTop="10dip" android:text="@string/imageText1"/> <ImageView android:contentDescription="@string/imageText1" android:src="@drawable/everest1" android:adjustViewBounds="true" android:maxWidth="200dip" android:maxHeight="200dip" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:paddingTop="10dip" android:text="@string/imageText2"/> <ImageView android:contentDescription="@string/imageText2" android:src="@drawable/everest2" android:adjustViewBounds="true" android:maxWidth="200dip" android:maxHeight="200dip" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:paddingTop="10dip" android:text="@string/imageText3"/> <ImageView android:contentDescription="@string/imageText3" android:src="@drawable/everest3" android:adjustViewBounds="true" android:maxWidth="200dip" android:maxHeight="200dip" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:paddingTop="10dip" android:text="@string/imageText4"/> <ImageView android:contentDescription="@string/imageText4" android:src="@drawable/everest4" android:adjustViewBounds="true" android:maxWidth="200dip" android:maxHeight="200dip" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:paddingTop="10dip" android:text="@string/imageText5"/> <ImageView android:contentDescription="@string/imageText5" android:src="@drawable/everest5" android:adjustViewBounds="true" android:maxWidth="200dip" android:maxHeight="200dip" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> </ScrollView>
We put the <TextView> and <ImageView> within <LinearLayout> which is a child element of <ScrollView>.
We put the images into /res/drawable:
and the label for each image is stored in /res/values/strings.xml:
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="hello">Hello World, ImageViewAActivity!</string> <string name="app_name">ImageViewA</string> </resources>
and the /res/values/resources.xml:
<?xml version="1.0" encoding="utf-8" ?> <resources> <string name="imageText1">Mt. Everest 1</string> <string name="imageText2">Mt. Everest 2</string> <string name="imageText3">Mt. Everest 3</string> <string name="imageText4">Mt. Everest 4</string> <string name="imageText5">Mt. Everest 5</string> </resources>
Our java class just sets the View, ImageViewAActivity.java:
package com.bogotobogo.ImageViewA; import android.app.Activity; import android.os.Bundle; public class ImageViewAActivity extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } }
Files used in section ImageView example,
ImageViewA.zip
In this example, a gallery of images that scrolls at the bottom of the screen landscape and upon selection of one image, it gets displayed as a larger image in the upper part using an ImageSwitcher.
So, as we can see from the layout file, main.xml, this example uses <ImageSwitcher> and <Gallery> as component of its View.
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent"> <ImageSwitcher android:id="@+id/imgswitcher" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_alignParentTop="true" android:layout_alignParentLeft="true"/> <Gallery android:id="@+id/gallery" android:background="#55000000" android:layout_width="fill_parent" android:layout_height="50dp" android:layout_alignParentBottom="true" android:layout_alignParentLeft="true" android:gravity="center_vertical" android:spacing="16dp"/> </RelativeLayout>
Here, we're using "#aarrggbb" form as a background color for the Gallery.
android:background="#55000000"
Let's look at our java code, ImageSwitcherA.java:
package com.bogotobogo.ImageSwitcherA; import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.animation.AnimationUtils; import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.Gallery; import android.widget.Gallery.LayoutParams; import android.widget.ImageSwitcher; import android.widget.ImageView; import android.widget.AdapterView.OnItemSelectedListener; import android.widget.ViewSwitcher.ViewFactory; public class ImageSwitcherAActivity extends Activity implements OnItemSelectedListener, ViewFactory { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.main); mSwitcher = (ImageSwitcher) findViewById(R.id.imgswitcher); mSwitcher.setFactory(this); mSwitcher.setInAnimation(AnimationUtils.loadAnimation(this, android.R.anim.fade_in)); mSwitcher.setOutAnimation(AnimationUtils.loadAnimation(this, android.R.anim.fade_out)); Gallery g = (Gallery) findViewById(R.id.gallery); g.setAdapter(new ImageAdapter(this)); g.setOnItemSelectedListener(this); } public void onItemSelected(AdapterView<?> parent, View v, int position, long id) { mSwitcher.setImageResource(mImageIds[position]); } public void onNothingSelected(AdapterView<?> parent) { } @Override public View makeView() { ImageView i = new ImageView(this); i.setBackgroundColor(0xFF000000); i.setScaleType(ImageView.ScaleType.FIT_CENTER); i.setLayoutParams(new ImageSwitcher.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); return i; } private ImageSwitcher mSwitcher; public class ImageAdapter extends BaseAdapter { public ImageAdapter(Context c) { mContext = c; } public int getCount() { return mThumbIds.length; } public Object getItem(int position) { return position; } public long getItemId(int position) { return position; } public View getView(int position, View convertView, ViewGroup parent) { ImageView i = new ImageView(mContext); i.setImageResource(mThumbIds[position]); i.setAdjustViewBounds(true); i.setLayoutParams(new Gallery.LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); i.setBackgroundResource(R.drawable.picture_frame); return i; } private Context mContext; } private Integer[] mThumbIds = { R.drawable.thumb1, R.drawable.thumb2, R.drawable.thumb3, R.drawable.thumb4, R.drawable.thumb5, R.drawable.thumb6, R.drawable.thumb7, R.drawable.thumb8}; private Integer[] mImageIds = { R.drawable.sample1, R.drawable.sample2, R.drawable.sample3, R.drawable.sample4, R.drawable.sample5, R.drawable.sample6, R.drawable.sample7, R.drawable.sample8}; }
Note that we create a class that not only extends Activity but also implements ViewFactory. The ViewFactory is a Interface that creates views that need to be shown in the ImageSwitcher.
In the overridden method makeView(), we set the attributes of the ImageView that would be shown within the ImageSwitcher such as its background, its scale, and its layout parameters.
@Override public View makeView() { ImageView i = new ImageView(this); i.setBackgroundColor(0xFF000000); i.setScaleType(ImageView.ScaleType.FIT_CENTER); i.setLayoutParams(new ImageSwitcher.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); return i; }
In onCreate() method, we set the layout and capture the View we want (our ImageSwitcher). Then, we set the factory used to create the two views between which the ImageSwitcher will flip.
mSwitcher = (ImageSwitcher) findViewById(R.id.imgswitcher); mSwitcher.setFactory(this);
The method setInAnimation() specifies the animation used to animate a View that enters the screen and setOutAnimation() specifies the animation used to animate a View that exit the screen.
We then set an Adapter, after we got the Gallery View, then we call ImageAdapter for the Gallery which is a new class that we'll create next.
g.setAdapter(new ImageAdapter(this));
Then we create an item selected listener for the Gallery. This is like a normal on-select listener (which you might be familiar with for buttons), but it listens to each item that we've added to the Gallery.
The onItemSelected() callback method receives the AdapterView where the selection occurred, the specific Image that received the click, the position of the Image clicked (zero-based), and the id of the item clicked (if applicable).
At the click of a gallery image, we would want to pass the selected image to the ImageSwitcher :
public void onItemSelected(AdapterView<?> parent, View v, int position, long id) { mSwitcher.setImageResource(mImageIds[position]); }
Files used in section ImageView example,
ImageSwitcherA.zip
Previous sections:
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization