GridView
is a ViewGroup
just like ListView
that allows us to display items in a two-dimensional scrolling grid. Just like a ListView
the data into a GridView
is populated by an Adapter
(ListAdapter
) that fetches data from various sources like an array or a database. It’s mostly used to build image, video, audio galleries.
Let’s see how we can fetch all the image media on the system and display their thumbnails in the GridView
to build an Image Gallery.
What's the one thing every developer wants? More screens! Enhance your coding experience with an external monitor to increase screen real estate.
Creating the GridView Layout
We’ll put this piece of code inside layout/activity_photo_gallery.xml
to create a GridView
:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <GridView android:id="@+id/gridView" android:layout_width="fill_parent" android:layout_height="fill_parent" android:columnWidth="100dp" android:numColumns="auto_fit" android:stretchMode="columnWidth" android:gravity="center" /> </LinearLayout>
The attributes are all easy to understand and well documented.
Creating Item Layout for GridView Items
We created our master layout that defines the grid. Next, we’ll define a layout for each items in the grid in layout/conversation_item.xml
. Our item layout will have only 1 View which is the ImageView
:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/imageView" /> </LinearLayout>
Custom GridView Adapter
We’ll need an adapter to render the grid items into the GridView
. For that we’ll create a custom adapter called ImageView
that’ll lie inside our Activity class as its inner class extending BaseAdapter
.
class ImageAdapter extends BaseAdapter { private Context mContext; public ImageAdapter(Context context) { mContext = context; } @Override public int getCount() { return mCursor.getCount(); } @Override public Object getItem(int position) { return null; } @Override public long getItemId(int position) { return 0; } // Convert DP to PX // Source: http://stackoverflow.com/a/8490361 public int dpToPx(int dps) { final float scale = getResources().getDisplayMetrics().density; int pixels = (int) (dps * scale + 0.5f); return pixels; } @Override public View getView(int position, View convertView, ViewGroup parent) { ImageView imageView; int imageID = 0; // Want the width/height of the items // to be 120dp int wPixel = dpToPx(120); int hPixel = dpToPx(120); // Move cursor to current position mCursor.moveToPosition(position); // Get the current value for the requested column imageID = mCursor.getInt(columnIndex); if (convertView == null) { // If convertView is null then inflate the appropriate layout file convertView = LayoutInflater.from(mContext).inflate(R.layout.conversation_item, null); } else { } imageView = (ImageView) convertView.findViewById(R.id.imageView); // Set height and width constraints for the image view imageView.setLayoutParams(new LinearLayout.LayoutParams(wPixel, hPixel)); // Set the content of the image based on the provided URI imageView.setImageURI( Uri.withAppendedPath(MediaStore.Images.Thumbnails.EXTERNAL_CONTENT_URI, "" + imageID) ); // Image should be cropped towards the center imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); // Set Padding for images imageView.setPadding(8, 8, 8, 8); // Crop the image to fit within its padding imageView.setCropToPadding(true); return convertView; } }
Firstly we override a couple of required methods from BaseAdapter
:
- Constructor – That’s self-explanatory.
-
getCount()
– Should return the number of items in the data set represented by the Adapter implementation. -
getItem()
– Should return the data item at the specified position (passed to it as an argument) of the data set. -
getItemId()
– Should return the row ID (unique) associated with the specified position (passed as an argument) in the data set. To get an in-depth understanding of Item IDs and how to use them, here’s a nice SO answer that you must read. Basically you can attach unique IDs to every item in a GridView or ListView which might have different types of items hence using theposition
might not help with fetching the right item from the data set. A good example is a ListView with rows and separators (used as group headers) where the items are checkable/selectable. Now the position for the first element from the data set should be0
but in the list will be1st
as the header is the0th
element. It gets complicated when you integrate search to the list which filters data in realtime and then hit on the checkbox to select items (lets say contacts). If every row is assigned a unique ID then usinggetCheckedItemIds()
we can get a list of rows checked with the given item IDs and use the list of item IDs to fetch appropriate items form our data set for further requirements.
getItem()
and getItemId()
are both ignored by returning null
and 0
as we don’t really need them in this context.
getView()
method is where the real work is done of creating the Views for each item (image) of the GridView. Whenever convertView
is null, which means a recycled View object is not passed, we inflate the XML layout file which creates different View objects from the contents of the file.
Finally we use various functions to customize our ImageView
according to our needs:
-
setLayoutParams()
– Set the width and height dimension constraints for the image view. -
setImageUri()
– Set the contents of the image view to the specified URI. -
setScaleType()
– Crop image towards the center if necessary. -
setPadding()
– Sets the padding on all edges. -
setCropToPadding()
– Crop the image to fit within its padding.
Using the Custom GridView Adapter in the Activity
Using the adapter to populate the GridView has to be done in the Activity class. We’ll write all the relevant code in our onCreate()
method:
protected Cursor mCursor; protected int columnIndex; protected GridView mGridView; protected ImageAdapter mAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_conversations); // Get all the images on phone String[] projection = { MediaStore.Images.Thumbnails._ID, MediaStore.Images.Thumbnails.IMAGE_ID }; mCursor = getContentResolver().query( MediaStore.Images.Thumbnails.EXTERNAL_CONTENT_URI, projection, null, null, MediaStore.Images.Thumbnails.IMAGE_ID + " DESC" ); columnIndex = mCursor.getColumnIndexOrThrow(projection[0]); // Get the GridView layout mGridView = (GridView) findViewById(R.id.gridView); mAdapter = new ImageAdapter(this); mGridView.setAdapter(mAdapter); }
Very basic code, we used the MediaStore Content Provider to fetch all the thumbnails of all the images on our system. Then we get the GridView
View object using findViewById()
and instantiate our ImageAdapter
(custom adapter) which is bound to mGridView
using setAdapter()
.
Bonus
Attaching Click Events to the GridView Items
If required, we can attach click events to the GridView
items like this:
mGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Toast.makeText(ConversationsActivity.this, "Selected Position: " + position, Toast.LENGTH_SHORT).show(); } });
This piece of code should go at the end of onCreate()
method and will show a Toast
message whenever an item is tapped (or clicked). The message will prompt the position of the clicked item. This can be useful to let’s say show the real image taking up the entire screen space on tap. For a long press listener there’s setOnItemLongClickListener()
.
Customizing the Background Color of Grid Item on Press
When pressing an item in the grid, by default it highlights in some color. We can change that if we want to. For that first we’ll have to define the colors in a drawable resource:
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android" android:exitFadeDuration="@android:integer/config_mediumAnimTime"> <item android:drawable="@color/blue" android:state_pressed="true"/> <item android:drawable="@color/blue" android:state_selected="true"/> <item android:drawable="@color/transparent"/> </selector>
Let’s say this code is placed in res/drawable/list_selector.xml
, we’ll need to reference it from the grid view layout file by adding this attribute to the GridView
element – android:listSelector="@drawable/list_selector"
. That’s it! On pressed and selected states the background color will be blue, else it’ll be transparent.
Using an inbuilt Adapter like ArrayAdapter
Built-in adapters can be used by writing your own custom adapters that extend them. But if you want to refrain from writing your custom adapters and simply use let’s say ArrayAdapter
with a GridView
for some purpose like testing then it’s mighty simple:
String[] months = { "Jan", "Feb", "Mar", "Apr", "May", "June", "July", "Aug", "Sept", "Oct", "Nov", "Dec" }; // Get the GridView layout mGridView = (GridView) findViewById(R.id.gridView); mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, months); mGridView.setAdapter(mAdapter);
Conclusion
Hopefully with this image gallery example, you’ll understand how easy it is to build grids in an Android app by using the GridView
view group. Use an in-built adapter or write a custom adapter for further control over the layout and views and attach it to the gridview.