i'm new to android developing and tried to implement a simple game realized with a surface view and a canvas. I used the LunarView sample as a draft.
The idea is to have a human (movement controlled by touch event) and a zombie that follows him.
So far I have the following classes (i removed the from my point of view useless code):
1) MainActivity
public class MainActivity extends ActionBarActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
2) ZombieView with the innner class ZombieThread and Person (Person is only used to save data needly)
public class ZombieView extends SurfaceView implements SurfaceHolder.Callback {
//Thread Class that performs drawing on Canvas
class ZombieThread extends Thread {
class Person {
...
}
private final SurfaceHolder surfaceHolder;
private ZombieView zombieView;
private Context context;
private boolean running = false;
//Canvas info
private int mCanvasWidth = 1;
private int mCanvasHeight = 1;
private Bitmap mBackgroundImage;
private final Drawable mHumanImage;
private final Drawable mZombieImage;
private Resources res;
//Game Variables
double hSpeed = 1;
double zSpeed = 0.8;
Person zombie;
Person human;
public ZombieThread(SurfaceHolder holder, ZombieView zombie, Context context) {
this.surfaceHolder = holder;
this.zombieView = zombie;
this.context = context;
this.zombie = new Person(100, 100, hSpeed);
this.zombie.setRunning(true); //zombie always walks
this.human = new Person(100, 200, zSpeed);
res = context.getResources();
mHumanImage = context.getResources().getDrawable(R.drawable.green_circle);
mZombieImage = context.getResources().getDrawable(R.drawable.red_circle);
mBackgroundImage = BitmapFactory.decodeResource(res,R.drawable.background);
}
#Override
public void run() {
Canvas c = null;
while (this.running) {
try {
c = surfaceHolder.lockCanvas(null);
synchronized (surfaceHolder) {
play();
onDraw(c);
checkCollision();
}
} finally {
if (c != null) {
surfaceHolder.unlockCanvasAndPost(c);
}
}
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void checkCollision() {
//do something
}
public void play() {
...
}
public void onDraw(Canvas c) {
//Do your drawing here...
// c.drawColor(Color.rgb(10, 0, 0));
c.drawBitmap(mBackgroundImage, 0, 0, null);
c.save();
//draw human and zombie
int width = mHumanImage.getIntrinsicWidth();
int height = mHumanImage.getIntrinsicHeight();
mHumanImage.setBounds((int) human.getX() - (width / 2), (int) human.getY() - (height / 2),
(int) human.getX() + (width / 2), (int) human.getY() + (height / 2));
mHumanImage.draw(c);
width = mZombieImage.getIntrinsicWidth();
height = mZombieImage.getIntrinsicHeight();
mZombieImage.setBounds((int) zombie.getX() - (width / 2), (int) zombie.getY() - (height / 2),
(int) zombie.getX() + (width / 2), (int) zombie.getY() + (height / 2));
mZombieImage.draw(c);
c.restore();
}
public boolean doTouchEvent(MotionEvent e) {
//do something
}
}
//ZombieView variables
private ZombieThread thread;
private final SurfaceHolder surfaceHolder;
private Context context;
public ZombieView(Context context) {
super(context);
surfaceHolder = getHolder();
surfaceHolder.addCallback(this);
this.context = context;
setFocusable(true); //makes sure we receive key-events
}
public ZombieView(Context context, AttributeSet attrs) {
super(context, attrs);
surfaceHolder = getHolder();
surfaceHolder.addCallback(this);
this.context = context;
setFocusable(true); //makes sure we receive key-events
}
public ZombieView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
surfaceHolder = getHolder();
surfaceHolder.addCallback(this);
this.context = context;
setFocusable(true); //makes sure we receive key-events
}
public ZombieThread getThread() {
return thread;
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
thread = new ZombieThread(surfaceHolder, this, context);
thread.setRunning(true);
thread.start();
}
}
XML:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
android:paddingBottom="#dimen/activity_vertical_margin" tools:context=".MainActivity">
<com.example.zombierun.zombierun.ZombieView android:background="#ccc"
android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="20dp"
android:paddingBottom="40dp"
android:focusable="true"
android:visibility="visible"
android:id="#+id/customView" />
So my question now is the following:
When I start the App I see the Activity and a grey area which should be the canvas / surfaceview. Unfortunately that's all. But when I minimize the app by pushing the home button I see the canvas (with the images and background color) for the time the app minimizes. There are no errors displayed in logcat.
I assume that the canvas somehow stays in the background or that there is an overlay between different vies? Is this possible?
Hope someone can help me displaying the canvas correctly.
Thanks in advance
You cannot use android:background="x" when working with a SurfaceView. You have to manually draw the color, in this case canvas.drawColor(Color.GREY) in your onDraw(Canvas c) call. Otherwise what you are doing is having the background color you set draw over your the surface of your surface view, rendering it an opaque grey. When onPause is called, the view is torn down and you momentarily are able to see the surface underneath the view's window overlay which painted #ccc.
Also: why are you sleeping the thread for 1/10 of a second every frame? You should remove this line. Or if you are trying to target a frame rate: realize 1000 ms / 100 ms = 10 frames, that is, you are drawing at ten fps.
Related
I am new to android programming. I am trying to write some really simple apps around basic graphics and input. The app I am working with now uses SurfaceView and draws onto that with a Canvas. It just draws a circle in the middle of the screen, and changes its color every second. Here's my SurfaceView:
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback {
public class DrawerThread extends Thread {
private SurfaceHolder mSurfaceHolder;
private long mTimeStep;
private long mTime;
private long mPhysicsTime;
private boolean mRun;
private int mCanvasWidth;
private int mCanvasHeight;
private int colorIter;
private int colors[][] = {
{255, 255, 0, 0},
{255, 0, 255, 0}
};
Paint mPaint;
public DrawerThread(SurfaceHolder surfaceHolder) {
mSurfaceHolder = surfaceHolder;
mTimeStep = 1000;
mRun = false;
mCanvasWidth = 1;
mCanvasHeight = 1;
colorIter = 0;
mPaint = new Paint();
mPaint.setAntiAlias(true);
}
public void setRunning(boolean value) {
if(value == true && mRun != true) {
mTime = System.currentTimeMillis();
mPhysicsTime = 0;
}
mRun = value;
}
public void setSurfaceSize(int width, int height) {
synchronized (mSurfaceHolder) {
mCanvasWidth = width;
mCanvasHeight = height;
}
}
private void doPhysics(long time) {
long deltaTime = time - mTime;
mPhysicsTime += deltaTime;
mTime = time;
int steps = (int) (mPhysicsTime / mTimeStep);
mPhysicsTime -= steps * mTimeStep;
colorIter = (colorIter + steps) % colors.length;
}
private void doDraw(Canvas canvas) {
canvas.drawColor(Color.argb(255, 0, 0, 0));
mPaint.setARGB(colors[colorIter][0], colors[colorIter][1], colors[colorIter][2], colors[colorIter][3]);
canvas.drawCircle(mCanvasWidth/2, mCanvasHeight/2, 100, mPaint);
}
#Override
public void run() {
while(mRun) {
Canvas canvas = null;
try {
synchronized (mSurfaceHolder) {
canvas = mSurfaceHolder.lockCanvas();
doPhysics(System.currentTimeMillis());
doDraw(canvas);
}
} finally {
if(canvas != null) {
mSurfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
}
private DrawerThread mDrawerThread;
public MySurfaceView(Context context) {
super(context);
SurfaceHolder surfaceHolder = getHolder();
surfaceHolder.addCallback(this);
mDrawerThread = new DrawerThread(surfaceHolder);
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
mDrawerThread.setRunning(true);
mDrawerThread.start();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
mDrawerThread.setSurfaceSize(width, height);
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
mDrawerThread.setRunning(false);
while(retry) {
try{
mDrawerThread.join();
retry = false;
} catch(InterruptedException e) {}
}
}
}
I wrote this code based on the Lunar Landing example provided here:
https://gitorious.org/replicant/development/source/3eda8fc3859c243df4a1f892a11e2da84b49cb94:samples/LunarLander/src/com/example/android/lunarlander/LunarView.java#L8
I tried to make it simpler than that so I omitted what I could. So the app is working until I change from profile view to landscape OR use the Recents button. Both of these actions crash my app on the canvas.drawColor(...) line in function doDraw(), nullpointer exception. How can I this problem?
Second question: I tried to limit my drawing thread by putting a Thread.sleep(<>) in my run() in the drawing thread. However, I noticed that while this thread is sleeping, the screen doesn't flip when I turn my phone sideways(go from profile to landscape). Why does the sleeping drawing thread block surface change?
Third question: why do I need the while loop in surfaceDestroyed()? Isn't mDrawerThread.join() a blocking function?
I know basic Java but I have almost 0 knowledge of android programming, so I would appreciate any constructive remarks.
(IDE: Android Studio)
good day... im a new programmer in android... and actually its my first time doing so...
but i have basic knowledge about java...
so here goes.. my game will have an icon that can be controlled by a joystick... and another icon that goes up and down the screen(non controllable) i want the speed to increase a little..... i have started the code but dont know where and how to start the collision.. another one is that the icon that is controllabe always passes the screen and pop out the opposite direction...
public class GameSurface1 extends SurfaceView implements SurfaceHolder.Callback {
private Context _context;
private GameThread1 _thread;
private GameControl _controls;
private GameJoystick1 _joystick;
private int y = 0;
private int xSpeed = 1;
private Bitmap _pointer, bmp;
public GameSurface1(Context context) {
super(context);
// TODO Auto-generated constructor stub
_context = context;
init();
}
private void init(){
//initialize our screen holder
SurfaceHolder holder = getHolder();
holder.addCallback( this);
//initialize our game engine
//initialize our Thread class. A call will be made to start it later
_thread = new GameThread1(holder, _context, new Handler(),this);
setFocusable(true);
_joystick = new GameJoystick1(getContext().getResources());
_pointer = (Bitmap)BitmapFactory.decodeResource(getResources(), R.drawable.icon1);
bmp = (Bitmap)BitmapFactory.decodeResource(getResources(), R.drawable.bad1);
//contols
_controls = new GameControl();
setOnTouchListener(_controls);
}
public void doDraw(Canvas canvas){
if (y == getHeight() - bmp.getHeight()) {
xSpeed = -1;
}
if (y == 0) {
xSpeed = 1;
}
y = y + xSpeed;
//update the pointer
_controls.update(null);
//draw the pointer
canvas.drawBitmap(_pointer, _controls._pointerPosition.x, _controls._pointerPosition.y, null);
//draw the joystick background
canvas.drawBitmap(_joystick.get_joystickBg(), 15,215, null);
//draw the dragable joystick
canvas.drawBitmap(_joystick.get_joystick(),_controls._touchingPoint.x - 26, _controls._touchingPoint.y - 26, null);
canvas.drawBitmap(bmp, 280, y, null);
}
//these methods are overridden from the SurfaceView super class. They are automatically called
//when a SurfaceView is created, resumed or suspended.
#Override
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {}
private boolean retry;
#Override
public void surfaceDestroyed(SurfaceHolder arg0) {
retry = true;
//code to end gameloop
_thread.state = GameThread1.STOPED;
while (retry) {
try {
//code to kill Thread
_thread.join();
retry = false;
} catch (InterruptedException e) {
}
}
}
#Override
public void surfaceCreated(SurfaceHolder arg0) {
if(_thread.state==GameThread1.PAUSED){
//When game is opened again in the Android OS
_thread = new GameThread1(getHolder(), _context, new Handler(),this);
_thread.start();
}else{
//creating the game Thread for the first time
_thread.start();
}
}
ill appreciate all the help you can give...
thank you
I've made a custom pie chart view that I want to animate starting when the pie chart is visible. Currently what I have is the pie chart animating but by the time you can actually see it on the screen the animation is half over. This is what I have:
public class SinglePieChart extends SurfaceView implements SurfaceHolder.Callback {
// Chart setting variables
private int emptyCircleCol, strokeColor, number, total;
// Paint for drawing custom view
private Paint circlePaint;
private RectF rect;
private Context context;
private AnimThread animThread;
private SurfaceHolder holder;
// animation variables
private float speed;
private float current = 0.0f;
private boolean percentsCalculated = false;
private float degree;
private int viewWidth, viewHeight;
public SinglePieChart(Context ctx, AttributeSet attrs) {
super(ctx, attrs);
context = ctx;
// Paint object for drawing in doDraw
circlePaint = new Paint();
circlePaint.setStyle(Style.STROKE);
circlePaint.setStrokeWidth(3);
circlePaint.setAntiAlias(true);
circlePaint.setDither(true);
rect = new RectF();
//get the attributes specified in attrs.xml using the name we included
TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.DashboardChartSmall, 0, 0);
try {
//get the colors specified using the names in attrs.xml
emptyCircleCol = a.getColor(R.styleable.DashboardChartSmall_smCircleColor, 0xFF65676E); // light gray is default
strokeColor = a.getColor(R.styleable.DashboardChartSmall_smColor, 0xFF39B54A); // green is default
// Default number values
total = a.getInteger(R.styleable.DashboardChartSmall_smTotal, 1);
number = a.getInteger(R.styleable.DashboardChartSmall_smNumber, 0);
} finally {
a.recycle();
}
this.setZOrderOnTop(true);
holder = getHolder();
holder.setFormat(PixelFormat.TRANSPARENT);
holder.addCallback(this);
}
protected void calculateValues() {
degree = 360 * number / total;
percentsCalculated = true;
speed = 10 * number / total;
viewWidth = this.getMeasuredWidth();
viewHeight = this.getMeasuredHeight();
float top, left, bottom, right;
if (viewWidth < viewHeight) {
left = 4;
right = viewWidth - 4;
top = ((viewHeight - viewWidth) / 2) + 4;
bottom = viewHeight - top;
} else {
top = 4;
bottom = viewHeight - 4;
left = ((viewWidth - viewHeight) / 2) + 4;
right = viewWidth - left;
}
rect.set(left, top, right, bottom);
}
protected void doDraw(Canvas canvas) {
if (total == 0) {
// Number values are not ready
animThread.setRunning(false);
return;
}
if (!percentsCalculated) {
calculateValues();
}
// set the paint color using the circle color specified
float last = current;
float start = -90;
circlePaint.setColor(strokeColor);
canvas.drawArc(rect, start, (last > degree) ? degree : last, false, circlePaint);
start += (last > number) ? number : last;
last = (last < number) ? 0 : last - number;
circlePaint.setColor(emptyCircleCol);
if (current > 360) {
current = 360;
}
canvas.drawArc(rect, start, 360 - current, false, circlePaint);
current += speed;
if (last > 0 || number == 0) {
// we're done
animThread.setRunning(false);
}
}
public void setNumbers(int num, int tot) {
number = num;
total = tot;
invalidate();
requestLayout();
}
public void setColor(int col) {
strokeColor = col;
}
public void redraw() {
calculateValues();
animThread.setRunning(true);
invalidate();
requestLayout();
}
#Override
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
}
#Override
public void surfaceCreated(SurfaceHolder arg0) {
animThread = new AnimThread(holder, context, this);
animThread.setRunning(true);
animThread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder arg0) {
animThread.setRunning(false);
boolean retry = true;
while(retry) {
try {
animThread.join();
retry = false;
} catch(Exception e) {
Log.v("Exception Occured", e.getMessage());
}
}
}
public class AnimThread extends Thread {
boolean mRun;
Canvas mcanvas;
SurfaceHolder surfaceHolder;
Context context;
SinglePieChart msurfacePanel;
public AnimThread(SurfaceHolder sholder, Context ctx, SinglePieChart spanel) {
surfaceHolder = sholder;
context = ctx;
mRun = false;
msurfacePanel = spanel;
}
void setRunning(boolean bRun) {
mRun = bRun;
}
#Override
public void run() {
super.run();
while (mRun) {
mcanvas = surfaceHolder.lockCanvas();
if (mcanvas != null) {
msurfacePanel.doDraw(mcanvas);
surfaceHolder.unlockCanvasAndPost(mcanvas);
}
}
}
}
}
Also if you see any programming errors, memory leaks, poor performing code, please let me know. I'm new to Android.
Here is the layout that uses the SinglePieChart class:
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<com.davidscoville.vokab.views.elements.SinglePieChart
android:id="#+id/smallPieChart"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView
android:id="#+id/dashSmNumber"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:textSize="25sp"
android:textColor="#FFFFFF" />
</RelativeLayout>
<TextView
android:id="#+id/dashSmLabel"
android:layout_width="match_parent"
android:layout_height="20dp"
android:textSize="14sp"
android:gravity="center"
android:textColor="#FFFFFF" />
</merge>
Alright I'm going with the my pie chart won't automatically animate and it will have a new function that the Activity will trigger to start animating once it's ready. I wish there was an easier way...
Alternatively you can use the animation framework(or nine old androids if you want to support older apis). This will allow you to animate properties on your view, in your case the start and current variables.
I'd set this to happen during onAttachedToWindow.
Note if you aren't doing a lot of other things in this pie chart a surfaceview might be overkill for your needs.
I am experimenting with drawing on a canvas using a thread to create a simple game engine but I'm having some weird issues I cannot explain.
The purpose of this "game" is to draw a circle every second on the canvas.
This works, but not the way I want it to work, it seems the app is switching between two canvasses and adding a circle to each canvas so you get a switch between two canvasses every second with the same number of circles but in a different place on the canvas.
I don't know what I'm doing wrong, but I'm not that familiar with Treadding, has it something to do with how many cores my android device has or something like that?
My code is shown below, so I just use a launchthread which uses a layoutfile that links to the animationthread which starts a thread and draws a circle on the canvas every second.
(You can ignore the touchevent, it isn't uses yet).
The project exists out of a main launchthread:
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}
which uses this layout file:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<com.androidtesting.AnimationView
android:id="#+id/aview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
</FrameLayout>
And my Surfaceview class with an inner Thread class:
class AnimationView extends SurfaceView implements SurfaceHolder.Callback {
private boolean touched = false;
private float touched_x, touched_y = 0;
private Paint paint;
private Canvas c;
private Random random;
private AnimationThread thread;
public AnimationView(Context context, AttributeSet attrs) {
super(context, attrs);
SurfaceHolder holder = getHolder();
holder.addCallback(this);
thread = new AnimationThread(holder);
}
class AnimationThread extends Thread {
private boolean mRun;
private SurfaceHolder mSurfaceHolder;
public AnimationThread(SurfaceHolder surfaceHolder) {
mSurfaceHolder = surfaceHolder;
paint = new Paint();
paint.setARGB(255,255,255,255);
paint.setTextSize(32);
}
#Override
public void run() {
while (mRun) {
c = null;
try {
c = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder) {
doDraw(c);
sleep(1000);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
if (c != null) {
mSurfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
private void doDraw(Canvas canvas) {
//clear the canvas
//canvas.drawColor(Color.BLACK);
random = new Random();
int w = canvas.getWidth();
int h = canvas.getHeight();
int x = random.nextInt(w-50);
int y = random.nextInt(h-50);
int r = random.nextInt(255);
int g = random.nextInt(255);
int b = random.nextInt(255);
int size = 20;
canvas.drawCircle(x,y,size,paint);
canvas.restore();
}
public void setRunning(boolean b) {
mRun = b;
}
}
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
#Override
public boolean onTouchEvent(MotionEvent event) {
touched_x = event.getX();
touched_y = event.getY();
int action = event.getAction();
switch(action){
case MotionEvent.ACTION_DOWN:
touched = true;
break;
case MotionEvent.ACTION_MOVE:
touched = true;
break;
default:
touched = false;
break;
}
return true;
}
public void surfaceCreated(SurfaceHolder holder) {
thread.setRunning(true);
thread.start();
}
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
thread.setRunning(false);
while (retry) {
try {
thread.join();
retry = false;
} catch (InterruptedException e) {
}
}
}
}
it seems the app is switching between two canvasses
Yes, this is how it works. It is called double buffering and you need to redraw all the frame each time:
The content of the Surface is never preserved between unlockCanvas() and lockCanvas(), for this reason, every pixel within the Surface area must be written.
So you need this line canvas.drawColor(Color.BLACK) to be uncommented in your code.
And you shouldn't call Thread.sleep(1000) while canvas is locked, it will cause starvation issue.
It sounds like you have this working, but I did just notice a small error that I should point out.
You called canvas.restore() without calling canvas.save() beforehand.
From the Android developer reference for Canvas: "It is an error to call restore() more times than save() was called."
I don't see any reason for you to call canvas.save() in your case, therefore you should remove the call to canvas.restore().
I'm trying to work with 3 SurfaceViews on one screen, one on top half (BoardView), one on bottom half (StatusView), and the last one as an extra layer above the top half (TileView) (see main.xml).
I created a class MySurfaceView, which is extended by BoardView, StatusView and TileView.
I've got multiple problems with this.
Let me first give the code.
main.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#color/main_background">
<com.niek.test.BoardView
android:id="#+id/boardview"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
<FrameLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_below="#+id/boardview">
<com.niek.test.StatusView
android:id="#+id/statusview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#F0931E"
android:layout_below="#+id/boardview" />
<com.niek.test.TileView
android:id="#+id/tileview"
android:layout_width="180dip"
android:layout_height="60dip"
android:layout_gravity="bottom"/>
</FrameLayout>
</RelativeLayout>
MainActivity.java:
package com.niek.test;
public class MainActivity extends Activity {
private Board board;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
board = new Board();
BoardView boardView = (BoardView) findViewById(R.id.boardview);
boardView.setBoard(board);
StatusView statusView = (StatusView) findViewById(R.id.statusview);
statusView.setBoard(board);
}
}
MySurfaceView.java
package com.niek.test;
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback {
protected DrawThread drawThread;
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
getHolder().addCallback(this);
setFocusable(true);
drawThread = new DrawThread(getHolder());
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// TODO Auto-generated method stub
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
// TODO Auto-generated method stub
drawThread.setRunning(true);
drawThread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
// we have to tell thread to shut down & wait for it to finish, or else
// it might touch the Surface after we return and explode
boolean retry = true;
drawThread.setRunning(false);
while (retry) {
try {
drawThread.join();
retry = false;
} catch (InterruptedException e) {
// we will try it again and again...
}
}
}
protected class DrawThread extends Thread {
private SurfaceHolder surfaceHolder;
private boolean isRunning;
public DrawThread(SurfaceHolder surfaceHolder) {
this.surfaceHolder = surfaceHolder;
isRunning = false;
}
public void setRunning(boolean run) {
isRunning = run;
}
public void run() {
Canvas c;
while (isRunning) {
try {
Thread.sleep(100);
} catch (Exception e) {
// TODO: handle exception
}
c = null;
try {
c = surfaceHolder.lockCanvas(null);
synchronized (surfaceHolder) {
onDraw(c);
postInvalidate();
}
} finally {
// do this in a finally so that if an exception is thrown
// during the above, we don't leave the Surface in an
// inconsistent state
if (c != null) {
surfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
}
These three classes extend MySurfaceView:
BoardView.java
package com.niek.test;
public class BoardView extends MySurfaceView {
private int squareSize, marginX, marginY;
private Board board;
Paint boardBorder;
public BoardView(Context context, AttributeSet attrs) {
super(context, attrs);
board = null;
}
public void setBoard(Board board) {
this.board = board;
}
private void init(SurfaceHolder holder) {
Canvas canvas = null;
try {
canvas = holder.lockCanvas();
/* Initialize the board */
squareSize = canvas.getWidth() / Board.GRIDSIZE;
/* Size the view */
LayoutParams lp = getLayoutParams();
lp.height = (squareSize * Board.GRIDSIZE) + 4;
setLayoutParams(lp);
/* Place the board neatly in the center */
marginX = (canvas.getWidth() - (squareSize * Board.GRIDSIZE)) / 2;
marginY = 1;
} finally {
holder.unlockCanvasAndPost(canvas);
}
boardBorder = new Paint();
boardBorder.setColor(Color.RED);
boardBorder.setStyle(Style.STROKE);
}
#Override
public void onDraw(Canvas canvas) {
drawBoard(board, canvas);
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
init(holder);
super.surfaceCreated(holder);
}
private void drawBoard(Board board, Canvas canvas) {
synchronized (board) {
if (board != null) {
for (Square[] ys : board.getSquares()) {
for (Square xs : ys) {
xs.onDraw(canvas, squareSize, squareSize, marginX, marginY);
}
}
}
canvas.drawRect(marginX - 1, marginY - 1, marginX + squareSize * Board.GRIDSIZE + 1, marginY + squareSize * Board.GRIDSIZE + 1, boardBorder);
}
}
}
StatusView.java
package com.niek.test;
public class StatusView extends MySurfaceView {
private Board board;
private Paint textPaint;
public StatusView(Context context, AttributeSet attrs) {
super(context, attrs);
board = null;
textPaint = new Paint();
textPaint.setColor(Color.BLACK);
textPaint.setTextSize(20);
textPaint.setTypeface(Typeface.DEFAULT_BOLD);
}
public void setBoard(Board board) {
this.board = board;
}
int tmp=0;
#Override
public void onDraw(Canvas c) {
if (board != null) {
c.drawText(tmp+"", 10, 20, textPaint);
tmp++;
System.out.println(tmp);
}
}
}
TileView.java
package com.niek.test;
public class TileView extends MySurfaceView {
public TileView(Context context, AttributeSet attrs) {
super(context, attrs);
System.out.println(0);
}
int tmp =0;
#Override
public void onDraw(Canvas c) {
System.out.println(2);
Paint p= new Paint();
p.setColor(Color.RED);
c.drawColor(Color.RED);
c.drawText(tmp+"",10,10,p);
tmp++;
}
}
Now what are my problems?
First off, as you can see in MySurfaceView I've got this:
try {
c = surfaceHolder.lockCanvas(null);
synchronized (surfaceHolder) {
onDraw(c);
postInvalidate();
}
}
When I only use onDraw(c), only the BoardView gets drawn, the StatusView doesn't get drawn, but the tmp increments in the onDraw of StatusView are being executed.
When I only use postInvalidate(), same story, but only StatusView gets drawn, BoardView doesn't.
So that's why I use both methods, and both Views get drawn.
Then there's TileView, the System.out(2) is being shown in logcat, but the view doesn't get drawn. It is a black square instead of the red square I ask it to be in the onDraw method.
When I turn the screen off and then on again, the TileView does get drawn, and the tmp increments are shown.
Who can help me?
For clarity, I've created this based on this tutorial.
You can have multiple SurfaceViewsin one layout. The "Multi-surface test" activity in Grafika has three.
The first post cited in #nonsleepr's answer was followed up 9 months later with this post by the same author, which mentioned the existence of SurfaceView#setZOrderMediaOverlay().
The key thing to understand is that SurfaceView is not a regular view. When your app comes to the foreground it gets a surface to draw on. Everything in your app's UI is rendered onto the app's surface by the app, and then that surface is composited with other surfaces (like the status bar and navigation bar) by the system compositor. When you create a SurfaceView, it's actually creating an entirely new surface that is composited by the system, not by your app.
You can control the Z-ordering (i.e. "depth") of the SurfaceView surface very loosely. There are four positions, from top to bottom:
SurfaceView + ZOrderOnTop
(app UI goes here)
SurfaceView + ZOrderMediaOverlay
SurfaceView (default)
If you have two SurfaceViews at the same depth, and they overlap, the results are undefined -- one will "win", but you can't control which.
The system compositor on modern devices is very efficient when you have N surfaces. At N+1 surfaces you hit a performance cliff. So while you can have three SurfaceViews, you're generally better off keeping the number down. The value of N varies from device to device.
Update: if you really want to understand how SurfaceView works, see the Android System-Level Graphics doc.
It looks like you are not supposed to create multiple SurfaceViews on one Layout.
According to this two posts written by Android framework engineer:
The way surface view is implemented is that a separate surface is created and Z-ordered behind its containing window, and transparent pixels drawn into the rectangle where the SurfaceView is so you can see the surface behind. We never intended to allow for multiple surface view.
and
you should effectively think of SurfaceView as an overlay you embed inside your window,
giving you an area in which you can directly draw independently of the normal view update system.
So, what you can do, is use one SurfaceView to draw all the graphics you want.
It sounds like the SurfaceViews are being drawn, but transparency is not enabled for whichever one is on top. In your MySurfaceView class in the surfaceCreated() method, make sure you are calling holder.setFormat(PixelFormat.TRANSPARENT);