Showing posts with label Java. Show all posts
Showing posts with label Java. Show all posts

Monday, April 11, 2011

Color changing rounded rectangles and Android

I recently came across an interesting problem. I needed to have a rounded rectangle as the background of one of my widgets, but I needed to allow the border color of the rounded rectangle to be set by a configuration setting.

Before the color changing requirement showed up, I was using a 9-patch to create my rounded rectangle. It was simple, and worked quite well. So, after some digging I came to the conclusion that I would need to change the colors in the 9-patch image in order to achieve my goals. I ended up burning a lot of time trying to figure out how to do this. (It isn't as simple as changing the colors in a normal .png file, unfortunately.)

While digging, I came across this post that hinted that I might not need the 9-patch to do what I wanted. But, the documentation for the shape XML is pretty much non-existent. Which made things hard.

A little more digging found an article that linked to this page. This provided me the attributes for the XML that allow me to do what I needed to do. So, I set out to give it a shot. I started with a rounded rectangle with a white background and a black line around the outside. The XML looked like this :

<!--?xml version="1.0" encoding="utf-8"?-->
<shape android="http://schemas.android.com/apk/res/android" shape="rectangle">
<solid color="#ffffffff">
<stroke width="2dip" color="#ff000000">
<corners radius="75dip">
</corners></stroke></solid></shape>


When I created the XML file in eclipse, there wasn't an option to create a shape XML file. So, I picked one of the other types, and just deleted what was automatically created, and replaced it with the code above. However, I also needed to move the XML file so that it was in the drawable directory. (If you are like I was, you are thinking, "But an XML file isn't a drawable!" But, you'll just have to trust me.)

This provided me the basic rounded rectangle that I needed. But, changing colors on-the-fly was still an issue. After a bit of digging, I found that using this XML to create a drawable would give me everything I needed to get the job done.

I decided to set the background for the layout that contained everything I was going to use. (Which is why I am using a LinearLayout instead of some other widget. But, any widget that allows a background to be set should work.)

For a test, I changed my onCreate() method to include this code :

workareaLayout = (LinearLayout)findViewById(R.id.rightColumnInner);

GradientDrawable sd = (GradientDrawable)this.getResources().getDrawable(R.drawable.roundrect);
sd.setColor(0xff00ff00);
sd.setStroke(10, 0xffff0000);
workareaLayout.setBackgroundDrawable(sd);

This created a nice rounded rectangle that is suitable for Christmas. (It has a green background with a large red border.)

The sd.setColor() call sets the background color for the rectangle. The color is an ARGB value, so we start with 0xff since we don't want any transparency. The 00ff00 portion indicates that we want to be the brightest green we can have.

The sd.setStroke() call specifies the width of the border line, along with the color. For this example, the width is 10, and the color is bright red, with no transparency. The documentation isn't clear what a width of "10" means. (Is it pixels? Or one of the other measures commonly used in Android development?) But, it appears that it is probably pixels.

And with that, you should have the ability to create all kinds of crazy rounded rectangles. However, keep in mind that this same method should work for other types of shapes that can be created with the shape XML.

Monday, May 31, 2010

Basic graphics with Android

I have been working on a small game, and come across a real lack of useful information on how to develop a non-real time game. (i.e. A game that is entirely event driven.)

I started with the usual route, attempting to use existing UI objects to create the look that I wanted. However, I was unable to get the look and behavior that I wanted. So, I looked closer at the Lunar Lander sample in the Android SDK. The problem is that the Lunar Lander sample was perfect if you wanted to develop a real-time game. But, I didn't.

So, what to do? After much Googling, I found suggestions that what I really wanted to do was create a specialized view that would contain my game board. Doing this turned out to be easier than I thought it would. Using the Eclipse wizards, I created a new class that would be my game board view. The class was derived from the View class. The simplistic class would look something like this :


package com.example.simplegame;

public class BoardView extends View {
public BoardView(Context context) {
super(context);

}

public BoardView(Context context, AttributeSet attrib)
{
super(context, attrib);

}
}


Now, let me save you some time and pain. When I started this effort, I used "super(context)" inside the second constructor. Doing that will prevent you from using findViewById() to get a pointer to the view in your form.

Okay, so now we have a basic class set up. But, it doesn't actually do anything useful. What we really wanted to do is have a small blue box drawn on our view. So how do we do that?

Simple. We add an onDraw() method to our class. onDraw() takes a single parameter, with is of type Canvas. The canvas is what we will draw on, and what will ultimately be shown on the screen. As previously stated, we want to draw a simple blue square on the screen, so our onDraw method should look something like this :


@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

Paint p = new Paint();
p.setAntiAlias(true);
p.setARGB(255, 0, 0, 255);

canvas.drawRect(0, 0, 10, 10, p);
}


Before we move on to talk about how to use our new View in a form, I want to point out a little bit more information on how onDraw() works. According to the Android documentation, you should assume that the canvas passed in to onDraw() is blank. (That is, you should never rely on anything you have previously drawn to still exist on the canvas.) Because of this, you will need to redraw your entire view each time onDraw() is called. In our sample, we draw the blue square each time onDraw() is called.

Now that we have a new view class set up, we need to figure out how to put it in a form, and use it. To make things easy, I first created a simple form with a generic View that fills the entire screen. It looks something like this :


<?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="fill_parent">
<View android:id="@+id/View01" android:layout_width="fill_parent" android:layout_height="fill_parent"></View>
</LinearLayout>


Now that we have our layout, we need to modify it to include our custom view. To do this, we change the <View> tags to be <com.example.simplegame.BoardView>. If you then take this class, and this form and wrap it in some boiler plate code that would display the form, you will get a form with a small blue square in the top left corner of the screen.

If you do some experimenting, you will find that you get the blue square on the screen even if you don't establish a pointer to the BoardView object in the form. While it may seem strange at first, if you think about it you will realize that all of the objects on a form exist as soon as the form is inflated. The findViewById() call is simply giving you a pointer to the object that already exists in memory. Therefore, the onDraw() method is called, even if your main program doesn't have a pointer to the view.

Sunday, August 30, 2009

Orientation of an Android screen...

This issue has been vexing me for a few weeks now. I have found several different e-mail threads, and blog posts that talk about this exact issue, but none of them really seemed to have the answer I was looking for. So, hopefully this helps someone else.

I have been working on a small Android app that, in a nutshell, goes out to the internet, grabs an XML file, parses it, and sticks it in the local database on the phone. Currently, I am writing ~937 records in to the database after reading this XML file. Needless to say, this takes some time. As a result, I throw the work in to a thread, and then put a dialog on the screen indicating that I am working on it.

The code I am using to spin up the thread is here (modified to make it short and sweet) :

new Thread(new Runnable(){
public void run(){
try {
dp = new XMLDataParser(getURLPath());

dp.parse();
System.out.println("Update db.");
updateDatabase();

} catch (Exception e) {
System.out.println("Exception : " + e.getMessage());

// Display an error.
}
}
}).start();


Hopefully, this code is pretty straight forward. It spins up a new thread, initializes the XMLDataParser class, while passing in the URL that I am getting the data from. If you were to add this code to a project, you would find that a thread was spun off, and was doing its thing. (Though, you wouldn't really be able to tell that it was actually doing anything. We'll get to that in a second.)

You could just throw up a progress dialog with a spinner before you start the thread, and then attempt to close the dialog when the thread is done. In my adventures, I attempted this, and actually managed to make it work! There are even supported ways to make the dialog go away when the thread is done. However if you do this, you are going to end up in one of two "difficult" situations :

  1. When the screen orientation changes, the thread will finish running, but your progress dialog will go away.
  2. When the screen orientation changes, the thread will try to access something in the Activity that was destroyed by the OS, and you will crash.
While option #1 would work, it probably isn't really what you would want. What you REALLY want is for the progress dialog to continue to display and update.

So, how the heck do we make that happen?

Well, we know that when the screen orientation changes the current Views and Activities are destroyed, and new ones are created. A little bit of messing around will also show us that the thread we started continues to run even after the Views and Activities are destroyed. So, "all" we need to do is figure out how to make it so that the thread can communicate with the newly created Views and Activities.

So, lets break this down in to two parts.

  1. Making something survive the screen rotation.
  2. Having that something update our newly created Activity and Views.

How to survive a rotation

If you have done much development, you may think the magic way to deal with this is to stick something in your bundle that will be passed to the newly created Activity. While it is possible that you might be able to make that work, we want to have an entire object survive the restart. To do that, we need to add onRetainNonConfigurationInstance() to our activity class. When the screen is rotated, this method is called, and allows us to return a class (derived from Object) that can be retrieved from the new Activity. Generally, you want this method to do as little as possible, since it will be called during the screen rotation. Here is a bit of code from my application showing the call :

 @Override
public Object onRetainNonConfigurationInstance() {
return myGUIUpdateHandler;
}


All we are doing is returning the object that we want to have access to when our current activity is destroyed, and the new one created. Once the new Activity is created, we can get the pointer to the object back by calling getLastNonConfigurationInstance(). Once we have done that, we will again have access to the object that we were attempting to save.

Which brings us to :

Having something update our newly created Activity and Views

Now, for the fun part. We can pass an object between the two Activities, so now we can just pass the ProgressDialog object we were using, call .show(), and everything just works, right?

No, not really. The ProgressDialog will be bound to our old View that was part of the Activity that was destroyed. So, attempting to work with it would end up crashing our application.

Instead, we want to create a handler that will allow us to pass messages between threads. In the Handler class, we want to override the handleMessage() method so that when a message comes in, we can do something with it. Here, again, is a sample from my code :


public class ThreadEvents extends Handler {

// Set up a random unique ID for message handler
public static final int UPDATEDATA = 12346;

// @Override
public void handleMessage(Message msg) {
switch (msg.what) {

case UPDATEDATA:
System.out.println("Thread sent us an update data event.");
break;

default:
break;
}

super.handleMessage(msg);
}
}

In this handler class, when the UPDATEDATA case is called, we want to update our UI thread. This could be something such as updating the status bar in our program.

The last piece of the puzzle is how to actually send events to this handle so that we can process them. The first thing that needs to be done before a thread can send a message through a handle is the handle class needs to be created prior to the thread being started. By doing that, the child thread will also have a pointer to the handler class. Then, any time the thread wants to send something to the parent thread, it only has to do something like this :

message = new Message();
message.what = ThreadEvents.UPDATEDATA;
myGUIUpdateHandler.sendMessage(message);


The final trick to making the ProgressDialog survive a screen rotation is to create a new instance of the dialog when an activity is started, and there is an object available from getLastNonConfigurationInstance(). Then, to update that new ProgressDialog instance as events come in from the child thread.


Final thoughts :

Obviously, I have not provided enough sample code here to do a full implementation. That never was the goal, so please don't post a follow-up asking for it. It is my experience that people that have to work a little to obtain knowledge keep it longer. My goal was to point you in the right direction so you can put the remaining pieces in place.

I also suspect that it might be "better" to use an AsyncTask to achieve the same ultimate result. However, this is the first method I discovered to achieve my goal. In the future, I intend to try doing the same thing with an AsyncTask, and will post another entry when I get around to that. In the mean time, this method seems to work well as I have been able to rotate the screen back and forth several times during a long operation without crashing my application.