User Tools

Site Tools


android-labs-s14:class-03

Class 03

In this class, we'll be learning about

  • GridViews and BaseAdapter
  • how to use intent to send data to another Activity
  • what's the best way to set up Application preferences
  • how to use Shared Preferences to store Application data

Reviewing GridView

  • A two-dimensional, scrollable grid

[Figure source: developer.android.com]

Examples

Adapter

  • Bridge between an AdapterView (ListView, GridView, etc.) and the underlying data for that view.
  • Provides access to the data items.
  • Responsible for making a View for each item in the data set.
  • Key method: getView() - The method gets a View that displays the data at the specified position in the grid.
  • Video explaining GridView, ListView and Adapters: Google IO 2010 - World of List View

Layouts

Frame Layout

(Figure source: http://mobile.tutsplus.com/)

Following is the xml code to get such a layout:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <ImageView
        android:id="@+id/ImageView01"
        android:layout_height="fill_parent"
        android:layout_width="fill_parent"
        android:src="@drawable/lake"
        android:scaleType="matrix"/>
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textColor="#000"
        android:textSize="40sp"
        android:text="This is the sky..." />
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="This is a reflection of the sky"
        android:layout_gravity="bottom"
        android:gravity="right"
        android:textColor="#fff"
        android:textSize="50sp" />
</FrameLayout>

Storage on Android

Your data storage options are the following:

Storage Option Description
Shared Preferences Store private primitive data in key-value pairs.
Internal Storage Store private data on the device memory.
External Storage Store public data on the shared external storage.
SQLite Databases Store structured data in a private database.
Network Connection Store data on the web with your own network server.

Extending the "ImageViewer" App

Let's extend ImageViewer App we built in Class-02, which will now allow users to:

  • set Application Preferences
  • add photograph captions, which can then be locally stored (in Shared Preferences)

So, in this app, we'll add two more screens:

  1. Image Detail Screen (ImageDetailActivity)
  2. Settings Screen (SettingsActivity)

Step 01 : Add Application Preferences

  • Create a folder named 'xml' just inside 'res' folder.
  • Inside 'xml' folder, create an Android XML file (lets call it 'preferences.xml')
preferences.xml
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
 
 	<PreferenceCategory
   		android:title="ImageViewer Preferences" >
 
  		<CheckBoxPreference
	     		android:key="allow_open_gallery"
	     		android:title="Access Gallery App"
	     		android:summary="Allow opening Gallery App from this app."
	     		android:defaultValue="true" />
 
 	</PreferenceCategory>
 
</PreferenceScreen>
  • In 'com.example.ImageViewer' package (inside 'src' folder), create a file called 'SettingsActivity.java' which will represent the Settings screen.
SettingsActivity.java
package com.example.imageviewer;
 
import android.os.Bundle;
import android.preference.PreferenceActivity;
 
public class SettingsActivity extends PreferenceActivity
{
	@Override
   	public void onCreate(Bundle savedInstanceState) 
	{
        	super.onCreate(savedInstanceState);
        	addPreferencesFromResource(R.xml.preferences);
    	}
}
  • Now we have to launch SettingsActivity from 'Settings' Button in Main Screen. So in the click callback of 'Settings' button (i.e., onButtonClick() method of MainActivity.java), add code to launch SettingsActivity. Hence, onButtonClick() method of MainActivity.java should now look like following:
ManActivity::onButtonClick()
	public void onButtonClick(View v)
	{
		switch(v.getId())
		{
		case R.id.button_uwimages:
 
			// Create new intent
			Intent i = new Intent(MainActivity.this, ImageGridActivity.class);
 
			startActivity(i);
			break;
 
		case R.id.button_gallery:
 
			// Open pre-installed Gallery Application
			Intent galleryIntent = new Intent(Intent.ACTION_PICK, 
						android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
			startActivityForResult(galleryIntent, 0);		
 
			break;
 
		case R.id.button_settings:
 
			// Launch Settings Activity.
			Intent intent = new Intent(this, SettingsActivity.class);
			startActivity(intent);
			break;
 
		default:
		}
	}
  • And ya, don't forget to register ImageDetailActivity in AndroidManifest.xml! So, open AndroidManifest.xml and add following xml code snippet as a child of <application> tag (like other <activity> tags).
        <!-- Settings Activity -->
	<activity
            android:name="com.example.imageviewer.SettingsActivity" />
  • The keys in preferences.xml and their values get stored in default shared preferences which can then be accessed using PreferenceManager. Now, while creating the Main Screen we need to check if we are even allowed to open gallery app from our app. If we are not allowed, then, just we can just hide the button. So, after adding kind of check, onCreate() method of MainActivity.java should look like following:
MainActivity::onCreate()
	@Override
	protected void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
 
		// Check if opening Gallery App is allowed.
		boolean allowToOpenGallery = PreferenceManager.getDefaultSharedPreferences(this)
				.getBoolean("allow_open_gallery", true);
 
		// If it is not allowed, then hide the 'Open Gallery' button
		if (!allowToOpenGallery)
		{
			Button openGalleryButton = (Button) findViewById(R.id.button_gallery);
			openGalleryButton.setVisibility(View.GONE);
		}
	}

Step 02 : Add Image Detail screen (ImageDetailActivity.java)

  • Go to 'res/layout' folder, and add a new 'Android XML File'. Lets call it 'activity_imagedetail.xml'.

  • Replace code in 'activity_imagedetail.xml' by the following:
activity_imagedetail.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 
	xmlns:android="http://schemas.android.com/apk/res/android"
    	android:layout_width="match_parent"
    	android:layout_height="match_parent" >
 
	<!-- The original image will show up here. -->
	<ImageView
		android:id="@+id/image_large"
		android:layout_width="match_parent"
		android:layout_height="match_parent"
		android:layout_alignParentTop="true"
		android:layout_above="@+id/image_caption_framelayout"/>
 
	<!-- To display TextView and EditText at same place -->
	<FrameLayout
	    	android:id="@id/image_caption_framelayout"
	    	android:layout_width="match_parent"
		android:layout_height="wrap_content"
		android:layout_alignParentBottom="true" 
		android:paddingLeft="10dp"
		android:paddingRight="10dp">
 
		<!-- To layout EditText and the Button horizontally -->
		<LinearLayout
			android:id="@+id/image_caption_linearlayout"
			android:layout_width="match_parent"
			android:layout_height="wrap_content" >
 
			<EditText
			    android:id="@+id/image_caption_edittext"
			    android:layout_width="match_parent"
			    android:layout_height="match_parent"
			    android:hint="Add a caption..." 
			    android:textSize="16sp"
			    android:layout_weight="1" />
 
			<Button
			    android:id="@+id/image_caption_button"
			    android:layout_width="match_parent"
			    android:layout_height="match_parent"
			    android:text="Add"
			    android:textSize="16sp"
			    android:layout_weight="5"
			    android:onClick="onButtonClick" />
 
		</LinearLayout>
 
		<TextView
			android:id="@+id/image_caption_textview"
			android:layout_width="match_parent"
			android:layout_height="match_parent"
			android:textSize="16sp"
			android:onClick="onTextViewClick"
			android:clickable="true" />	
 
	</FrameLayout>    
</RelativeLayout>

Things to note:

  1. FrameLayout is used to have TextView over EditText.
  2. TextView here is clickable.
  3. Default orientation of LinearLayout is 'horizontal', which is why it is not mentioned.
  • Now, create ImageDetailActivity class (with superclass being android.app.Activity), as shown in the figure below:

  • Add following onCreate() method to ImageDetailActivity class
ImageDetailActivity::onCreate()
	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_imagedetail);
	}
  • Don't forget to register ImageDetailActivity in AndroidManifest.xml! So, open AndroidManifest.xml and add following xml code snippet as a child of <application> tag (like other <activity> tags).
        <!-- ImageDetail Activity -->
        <activity
		android:name="com.example.imageviewer.ImageDetailActivity"
		android:theme="@android:style/Theme.NoTitleBar.Fullscreen" />

Step 03 : Pass data from ImageGridActivity to ImageDetailActivity

  • First of all, give a click callback to every item of GridView (i.e. image thumbnails) by setting ItemClickListener for the GridView in ImageGrid Screen. And, in the ItemClickListener callback, launch ImageDetailActivity and pass the id (image position in the grid) of the image clicked. So, 'onCreate()' method of ImageGridActivity class should now look like following:
ImageGridActivity::onCreate()
	@Override
	protected void onCreate(Bundle savedInstanceState) 
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_imagegrid);
 
		// Get GridView from xml
		GridView gridView = (GridView) findViewById(R.id.grid_view);
 
		// Set Adapter for GridView
		gridView.setAdapter(new ImageAdapter(this));
 
		/**
		* On Click event for Single GridView Item
		**/
		gridView.setOnItemClickListener(new OnItemClickListener()
		{
			@Override
			public void onItemClick(AdapterView<?> parent, View v, int position, long id)
			{
				// Create new intent
				Intent i = new Intent(ImageGridActivity.this, ImageDetailActivity.class);
 
				// Send Image ID to ImageActivity
				i.putExtra("id", position);
				startActivity(i);
			}
		});
	}
  • Now, we also have to make sure that ImageDetailActivity class receives the above intent and its data, and display image accordingly. For that, replace ImageDetailActivity.java code by the following code:
ImageDetailActivity.java
package com.example.imageviewer;
 
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
 
public class ImageDetailActivity extends Activity
{
 
	Integer mCurrentImagePosition = -1;	// Currently displayed image 
 
	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_imagedetail);
 
		// Get intent data
		Intent i = getIntent();
		// Extract Image ID (position) from the passed intent
		mCurrentImagePosition = i.getExtras().getInt("id");
 
		// First, set the image user want to be displayed.
		ImageView imageView = (ImageView) findViewById(R.id.image_large);
		imageView.setImageResource(mPics[mCurrentImagePosition]);
	}
 
	// Actual Full Size Images
	public Integer[] mPics =
	{
		R.drawable.pic0, R.drawable.pic1, R.drawable.pic2, R.drawable.pic3,
		R.drawable.pic4, R.drawable.pic5, R.drawable.pic6, R.drawable.pic7,
		R.drawable.pic8, R.drawable.pic9, R.drawable.pic10, R.drawable.pic11,
		R.drawable.pic12, R.drawable.pic13, R.drawable.pic14, R.drawable.pic15,
		R.drawable.pic16, R.drawable.pic17, R.drawable.pic18, R.drawable.pic19,
		R.drawable.pic20, R.drawable.pic21, R.drawable.pic22, R.drawable.pic23,
		R.drawable.pic24, R.drawable.pic25, R.drawable.pic26, R.drawable.pic27,
		R.drawable.pic28, R.drawable.pic29, R.drawable.pic30, R.drawable.pic31,
		R.drawable.pic32, R.drawable.pic33, R.drawable.pic34, R.drawable.pic35,
		R.drawable.pic36, R.drawable.pic37, R.drawable.pic38, R.drawable.pic39
	};
}

Step 04: Getting and Setting Image Caption

  • To set an image caption, first of all, we need to set the click callback for the 'Add' button on Image Detail screen. Then, we need to store the caption text added by the user in SharedPreferences for that particular image (identified by its position in the grid). To get the caption of an image, we look in the Shared Preferences to see if there is any caption added before for a particular image (again identified by its position in the grid). Hence, adding those methods, ImageDetailActivity now looks like following:
ImageDetailActivity.java
package com.example.imageviewer;
 
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
 
public class ImageDetailActivity extends Activity
{
 
	Integer mCurrentImagePosition = -1;	// Currently displayed image 
 
	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_imagedetail);
 
		// Get intent data
		Intent i = getIntent();
		// Extract Image ID (position) from the passed intent
		mCurrentImagePosition = i.getExtras().getInt("id");
 
		// First, set the image user want to be displayed.
		ImageView imageView = (ImageView) findViewById(R.id.image_large);
		imageView.setImageResource(mPics[mCurrentImagePosition]);
 
		// Set UI controls
		setCaptionUIControls();
	}
 
	/** 
	 * Method to set UI related to Image caption.
	 */
	public void setCaptionUIControls()
	{
		// See if a caption has already been added for this image. 
		LinearLayout captionLinearLayout = (LinearLayout) findViewById(R.id.image_caption_linearlayout);
		TextView captionTextView = (TextView) findViewById(R.id.image_caption_textview);
 
		String caption = getImageCaption();
		if(caption!= null && !caption.equals(""))
		{
			captionTextView.setVisibility(View.VISIBLE);
			captionTextView.setText(caption);
			captionLinearLayout.setVisibility(View.GONE);
		}
		else
		{
			captionTextView.setVisibility(View.GONE);
			captionLinearLayout.setVisibility(View.VISIBLE);
		}
	}
 
	// Actual Full Size Images
	public Integer[] mPics =
	{
		R.drawable.pic0, R.drawable.pic1, R.drawable.pic2, R.drawable.pic3,
		R.drawable.pic4, R.drawable.pic5, R.drawable.pic6, R.drawable.pic7,
		R.drawable.pic8, R.drawable.pic9, R.drawable.pic10, R.drawable.pic11,
		R.drawable.pic12, R.drawable.pic13, R.drawable.pic14, R.drawable.pic15,
		R.drawable.pic16, R.drawable.pic17, R.drawable.pic18, R.drawable.pic19,
		R.drawable.pic20, R.drawable.pic21, R.drawable.pic22, R.drawable.pic23,
		R.drawable.pic24, R.drawable.pic25, R.drawable.pic26, R.drawable.pic27,
		R.drawable.pic28, R.drawable.pic29, R.drawable.pic30, R.drawable.pic31,
		R.drawable.pic32, R.drawable.pic33, R.drawable.pic34, R.drawable.pic35,
		R.drawable.pic36, R.drawable.pic37, R.drawable.pic38, R.drawable.pic39
	};
 
	// Preferences Name
	private static final String PREFS_NAME = "com.example.ImageViewer.caption";
 
	/**
	 * Method to get the Image Caption (if any) from Shared Preferences.
	 * 
	 * @return	Image caption text
	 */
	private String getImageCaption()
	{
		// Check if SharedPreferences has a caption associated for the requested image.
		SharedPreferences prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
		return prefs.getString(mCurrentImagePosition.toString(), null);
	}
 
	/**
	 * Method to store Image caption in SharePreferences.
	 * 
	 * @param caption 	Caption text
	 */
	private void setImageCaption(String caption)
	{
		// Get Shared Preferences Editor
		SharedPreferences prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
		SharedPreferences.Editor editor = prefs.edit();
 
		// Edit the value for this particular key (Image position) and commit the change.
		editor.putString(mCurrentImagePosition.toString(), caption);
		editor.commit();
	}
 
	/**
	 * Click callback for 'Add' button on ImageDetail Screen.
	 * 
	 * @param v		View clicked
	 */
	public void onButtonClick(View v)
	{
		switch(v.getId())
		{
		case R.id.image_caption_button:
 
			EditText captionEditText = (EditText) findViewById(R.id.image_caption_edittext);
			String caption = captionEditText.getText().toString();
 
			if(caption != null && !caption.equals(""))
			{
				// Store the caption string in Shared Preferences against this image.
				setImageCaption(caption);
 
				// Refresh UI
				setCaptionUIControls();
			}
			break;
 
		default:
		}
	}
}
  • Now, as a final step, lets add a click callback to TextView on Image Detail Screen so that we can edit an existing caption by tapping on it. So, add following code snippet to ImageDetailActivity class:
ImageDetailActivity::onTextViewClick
	/**
	 * Click callback for 'TextView' on ImageDetail Screen.
	 * 
	 * @param v	View clicked
	 */
	public void onTextViewClick(View v)
	{
		switch(v.getId())
		{
		case R.id.image_caption_textview:
 
			TextView captionTextView = (TextView) findViewById(R.id.image_caption_textview);
			LinearLayout captionLinearLayout = (LinearLayout) findViewById(R.id.image_caption_linearlayout);
			EditText captionEditText = (EditText) findViewById(R.id.image_caption_edittext);
 
			// Put the text of TextView in EditText
			String caption = captionTextView.getText().toString();
			captionEditText.setText(caption);
 
			if(captionEditText.requestFocus()) 
			    getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
 
			// And, make EditText & Button visible
			captionTextView.setVisibility(View.GONE);
			captionLinearLayout.setVisibility(View.VISIBLE);
 
			break;
 
		default:
		}
	}

ImageViewer Source Code

You can download ImageViewer source code from Github.


Further Enhancements

You can further extend this app by adding more features. Here are some suggestions:

  • Download images from Instagram and display them, rather than pre-bundling them in the app.
  • Use a list view instead of GridView.
  • Add captions in the GridView (along with thumbnail images)
  • Add more preferences:
    • # of images to display in one column
    • Allow adding/displaying caption to the photos
    • Sort images by Date Created
  • On long pressing the image thumbnail in the GridView, give options of what you can do with the image (e.g., delete that image).
android-labs-s14/class-03.txt · Last modified: 2014/01/31 14:22 by prakhar