Index

Android apps - Grids

See AndroidStudio's tutorial on Gridview.

The code below is mostly taken from Make a mosaic but some has been improved!

The type of grid described here is based on GridView. This makes a regular layout of images on the screen - a grid. The images will all be the same size, and scaled to fit (and THAT can cause problems!)

In app/res/layout/activity_main.xml, the position and "id" of the grid is specified in number of columns (images across the screen). Nothing else is specified there except the number of columns. See Screen - Gridview.

Everything else about the grid is coded in app/java/com.(the project name)/MainActivity (in Java) or other Java classes - see below:

Format the Gridview
Images needed for the grid
Set up the grid in app/java/com.(the project name)/MainActivity
Grid setup class
Detect clicking on grid element
More complex grid
Scrolling
Make elements next to each other without spacing
Setting column number within code
Getting the individual views within the grid

Format the Gridview

I need to set up a GridView in app/res/layout/activity_main.xml.

<GridView android:id="@+id/paletteView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:numColumns="10" ndroid:layout_below="@id/message" />

For most of this, see Gridview. The important bits are the "id" of "paletteView", and the number of columns, which is 10. This can also be set within the code of the MainActivity (Java).


Images needed for the grid

I am going to need images files (pictures) to put in the grid. Copy these into app/res/drawable (see Pictures). In this example, I've called them "colour0", "colour1", etc. (They are coloured squares that the user will click on to choose a colour.)

I also need to put these into an array, so I can access them easily in my code. I think that the best place to do this is at the start of your MainActivity, before the onCreate method. This will means that everything can find it if necesary. The name of this array is "palette[]" and we are putting the image files from app/res/drawable into it.

public static Integer[] palette = { R.drawable.colour0, R.drawable.colour1, R.drawable.colour2, R.drawable.colour3, R.drawable.colour4, R.drawable.colour5, R.drawable.colour6, R.drawable.colour7, R.drawable.colour8, R.drawable.colour9 };

Set up the grid

In app/java/com.(the project name)/MainActivity, in the onCreate method, code this (or equivalent): GridView palette = (GridView) findViewById(R.id.paletteView); palette.setAdapter(new ImagePalette(this));

"palette" - internal name I've given for this "findViewById" - used in the second line as well.
"paletteView" - "id" of my grid in app/res/layout/activity_main.xml.
"ImagePalette" - this is also my name, but I haven't done it yet! See below.
The rest is Java stuff - just copy it.


Grid setup class

The grid set up (above) refers to a new class, which I've called "ImagePalette". This needs to be created. I say - created - because it isn't part of app/java/com.(the project name)/MainActivity but a whole new lump of code.

Click on app/java/com.(the project name)/MainActivity, then New, then Java class. Give a name of "ImagePalette", or whatever name you are using instead. Copy in this lot:

package com.edkins.symmetry; import ... public class ImagePalette extends BaseAdapter { private AppCompatActivity mContext; public ImagePalette(AppCompatActivity c) {mContext = c;} public int getCount() {return MainActivity.palette.length;} public Object getItem(int position) {return null;} public long getItemId(int position) {return 0;} // create new ImageView for palette public View getView(int position, View convertView, ViewGroup parent) { ImageView imageView; if (convertView == null) { imageView = new ImageView(mContext); } else { imageView = (ImageView) convertView; } // initialise the view element (to one of the colours) imageView.setImageResource(MainActivity.palette[position]); return imageView; } }

What a lot of code! What does it mean? Search me! But this is what you need to know to use it:

"position" is the current element number in the grid. This starts from zero, as usual, and goes up to (count number - 1). This class is merely setting up one grid element. It gets actioned repeatedly for the whole grid.

"ImagePalette" - name of this class that you're creating.

"public int getCount() {return palette.length;}" - this is how many images there are going to be in the grid. It could just be a number. But here we say that it's the number of elements in the array "palette[]" that we defined above.

"imageView.setImageResource(palette[position]);" - this actually puts an image in the grid. This is taken from "palette[] again.

So this puts one picture from "palette[]" into the relevant slot in the grid. Each slot will be different. This is just a one row grid, by the way.


Detect clicking on grid element

So far, we just have a grid sitting there not doing anything. Now to code what happens when the user clicks on an element.

In app/java/com.(the project name)/MainActivity, in the onCreate method, after the grid set up business, you need to add a "setOnItemClickListener". This detects the click. The whole onCreate method now looks like this:

protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); GridView palette = (GridView) findViewById(R.id.paletteView); palette.setAdapter(new ImagePalette(this)); palette.setOnItemClickListener(new AdapterView.OnItemClickListener() { public void onItemClick(AdapterView<?> parent, View v, int position, long id) { processPaletteClick(position); } }); }

"The "palette." is referring to the "findViewById" statement, so we know which grid we are dealing with (an app may have more than one).

The rest of the additional code is Java stuff (just copy it) except for "processPaletteClick(position);" - this is a method which I must code:

public void processPaletteClick(int position) { paint = position; }

Not a massive amount of code! It's just saying - if the user clicks on one of the images in the grid, set an internal app variable to "position" (which was, you remember, where the element comes in the grid).


More complex grid

The grid above was just a single row of images, and they didn't change. Here is a good method to deal with images in a grid that can be changed.

First - how do we get a 2 dimensional grid? Well, it gets laid out automatically for you. If the total number of images ("getCount()" in the class you created to set up the grid) is greater than the number of columns ("numColumns" in app/res/layout/activity_main.xml) then it goes onto a second line, and so on, until all images have been displayed. That means that if you want to work out the row and column, then you have to calculate it from "position" and the number of columns.

The previous example loaded up the grid with images from an array in a simplistic way. But I can define an array how I want, and use this to set up the grid. Say I have 100 images in 10 rows of 10 columns. They all start out as "colour0", but you can change them by clicking on them. So I define an array, but this time it doesn't contain image files, just numbers. These will all start as zero.

public static int [] tile = new int [100]; <XMP> <P>I need to set up a GridView in <B>app/res/layout/activity_main.xml</B> similar to above - this time it will have an "id" of "tesseraView". The setting up of the grid and Click Listener is similar to above (although they will need different names so you don't get confused - I suggest replacing "palette" by "tessera" wherever it occurs!) However, I do NOT need to put any more actual image files into <B>app/res/drawable</B>, as I am going to use the ones already there. I'm going to use the "palette[]" array as well. The new class to set up this new grid is like this (remember that you have to set up a new chunk of code - see above):</P> <XMP> package com.edkins.symmetry; import ... public class ImageTessera extends BaseAdapter { private AppCompatActivity mContext; public ImageTessera(AppCompatActivity c) {mContext = c;} public int getCount() {return 100;} public Object getItem(int position) {return null;} public long getItemId(int position) {return 0;} // create new ImageView for tessera public View getView(int position, View convertView, ViewGroup parent) { ImageView imageView; if (convertView == null) { imageView = new ImageView(mContext); } else { imageView = (ImageView) convertView; } // set it to the colour in the tile array int sub = MainActivity.tile[position]; imageView.setImageResource(MainActivity.palette[sub]); return imageView; } }

That is similar to the previous one (apart from all the changing of "palette" to "tessera"!). The main change of code is that instead of setting the image to "palette[position]", I pick up the colour I want from my array "tile[position]", and use THAT to put out the colour. So as this class gets used to set one image after another, it is setting them to the colour numbers held in "tile[]".

But "tile[]" just held zeros, didn't it? Yes, so I need to code the change to it. In app/java/com.(the project name)/MainActivity, the onCreate method now looks like this:

protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); GridView palette = (GridView) findViewById(R.id.paletteView); palette.setAdapter(new ImagePalette(this)); palette.setOnItemClickListener(new AdapterView.OnItemClickListener() { public void onItemClick(AdapterView<?> parent, View v, int position, long id) { processPaletteClick(position); } }); GridView tessera = (GridView) findViewById(R.id.tesseraView); tessera.setAdapter(new ImagePalette(this)); tessera.setOnItemClickListener(new AdapterView.OnItemClickListener() { public void onItemClick(AdapterView<?> parent, View v, int position, long id) { processTesseraClick(position); } }); }

So I need to code the method "processTesseraClick(position)" (also in app/java/com.(the project name)/MainActivity).

public void processTesseraClick(int position) { tile[position] = paint; GridView tessara = (GridView) findViewById(R.id.tessaraView); tessara.setAdapter(new ImageTessera(this)); }

"paint" (if you remember above) was set by the user clicking on a palette grid square. Now I use it to set the relevant "tile[position]" (as the user has clicked on a tessera grid square, and wants it coloured in that colour). Then I just reset the entire grid! (This is what the "findViewById" and "setAdapter" does.) Sounds a bit over the top, perhaps, and there is an alternative way to set this grid element explicitly (see Pictures), but there are advantages in doing it this way. I might want to set other grid elements and this way means that I just have to set the relevant "tile[]" and then, again, redo the entire grid. For example, I might want to clear the entire grid, to start another design. Set all of "tile[]" to zeros, and redo the grid!


Scrolling

There is a more serious reason for having an array (such as "tile[]") which represents the grid in some way, and then the code uses that array to redo the grid, as above. If the grid takes up more than a screen, then GridView scrolls. It only scrolls downwards, not sideways. The downwards scrolling happens automatically, using "ImageTessera()" (in this example). If you changed the images explicitly (rather than redoing the entire grid from your array), then it would scroll fine. But when you scrolled back, you find all your beautifully changed pictures had disappeared! So I think that the array / reset the entire grid is the best way to do it.


Make elements next to each other without spacing

Some of my apps needs the tiles or tessera to be positioned right next door to each other, without gaps. It was quite tricky finding out how to do this. The problem is that if you leave it to the app to figure it out, it scales the pictures to fit the row, using the number of columns you gave, with a little gap round each image. And I didn't want the gap! The following seemed to work. This goes in the app/java/com.(the project name)/ImageTessera, after the "RelativeLayout" statement.

imageView.setLayoutParams(new GridView.LayoutParams(80, 80));

It sets the width and height of the element explicitly, to 80. (80 what? Search me!) I tried various numbers (I can't remember if 80 was the final one) until I found one that worked for my Fire Kindle. Then I found that it didn't work for my husband's, where the elements ended up rectangular rather than square... Eventually we worked out that we had different sized screens. So, I need to find the screen size, divide it by the number of columns (10, remember?) and use that in the "setLayoutParams". Here is the code:

GridView rel = (GridView) mContext.findViewById(R.id.relLayout); int w = rel.getWidth() / MainActivity.whole; imageView.setLayoutParams(new GridView.LayoutParams(w, w));

The parameters "(w,w)" is because we are dealing with a square picture, so width and height are the same. If they are different, then it is "(width,height)". The width will be calculated as above, and the height, presumably, as a fraction of the width.

And if that doesn't work, try adding:

imageView.setScaleType(ImageView.ScaleType.FIT_XY); <?XMP> <HR><A NAME="numcol"></A> <H3>Setting column number within code</H3> <P>The number of columns can be set at the start in the GridView, if it doesn't change. However, it is possible to set it in <B>app/java/com.(the project name)/MainActivity</B>.</P> <XMP> GridView mosaic = (GridView) findViewById(R.id.mosaicView); mosaic.setNumColumns(whole); mosaic.setAdapter(new ImageTessera(this));

Setting column number within code

You can set the number of columns in the Gridview, but you can also set it while running the Java code.

GridView tile = (GridView) findViewById(R.id.tileView); tile.setNumColumns(floodWidth); tile.setAdapter(new ImageTile(this));

Getting the individual views within the grid

You might need to get the individual image views within the grid, for example, if you are doing animation. Here is a way to do this.

Define an array to hold the views. Make this a global variable, before the onCreate method. 'tileV' is the name of this array (choose your own if you wish) and 'size*size' is the number of elements in the grid.

public static ImageView[] tileV = new ImageView [size*size];

Initialise this inside the onCreate method, before setting up the grid.

for(int i = 0; i < tileV.length; i++) { tileV[i] = new ImageView(this); }

The class that sets up the grid is subtly different.

public class ImageGrid extends BaseAdapter { private AppCompatActivity mContext; public ImageGrid(AppCompatActivity c) {mContext = c;} public int getCount() {return MainActivity.size * MainActivity.size;} public Object getItem(int position) {return null;} public long getItemId(int position) {return 0;} public View getView(int position, View convertView, ViewGroup parent) { ImageView imageView; if (convertView == null) { //imageView = new ImageView(mContext); imageView = MainActivity.tileV[position]; GridView grid = (GridView) mContext.findViewById(R.id.gridView); MainActivity.width = grid.getWidth() / MainActivity.size; imageView.setLayoutParams(new GridView.LayoutParams(MainActivity.width, MainActivity.width)); } else { imageView = (ImageView) convertView; } int x = position % MainActivity.size; int y = position / MainActivity.size; int i = MainActivity.tile[y][x]; imageView.setImageResource(MainActivity.shape[i]); //MainActivity.tileV[position] = imageView; return imageView; } }

The two comments show which statements are different. (This example is setting up a grid from a 2D array. It works similarly for a simple array.)

Then to, for example, start an animation:

Animation popAnimation = AnimationUtils.loadAnimation(this, R.anim.pop); tileV[y*size +x].startAnimation(popAnimation);

This has the added advantage that now you can use these individual grid views explicitly. So you can alter, for example, one element of the grid without redoing the entire screen:

tileV[y * size + x].setImageResource(pix);

Far more efficient, but you might run into problems if you scrolled!



© Jo Edkins 2016 - Index to Android app index