Wednesday, June 30, 2010

Chronometer.OnChronometerTickListener()

Modify from the previous exercise "Android Chronometer", Chronometer.OnChronometerTickListener() is implemented. Chronometer.OnChronometerTickListener() is a callback that notifies when the chronometer has incremented on its own.

Chronometer.OnChronometerTickListener()

Inside Chronometer.OnChronometerTickListener(), the elapsed time in millisecons can be get from: SystemClock.elapsedRealtime() - myChronometer.getBase().

AndroidChronometer.java
package com.exercise.AndroidChronometer;

import android.app.Activity;
import android.os.Bundle;
import android.os.SystemClock;
import android.view.View;
import android.widget.Button;
import android.widget.Chronometer;
import android.widget.Toast;

public class AndroidChronometer extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

final Chronometer myChronometer = (Chronometer)findViewById(R.id.chronometer);
Button buttonStart = (Button)findViewById(R.id.buttonstart);
Button buttonStop = (Button)findViewById(R.id.buttonstop);
Button buttonReset = (Button)findViewById(R.id.buttonreset);

buttonStart.setOnClickListener(new Button.OnClickListener(){

@Override
public void onClick(View v) {
// TODO Auto-generated method stub
myChronometer.start();
}});

buttonStop.setOnClickListener(new Button.OnClickListener(){

@Override
public void onClick(View v) {
// TODO Auto-generated method stub
myChronometer.stop();

}});

buttonReset.setOnClickListener(new Button.OnClickListener(){

@Override
public void onClick(View v) {
// TODO Auto-generated method stub
myChronometer.setBase(SystemClock.elapsedRealtime());

}});

myChronometer.setOnChronometerTickListener(
new Chronometer.OnChronometerTickListener(){

@Override
public void onChronometerTick(Chronometer chronometer) {
// TODO Auto-generated method stub
long myElapsedMillis = SystemClock.elapsedRealtime() - myChronometer.getBase();
String strElapsedMillis = "Elapsed milliseconds: " + myElapsedMillis;
Toast.makeText(AndroidChronometer.this, strElapsedMillis, Toast.LENGTH_SHORT).show();
}}
);
}
}



Download the files.

Tuesday, June 29, 2010

New Android 2.2 Software Update for Nexus One phones

Starting today, Nexus One users will begin to receive the Android 2.2 (codenamed Froyo) over-the-air software update on their phones. This update provides some great new features including support for making your handset a portable hotspot and support for Adobe Flash within the browser. For a complete list of everything we’ve included in Android 2.2, please see the Android 2.2 Platform Highlights.

In order to access the update, you will receive a message on your phone's notification bar. Just download the update, wait for it to install, and you should be all set. This update will be rolled out gradually to phones - and most users will receive the notification by the end of the week . We hope you enjoy these new features.

source: Nexus One | News and Updates Monday, June 28, 2010


Eclipse Helios with Android SDK


Eclipse Helios released with Eclipse 3.6.0. I have tried to install on ubuntu 10.04. The installation is same as Install Android SDK on Eclipse 3.5 Galileo, in Ubuntu 9.10. Except that you have to download Eclipse IDE for Java Developers from Eclipse web site, instead of install using Synaptic Package Manager.

Android Chronometer

Chronometer is a class that implements a simple timer.

You can give it a start time in the elapsedRealtime() timebase, and it counts up from that, or if you don't give it a base time, it will use the time at which you call start(). By default it will display the current timer value in the form "MM:SS" or "H:MM:SS", or you can use setFormat(String) to format the timer value into an arbitrary string.

Android Chronometer
main.xml


<?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:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
/>
<Chronometer
android:id="@+id/chronometer"
android:layout_gravity="center_horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<Button
android:id="@+id/buttonstart"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Start"
/>
<Button
android:id="@+id/buttonstop"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Stop"
/>
<Button
android:id="@+id/buttonreset"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Reset"
/>
</LinearLayout>


AndroidChronometer.java

package com.exercise.AndroidChronometer;

import android.app.Activity;
import android.os.Bundle;
import android.os.SystemClock;
import android.view.View;
import android.widget.Button;
import android.widget.Chronometer;

public class AndroidChronometer extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

final Chronometer myChronometer = (Chronometer)findViewById(R.id.chronometer);
Button buttonStart = (Button)findViewById(R.id.buttonstart);
Button buttonStop = (Button)findViewById(R.id.buttonstop);
Button buttonReset = (Button)findViewById(R.id.buttonreset);

buttonStart.setOnClickListener(new Button.OnClickListener(){

@Override
public void onClick(View v) {
// TODO Auto-generated method stub
myChronometer.start();
}});

buttonStop.setOnClickListener(new Button.OnClickListener(){

@Override
public void onClick(View v) {
// TODO Auto-generated method stub
myChronometer.stop();

}});

buttonReset.setOnClickListener(new Button.OnClickListener(){

@Override
public void onClick(View v) {
// TODO Auto-generated method stub
myChronometer.setBase(SystemClock.elapsedRealtime());

}});


}
}


Download the files.


related article: Chronometer.OnChronometerTickListener()

Thursday, June 24, 2010

Apply custom adapter to ListView for RSS Reader

With the help of the exercise "Custom ArrayAdapter, with different icons", we know how to create our custom ListView. Now we go back to the old exercise of RSS Reader "A simple RSS reader III, show details once item clicked".

Apply custom adapter to ListView for RSS Reader
Modify the Rss Reader exercise to have a custom ListView, in which both title and PubDate will be displayed on each item in the ListView.

Create /res/layout/row.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/listtitle"
android:textSize="22px"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/listpubdate"
android:textSize="10px"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
</LinearLayout>


Modify AndroidRssReader.java to create a new class MyCustomAdapter extends ArrayAdapter, with getView() overrided. setListAdapter using the custom adapter.
package com.exercise.AndroidRssReader;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

import android.app.ListActivity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;

public class AndroidRssReader extends ListActivity {

private RSSFeed myRssFeed = null;

public class MyCustomAdapter extends ArrayAdapter<RSSItem> {

public MyCustomAdapter(Context context, int textViewResourceId,
List<RSSItem> list) {
super(context, textViewResourceId, list);
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
//return super.getView(position, convertView, parent);

View row = convertView;

if(row==null){
LayoutInflater inflater=getLayoutInflater();
row=inflater.inflate(R.layout.row, parent, false);
}

TextView listTitle=(TextView)row.findViewById(R.id.listtitle);
listTitle.setText(myRssFeed.getList().get(position).getTitle());
TextView listPubdate=(TextView)row.findViewById(R.id.listpubdate);
listPubdate.setText(myRssFeed.getList().get(position).getPubdate());

if (position%2 == 0){
listTitle.setBackgroundColor(0xff101010);
listPubdate.setBackgroundColor(0xff101010);
}
else{
listTitle.setBackgroundColor(0xff080808);
listPubdate.setBackgroundColor(0xff080808);
}

return row;
}
}

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

try {
URL rssUrl = new URL("http://www.gov.hk/en/about/rss/govhkrss.data.xml");
SAXParserFactory mySAXParserFactory = SAXParserFactory.newInstance();
SAXParser mySAXParser = mySAXParserFactory.newSAXParser();
XMLReader myXMLReader = mySAXParser.getXMLReader();
RSSHandler myRSSHandler = new RSSHandler();
myXMLReader.setContentHandler(myRSSHandler);
InputSource myInputSource = new InputSource(rssUrl.openStream());
myXMLReader.parse(myInputSource);

myRssFeed = myRSSHandler.getFeed();

} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ParserConfigurationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SAXException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

if (myRssFeed!=null)
{
TextView feedTitle = (TextView)findViewById(R.id.feedtitle);
TextView feedDescribtion = (TextView)findViewById(R.id.feeddescribtion);
TextView feedPubdate = (TextView)findViewById(R.id.feedpubdate);
TextView feedLink = (TextView)findViewById(R.id.feedlink);
feedTitle.setText(myRssFeed.getTitle());
feedDescribtion.setText(myRssFeed.getDescription());
feedPubdate.setText(myRssFeed.getPubdate());
feedLink.setText(myRssFeed.getLink());

MyCustomAdapter adapter =
new MyCustomAdapter(this, R.layout.row, myRssFeed.getList());
setListAdapter(adapter);

}
}

@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
// TODO Auto-generated method stub
Intent intent = new Intent(this,ShowDetails.class);
Bundle bundle = new Bundle();
bundle.putString("keyTitle", myRssFeed.getItem(position).getTitle());
bundle.putString("keyDescription", myRssFeed.getItem(position).getDescription());
bundle.putString("keyLink", myRssFeed.getItem(position).getLink());
bundle.putString("keyPubdate", myRssFeed.getItem(position).getPubdate());
intent.putExtras(bundle);
startActivity(intent);
}
}


Download the files.

next: Simple RSS Reader, with Options Menu to reload RSS




Tuesday, June 22, 2010

Problem of onTouchEvent(MotionEvent, MapView) on MapView

It's a follow-up of my old article "Move the marker on MapView". In the article, onTouchEvent(MotionEvent, MapView) was overrided to handle user touching on screen to update the marker. It work fine on Android emulator, BUT TOTALLY NO WORKING on true phone, such as Nexus One. It seem that the onTouchEvent(MotionEvent, MapView) cannot be triggered. I don't know why!

Instead of onTouchEvent(MotionEvent, MapView), I override onTap(GeoPoint, MapView) and test on Nexus One. It work as expect.

public boolean onTap(GeoPoint p, MapView mapView) {
String strLocation = String.valueOf((float)p.getLongitudeE6()/1000000)
+ " : "
+ String.valueOf((float)p.getLatitudeE6()/1000000);
Toast.makeText(AndroidMapView.this, strLocation, Toast.LENGTH_LONG).show();
// TODO Auto-generated method stub
//return super.onTap(p, mapView);

myMapView.getOverlays().remove(0);

CenterLocation(p);

return true;
}

Obtain a Maps API Key for Your Signing Certificate

If you are looking for obtain Map API for debug on emulator in your local development platform, refer to the article "Obtaining a Maps API Key, for debug".

If you are going to deploy a Android application with MapView, you have to obtain a Maps API Key for Your Signing Certificate.

You have to get the MD5 Fingerprint of Your Signing Certificate. Refer to last article "Sign and deploy Android App", you know where is your keystore and alias. Start a Terminal, switch to the folder of the keystore, type the command with your own alias_name and my-release-key.keystore.

$ keytool -list -alias alias_name -keystore my-release-key.keystore

get the MD5 Fingerprint of Your Signing Certificate

Now you can log-in with your Google account and go to Sign Up for the Android Maps API.

Accept the terms and conditions, fill in with your MD5 fingerprint, and Generate API Key.

Monday, June 21, 2010

Sign and deploy Android App

So far all my exercise run on emulator in local development platform. In order to make it runnable on true device, it have to be signed and deployed to apk file.

It's a easy way in Eclipse to sign a Android App.

- In Eclipse, right click the proect and select Export...
Sign and deploy Android App

- Select Android -> Export Android Application then click Next.
Sign and deploy Android App

- Browse to select project, it should be selected already, just click Next.
Sign and deploy Android App

-
Select Create neww keystore it it's the first time you create keystore for a project.
Select the location of keystore you want to create.
Enter and confirm password
Sign and deploy Android App

- Enter some info. for the certificate, then Next
Sign and deploy Android App

- Browse to select the target location you want to save the generated apk file, then click Finish.
Sign and deploy Android App

- Now, you can upload the generated apk file to internet and lets Android device to download and install.

Wednesday, June 16, 2010

ListView with icon loaded from internet

With the last exercise "Load ImageView with bitmap from internet", we can now modify the the former exercise "Using convertView in getView() to make ListView efficient" to load the icon from internet, instead of loading from our resources.

ListView with icon loaded from internet

please note that in order to permit the App to access to internet, we have to grand it permission of "android.permission.INTERNET"; refer to the last exercise "Load ImageView with bitmap from internet"

Keep usng the /res/layout/row.xml as previous exercise, "ListView, with icon".

AndroidList.java
package com.exercise.AndroidList;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;

import android.app.ListActivity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

public class AndroidList extends ListActivity {

String image_URL=
"http://4.bp.blogspot.com/_C5a2qH8Y_jk/StYXDpZ9-WI/AAAAAAAAAJQ/sCgPx6jfWPU/S1600-R/android.png";

public class MyCustomAdapter extends ArrayAdapter<String> {

Bitmap bm;

public MyCustomAdapter(Context context, int textViewResourceId,
String[] objects) {
super(context, textViewResourceId, objects);
// TODO Auto-generated constructor stub

BitmapFactory.Options bmOptions;
bmOptions = new BitmapFactory.Options();
bmOptions.inSampleSize = 1;
bm = LoadImage(image_URL, bmOptions);

}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
//return super.getView(position, convertView, parent);

View row = convertView;

if(row==null){
LayoutInflater inflater=getLayoutInflater();
row=inflater.inflate(R.layout.row, parent, false);
}

TextView label=(TextView)row.findViewById(R.id.weekofday);
label.setText(month[position]);
ImageView icon=(ImageView)row.findViewById(R.id.icon);

icon.setImageBitmap(bm);

return row;
}
}

String[] month = {
"January", "February", "March", "April",
"May", "June", "July", "August",
"September", "October", "November", "December"
};

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.main);
/*setListAdapter(new ArrayAdapter<String>(this,
R.layout.row, R.id.weekofday, DayOfWeek));*/
setListAdapter(new MyCustomAdapter(AndroidList.this, R.layout.row, month));
}

@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
// TODO Auto-generated method stub
//super.onListItemClick(l, v, position, id);
String selection = l.getItemAtPosition(position).toString();
Toast.makeText(this, selection, Toast.LENGTH_LONG).show();
}

private Bitmap LoadImage(String URL, BitmapFactory.Options options)
{
Bitmap bitmap = null;
InputStream in = null;
try {
in = OpenHttpConnection(URL);
bitmap = BitmapFactory.decodeStream(in, null, options);
in.close();
} catch (IOException e1) {
}
return bitmap;
}

private InputStream OpenHttpConnection(String strURL) throws IOException{
InputStream inputStream = null;
URL url = new URL(strURL);
URLConnection conn = url.openConnection();

try{
HttpURLConnection httpConn = (HttpURLConnection)conn;
httpConn.setRequestMethod("GET");
httpConn.connect();

if (httpConn.getResponseCode() == HttpURLConnection.HTTP_OK) {
inputStream = httpConn.getInputStream();
}
}
catch (Exception ex)
{
}
return inputStream;
}

}


next: Load ListView in background AsyncTask



Load ImageView with bitmap from internet

It's a simple way to load ImageView with a bitmap from internet, via http connection.

Load ImageView with bitmap from internet

In order to load something from internet, the AndroidManifest.xml have to be modified to grand permission for internet access.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.exercise.AndroidWebImage"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".AndroidWebImage"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

</application>
<uses-sdk android:minSdkVersion="4" />
<uses-permission android:name="android.permission.INTERNET" />
</manifest>


Modify main.xml to include a ImageView
<?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:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
/>
<ImageView
android:id="@+id/image"
android:scaleType="center"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
</LinearLayout>


java code:
package com.exercise.AndroidWebImage;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.widget.ImageView;

public class AndroidWebImage extends Activity {

String image_URL=
"http://4.bp.blogspot.com/_C5a2qH8Y_jk/StYXDpZ9-WI/AAAAAAAAAJQ/sCgPx6jfWPU/S1600-R/android.png";

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

ImageView bmImage = (ImageView)findViewById(R.id.image);
BitmapFactory.Options bmOptions;
bmOptions = new BitmapFactory.Options();
bmOptions.inSampleSize = 1;
Bitmap bm = LoadImage(image_URL, bmOptions);
bmImage.setImageBitmap(bm);
}

private Bitmap LoadImage(String URL, BitmapFactory.Options options)
{
Bitmap bitmap = null;
InputStream in = null;
try {
in = OpenHttpConnection(URL);
bitmap = BitmapFactory.decodeStream(in, null, options);
in.close();
} catch (IOException e1) {
}
return bitmap;
}

private InputStream OpenHttpConnection(String strURL) throws IOException{
InputStream inputStream = null;
URL url = new URL(strURL);
URLConnection conn = url.openConnection();

try{
HttpURLConnection httpConn = (HttpURLConnection)conn;
httpConn.setRequestMethod("GET");
httpConn.connect();

if (httpConn.getResponseCode() == HttpURLConnection.HTTP_OK) {
inputStream = httpConn.getInputStream();
}
}
catch (Exception ex)
{
}
return inputStream;
}

}


Monday, June 14, 2010

Using convertView in getView() to make ListView efficient

Refer to document of Android Developers about getView(); it's stated that :

convertView - The old view to reuse, if possible. Note: You should check that this view is non-null and of an appropriate type before using. If it is not possible to convert this view to display the correct data, this method can create a new view.

If the convertView is null, we have to run the code below to create a new View.
LayoutInflater inflater=getLayoutInflater();
row=inflater.inflate(R.layout.row, parent, false);

If convertView is NOT null, we can simple re-use the convertView as the new View. It will happen when a new row appear and a old row in the other end roll out.

There are only seven elements in DayOfWeek[], it's not enough to demo this case. So we modify to use month[] of 12 elements.

Using convertView in getView() to make ListView efficient

Note the change in the method getView():

AndroidList.java changed from last exercise "Custom ArrayAdapter, with with different icons".
package com.exercise.AndroidList;

import android.app.ListActivity;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

public class AndroidList extends ListActivity {

public class MyCustomAdapter extends ArrayAdapter<String> {

public MyCustomAdapter(Context context, int textViewResourceId,
String[] objects) {
super(context, textViewResourceId, objects);
// TODO Auto-generated constructor stub
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
//return super.getView(position, convertView, parent);

View row = convertView;

if(row==null){
LayoutInflater inflater=getLayoutInflater();
row=inflater.inflate(R.layout.row, parent, false);
}

TextView label=(TextView)row.findViewById(R.id.weekofday);
label.setText(month[position]);
ImageView icon=(ImageView)row.findViewById(R.id.icon);

if (month[position]=="December"){
icon.setImageResource(R.drawable.icon);
}
else{
icon.setImageResource(R.drawable.icongray);
}

return row;
}
}

String[] month = {
"January", "February", "March", "April",
"May", "June", "July", "August",
"September", "October", "November", "December"
};

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.main);
/*setListAdapter(new ArrayAdapter<String>(this,
R.layout.row, R.id.weekofday, DayOfWeek));*/
setListAdapter(new MyCustomAdapter(AndroidList.this, R.layout.row, month));
}

@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
// TODO Auto-generated method stub
//super.onListItemClick(l, v, position, id);
String selection = l.getItemAtPosition(position).toString();
Toast.makeText(this, selection, Toast.LENGTH_LONG).show();
}

}

Custom ArrayAdapter, with different icons.

In the last exercises "A simple ListView, extends ListActivity", "ListView, with icon" and "Implement onListItemClick() of ListActivity", all use the build-in ArrayAdapter with standard layout in each row. All row have the same icon.

In this article, a custom ArrayAdapter will be created. The method getView() have to be re-implement. Such that we can have a List with different icons on each row.

In this exercise, we will show original icon come from Project Wizard for Sunday, and a gray icon for all others.

Custom ArrayAdapter, with with different icons.

Create folder /res/drawable and save the gray icon inside.
icongray.png

Keep usng the /res/layout/row.xml as previous exercise, "ListView, with icon".

AndroidList.java
package com.exercise.AndroidList;

import android.app.ListActivity;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

public class AndroidList extends ListActivity {

public class MyCustomAdapter extends ArrayAdapter<String> {

public MyCustomAdapter(Context context, int textViewResourceId,
String[] objects) {
super(context, textViewResourceId, objects);
// TODO Auto-generated constructor stub
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
//return super.getView(position, convertView, parent);
LayoutInflater inflater=getLayoutInflater();
View row=inflater.inflate(R.layout.row, parent, false);
TextView label=(TextView)row.findViewById(R.id.weekofday);
label.setText(DayOfWeek[position]);
ImageView icon=(ImageView)row.findViewById(R.id.icon);

if (DayOfWeek[position]=="Sunday"){
icon.setImageResource(R.drawable.icon);
}
else{
icon.setImageResource(R.drawable.icongray);
}

return row;
}
}

String[] DayOfWeek = {"Sunday", "Monday", "Tuesday",
"Wednesday", "Thursday", "Friday", "Saturday"
};

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.main);
/*setListAdapter(new ArrayAdapter<String>(this,
R.layout.row, R.id.weekofday, DayOfWeek));*/
setListAdapter(new MyCustomAdapter(AndroidList.this, R.layout.row, DayOfWeek));
}

@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
// TODO Auto-generated method stub
//super.onListItemClick(l, v, position, id);
String selection = l.getItemAtPosition(position).toString();
Toast.makeText(this, selection, Toast.LENGTH_LONG).show();
}

}
***********
It's a programmatic pitfall here:
in getView(), Condition checking of String ("Sunday") should be checked with:
(DayOfWeek[position].equals("Sunday"))

"==" not always work! refer String Comparison: equals()? ==?
***********

Download the files.

next: Using convertView in getView() to make ListView efficient

Saturday, June 12, 2010

Implement onListItemClick() of ListActivity

Modify the java code of the last exercise "ListView, with icon" to add listener of onListItemClick()

Implement onListItemClick() of ListActivity

AndroidList.java
package com.exercise.AndroidList;

import android.app.ListActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;

public class AndroidList extends ListActivity {

String[] DayOfWeek = {"Sunday", "Monday", "Tuesday",
"Wednesday", "Thursday", "Friday", "Saturday"
};

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.main);
setListAdapter(new ArrayAdapter<String>(this,
R.layout.row, R.id.weekofday, DayOfWeek));
}

@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
// TODO Auto-generated method stub
//super.onListItemClick(l, v, position, id);
String selection = l.getItemAtPosition(position).toString();
Toast.makeText(this, selection, Toast.LENGTH_LONG).show();
}
}


next: Custom ArrayAdapter, with with different icons.



ListView, with icon

Last exercise display a ListView in simplest form with plain text only. This exercise describe how to add a icon in ListView.

ListView, with icon

create a new file in /res/layout/row.xml, to setup our layout on each row.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/icon"/>
<TextView
android:id="@+id/weekofday"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>


AndroidList.java
package com.exercise.AndroidList;

import android.app.ListActivity;
import android.os.Bundle;
import android.widget.ArrayAdapter;

public class AndroidList extends ListActivity {

String[] DayOfWeek = {"Sunday", "Monday", "Tuesday",
"Wednesday", "Thursday", "Friday", "Saturday"
};

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.main);
setListAdapter(new ArrayAdapter<String>(this,
R.layout.row, R.id.weekofday, DayOfWeek));
}
}



next: Implement onListItemClick() of ListActivity


Related article:
- Custom Spinner with icon

A simple ListView, extends ListActivity

It's the simplest form of ListView, extends ListActivity. The list is displayed as a single line of plain text, using the simple Android built-in layout "android.R.layout.simple_list_item_1".

A simple ListView, extends ListActivity

package com.exercise.AndroidList;

import android.app.ListActivity;
import android.os.Bundle;
import android.widget.ArrayAdapter;

public class AndroidList extends ListActivity {
 
 String[] DayOfWeek = {"Sunday", "Monday", "Tuesday",
   "Wednesday", "Thursday", "Friday", "Saturday"
 };
 
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.main);
        setListAdapter(new ArrayAdapter<String>(this, 
          android.R.layout.simple_list_item_1, DayOfWeek));
    }
}


Download the souce files.

next: ListView, with icon



Tuesday, June 8, 2010

Android background thread, by implementing Thread object, provided with a Runnable object.

In last exercise "Android background thread, by extending Thread", a thread was created by implementing a object of a new class extends Thread. It's another way to create a thread, implement a object of Thread class with a Runnable object provided. Actually, both version have the same out, difference in implementation only.

main.xml is same as last exercise "Android background thread, by extending Thread"

AndroidBackgroundThread.java
package com.exercise.AndroidBackgroundThread;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

public class AndroidBackgroundThread extends Activity {

Thread backgroundThread;
TextView myText;
boolean myTextOn = true;
boolean running = false;

Handler handler = new Handler(){

@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
//super.handleMessage(msg);
if (myTextOn){
myTextOn = false;
myText.setVisibility(View.GONE);
}
else{
myTextOn = true;
myText.setVisibility(View.VISIBLE);
}
}

};

void setRunning(boolean b){
running = b;
}

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

myText = (TextView)findViewById(R.id.mytext);

Toast.makeText(this, "onCreate()", Toast.LENGTH_LONG).show();
}



@Override
protected void onStart() {
// TODO Auto-generated method stub
super.onStart();

Toast.makeText(this, "onStart()", Toast.LENGTH_LONG).show();

backgroundThread = new Thread(new Runnable(){

@Override
public void run() {
// TODO Auto-generated method stub
while(running){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
handler.sendMessage(handler.obtainMessage());
}
}

});

setRunning(true);
backgroundThread.start();
}

@Override
protected void onStop() {
// TODO Auto-generated method stub
super.onStop();

boolean retry = true;
setRunning(false);

while(retry){
try {
backgroundThread.join();
retry = false;
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}

Toast.makeText(this, "onStop()", Toast.LENGTH_LONG).show();

}
}


Download the files.

Monday, June 7, 2010

Android background thread, by extending Thread

Android background thread, by extending Thread

In this exercise, a new class, BackgroundThread, was created by extending Thread class. The object of this class run in background thread. A TextView will be toggled between on and off by this background thread. In Android, only the UI thread can modify UI elements, the method run in background thread cannot modify UI elements directly. So we have to implement a Handler, the background thread will send a Message to the Handler to change Visibility of the TextView.

Android background thread, by extending Thread

main.xml
<?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:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
/>
<TextView
android:id="@+id/mytext"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="This Text will be Turn ON/OFF triggered by a background thread."
/>
</LinearLayout>


AndroidBackgroundThread.java
package com.exercise.AndroidBackgroundThread;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

public class AndroidBackgroundThread extends Activity {

BackgroundThread backgroundThread;
TextView myText;
boolean myTextOn = true;


public class BackgroundThread extends Thread {

boolean running = false;

void setRunning(boolean b){
running = b;
}

@Override
public void run() {
// TODO Auto-generated method stub
//super.run();
while(running){
try {
sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
handler.sendMessage(handler.obtainMessage());
}
}

}

Handler handler = new Handler(){

@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
//super.handleMessage(msg);
if (myTextOn){
myTextOn = false;
myText.setVisibility(View.GONE);
}
else{
myTextOn = true;
myText.setVisibility(View.VISIBLE);
}
}

};



/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

myText = (TextView)findViewById(R.id.mytext);

Toast.makeText(this, "onCreate()", Toast.LENGTH_LONG).show();

}



@Override
protected void onStart() {
// TODO Auto-generated method stub
super.onStart();

backgroundThread = new BackgroundThread();
backgroundThread.setRunning(true);
backgroundThread.start();
Toast.makeText(this, "onStart()", Toast.LENGTH_LONG).show();
}

@Override
protected void onStop() {
// TODO Auto-generated method stub
super.onStop();

boolean retry = true;
backgroundThread.setRunning(false);

while(retry){
try {
backgroundThread.join();
retry = false;
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}

Toast.makeText(this, "onStop()", Toast.LENGTH_LONG).show();

}
}



Download the files.

another version: Android background thread, by implementing Thread object, provided with a Runnable object.

Related article:
- ProgressDialog + Thread

Saturday, June 5, 2010

lifecycle of Android activity

It's a exercise to understand the lifecycle of a Android activity: onCreate(), onStart(), onRestart(), onResume(), onPause(), onStop(), onDestroy(). Through the exercise, you can note the difference among exit a activity by HOME key, BACK key and finish() call.



main.xml
<?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:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
/>
<TextView
android:id="@+id/textlife"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="24px"
android:text="App first started"
/>
<Button
android:id="@+id/buttonafterstart"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Press It!"
/>
<Button
android:id="@+id/buttonexit"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="EXIT"
/>
</LinearLayout>


AndroidLifeCycle.java
package com.exercise.AndroidLifeCycle;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

public class AndroidLifeCycle extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

Toast.makeText(this, "onCreate()", Toast.LENGTH_LONG).show();

final TextView tvTextLife = (TextView)findViewById(R.id.textlife);
Button btAfterStart = (Button)findViewById(R.id.buttonafterstart);
Button btExit = (Button)findViewById(R.id.buttonexit);

btAfterStart.setOnClickListener(new Button.OnClickListener(){

@Override
public void onClick(View v) {
// TODO Auto-generated method stub
tvTextLife.setText("App started before!");
}});

btExit.setOnClickListener(new Button.OnClickListener(){

@Override
public void onClick(View v) {
// TODO Auto-generated method stub
finish();
}});
}

@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
Toast.makeText(this, "onDestroy()", Toast.LENGTH_LONG).show();
}

@Override
protected void onPause() {
// TODO Auto-generated method stub
super.onPause();
Toast.makeText(this, "onPause()", Toast.LENGTH_LONG).show();
}

@Override
protected void onRestart() {
// TODO Auto-generated method stub
super.onRestart();
Toast.makeText(this, "onRestart()", Toast.LENGTH_LONG).show();
}

@Override
protected void onResume() {
// TODO Auto-generated method stub
super.onResume();
Toast.makeText(this, "onResume()", Toast.LENGTH_LONG).show();
}

@Override
protected void onStart() {
// TODO Auto-generated method stub
super.onStart();
Toast.makeText(this, "onStart()", Toast.LENGTH_LONG).show();
}

@Override
protected void onStop() {
// TODO Auto-generated method stub
super.onStop();
Toast.makeText(this, "onStop()", Toast.LENGTH_LONG).show();
}


}


Download the files.



Thursday, June 3, 2010

IllegalThreadStateException in LunarLander, a sample Android code from Google

I think it can be classified as a bug in Lunar Lander game, in the SDK samples folder: /samples/LunarLander/:

Start the game and exit by pressing HOME key when the space ship is falling downing, then re-enter the game, it will be a IllegalThreadStateException thrown! and the App will be terminated.



My previous exercise("Android SurfaceView", "SurfaceView in a FrameLaout inside another LinearLayout" and "SurfaceView overlap with a LinearLayout") was implemented by reference to the sample code. Such that all of them inherit the bug.

My solution is to move the code "thread = new MySurfaceThread(getHolder(), this);" from constructor of the SurfaceView to inside the surfaceCreated() method.



From the video, it can be noted that if the App exit by HOME key, then re-enter, the onCreate() method of the Activity, and also the constructor of the SurfaceView will not be called, the App continuous from the previous state; so no object of the Thread will be instanced. That's why the IllegalThreadStateException thrown in the original approach.

But...if the App exit by BACK key, then re-enter, the onCreate() method of the Activity, and the constructor of the SurfaceView will be called, and will not have this problem.

The modified version of AndroidMergeSurfaceView can be downloaded here.





Google I/O 2010 - A beginner's guide to Android

Android - Reto Meier

This session will introduce some of the basic concepts involved in Android development. Starting with an overview of the SDK APIs available to developers, we will work through some simple code examples that explore some of the more common user features including using sensors, maps, and geolocation.

Download the PDF



more from Google Code Blog: Android at Google I/O 2010

Steve Jobs Talks About Google and Android




Steve Jobs Talks About Google and Android at the ATD 8 (All things Digital) conference.

Tuesday, June 1, 2010

Exercise of SurfaceView: SurfaceView overlap with a LinearLayout

Further work on the previous exercise "Another exercise of SurfaceView, in a FrameLaout inside another LinearLayout"; the FrameLayout composed of a SurfaceView overlap with another LinearLayout with a Spinner used to select the color of the bouncing dot in the SurfaceView.



main.xml show the details in the layout structure.

<?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">
<Button
android:id="@+id/showhide"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Toggle The Another Button Show/Hide" />
<Button
android:id="@+id/dummy"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="a Button" />
<FrameLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<com.exercise.AndroidMergeSurfaceView.MySurfaceView
android:id="@+id/myCustomSurface"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
<LinearLayout
android:orientation="vertical"
android:gravity="bottom"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<Spinner
android:id="@+id/spinner"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
</FrameLayout>
</LinearLayout>


Download the files.








It's a bug here!


Pls. refer to the article
"IllegalThreadStateException in LunarLander"
for details.


I'm Sorry about that! ()



Cannot complete the install because one or more required items could not be found

I setup a old notebook with the newest Fedora 13 recently. The first job is to install Android development environment. The Eclipse was installed using Fedora's Add/Remove Software. Because the default setting of the Eclipse setup, I face the error "Cannot complete the install because one or more required items could not be found" again. It's because the Galileo Download site is not included.

To solve it adding the following site in Eclipse Available Software Sites list.

http://download.eclipse.org/releases/galileo

Add Available Software Sites

For details to install Install Android SDK on Eclipse, in Ubuntu, refer here.