GridView with Auto Resized Images on Android

posted on November 01 2013 - 00:03
android androiddev android-gridview

So as I explained in the excerpt, I was looking for a way to create a GridView with two columns and auto resized images inside each view, with a label in the bottom of each image, attached to the view, so my final result would look like this.

Android GridView

So the problem was, the images wasn't being handled correctly, in a way that the images were being stretched and the label was following the images,so if the image wouldn't fill the entire view, centering in crop, the label would follow the image, and resulting in not being attached to the bottom of the View, so what I was getting was this.

The wrong result I was getting


So, there was still some tricks to be done. And it is expalined in the next lines.



So, first of all, after you created your project correctly, let's start coding. Throw a GridView into your layout, setting the stretch mode to stretch the column widths, set the spacing to 0 (or whatever you want), and set the number of columns to 2. Let's call this file activity_main.xml, and this will be the main layout of your activity.

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <GridView
        android:id="@+id/gridview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:verticalSpacing="0dp"
        android:horizontalSpacing="0dp"
        android:stretchMode="columnWidth"
        android:numColumns="2" />
</FrameLayout>


Now the trick is, create a custom class that will extend ImageView, calling it SquareImageView.java.

package com.rogcg.gridviewexample;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.ImageView;

public class SquareImageView extends ImageView
{
    public SquareImageView(Context context)
    {
        super(context);
    }

    public SquareImageView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
    }

    public SquareImageView(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(getMeasuredWidth(), getMeasuredWidth()); //Snap to width
    }
}

And now we make a layout for a grid item using this SquareImageView class and set the scaleType to centerCrop. Let's call this file gridview_item.xml.

<?xml version="1.0" encoding="utf-8"?>

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <com.rogcg.gridviewexample.SquareImageView
        android:id="@+id/picture"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="centerCrop"
        />
    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:paddingTop="15dp"
        android:paddingBottom="15dp"
        android:layout_gravity="bottom"
        android:textColor="@android:color/white"
        android:background="#55000000"
        />
</FrameLayout>

As you can see in this file, instead of adding an element of the type ImageView, we add our custom element SquareImageView that extends ImageView. Also our label will be the TextView, and as you can see we've set transparence in it's color, also we've set it's layoutGravity property to bottom, so it will be on the view's bottom.

Now we make the adapter to handle this list. In this example, the adapter is defined in an inner class (no need to create a new class file). Let's call it MyAdapter and make it extend the BaseAdapter class. So your MainActivity.java class will be like this.

package com.rogcg.gridviewexample;

import android.content.Context;
import android.os.Bundle;
import android.app.Activity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends Activity
{

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        GridView gridView = (GridView)findViewById(R.id.gridview);
        gridView.setAdapter(new MyAdapter(this));
    }

    private class MyAdapter extends BaseAdapter
    {
        private List<Item> items = new ArrayList<Item>();
        private LayoutInflater inflater;

        public MyAdapter(Context context)
        {
            inflater = LayoutInflater.from(context);

            items.add(new Item("Image 1", R.drawable.nature1));
            items.add(new Item("Image 2", R.drawable.nature2));
            items.add(new Item("Image 3", R.drawable.tree1));
            items.add(new Item("Image 4", R.drawable.nature3));
            items.add(new Item("Image 5", R.drawable.tree2));
        }

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

        @Override
        public Object getItem(int i)
        {
            return items.get(i);
        }

        @Override
        public long getItemId(int i)
        {
            return items.get(i).drawableId;
        }

        @Override
        public View getView(int i, View view, ViewGroup viewGroup)
        {
            View v = view;
            ImageView picture;
            TextView name;

            if(v == null)
            {
               v = inflater.inflate(R.layout.gridview_item, viewGroup, false);
               v.setTag(R.id.picture, v.findViewById(R.id.picture));
               v.setTag(R.id.text, v.findViewById(R.id.text));
            }

            picture = (ImageView)v.getTag(R.id.picture);
            name = (TextView)v.getTag(R.id.text);

            Item item = (Item)getItem(i);

            picture.setImageResource(item.drawableId);
            name.setText(item.name);

            return v;
        }

        private class Item
        {
            final String name;
            final int drawableId;

            Item(String name, int drawableId)
            {
                this.name = name;
                this.drawableId = drawableId;
            }
        }
    }
}

And this is the final result of this example.

Android GridView final result

If you want to make the items clickable, just set it on the gridview object, just after you've set the adapter, just like this.

gridView.setOnItemClickListener(new OnItemClickListener() 
{
    public void onItemClick(AdapterView<?> parent, View v, int position, long id) 
    {
        // this 'mActivity' parameter is Activity object, you can send the current activity.
        Intent i = new Intent(mActivity, ActvityToCall.class);
        mActivity.startActivity(i);
    }
});

In this case, the mActivity, is an Activity object that you must send to the Intent constructor. By doing this, you are telling your Intent object that you want to open the ActivityToCall activity when clicking on an item. More informations about Intents here.

Now let me explain someting else. Why did we had to create a class for the ImageView and why use something like this <com.rogcg.gridviewexample.SquareImageView> instead of a simple <ImageView> tag in the xml file. Also, if we didn't create this class, how it would affects the items?

Well, basically, in Android's ImageView class, there's no way to simply specify, "hey, keep a square aspect ratio (width / height) for this view", unless you hard code width and height. You could do some manual adjustment of LayoutParams in the adapter's getView, but frankly, it's much simpler to let ImageView handle all the measurements, and just override the results to say, "whatever the width ends up being, make my height stay the same". You never have to think about it, it's always square, and it just works as expected. Basically this is the easiest way to keep the view square.

Well, I hope this post helped you understand how to make an efficient GridView with auto resized images.

Download Source Code

Please share, and contribute with opinions on comments below, or click the Flattr button on top of this post to contribute to this blog.

This post is based on an answer by @kcoppock to a question I've made on stackoverflow.