URL:http://www.droidnova.com/2d-tutorial-series-part-v,848.html
This tutorial series is build against Android 2.1 and should also work on newer versions. This version is still supported because of the amount of devices that still run this version. There is an earlier, but a bit outdated version of this part.
You are new to this series? Please start with the first part.
The fifth part shows you how to animate the bitmaps. That means you touch the screen and from there on the icons move around the screen. The direction of the movement and the speed itself will be random. The animation will be constant and independent of the frames per seconds (FPS). The FPS will be displayed on the left top corner of the screen so that everybody can see the changes in the performance over time.
First of all we take a look at our Element class which represents each icon we draw on the screen.
1
2
3
4
5
6
7
8
9
10
11 | private int mSpeedX;
private int mSpeedY;
public Element(Resources res, int x, int y) {
Random rand = new Random();
mBitmap = BitmapFactory.decodeResource(res, R.drawable.icon);
mX = x - mBitmap.getWidth() / 2;
mY = y - mBitmap.getHeight() / 2;
mSpeedX = rand.nextInt(7) - 3;
mSpeedY = rand.nextInt(7) - 3;
} |
This code shows the modified constructor, where we randomly set a speed on both axis. These values define how fast our element will move over the screen and in which direction.
The next method is
animate() to animate the element. In our case it just changes the position of the element by the given speed and the elapsed time since the last call.
1
2
3
4
5 | public void animate(long elapsedTime) {
mX += mSpeedX * (elapsedTime / 20f);
mY += mSpeedY * (elapsedTime / 20f);
checkBorders();
} |
As you might see, we call the method
checkBorders() right after changing the position.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | private void checkBorders() {
if (mX <= 0) {
mSpeedX = -mSpeedX;
mX = 0;
} else if (mX + mBitmap.getWidth() >= Panel.mWidth) {
mSpeedX = -mSpeedX;
mX = Panel.mWidth - mBitmap.getWidth();
}
if (mY <= 0) {
mY = 0;
mSpeedY = -mSpeedY;
}
if (mY + mBitmap.getHeight() >= Panel.mHeight) {
mSpeedY = -mSpeedY;
mY = Panel.mHeight - mBitmap.getHeight();
}
} |
This method simply checks if the element is inside the displayed area. If it isn’t the case, it will change the speed for this axis and set the position to the farthest still visible point on the border. That prevents the elements to move out of sight.
The next class we take a look at is our Panel. Lets start with a slight change of the
surfaceChanged() method.
1
2
3
4
5
6
7
8 | public static float mWidth;
public static float mHeight;
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
mWidth = width;
mHeight = height;
} |
As you can see, we set the variables
mWidth and
mHeight which are used in the already mentioned method
checkBorders(). Its important as this defines the dimension of our
SurfaceView.
The next method
doDraw() changed a bit, too.
1
2
3
4
5
6
7
8
9 | public void doDraw(long elapsed, Canvas canvas) {
canvas.drawColor(Color.BLACK);
synchronized (mElements) {
for (Element element : mElements) {
element.doDraw(canvas);
}
}
canvas.drawText("FPS: " + Math.round(1000f / elapsed) + " Elements: " + mElementNumber, 10, 10, mPaint);
} |
It needs now two parameter instead of only the canvas. The first parameter is the elapsed time, the second the canvas we used to draw on. The elapsed time is used to calculate the FPS which is drawn with the very last statement.
The
onTouchEvent() just changed slightly, because it now updates the variable
mElementNumber so that it doesn’t need to be called on every frame generation in the
doDraw() method. Remember: every time you call
size() in a loop, god kills a kitten! (and
doDraw() is called in a loop!)
1
2
3
4
5
6
7
8
9
10 | private int mElementNumber = 0;
@Override
public boolean onTouchEvent(MotionEvent event) {
synchronized (mElements) {
mElements.add(new Element(getResources(), (int) event.getX(), (int) event.getY()));
mElementNumber = mElements.size();
}
return super.onTouchEvent(event);
} |
The method
animate() is called on every loop of the thread, to this method simply delegates the call to each element and call their
animate().
1
2
3
4
5
6
7 | public void animate(long elapsedTime) {
synchronized (mElements) {
for (Element element : mElements) {
element.animate(elapsedTime);
}
}
} |
The last part is the thread itself where we calculate the elapsed time, call the
animate() and the
doDraw().
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | private long mStartTime;
private long mElapsed;
@Override
public void run() {
Canvas canvas = null;
mStartTime = System.currentTimeMillis();
while (mRun) {
canvas = mHolder.lockCanvas();
if (canvas != null) {
mPanel.animate(mElapsed);
mPanel.doDraw(mElapsed, canvas);
mElapsed = System.currentTimeMillis() - mStartTime;
mHolder.unlockCanvasAndPost(canvas);
}
mStartTime = System.currentTimeMillis();
}
} |
If you run the code now, you should be able to touch the screen and a new icon should appear and move in random direction over the screen.

Source:
2d-tutorial-part5.zip
Final words:
Even with this simple code doesn’t work perfectly on all devices or android versions. Somehow there must be a bug in one of the android versions and/or settings, because on a Samsung Galaxy Tablet I got a issue, where the FPS drops to 25 as soon as I added the 26th element. With 25 displayed elements, the FPS stays at constant 60fps. It doesn’t seem to be only related to the galaxy tablet, its also happens on other devices but is still not really reproducible. Its discussed here on stackoverflow:
Weird performance issue with Galaxy Tab and here on the blog:
Performance issue on the Samsung Galaxy Tab.
No comments:
Post a Comment