Sunday, January 31, 2010

Setting width of Child View using Percentage

Category: UI
Subcategory: LinearLayout, Style
Android Platform: 1.5, Google APIs 3
Referenced Classes: android.widget.LinearLayout, android.widget.TextView

There have been cases where I needed to the set the width of a Child View using percentage, as opposed to dimension value (i.e. pixel). As an example, I wanted three TextViews of equal width to be contained in a horizontal LinearLayout, filling an entire row. Consequently, each TextView would need to have a width of 33%. In HTML, a simple way to do this would be:
code snippet
<table width="100%">
<tr>
<td width="33%">...</td>
<td width="33%">...</td>
<td width="33%">...</td>
</tr>
...
...
In Android, this can be done by using android:weightSum, android:layout_weight, and android:layout_width:
code snippet
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:orientation="horizontal"
android:weightSum="1.0">
<TextView
android:id="@+id/textbox1"
android:layout_height="wrap_content"
android:layout_weight=".33"
android:layout_width="0dip"
android:textSize="12sp" />
<TextView
android:id="@+id/textbox2"
android:layout_height="wrap_content"
android:layout_weight=".33"
android:layout_width="0dip"
android:textSize="12sp" />
<TextView
android:id="@+id/textbox3"
android:layout_height="wrap_content"
android:layout_weight=".33"
android:layout_width="0dip"
android:textSize="12sp" />
</LinearLayout>
Note, that android:layout_width must be set to 0 for android:layout_weight to be effective.

Tuesday, January 19, 2010

Centering Title of Window

Category: UI
Subcategory: WindowTitle, Style
Android Platform: 1.5, Google APIs 3
Referenced Classes: android.app.Activity, android.view.Gravity, android.view.View, android.widget.FrameLayout, android.widget.LinearLayout, android.widget.TextView

After failing to center the title of my Window (WindowTitle) using android:windowTitleStyle, as discussed in the following thread (http://groups.google.com/group/android-developers/browse_thread/thread/d39ea52ee066dfa8/fccce4564395e08b?lnk=gst&q=center+title+gravity#fccce4564395e08b), I decided to investigate further. After looking at the source code, I narrowed my search to the internal class com.android.internal.policy.impl.PhoneWindow and layout file, screen_title.xml. Due to
the attribute android:gravity being specified on the "title" TextView, it seems to override any android:gravity set on the android:windowTitleStyle:
code snippet
<TextView android:id="@android:id/title"
style="?android:attr/windowTitleStyle"
android:background="@null"
android:fadingEdge="horizontal"
android:gravity="center_vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
To get around this, I came up with the following:
code snippet
ViewGroup decorView=
(ViewGroup) activity.getWindow().getDecorView();
LinearLayout root= (LinearLayout) decorView.getChildAt(0);
FrameLayout titleContainer= (FrameLayout) root.getChildAt(0);
TextView title= (TextView) titleContainer.getChildAt(0);
title.setGravity(Gravity.CENTER);
This should be called in the Activity.onCreate(...) after setting the content view.

Note, this does not take into account future changes to the internal window layout.

Monday, January 18, 2010

Launching SMS/Messages Activity using Intent

Category: SMS
Subcategory: SMS/Messages Activity, Intent
Android Platform: 1.5, Google APIs 3
Referenced Classes: android.content.Intent

As with other Android Apps, you can launch the built-in SMS/Messages Activity using an intent, as described in the following thread (http://jtribe.blogspot.com/2008/12/sending-sms-using-android-intents.html). In order to pre-populate the recipients (or "To" text box) in this manner, set the address attribute on the calling intent:
code snippet
Intent sendIntent= new Intent(Intent.ACTION_VIEW);
sendIntent.putExtra("sms_body", "smsBody");
sendIntent.putExtra("address", "phoneNumber1;phoneNumber2;...");
sendIntent.setType("vnd.android-dir/mms-sms");
startActivity(sendIntent);
Note, multiple recipients can be passed using a semi-colon delimited list.

Wednesday, January 6, 2010

Getting Height of Status and Title Bar

Category: UI
Subcategory: Window/Layout Dimensions
Android Platform: 1.5, Google APIs 3
Referenced Classes: android.app.Activity, android.app.Dialog, android.view.View, android.view.Window
Additional Info: Vertical Orientation

In some cases, determining the height for both Status and Title Bar is necessary. Using Window.getDecorView().getWindowVisibleDisplayFrame(...), I was able to find the height for both:
code snippet
Rect rect= new Rect();
Window window= activity.getWindow();
window.getDecorView().getWindowVisibleDisplayFrame(rect);
int statusBarHeight= rect.top;
int contentViewTop=
window.findViewById(Window.ID_ANDROID_CONTENT).getTop();
int titleBarHeight= contentViewTop - statusBarHeight;
Note, that decorView must be attached to the Window (i.e. Dialog showing), for getWindowVisibleDisplayFrame(...) to return coords properly, otherwise both rect.top and rect.left will be 0. Alternatively, any attached view from within the layout should suffice. Please, see my comments.

Tuesday, January 5, 2010

Using the Date & Time Picker Dialog

Category: UI
Subcategory: Form Design
Android Platform: 1.5, Google APIs 3
Referenced Classes: android.app.DatePickerDialog, android.app.DatePickerDialog.OnDateSetListener, android.app.TimePickerDialog

I wanted a user-friendly way to specify both date and time with a minimum number of clicks. After considering suggestions I had found in the following thread (http://groups.google.com/group/android-developers/browse_thread/thread/de5f380c370e6410/a312e14b2fe8d915?lnk=gst&q=date+picker+dialog+load+time#a312e14b2fe8d915), I decided to show the DatePickerDialog and TimePickerDialog in sequence. I did this by using DatePickerDialog.OnDateSetListener:
code snippet
private DatePickerDialog datePickerDialog= ...
private TimePickerDialog timePickerDialog= ...
private Calendar currentTime= ...

public void onDateSet(DatePicker view,
int year, int monthOfYear, int dayOfMonth)
{
// dismiss DatePickerDialog, then show TimePickerDialog
this.datePickerDialog.dismiss();
this.timePickerDialog.updateTime(
this.currentTime.get(Calendar.HOUR_OF_DAY),
this.currentTime.get(Calendar.MINUTE));
this.timePickerDialog.show();
}

Sunday, January 3, 2010

Getting Relative Coordinates from MotionEvent

Category: UI
Subcategory: Window/View Coordinates (X, Y)
Android Platform: 1.5, Google APIs 3
Referenced Classes: android.app.Activity, android.view.GestureDetector, android.view.GestureDetector.SimpleOnGestureListener, android.view.MotionEvent, android.view.View, android.view.Window, com.google.android.maps.GeoPoint, com.google.android.maps.MapView
Additional Info: Vertical Orientation

I wanted to place OverlayItems where long-press MotionEvents occurred. In order to determine the position of an OverlayItem, I needed to get the relative X and Y coordinates of the corresponding MotionEvent. As mentioned in the following forum (http://stackoverflow.com/questions/1410885/how-do-i-know-if-a-motionevent-is-relative-or-absolute), I was unable to obtain the relative X and Y coordinates using the methods MotionEvent.getX() and MotionEvent.getY(), respectively. Given that my MapView filled the entire screen, I came up with the following method:
code snippet
public static float[] getRelativeCoords(Activity activity,
MotionEvent e)
{
// MapView
View contentView= activity.getWindow().
findViewById(Window.ID_ANDROID_CONTENT);
return new float[] {
e.getRawX() - contentView.getLeft(),
e.getRawY() - contentView.getTop()};
}
Using GestureDetector and GestureDetector.SimpleOnGestureListener, and calling the above getRelativeCoords(...), I was able to place an OverlayItem where every long-press MotionEvent occurred:
code snippet
final MapView mapView= ...
final Activity mapViewActivity= this;

new GestureDetector(mapViewActivity,
new SimpleOnGestureListener()
{
public void onLongPress(MotionEvent e)
{
float[] coords= getRelativeCoords(mapViewActivity, e);
GeoPoint point= mapView.getProjection().fromPixels(
(int) coords[0],
(int) coords[1]);
// create OverlayItem with point
...
...
}
});