Search This Blog

Friday, October 29, 2010

Experience - Android Drag and Drop List

I am continually surprised by the fact that the things I need for my application do not exist in android. This time I needed a drag and drop list. I have been unsuccessful in finding a simple, working drag and drop list that I can extend. I have seen numerous places that reference android's music app as a starting place. The main classes of importance from the music app were TouchInterceptor and TrackBrowserActivity. Many of the examples that I have seen struggled to use it well and most ended up having serious problems as a result. I would rate the TouchInterceptor code with a rating of "F" because it is not simple and it is not easily extendable. Check out this article for a way to rate code. It left a good impression on me.

This experience is about my efforts to build a simplified drag and drop list. I hope time will prove that it is easy to extend as well. The first task was to get a copy of TouchInterceptor and TrackBrowserActivity running in a simplified project. This took time because of all the dependencies and interconnections between these two classes and the rest of the app. As I look back on the matter the only real benefit this provided me with was a better understanding of how not to make TouchInterceptor work. After I had a runnable project, I attempted to modify the drag animation from an expanding/contracting items on drag to one that rearranged items in place. I was almost finished with this change when I ran into a technical issue that I didn't want to spend time to fix. I then discovered that the Launcher app provided a better drag and drop example because it was simplified compared to some of the complex interactions in the music app. Using both the music app and the launcher app as a reference I wrote a simplified drag and drop project. You can find the project here.

I wanted to provide methods that allowed modifying/customizing the view of the item being dragged, but not fall into the same complex interactions of the music app. I chose to use the DragListener with the intended purpose of handling view changes. In the sample project, I made the item's view invisible to show where the drag view came from, and I changed the background color to show that a drag view is different from the rest of the item views. It is important to revert any changes to the item view because it will be recycled back into the list view for item views.

This project code is simply to share a simple working drag and drop list since I was unable to find one elsewhere on the web. If you add any extensions to this simple project I would like to hear about it in the comments.

Wednesday, October 13, 2010

Experience - Customizing Android's Tab Indicator

I was displeased by the initial look-n-feel of the Tabs on my TabActivity. When I ran the app on the emulator the non-selected tabs had a nice gray background and the selected tab was white. No issue here, but when I ran the exact same app on my device all the backgrounds were white as well as the text on each tab. This was a horrible combination it was nearly impossible to see any part of the Tabs. A description that I like of this problem can be found here.

Below I have listed several ways that I tried to fix the problem as well as the final solution that I decided on.

1) Customize theme & xml for Tab.
I started off looking for a way to customize the xml on TabWidget, TabHost, and even TabHost.Tabspec. I found out that TabHost didn't have much. TabWidget looked promising as I could change the look-n-feel of the divider and the bottom strip. Two nice features but what about the tab indicator? I then looked at TabHost.TabSpec this seemed to be the most promising but I found no xml attributes to change. Still wanting to change the look with xml I started digging into the theme looking for a way to change the look of a tab indicator. I was operating under the impression that this was just a theme problem and I thought a simple customization to the theme and all TabWidgets would display properly. The best resource I found can be found here, however this did not solve my problem.

2) Accessing TabWidget's children in code.
The second approach I took after a quick Google search was to try Advance Tab's idea. I was able to successfully change the tab indicators in code using a line similar to the following:

final TextView tv = (TextView) tabWidget.getChildAt(i).findViewById(android.R.id.title);

tv.setTextColor(this.getResources().getColorStateList(R.color.text_tab_indicator));


Changing the look through code is less than ideal and calling TabWidget's getChildAt() method was definitely not how the designers intended to change the tab indicator. I was very happy to have made some progress. However, I still wanted to be able to change the look in xml, so this approach didn't really solve my problem either.

3) Building a view for Tab's Indicator.
After several attempts and time spent on things that produced less than desirable results, I realized I had overlooked something simple. The "ah ha" moment (as I like to call it) came when I took another look at the TabHost.TabSpec's setIndicator() method. Since API level 4 we have been able to create a view and set it as the tab indicator. I felt like this way was simplistic and easy.

Below is a quick overview of how I configured my tab indicator view.
I created a tabindicator.xml like the following:

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:background="@drawable/tabindicatorselector"
android:layout_width="fill_parent" android:layout_height="fill_parent">

<TextView android:id="@+id/title"
android:layout_below="@id/title" android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true" android:text="Title">
</TextView>
<ImageView android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"/>
</RelativeLayout>


The code in my TabActivity looks like this:

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

View indicator1 = getLayoutInflater().inflate(R.layout.tabindicator, null);
TextView title1 = (TextView)indicator1.findViewById(R.id.title);
title1.setText(R.string.Tab1Title);

TabHost tabHost = getTabHost();
tabHost.addTab(tabHost.newTabSpec("tab1")
.setIndicator(indicator1)
.setContent(new Intent(this, TabGroup1Activity.class)));
}


I have left out some details, but hopefully this gives the basic idea on how I built my own tab indicator view. Good luck!

Wednesday, September 29, 2010

Experience - Multiple Android Activities in a TabActivity

The problem that I had was when I started a new activity it covered up the Tabs on my TabActivity. I could put a single Activity into a Tab, but adding a second Activity resulted in losing view of the Tabs. Then I learned about ActivityGroups which have a LocalActivityManager. I realized it was possible to have multiple Activities within an ActivityGroup after all this is what a TabHost does. I then realized if I set the content of each Tab to an ActivityGroup I could start multiple Activities in each Tab. I developed the below ActivityGroup.

import java.util.ArrayList;

import android.app.Activity;
import android.app.ActivityGroup;
import android.app.LocalActivityManager;

import android.content.Intent;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.Window;

/**
* The purpose of this Activity is to manage the activities in a tab.
* Note: Child Activities can handle Key Presses before they are seen here.
* @author Eric Harlow
*/
public class TabGroupActivity extends ActivityGroup {

private ArrayList<String> mIdList;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (mIdList == null) mIdList = new ArrayList<String>();
}

/**
* This is called when a child activity of this one calls its finish method.
* This implementation calls {@link LocalActivityManager#destroyActivity} on the child activity
* and starts the previous activity.
* If the last child activity just called finish(),this activity (the parent),
* calls finish to finish the entire group.
*/
@Override
public void finishFromChild(Activity child) {
LocalActivityManager manager = getLocalActivityManager();
int index = mIdList.size()-1;

if (index < 1) {
finish();
return;
}

manager.destroyActivity(mIdList.get(index), true);
mIdList.remove(index);
index--;
String lastId = mIdList.get(index);
Intent lastIntent = manager.getActivity(lastId).getIntent();
Window newWindow = manager.startActivity(lastId, lastIntent);
setContentView(newWindow.getDecorView());
}

/**
* Starts an Activity as a child Activity to this.
* @param Id Unique identifier of the activity to be started.
* @param intent The Intent describing the activity to be started.
* @throws android.content.ActivityNotFoundException.
*/
public void startChildActivity(String Id, Intent intent) {
Window window = getLocalActivityManager().startActivity(Id,intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP));
if (window != null) {
mIdList.add(Id);
setContentView(window.getDecorView());
}
}

/**
* The primary purpose is to prevent systems before android.os.Build.VERSION_CODES.ECLAIR
* from calling their default KeyEvent.KEYCODE_BACK during onKeyDown.
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
//preventing default implementation previous to android.os.Build.VERSION_CODES.ECLAIR
return true;
}
return super.onKeyDown(keyCode, event);
}

/**
* Overrides the default implementation for KeyEvent.KEYCODE_BACK
* so that all systems call onBackPressed().
*/
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
onBackPressed();
return true;
}
return super.onKeyUp(keyCode, event);
}

/**
* If a Child Activity handles KeyEvent.KEYCODE_BACK.
* Simply override and add this method.
*/
@Override
public void onBackPressed () {
int length = mIdList.size();
if ( length > 1) {
Activity current = getLocalActivityManager().getActivity(mIdList.get(length-1));
current.finish();
}
}
}


I got caught up trying to figure out if and how LocalActivityManager handled stepping back up the view hierarchy. After viewing the note on the LocalActivityManager.dispatchCreate(bundle state) which states "Note: This does not change the current running activity, or start whatever activity was previously running when the state was saved. That is up to the client to do, in whatever way it thinks is best."
I decided to add back tracking to the ActivityGroup. I attempted to make it behave the same as Activities outside an ActivityGroup by only needing to call finish() on an Activity and the previous Activity will be restarted or recreated.
I have also added the ability for the ActivityGroup to manage the back button being pressed. Android OS pre 2.0 handles things differently than the newer interface. A reference to this can be found here under Key Events Executed on Key-Up. I have tested the code on a device with OS 1.6 and the emulator with OS 2.1. Note: Child Activities like a ListActivity may handle the back button just override the appropriate methods for your OS version, and call onBackPressed() for the (parent) ActivityGroup. An example would look like this for OS 2.1.

@Override
public void onBackPressed() {
TabGroupActivity parentActivity = (TabGroupActivity)getParent();
parentActivity.onBackPressed();
}


The following section explains how I used this ActivityGroup. The first thing I did was subclass this ActivityGroup like this.

import android.content.Intent;
import android.os.Bundle;

public class TabGroup1Activity extends TabGroupActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
startChildActivity("OptionsActivity", new Intent(this,OptionsActivity.class));
}
}


I added this TabGroup1Activity into my TabActivity like this.

TabHost tabHost = getTabHost();

tabHost.addTab(tabHost.newTabSpec("tab1")
.setIndicator("MESSAGES")
.setContent(new Intent(this, TabGroup1Activity.class)));


Then any Activity that you want to start in the ActivityGroup can be done in a way similar to this.

Intent frequentMessages = new Intent(getParent(), FrequentMessageActivity.class);
TabGroupActivity parentActivity = (TabGroupActivity)getParent();
parentActivity.startChildActivity("FrequentMessageActivity", frequentMessages);


And that is how I added multiple Activities with a back tracking ability to a Tab of a TabActivity. Remember to always add Activities to your AndroidManifest.xml. A sample project for this article can be found here.

Monday, August 23, 2010

Experience - Configuring Android ListView

I have configured my ListView differently than the standard Android example. This article is what I did and how it was accomplished. The biggest difference between the android example and mine is I didn't want a full screen ListView. This impacted both the code and the visuals of the ListView. In the standard example it is suggested that a ListActivity is created. I on the other hand already have an Activity that defines a screen and is bound to a XML layout file. In the layout file I added a ListView element with the id MessageList. I then reference this ListView in my Activity and add the needed components to it. Below are excerpts from the Activity.

public class MessageActivity extends Activity {

private ListView mMessageList;
//Additional properties ...

public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.messagepage);

//Bind XML objects
mMessageList = (ListView)findViewById(R.id.MessageList);
//...

//Additional setup and configuation
//...

//MessageList is a dummy content string array
//located in res/values/strings.xml
String[] mMessages =  

getResources().getStringArray(R.array.MessageList);

//Simple way for example
mMessageList.setAdapter(new ArrayAdapter(this,
android.R.layout.simple_list_item_1, mMessages));

// mMessageList.setAdapter(
// new EfficientAdapter(this, android.R.layout.simple_list_item_1,
// android.R.id.text1, mMessages));
}
}

 

At this point I have a ListView defined on a page with other content. The ListView is connected to an ArrayAdapter which supplies it with dummy content through a string array in res/values/strings.xml. Next I wanted to add a border to the ListView to help define its size and make it clear where the edges of the ListView were. I was surprised that it was so difficult to figure out. There is no border property in ListView which was disappointing. I tried various options and found the solution I like the best is putting the ListView in a FrameLayout. I set the Background color of the FrameLayout to the color I want for my border and set the padding to the desired border width. The results look something like this.

Thursday, August 19, 2010

Experience - Android Orientation Change

I must admit I fell into a bit of a trap on this experience. As a result I will present failed attempts I had at detecting an orientation change and then give the solution I ended up with at the end of the article. My goal was to change the layout of some widgets when in portrait mode. I had designed the screen for landscape mode. Then when I rotated it on the emulator I was unsatisfied with the results. I had set up my layout so that only two LinearLayouts needed to have their orientation set from horizontal to verticle. I then set out to discover the best way to detect a change in orientation.

The first approach I tried was to use an android.view.OrientationEventListener. I used some slightly modified code from this article to test it out. I think that article is great, but I was less than satisfied with the results from OrientationEventListener.  First the emulator appeared to take longer when switching orientation. But the biggest problem was the onOrientationChanged() method was never called! Very disappointing! I assume I forgot to set something up properly, but it is hard without finding some proper documentation. I have also seen various blog articles stating problems with it since 2007 and it still doesn't seem to function as one would think here in 2010. :(

My second approach was to follow this form post. I thought this was a cleaver little trick that just might do the job for me. I got it to work by adding android:configChanges="orientation" to my AndroidManifest.xml file and

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
}

to my Activity. I had to add this method without the @Override compile and run and then add back the @Override. Eclipse was throwing an error at me. Within the newConfig parameter was an orientation property that I used for changing the layout logic. I really liked this option but the downside was onConfigurationChanged() was only being called when changing from landscape to portrait which is all I really needed but I didn't like that it only worked half the time it was suppose to.

The third approach I looked at was from this article. I read the article which I liked and saw the comments about SensorManager's API has been changed three times, and could use at least one more update in my opinion. I thought that I would be able to make it work and was able to until I tried to register the SensorEventListener with the SensorManager. At this point I considered it another failure and maybe a lost cause.

After these three failed attempts I changed my thinking. I had been trying to find a clean and efficient way of detecting an orientation change and changing a couple properties on my layouts. A new thought that I had was what if I just create an entirely new layout to be shown in portrait mode.

These are the steps I used.
I opened up my xml layout that I wanted to change. At the top were some options that look like this.




I changed the config drop down to portrait, and then hit the create button on the right hand side.
The only option I selected was portrait.



and with that I had a new xml that I was editing and setting up for portrait mode. When I run the app in the emulator it chooses the right layout for portrait and the other layout for landscape.
Done deal.
P.S.
    Which turns out to be a good thing because the portrait layout is going to be arranged completely different now. :)

Monday, August 2, 2010

Experience - Android UI Tab View

I have started an Android app at work. I was diving in head first and started with hello views tutorial. The tab view was interesting, but after copy-n-pasting some code and running the app I was less than satisfied with the results. First I was unable to view the XML layout of the Tabs and second there was a null pointer exception. I have seen a couple other notes and questions about this type of problem.

TabWidget causing null pointer
Why do I get a null pointer exception from TabWidget?

This article explains some of the things I did to improve on this issue.

Using XML to layout and define the TabHost and TabWidget I found no way around the null pointer exception. I was able to instead define these in code. The activity that runs on startup is called SampleTab. Below is its source.

package app.tabsample;

import android.app.TabActivity;
import android.os.Bundle;
import android.widget.TabHost;
import android.content.Intent;

public class TabSample extends TabActivity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

TabHost tabHost = getTabHost();

tabHost.addTab(tabHost.newTabSpec("tab1")
.setIndicator("MSG")
.setContent(new Intent(this, SelectActivity.class)));

tabHost.addTab(tabHost.newTabSpec("tab2")
.setIndicator("ARROW")
.setContent(new Intent(this, ArrowsActivity.class)));

tabHost.addTab(tabHost.newTabSpec("tab3")
.setIndicator("OPT")
.setContent(new Intent(this, OptionsActivity.class)));

tabHost.addTab(tabHost.newTabSpec("tab4")
.setIndicator("EDIT")
.setContent(new Intent(this, EditActivity.class)));

tabHost.setCurrentTab(1);
}
}


For the tab indicator, your choices are: 1) set a label 2) set a label and an icon. Note: api level 4 also adds 3) set a view. For the tab content, your choices are: 1) the id of a View 2) a TabContentFactory that creates the View content. 3) an Intent that launches an android.app.Activity. For simplicity I chose to set a label for the tab indicator and an Intent that launches an activity for the tab content. Note that the Activities need to be listed in the AndroidManifest.xml. Now looking at the first tab which calls the SelectActivity.

package app.tabsample;

import android.app.Activity;
import android.os.Bundle;

/**
* @author Administrator
*
*/
public class SelectActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.tabstest1);
}
}


This activity simply sets its content to the XML file tabstest1.xml. There we have it a simple and easy to use way to create a TabHost and TabWidget without having a null pointer exception and still being able to see the XML tab content graphically in Eclipse. Just for reference my tabstest1.xml contains the following.

<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" />
<spinner android:id="@+id/Spinner01" android:layout_width="wrap_content">
android:layout_height="wrap_content" android:entries="@array/Spinner_Content">
</spinner>
</textview>
</linearlayout>


Below is a screen shot of my tabstest1 with the layout tab.

Tuesday, July 13, 2010

Experience - NETMF contribution

I had the opportunity earlier this year to investigate the .NET Micro Framework. I really like the idea and think it is a great project. Through the netmf Product Change Proposal  forum I responded to a request for a Hashtable.  I later received an email from the Developer Manager of the .NET Micro Framework team.  It was a welcome on board message that introduced me to three other guys on the MF team. It also outlined the objective and project flow.


The objective:
The target for this effort is to come up with an associative container for the .NET Micro Framework to contribute in source code format to the MF object model for the v4.1 release.

Some ground rules:
You, as the contributor, are entitled to the final technical choices for the component you are developing.  You should work as independently as possible, especially in the coding and testing phase.  The .NET MF team will decide whether to integrate your component code component or not based on its quality.  ... can assist you with guidance: they are your point of reference for any suggestion you may need about best practices, design choices, and so on, please be considerate of their time and use their suggestions and time wisely.  I will assist both you and the core tech team members for anything you might need.  This is our first contribution work from a community member (!), therefore I will ask you to be patient with us if all resources are not aligned correctly from the start.   

with that I started writing a design document and figuring out my plan of attack. I started working on this project a couple hours once a week for the first month then stepped it up the second month. After two months total I submitted my finished code and it was incorporated into the NETMF! An article was posted on the netmf blog here. I feel very privileged to have worked on a little project like building a Hashtable that is now part of something bigger the .NET Micro Framework. Thanks NET Micro Framework Core Tech team! Not only did I have a great deal of fun doing this little project, at the time I had read an article that expresses some concerns I have being a solo, entry-level developer recently out of college. If anyone is in a similar situation I would highly suggest finding a mini project like building a hashtable to increase your experience.