Showing posts with label graphics. Show all posts
Showing posts with label graphics. 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.

Saturday, August 7, 2010

Its all about the layers..

For an Android project I am currently working on, I wanted to take an image of the last state of my game board, and overlay another image on it. While this seems like a simple thing it wasn't terribly simple to figure out. (Though, it is surprisingly simple to implement.)

To make this easy, lets assume you have two bitmaps. We will call them bitmap1, and bitmap2. Also to make it easy, we will assume they are stored as drawables in your Eclipse project.

It is also important to note that bitmap2 must have some transparent areas on it. If bitmap2 is completely opaque, you won't see any of bitmap1, and this code will be a waste of effort. (Since you could just draw bitmap2 by itself.)

The first thing we want to do is get our drawables stored in Bitmap objects. These bitmap objects should be called bitmap1 and bitmap2. This is easily done with the following code :


BitmapDrawable draw1 = (BitmapDrawable)res.getDrawable(R.drawable.bitmap1);
Bitmap bitmap1 = draw1.getBitmap();

BitmapDrawable draw2 = (BitmapDrawable)res.getDrawable(R.drawable.bitmap2);
Bitmap bitmap2 = draw2.getBitmap();



That was easy. Now, we have to figure out how to combine the two. This is where things start to get a little less obvious. After Googling around, I found several different methods that could do it. But, the method I am going to use below seemed the most straightforward to me.

First, the code, then the explanation :



Bitmap layers = Bitmap.createBitmap(bitmap1.getWidth(), bitmap1.getHeight(), bitmap1.getConfig());
Canvas c = new Canvas(layers);
c.drawBitmap(bitmap1, new Matrix(), null);
c.drawBitmap(bitmap2, new Matrix(), null);



Believe it or not, you now have a merged bitmap in memory. (But how? It looks like all I did was draw on a canvas!?)

Simple, if you look at the Canvas class, you will find that one of the constructors allows you to pass in a Bitmap object. When you do this, anything that is drawn on the canvas is put in to the Bitmap that was passed in. So, in our example above, if we wanted to see the result, we could draw the Bitmap object 'layers' to the screen, and we would have the image of bitmap2 laid over bitmap1.

Or, to look at it another way. If you were working with a SurfaceView, and wanted to draw the bitmaps there, you would create a Canvas object, draw the two bitmaps in order, and pass the Canvas to the SurfaceView where it would be drawn. The code here does the same basic thing. However, instead of having the code drawn immediately on the SurfaceView, you have the output sent to a Bitmap.

But, lets look at some assumptions in this code real quick. When we create the 'layers' Bitmap object, we have to tell the Bitmap object a little bit about the bitmap we want to create. Specifically, we have to tell it what the size of the bitmap is, and what the configuration of the bitmap is. The size is fairly obvious. But, in the sample code above, we are assuming that either the two bitmaps are the same size, or that bitmap2 is smaller than bitmap1. If bitmap1 is actually smaller than bitmap2, then the final bitmap will be the same size as bitmap1, and bitmap2 will be clipped to fit. But what about the configuration? What is that? The configuration is a value that tells the bitmap what kind of data it will be storing. Or, another way to look at it, how big each pixel will be in memory. Without getting in to a bunch of detail, under normal circumstances, you will want to use ARGB_8888.

Why the heck would you ever do this? Well, if you needed a composite image that would be used repeatedly in your program you could either try to draw it in real-time every time it was needed, or draw it once and just reuse the drawing. If the image is more than a few pixels, recreating the overlay each time could be an expensive operation. Caching the result and using that instead can save you a fair bit of processing time that you can use for other cool effects in your program!

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.