Thursday, February 10, 2011

Accelerating Android XML Layouts with AutoHotkey - NOW WITH VIDEO!


I know that building your XML layouts can be frustrating at times. This won't magically build your UI's for you, but it will speed you on your way.
AutoHotkey
If you haven't heard of AutoHotkey, go and learn about it: http://www.autohotkey.com/. It's one of the most useful tools out there. The basic concept is that you can program your own hotkeys for use all over windows. You can manipulate text, replace text, call other macros and scripts, build custom gui's and much more. This is only available for Windows, sorry everyone else. You will need to download and install this application first.
The Script
AutoHotkey is designed to allow you to write your own custom scripts and will auto-load any scripts on your machine with the .ahk extension. You can get my script here: android helper script. Once you have AutoHotkey installed, just download my script, rename it with an .ahk extension, double click on it and you'll be good to go.
How it works

Once the script is installed go into Eclipse or whatever other environment you code in and create a new xml layout file. Call it test_layout.xml or something. Once there follow these steps:
  • type: xml_heading^
  • press enter
  • type: relative_layout^
  • press enter
  • move your cursor to right after the text "
  • make a few lines inside your relative layout and indent and type: button^
  • press enter
  • type: image_view^
  • press enter
You get the idea. Here is a list of all the XML elements the script can input:
  • LinearLayout
  • RelativeLayout
  • FrameLayout
  • WebView
  • Button
  • ImageView
  • EditText
  • TextView
  • TableLayout
It can also insert some other random stuff:
  • xml heading thing at the top of an xml document(xml_heading^)
  • android namespace(android_namespace^)
  • the onCreate method in an activity. I hate typing that out every time! (on_create^)
That's it so far. For the layouts and widgets, the format for the hotkey replacements is just underscored names. If you add to the script, be kind and post it back to this thread. Thanks for reading. Enjoy


Update: I've uploaded a screen cast to YouTube. http://www.youtube.com/watch?v=c7EdFXb6-rw

Sunday, October 4, 2009

Android 1.6 "Donut" - simply amazing

Today I flashed my system with the new Android 1.6 "Donut" release and I have to say that it was well worth the 30 minutes it took. My device is a HTC Ion (the one Google gave us at I/O) and I have been relatively pleased with it in terms of features and form factor. My biggest complaint and disappointment of the Android OS is its lack of responsiveness when it starts to get a heavy workload -- particularly with regard to the virtual keyboard. When more than a few apps were loaded on the phone it was at times an absolute nightmare to write an email or perform a Google search.

I had high hopes that a lot of these performance issues would be addressed with the 1.6 release of android and after test driving it for the past hour and putting it through everything I could think of, Donut has far surpassed my highest expectations. Keyboard lag is virtually gone. The home screen is snappy and responsive even while running a browser, a game, gmail, gtalk, and many other apps. In addition to this much needed performance boost in the home screen and virtual keyboard I found that all of my other apps fired up faster. Google Sky Map was up and running in just seconds and performing better than it ever has before.

The best improvement in my opinion was not one that I expected to see. I found that the browser's rendering performance in 1.6 was in my estimation at least 50% faster than Cupcake's. Utah just got a 3G network a couple weeks ago, but web pages were still loading slow. And now I realize what the bottle neck has been all along.

There are a lot of cool new features in Donut as well. The universal search box is very useful. I can't wait to expose some data from one of my applications for it. I'll make a post later on this. The new Android Market is also a major improvement. I've found that the new user interface is more inviting and intuitive. It also tends to emphasize paid apps over free apps. I think this is an important milestone in the Android road map. I don't think the platform will really take off unless there are tons of compelling apps for the device, and those apps won't come unless the best developers have a financial reason to build apps for the platform. So the market update is huge as well.

On the downside, I have found that in one of my applications, Donut does not draw my GridViews in the same way that Cupcake did. I'll have to do some investigation on this issue, but it's probably something that I did wrong to begin with.

In conclusion, Donut is a solid release.  The performance jump over the last iteration provides some exciting foreshadowing of what is to come for Android. With a powerful open platform that is easy to develop for, multiple devices on the market all running the same software, and performance that approaches the iPhone; I think the Android could give the people at One Infinite Loop a run for their money.

Monday, September 21, 2009

Implementing a swipe gesture

One of the main difficulties in developing an application on any mobile platform is screen space. You will find that it is fairly easy to implement navigation controls in your app through the use of simple buttons. However you will also find that using one button lends itself to using more buttons and soon it is that a good portion of your precious screen real estate is taken up by navigation controls. Such was the case with my application, a scripture reader. I needed a way to navigate from one chapter to the next without having to use up screen space with a button. So I decided upon a swipe gesture as if turning a page to go from one chapter to the next. To accomplish this I use a ViewSwitcher widget along with Android built in gesture detection support.

ViewSwitcher

Below is a slideshow that shows the basic theory of operation. Just click through the slides.


The layout xml code:
<ViewFlipper xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/flipper"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ScrollView android:id="@+id/scripScroll0"
android:layout_below="@id/navBar"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#fff"
android:fadingEdgeLength="6px"
>
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView android:id="@+id/text0"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="15sp"
android:layout_marginLeft="5px"
android:layout_marginRight="5px"
android:textColor="#111"
android:typeface="serif"
/>
</RelativeLayout>
</ScrollView>
<ScrollView android:id="@+id/scripScroll1"
android:layout_below="@id/navBar"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#fff"
android:fadingEdgeLength="6px"
>
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView android:id="@+id/text1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="15sp"
android:layout_marginLeft="5px"
android:layout_marginRight="5px"
android:textColor="#111"
android:typeface="serif"
/>
</RelativeLayout>
</ScrollView>
<ScrollView android:id="@+id/scripScroll2"
android:layout_below="@id/navBar"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#fff"
android:fadingEdgeLength="6px"
>
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView android:id="@+id/text2"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="15sp"
android:layout_marginLeft="5px"
android:layout_marginRight="5px"
android:textColor="#111"
android:typeface="serif"
/>
</RelativeLayout>
</ScrollView>
</ViewFlipper> 

 
The ViewFlipper contains 3 views that each contain a scrollView widget and inside of that is a RelativeLayout with a TextView
inside of that. In your code you will have to capture the swipe gesture and have it trigger your ViewFlipper to advance
to the next view. Keeping track of your views is a little tricky and I'll show you how I do it a little later.
 
This is how you do the gesture detection. First you need to set up some constants so that we can
find out if the touch event was a legitimate horizontal swipe gesture. So place the following in your variable declaration.

private Animation slideLeftIn;
private Animation slideLeftOut;
private Animation slideRightIn;
private Animation slideRightOut;

//swipe gesture constants
private static final int SWIPE_MIN_DISTANCE = 120;
private static final int SWIPE_MAX_OFF_PATH = 250;
private static final int SWIPE_THRESHOLD_VELOCITY = 200;
private GestureDetector gestureDetector; 

 
Next, you need to set up the animations and capture your xml layout elements in your onCreate() method.

scroller0 = (ScrollView)findViewById(R.id.scripScroll0);
scroller1 = (ScrollView)findViewById(R.id.scripScroll1);
scroller2 = (ScrollView)findViewById(R.id.scripScroll2);

flipper = (ViewFlipper)findViewById(R.id.flipper);//you will need to use the flipper object later in your SimpleOnGestureListener class to flip the views
slideLeftIn = AnimationUtils.loadAnimation(this, R.anim.slide_left_in);
slideLeftIn.setAnimationListener(new ScrollLeft());
slideLeftOut = AnimationUtils.loadAnimation(this, R.anim.slide_left_out);
slideRightIn = AnimationUtils.loadAnimation(this, R.anim.slide_right_in);
slideRightIn.setAnimationListener(new ScrollRight());
slideRightOut = AnimationUtils.loadAnimation(this, R.anim.slide_right_out);


the following sets up your event listeners on your scrollers. This also goes in your onCreate method.

gestureDetector = new GestureDetector(new MyGestureDetector());

scroller0.setOnTouchListener(new View.OnTouchListener() {

@Override
public boolean onTouch(View v, MotionEvent event) {
if (gestureDetector.onTouchEvent(event))
return true;
else
return false;
}
});
scroller1.setOnTouchListener(new View.OnTouchListener() {

@Override
public boolean onTouch(View v, MotionEvent event){
if (gestureDetector.onTouchEvent(event))
return true;
else
return false;
}
});
scroller2.setOnTouchListener(new View.OnTouchListener() {

@Override
public boolean onTouch(View v, MotionEvent event) {
if (gestureDetector.onTouchEvent(event))
return true;
else
return false;
}
});


 
Create a SimpleOnGestureListener like the following. You only need to override the onFling() method. Here we use the values declared earlier to find out if it is a left or right swipe.


Gesture Detection:
class MyGestureDetector extends SimpleOnGestureListener {
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
// int delta = 0;
if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH)
return false;
else{
try {
// right to left swipe
if(e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
if(canFlipRight()){
flipper.setInAnimation(slideLeftIn);
flipper.setOutAnimation(slideLeftOut);
flipper.showNext();
}else{
return false;
}
//left to right swipe
} else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
if(canFlipLeft()){
flipper.setInAnimation(slideRightIn);
flipper.setOutAnimation(slideRightOut);
flipper.showPrevious();
}else{
return false;
}
}
} catch (Exception e) {
// nothing
}
return true;
}
}
}


Animation listener
the following is a very important component. Without it you will certainly get it to work, but it will work very slowly and your application will probably crash. This is because the Android UI framework is single threaded. No other threads are allowed to touch UI objects. As such, any processing that you need to do as a result of a UI event must therefore be done on the UI thread if it's going to affect any UI objects. In our project this is exactly the case. When you fling left or right, your code tells the flipper to change positions. Then your program needs to load the appropriate page onto the appropriate view. This work is probably very expensive if you have any SpannableStringBuilders or any HTML parsing. This means that the event will fire and all of that processing will be done before the UI changes regardless of where you stick the code. I lost many a sleepless night to this problem, so hopefully you won't have to. Luckily for us, the UI framework offers a few multi-threaded helper functions that allow you to do work outside the main UI thread, and then merge the changes back onto the UI thread. The one that I use is .postDelayed() as seen below. I'm using this particular one because for some odd reason the regular post() method ran my code just a fraction of a second too soon. So use the postDelayed() in this case. you can find documentation on all of these methods in the View object documentation . You will notice that back in our onCreate() method, these two classes were given to the flipper. The flipper fires the onAnimationEnd and then the chapter configuration method runs to guarantee that the Animation runs first -- providing a more seamless user experience.

class ScrollRight implements AnimationListener{

@Override
public void onAnimationEnd(Animation animation) {
flipper.postDelayed(new Runnable(){
@Override
public void run(){
configureChapters(-1);
}
}, 10);
}

@Override
public void onAnimationRepeat(Animation animation) {
// TODO Auto-generated method stub

}

@Override
public void onAnimationStart(Animation animation) {
// TODO Auto-generated method stub

}

}

class ScrollLeft implements AnimationListener{

@Override
public void onAnimationEnd(Animation animation) {
flipper.postDelayed(new Runnable(){
@Override
public void run(){
configureChapters(1);
}
}, 10);
}

@Override
public void onAnimationRepeat(Animation animation) {
// TODO Auto-generated method stub

}

@Override
public void onAnimationStart(Animation animation) {
// TODO Auto-generated method stub

}

}


Maybe the most difficult part about this whole feature is keeping track of what views to load and where. The following is the method I wrote for accomplishing it. The delta value can either be 0,1,or -1 for initial, right, and left movement respectively. The onCreate() method calls this when the activity loads and passes a delta value of 0 so that the initial chapter is fetched and presented and the appropriate previous and next chapters and set up. After this initial load, the ScrollRight and ScrollLeft classes call this method exclusively. It is just a matter of managing which variable points to which textView object. you either need to reset the previous or next textViews and then reset the aliases to reflect your new position. Be careful to do all of this in the correct order though so that you don't create circular references and find some strange things happening in your program.

private void configureChapters(int delta){
if(delta == 0){//this is initial loading value only


String current = getFormattedText(chapterNum);
String previous = "";
if(canFlipLeft()){
previous = getFormattedText(chapterNum - 1);
}
String next = "";
if(canFlipRight()){
next = getFormattedText(chapterNum + 1);
}
text0.setText(current);
text1.setText(next);
text2.setText(previous);

//set aliases
currentTextView = text0;
nextTextView = text1;
previousTextView = text2;
}
if(delta == 1){

int nextChapter = chapterNum +1;

TextView currTemp = currentTextView;
TextView prevTemp = previousTextView;
TextView nextTemp = nextTextView;

chapterNum = nextChapter;
String next = "";
if(canFlipRight()){
next = getFormattedText(chapterNum+1);
}

previousTextView.setText(next);

//reset aliases
previousTextView = currTemp;
currentTextView = nextTemp;
nextTextView = prevTemp;

}
if(delta == -1){
chapterNum = chapterNum - 1;

TextView currTemp = currentTextView;
TextView prevTemp = previousTextView;
TextView nextTemp = nextTextView;

String previous = "";
if(canFlipLeft()){
previous = getFormattedText(chapterNum - 1);
}

nextTextView.setText(previous);

//reselt aliases
currentTextView = prevTemp;
nextTextView = currTemp;
previousTextView = nextTemp;
}

// Set text on nav bar
navText.setText(subdiv);
if (numChapters > 1) {
navText.append(" " + chapterNum);
}
}


And that's all I have. I tried a number of tings including rewriting the Android ScrollView so that it would support horizontal scrolling(which it currently doesn't). But in the end, this was the least complicated and it works extremely fast, just give it a shot. You'll be glad you did. If anyone comes up with a better way, please post a comment and let me know how you did it. Thank you.

Saturday, September 5, 2009

Simple home screen widget

Overview

One of the great improvements in Android 1.5 is the capability of adding custom home screen widgets to you desktop. Prior to 1.5, only the standard widgets that came with the OS were available. Android 1.5 provides a simple way to create your own home screen widgets that allow functionality of your application to bleed out into other areas of the system and provide a more integrated user experience. In this tutorial however, we are going to just get something on the home screen. Once you have that, you can go ahead and add some more functionality which will be discussed in later posts. This is intended to just get you started.

Limitations

Before we begin with this brief tutorial however, you should be aware that there are some limitations to the home screen widgets. The limitation that disappointed me the most was the slim list of allowable UI widgets to choose from in building the widgets. I had hoped to build a custom search widget that would provide quick access to some information in my application, but alas, and EditText widget is not available in custom home screen widgets. With this limitation in mind, here is a complete list of compatible UI widgets that you can use:
Also note that descendants of these classes are also not allowed.

Getting Started

There are really only a few things you have to have in order to get a widget up on the home screen.
Now keep in mind that these are the bare bones minimum  requirements just to get something on the home screen. More options and functionality will be explored later.

AppWidgetProvider
First create a class that extends AppWidgetProvider. If you are using eclipse, once you have created the class you can right click in the window and select "Override/Implement methods". Go ahead and select all of the methods from AppWidgetProvider and click OK. You should have all the method stubs that you need to add any functionality you want to your widget. The main method in this class is onUpdate(). This method gets called whenever your application issues an update to the widget based on a timing definition set in you AppWidgetProviderInfo class. For now, just leave this class as it is since we won't be defining any real functionality for the widget just yet.

AppWidgetProviderInfo
This class is to be purely defined in xml. Example code is as follows:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="318px"
    android:minHeight="72px"
    android:updatePeriodMillis="86400000"
    android:initialLayout="@layout/searchwidget_layout"
    >
</appwidget-provider>

As you can see the provider info class sets up the container size and also defines the update interval for the widget. the value displayed is the millis for a whole day. Put this file into your res/xml folder.

XML Layout
This file simply defines the UI elements you are going to use and their organization. If you are not familiar with setting up layouts in XML, I would suggest you see the android developers site and learn how. The following example code uses are relative layout with 2 child, a text view and a button. Yes, unimaginative I know.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_height="wrap_content"
 android:layout_width="fill_parent"
 android:background="@drawable/widget_frame"
 android:focusable="true"
 android:textColor="#000">
 <TextView 
  android:layout_width="wrap_content"
  android:layout_height="fill_parent"
  android:layout_alignParentLeft="true"
  android:text="Click the button"
  android:layout_centerVertical="true"
  android:gravity="center"
  android:layout_marginLeft="20px"
  />
 
 <Button
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:layout_alignParentRight="true"
  android:text="Click Me!"
  android:layout_centerVertical="true"
  android:layout_marginRight="20px"/>

</RelativeLayout>

Manifest Entry
In order for the home screen, which is the WidgetHost, to see your application's widget, you must define it in your Android Manifest file. Place the following node inside your Application tag.
<application
     ......
      .......>
<receiver android:name=".MyWidgetProviderClass" label="Name of my widget">
 <intent-filter>
  <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
 </intent-filter>
 <meta-data android:name="android.appwidget.provider"
      android:resource="@xml/widget_provider_info_file" />
</receiver>
 .....
 .....
 </application>

Wrap up
That's it. Run your application and long press on the home screen. Click on widgets and you should see your application's widget in the list. Click on it and admire it sitting there on your home screen doing . . . nothing. Yes the widget is worthless, and next time we'll actually give it some functionality, but for now, you should have a foundational knowledge of how to actually get your widget on the screen.