I am currently implementing a view in Android that involves using a larger than the screen size bitmap as a background and then having drawables drawn ontop of this. This is so as to simulate a "map" that can be scrolled horizontally aswell as vertically.
Which is done by using a canvas and then drawing to this the full "map" bitmap, then putting the other images on top as an overlay and then drawing only the viewable bit of this to screen.
Overriding the touch events to redraw the screen on a scroll/fling.
I'm sure this probably has a huge ammount of overhead (by creating a canvas of the full image whilst using(drawing) only a fifth of it) and could be done in a different way as to the explained, but I was just wondering what people would do in this situation, and perhaps examples?
If you need more info just let me know,
Thanks,
Simon
I cooked up an example of how to do this using the BitmapRegionDecoder API. The example has a large (6000,4000) image of the world that the user can scroll around in full resolution. The initial tag is quite small and easy to understand.
I write this class to an project I'm developing.
public class ScrollableImage extends View {
private Bitmap bmLargeImage; // bitmap large enough to be scrolled
private Rect displayRect = null; // rect we display to
private Rect scrollRect = null; // rect we scroll over our bitmap with
private int scrollRectX = 0; // current left location of scroll rect
private int scrollRectY = 0; // current top location of scroll rect
private float scrollByX = 0; // x amount to scroll by
private float scrollByY = 0; // y amount to scroll by
private int width, height;
private Paint background;
public ScrollableImage(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ScrollableImage(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setSize(int width, int height) {
background = new Paint();
background.setColor(Color.WHITE);
this.width = width;
this.height = height;
// Destination rect for our main canvas draw. It never changes.
displayRect = new Rect(0, 0, width, height);
// Scroll rect: this will be used to 'scroll around' over the
// bitmap in memory. Initialize as above.
scrollRect = new Rect(0, 0, width, height);
// scrollRect = new Rect(0, 0, bmp.getWidth(), bmp.getHeight());
}
public void setImage(Bitmap bmp) {
if (bmLargeImage != null)
bmLargeImage.recycle();
bmLargeImage = bmp;
scrollRect = new Rect(0, 0, width, height);
scrollRectX = 0;
scrollRectY = 0;
scrollByX = 0;
scrollByY = 0;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
return true; // done with this event so consume it
}
public void notifyScroll(float distX, float distY) {
scrollByX = distX; // move update x increment
scrollByY = distY; // move update y increment
}
#Override
protected void onDraw(Canvas canvas) {
if (bmLargeImage == null)
return;
if (scrollByX != 0 || scrollByY != 0) {
// Our move updates are calculated in ACTION_MOVE in the opposite direction
// from how we want to move the scroll rect. Think of this as
// dragging to
// the left being the same as sliding the scroll rect to the right.
int newScrollRectX = scrollRectX - (int) scrollByX;
int newScrollRectY = scrollRectY - (int) scrollByY;
scrollByX = 0;
scrollByY = 0;
// Don't scroll off the left or right edges of the bitmap.
if (newScrollRectX < 0)
newScrollRectX = 0;
else if (newScrollRectX > (bmLargeImage.getWidth() - width))
newScrollRectX = (bmLargeImage.getWidth() - width);
// Don't scroll off the top or bottom edges of the bitmap.
if (newScrollRectY < 0)
newScrollRectY = 0;
else if (newScrollRectY > (bmLargeImage.getHeight() - height))
newScrollRectY = (bmLargeImage.getHeight() - height);
scrollRect.set(newScrollRectX, newScrollRectY, newScrollRectX
+ width, newScrollRectY + height);
scrollRectX = newScrollRectX;
scrollRectY = newScrollRectY;
}
canvas.drawRect(displayRect, background);
// We have our updated scroll rect coordinates, set them and draw.
canvas.drawBitmap(bmLargeImage, scrollRect, displayRect, background);
}
}
And in the gesture listener I has this implementation of onScroll
Where img is your ScrollableImage instance.
Remember to use the setImage with your large image.
Edit: Also use setSize to set the size of your display.
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
img.notifyScroll(-distanceX, -distanceY);
img.invalidate();
return true;
}
I would divide the huge image into tiles and then draw the appropriate tiles depending on which part of the image has to be shown. Pretty much what Google Maps does. You can check http://openzoom.org/. There is nothing in Android but I think you can follow the same approach.
Related
Good day. I am pretty much frustrated as what I want to achieve is the simplest thing in world but nowhere I could get anything. I just want to draw an simple HORIZONTAL line in the centre of the GL surface view (the idea is to make audio wave animation based on the amplitude of the recording) so I did achieve that simply and easy with canvas, but with GL surface view there are not even one simple example or one simple question on how to exactly draw that line on the surface of gl.
Anyway here is what I got and have no clue where I should write the draw code and what should I write to achieve above desired function.
GL Render class
public class MyGLRenderer implements GLSurfaceView.Renderer {
GL10 unused;
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
// Set the background frame color
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
}
public void onDrawFrame(GL10 unused) {
// Redraw background color
// GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
this.unused = unused;
}
public void onSurfaceChanged(GL10 unused, int width, int height) {
GLES20.glViewport(0, 0, width, height);
}
}
GL View class (this class is copy pasted from another one which extends SurfaceView so do not take a notice of the canvas and etc.They will be deleted)
public class GLView extends GLSurfaceView {
// The number of buffer frames to keep around (for a nice fade-out visualization).
private static final int HISTORY_SIZE = 1;
private MyGLRenderer mRenderer;
Handler handler = new Handler();
private boolean isFinished=true;
private Runnable runnable = new Runnable() {
#Override
public void run() {
int colorDelta = 255 / (HISTORY_SIZE + 1);
int brightness = colorDelta;
final Random rnd = new Random();
mPaint.setColor(Color.argb(brightness, rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256)));
isFinished = true;
brightness += colorDelta;
}
};
// To make quieter sounds still show up well on the display, we use +/- 8192 as the amplitude
// that reaches the top/bottom of the view instead of +/- 32767. Any samples that have
// magnitude higher than this limit will simply be clipped during drawing.
private static final float MAX_AMPLITUDE_TO_DRAW = 10000.0f;
// The queue that will hold historical audio data.
private final LinkedList<short[]> mAudioData;
private final Paint mPaint;
public GLView(Context context) {
super(context);
// Create an OpenGL ES 2.0 context
setEGLContextClientVersion(2);
mAudioData = new LinkedList<short[]>();
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.WHITE);
mPaint.setStrokeWidth(5);
mPaint.setAntiAlias(true);
mRenderer = new MyGLRenderer();
// Set the Renderer for drawing on the GLSurfaceView
setRenderer(mRenderer);
}
public GLView(Context context, AttributeSet attrs) {
super(context, attrs);
mAudioData = new LinkedList<short[]>();
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.WHITE);
mPaint.setStrokeWidth(5);
mPaint.setAntiAlias(true);
mRenderer = new MyGLRenderer();
// Set the Renderer for drawing on the GLSurfaceView
setRenderer(mRenderer);
}
/**
* Updates the waveform view with a new "frame" of samples and renders it. The new frame gets
* added to the front of the rendering queue, pushing the previous frames back, causing them to
* be faded out visually.
*
* #param buffer the most recent buffer of audio samples
*/
public synchronized void updateAudioData(short[] buffer) {
short[] newBuffer;
// We want to keep a small amount of history in the view to provide a nice fading effect.
// We use a linked list that we treat as a queue for this.
if (mAudioData.size() == HISTORY_SIZE) {
newBuffer = mAudioData.removeFirst();
System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
} else {
newBuffer = buffer.clone();
}
mAudioData.addLast(newBuffer);
final Canvas canvas = getHolder().lockCanvas();
if (canvas != null) {
drawWaveform(canvas);
getHolder().unlockCanvasAndPost(canvas);
}
}
/**
* Repaints the view's surface.
*
* #param canvas the {#link Canvas} object on which to draw
*/
private void drawWaveform(Canvas canvas) {
// Clear the screen each time because SurfaceView won't do this for us.
canvas.drawColor(Color.BLACK);
float width = getWidth();
float height = getHeight();
float centerY = height / 2;
// We draw the history from oldest to newest so that the older audio data is further back
// and darker than the most recent data.
int colorDelta = 255 / (HISTORY_SIZE + 1);
int brightness = colorDelta;
final Random rnd = new Random();
if(isFinished){
isFinished = false;
handler.postDelayed(runnable, 500);
}
for (short[] buffer : mAudioData) {
// mPaint.setColor(Color.argb(brightness, 128, 255, 192));
float lastX = -1;
float lastY = -1;
// For efficiency, we don't draw all of the samples in the buffer, but only the ones
// that align with pixel boundaries.
for (int x = 0; x < width; x++) {
int index = (int) ((x / width) * buffer.length);
short sample = buffer[index];
float y = (sample / MAX_AMPLITUDE_TO_DRAW) * centerY + centerY;
if (lastX != -1) {
canvas.drawLine(lastX, lastY, x, y, mPaint);
}
lastX = x;
lastY = y;
}
brightness += colorDelta;
}
}
}
Please...can you help me and tell me how to draw horizontal centered line on this gl surface view?
I have a problem with my implementation of a hexagonal grid based game. Because the game requires a 91-cell hex board, like my simple board below, I wanted the user to be able to scale the board with zoom/pinch, and move it around with panning. However, none of my implementations of scaling and moving allow me to simultaneously have:
1) Consistent bounds, allowing me to be able to drag and drop pieces onto the Drawables that make up each cell.
2) Consistent relative positioning, allowing the cells to remain next to each other in the same configuration below.
3) Smooth scaling and panning, allowing the game to run quickly and have a zoom/pinch experience similar to that of other apps.
Some things I've tried (All being done using an Activity -> SurfaceView -> Thread, like in the LunarLander example) :
Drawing to a bitmap, scaling the bitmap with a matrix, translating the canvas. Handles points 2 and 3, but I can't figure out how to keep the bounds of the cell consistent. A HexCell is a class that holds a Drawable of a single cell, and the two dimensional array, board, contains HexCells.
public void drawBoard(Canvas c, int posX, int posY, float scaleFactor, float pivotPointX, float pivotPointY, boolean firstDraw) {
for(int i = 0; i < board.size(); i++) {
for(int j = 0; j < board.get(i).size(); j++) {
board.get(i).get(j).draw(bitmapCanvas);
}
}
if(firstDraw) {
int width = bitmap.getWidth();
int height = bitmap.getHeight();
float scale;
if(canvasWidth < canvasHeight) {
scale = ((float) canvasWidth) / width;
}
else {
scale = ((float) canvasHeight) / height;
}
Matrix matrix = new Matrix();
// Resize the bit map
matrix.postScale(scale, scale);
// Recreate the new Bitmap
bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, false);
c.drawBitmap(bitmap, matrix, null);
}
c.save();
c.translate(posX, posY);
Matrix matrix = new Matrix();
matrix.postScale(scaleFactor, scaleFactor, pivotPointX, pivotPointY);
c.drawBitmap(bitmap, matrix, null);
c.restore();
}
Modifying the bounds of the Drawable using a Matrix. This updates the bounds for point 1, and with some tweaking I think I could get 2 and 3 but I'm unsure how to make the scaling stop being "sticky", meaning not as smooth as the first method. In addition, when scaling the cells by a large amount, some of them become different sizes and start moving out of position relative to the other cells.
public void drawBoard(Canvas c, int posX, int posY, float scaleFactor, float pivotPointX, float pivotPointY, boolean firstDraw) {
for(int i = 0; i < board.size(); i++) {
for(int j = 0; j < board.get(i).size(); j++) {
Rect bounds = board.get(i).get(j).getBounds();
RectF boundsF = new RectF(bounds.left, bounds.top, bounds.right, bounds.bottom);
matrix.mapRect(boundsF);
bounds = new Rect((int)boundsF.left, (int)boundsF.top, (int)boundsF.right, (int)boundsF.bottom);
board.get(i).get(j).setBounds(bounds);
board.get(i).get(j).draw(c);
}
}
}
Directly modifying the bounds of the Drawable. This updates the bounds for point 1, but lacks in points 2 and 3. In addition, because I'm not using the matrix postScale with pivotPoints, the cells stay centered where they are and become smaller/larger without moving to stay next to each other.
public void resize(int dx, int dy, float scaleFactor) {
xPos += dx;
yPos += dy;
width *= scaleFactor;
height *= scaleFactor;
cell.setBounds(xPos, yPos, xPos + width, yPos + height);
}
What should I do? How can I update bounds while scaling and moving so that I can eventually place pieces on the board? Should I scrap my desire for scaling and panning, and instead use a GridView or something like that to implement the board?
Edit:
Working on this some more, I've determined that option 1 is the best way to go. It is much faster, and keeps the cells in a consistent formation. I found out that if you invert the transformation applied to the canvas and apply that to the coordinates from a touch event, you can get back to the original bounds of the cells, and therefore select them appropriately.
However, I'm having trouble accurately inverting the transformation.
x /= scaleFactor;
y /= scaleFactor;
x -= posX + pivotPointX;
y -= posY + pivotPointY;
is not a working inversion of:
canvas.save();
canvas.translate(posX, posY);
canvas.scale(scaleFactor, scaleFactor, pivotPointX, pivotPointY);
Does anyone know how to invert it appropriately?
In the drawBoard() method I did:
canvas.save();
canvas.translate(posX, posY);
canvas.scale(scaleFactor, scaleFactor, pivotPointX, pivotPointY);
canvas.getMatrix(canvasMatrix); // Save the matrix that has the transformations so we can invert it
for(int i = 0; i < board.size(); i++) {
for(int j = 0; j < board.get(i).size(); j++) {
board.get(i).get(j).draw(c);
}
}
canvas.restore();
In the onTouchEvent(MotionEvent ev) method in the View I did:
float x = ev.getX();
float y = ev.getY();
float[] pts = {x, y};
Matrix canvasMatrix = boardThread.getCanvasMatrix(); // get the matrix with the transformations
Matrix invertMatrix = new Matrix();
canvasMatrix.invert(invertMatrix); // invert the matrix
invertMatrix.mapPoints(pts); // map the inversion to the points x and y
boardThread.selectHex((int)pts[0], (int)pts[1]);
This does the trick! Now if anyone has any suggestions to avoid using the deprecated canvas.getMatrix() method, that would be great :)
I've been trying to enable my application to scroll my custom view (both horizontally and vertically) to enable the user to draw on more than just the initial screen size. The application is to be used for basic house plan generation e.g. a user draws connected rectangles representing their home.
I am using a custom View which I have set up (as an inner class) inside the activity. After extensive searching and seeing the issues people are having with scroll views, this is as far as I've managed to get. Here's my XML:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<com.example.HomerProject1stDraft.DrawNewPlans.HomerView
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</com.example.HomerProject1stDraft.DrawNewPlans.HomerView>
</LinearLayout>
As you can see I'm trying to add my HomerView class which is inside the DrawNewPlans class (the activity).
Here is the onCreate() method:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.drawnew);
}
Here's the code for my custom view (bearing in mind this is contained in another class):
class HomerView extends View { // the custom View for drawing on
// set up Bitmap, canvas, path and paint
private Bitmap myBitmap; // the initial image we turn into our canvas
private Canvas myCanvas; // the canvas we are drawing on
private Rect myRect; // the mathematical path of the lines we draw
private Paint myBitmapPaint; // the paint we use to draw the bitmap
// get the width of the entire tablet screen
private int screenWidth = getContext().getResources()
.getDisplayMetrics().widthPixels;
// get the height of the entire tablet screen
private int screenHeight = (getContext().getResources()
.getDisplayMetrics().heightPixels);
private int mX, mY, iX, iY; // current x,y and in
public HomerView(Context context) { // constructor of HomerView
super(context);
System.out.println("Screen Width = "+screenWidth+" and screen height = "+screenHeight);
myBitmap = Bitmap.createBitmap(screenWidth, screenHeight,
Bitmap.Config.ARGB_8888); // set our drawable space - the bitmap which becomes the canvas we draw on
myCanvas = new Canvas(myBitmap); // set our canvas to our bitmap which we just set up
myRect = new Rect(); // make a new rect
myBitmapPaint = new Paint(Paint.DITHER_FLAG); // set dither to ON in our saved drawing - gives better color interaction
}
protected void onDraw(Canvas canvas) { // method used when we want to draw something to our canvas
super.onDraw(canvas);
if (addObjectMode == true || addApplianceMode == true) {
canvas.drawColor(Color.TRANSPARENT); // sets canvas colour
canvas.drawBitmap(myBitmap, 0, 0, myBitmapPaint); // save the canvas to bitmap - the numbers are the x, y coords we are drawing from
for (int i = 0; i < rectList.size(); i++) {
canvas.drawRect(rectList.get(i), myPaint);
}
if (addApplianceMode == true) {
canvas.drawBitmap(bmp, iX-50, iY-50, myBitmapPaint);
}
canvas.drawRect(myRect, myPaint); // draw the rectangle that the user has drawn using the paint we set up
}
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) { // if screen size changes, alter the bitmap size
super.onSizeChanged(w, h, oldw, oldh);
}
private void touch_Start(float x, float y) { // on finger touchdown
// check touch mode
iX = (int) (Math.round(x));
iY = (int) (Math.round(y));
mX = (int) (Math.round(x));
mY = (int) (Math.round(y));
if (addObjectMode == true) {
myRect.set(iX, iY, mX, mY);
} else if (addApplianceMode == true) {
// code to draw an appliance icon at mX, mY (with offset so icon is centered)
if (isLamp == true) {
Resources res = getResources();
bmp = BitmapFactory.decodeResource(res, R.drawable.lamp);
myCanvas.drawBitmap(bmp, iX - 50, iY - 50, myBitmapPaint);
} else if (isPC == true) {
Resources res = getResources();
bmp = BitmapFactory.decodeResource(res, R.drawable.pc);
myCanvas.drawBitmap(bmp, iX - 50, iY - 50, myBitmapPaint);
} else if (isKettle == true) {
Resources res = getResources();
bmp = BitmapFactory.decodeResource(res, R.drawable.kettle);
myCanvas.drawBitmap(bmp, iX - 50, iY - 50, myBitmapPaint);
} else if (isOven == true) {
Resources res = getResources();
bmp = BitmapFactory.decodeResource(res, R.drawable.oven);
myCanvas.drawBitmap(bmp, iX - 50, iY - 50, myBitmapPaint);
} else if (isTV == true) {
Resources res = getResources();
bmp = BitmapFactory.decodeResource(res, R.drawable.tv);
myCanvas.drawBitmap(bmp, iX - 50, iY - 50, myBitmapPaint);
}
}
}
private void touch_Move(float x, float y) { // on finger movement
float dX = Math.abs(x - mX); // get difference between x and my X
float dY = Math.abs(y - mY);
if (dX >= TOUCH_TOLERANCE || dY >= TOUCH_TOLERANCE) { // if coordinates are outside screen? if touching hard enough?
mX = (int) (Math.round(x));
mY = (int) (Math.round(y));
if (addObjectMode == true) {
myRect.set(iX, iY, mX, mY);
}
}
}
#SuppressWarnings("deprecation")
private void touch_Up() { // on finger release
if (addObjectMode == true) {
myRect.set(iX, iY, mX, mY);
System.out.println("MyRect coords = "+iX+","+iY+","+mX+","+mY);
rectList.add(myRect);
myCanvas.drawRect(iX, iY, mX, mY, myPaint); // if this isnt present - no lasting copy is drawn to screen. It is erased every draw.
if (eraseMode == false) {
dialogStarter();
}
} else if (addApplianceMode == true) {
showDialog(DIALOG_DEVICE_ENTRY);
}
}
public boolean onTouchEvent(MotionEvent event) { // on any touch event
if (addObjectMode == true || addApplianceMode == true) {
float x = event.getX(); // get current X
float y = event.getY(); // get current Y
switch (event.getAction()) { // what action is the user performing?
case MotionEvent.ACTION_DOWN: // if user is touching down
touch_Start(x, y);
invalidate();
break;
case MotionEvent.ACTION_MOVE: // if user is moving finger while touched down
touch_Move(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP: // if user has released finger
touch_Up();
invalidate();
break;
}
return true;
} else {
return false;
}
}
}
I realise there are many things I need to add - but what are they? awakenScrollBars()?
Also: at the moment I get this error Error inflating class com.example.HomerProject1stDraft.DrawNewPlans.HomerView in the XML file. Is there a mistake in the way I'm creating the custom view? Can you even create a view from an inner class?
Many Thanks.
Firstly , i would suggest not to wrap your custom scrollview in another scrollview.If it is avoidable, avoid it.Also, you can check below link where i have implemented a custom scrollview.
https://stackoverflow.com/questions/15188842/customization-of-imageview-and-layouts
ScrollView only supports verticall scrolling. You need scrolling on both sides horizontal and vertical for that you will need a special class known as TwoDScrollView.
more information about this class and source code is available at:
http://blog.gorges.us/2010/06/android-two-dimensional-scrollview/
In your xml use this class instead of ScrollView and you should be able to scroll both horizontally and vertically.
For your other error please share the source code and state where you get the problem.
I've edited this code to move the Rect instantiations out of the onDraw method. I've tested it on several devices.
public class BallBouncesActivity extends Activity {
BallBounces ball;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ball = new BallBounces(this);
setContentView(ball);
}
}
class BallBounces extends SurfaceView implements SurfaceHolder.Callback {
GameThread thread;
int screenW; //Device's screen width.
int screenH; //Devices's screen height.
int ballX; //Ball x position.
int ballY; //Ball y position.
int initialY ;
float dY; //Ball vertical speed.
int ballW;
int ballH;
int bgrW;
int bgrH;
int angle;
int bgrScroll;
int dBgrY; //Background scroll speed.
float acc;
Bitmap ball, bgr, bgrReverse;
boolean reverseBackroundFirst;
boolean ballFingerMove;
Rect toRect1, fromRect1;
Rect toRect2, fromRect2;
//Measure frames per second.
long now;
int framesCount=0;
int framesCountAvg=0;
long framesTimer=0;
Paint fpsPaint=new Paint();
//Frame speed
long timeNow;
long timePrev = 0;
long timePrevFrame = 0;
long timeDelta;
public BallBounces(Context context) {
super(context);
ball = BitmapFactory.decodeResource(getResources(), R.drawable.rocket); //Load a ball image.
bgr = BitmapFactory.decodeResource(getResources(), R.drawable.sky_bgr); //Load a background.
ballW = ball.getWidth();
ballH = ball.getHeight();
toRect1 = new Rect(0, 0, bgr.getWidth(), bgr.getHeight());
fromRect1 = new Rect(0, 0, bgr.getWidth(), bgr.getHeight());
toRect2 = new Rect(0, 0, bgr.getWidth(), bgr.getHeight());
fromRect2 = new Rect(0, 0, bgr.getWidth(), bgr.getHeight());
//Create a flag for the onDraw method to alternate background with its mirror image.
reverseBackroundFirst = false;
//Initialise animation variables.
acc = 0.2f; //Acceleration
dY = 0; //vertical speed
initialY = 100; //Initial vertical position
angle = 0; //Start value for the rotation angle
bgrScroll = 0; //Background scroll position
dBgrY = 1; //Scrolling background speed
fpsPaint.setTextSize(30);
//Set thread
getHolder().addCallback(this);
setFocusable(true);
}
#Override
public void onSizeChanged (int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//This event-method provides the real dimensions of this custom view.
screenW = w;
screenH = h;
bgr = Bitmap.createScaledBitmap(bgr, w, h, true); //Scale background to fit the screen.
bgrW = bgr.getWidth();
bgrH = bgr.getHeight();
//Create a mirror image of the background (horizontal flip) - for a more circular background.
Matrix matrix = new Matrix(); //Like a frame or mould for an image.
matrix.setScale(-1, 1); //Horizontal mirror effect.
bgrReverse = Bitmap.createBitmap(bgr, 0, 0, bgrW, bgrH, matrix, true); //Create a new mirrored bitmap by applying the matrix.
ballX = (int) (screenW /2) - (ballW / 2) ; //Centre ball X into the centre of the screen.
ballY = -50; //Centre ball height above the screen.
}
//***************************************
//************* TOUCH *****************
//***************************************
#Override
public synchronized boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN: {
ballX = (int) ev.getX() - ballW/2;
ballY = (int) ev.getY() - ballH/2;
ballFingerMove = true;
break;
}
case MotionEvent.ACTION_MOVE: {
ballX = (int) ev.getX() - ballW/2;
ballY = (int) ev.getY() - ballH/2;
break;
}
case MotionEvent.ACTION_UP:
ballFingerMove = false;
dY = 0;
break;
}
return true;
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
//Draw scrolling background.
fromRect1.set(0, 0, bgrW - bgrScroll, bgrH);
toRect1.set(bgrScroll, 0, bgrW, bgrH);
fromRect2.set(bgrW - bgrScroll, 0, bgrW, bgrH);
toRect2.set(0, 0, bgrScroll, bgrH);
// Rect fromRect1 = new Rect(0, 0, bgrW - bgrScroll, bgrH);
// Rect toRect1 = new Rect(bgrScroll, 0, bgrW, bgrH);
//
// Rect fromRect2 = new Rect(bgrW - bgrScroll, 0, bgrW, bgrH);
// Rect toRect2 = new Rect(0, 0, bgrScroll, bgrH);
if (!reverseBackroundFirst) {
canvas.drawBitmap(bgr, fromRect1, toRect1, null);
canvas.drawBitmap(bgrReverse, fromRect2, toRect2, null);
}
else{
canvas.drawBitmap(bgr, fromRect2, toRect2, null);
canvas.drawBitmap(bgrReverse, fromRect1, toRect1, null);
}
//Next value for the background's position.
if ( (bgrScroll += dBgrY) >= bgrW) {
bgrScroll = 0;
reverseBackroundFirst = !reverseBackroundFirst;
}
//Compute roughly the ball's speed and location.
if (!ballFingerMove) {
ballY += (int) dY; //Increase or decrease vertical position.
if (ballY > (screenH - ballH)) {
dY=(-1)*dY; //Reverse speed when bottom hit.
}
dY+= acc; //Increase or decrease speed.
}
//Increase rotating angle
if (angle++ >360)
angle =0;
//DRAW BALL
//Rotate method one
/*
Matrix matrix = new Matrix();
matrix.postRotate(angle, (ballW / 2), (ballH / 2)); //Rotate it.
matrix.postTranslate(ballX, ballY); //Move it into x, y position.
canvas.drawBitmap(ball, matrix, null); //Draw the ball with applied matrix.
*/// Rotate method two
canvas.save(); //Save the position of the canvas matrix.
canvas.rotate(angle, ballX + (ballW / 2), ballY + (ballH / 2)); //Rotate the canvas matrix.
canvas.drawBitmap(ball, ballX, ballY, null); //Draw the ball by applying the canvas rotated matrix.
canvas.restore(); //Rotate the canvas matrix back to its saved position - only the ball bitmap was rotated not all canvas.
//*/
//Measure frame rate (unit: frames per second).
now=System.currentTimeMillis();
canvas.drawText(framesCountAvg+" fps", 40, 70, fpsPaint);
framesCount++;
if(now-framesTimer>1000) {
framesTimer=now;
framesCountAvg=framesCount;
framesCount=0;
}
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
thread = new GameThread(getHolder(), this);
thread.setRunning(true);
thread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
thread.setRunning(false);
while (retry) {
try {
thread.join();
retry = false;
} catch (InterruptedException e) {
}
}
}
class GameThread extends Thread {
private SurfaceHolder surfaceHolder;
private BallBounces gameView;
private boolean run = false;
public GameThread(SurfaceHolder surfaceHolder, BallBounces gameView) {
this.surfaceHolder = surfaceHolder;
this.gameView = gameView;
}
public void setRunning(boolean run) {
this.run = run;
}
public SurfaceHolder getSurfaceHolder() {
return surfaceHolder;
}
#Override
public void run() {
Canvas c;
while (run) {
c = null;
//limit frame rate to max 60fps
timeNow = System.currentTimeMillis();
timeDelta = timeNow - timePrevFrame;
if ( timeDelta < 16) {
try {
Thread.sleep(16 - timeDelta);
}
catch(InterruptedException e) {
}
}
timePrevFrame = System.currentTimeMillis();
try {
c = surfaceHolder.lockCanvas(null);
synchronized (surfaceHolder) {
//call methods to draw and process next fame
gameView.onDraw(c);
}
} finally {
if (c != null) {
surfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
}
If you notice, there's code to measure the frame rate:
now=System.currentTimeMillis();
canvas.drawText(framesCountAvg+" fps", 40, 70, fpsPaint);
framesCount++;
if(now-framesTimer>1000) {
framesTimer=now;
framesCountAvg=framesCount;
framesCount=0;
}
I'm seeing that on both of my Galaxy Nexus devices, running Android 4.0 and 4.2, it's around 22-24fps. On my HTC Desire, running Android 2.2, it's more like 60fps.
You'll also notice that I'm not allocating anything in the onDraw method. I'm not creating new Paint objects either. I really don't see how this is running so, so, so much slower on the my Galaxy Nexus devices. There's a lot of stuttering and the ball moves very slowly.
Does anyone know whether there is a setting I can undo or a known issue with re-drawing on the Galaxy Nexus? This is happening to my Galaxy Nexus that's running 4.0 and the one that's running 4.2, so I'm not sure it's OS-specific. I have turned off the window and transition animations in Developer Options. It doesn't make a difference whether I force 2D acceleration.
Have you tested android:hardwareAccelerated="true|false" flag in application?
Android Dev Doc: http://developer.android.com/guide/topics/graphics/hardware-accel.html
You can add before setContentView:
if (android.os.Build.VERSION.SDK_INT >= 11) {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
}
I've been thinking about this, as I'm still having performance issues on my Nexus 10 with surfaceview, like I said previously, I increased my performance a lot my removing the use of paint objects, it's a long shot as you are only using one, but you could try to remove the text drawing section from your onDraw() just to see if it makes any difference to your drawing speed.
Other than that, I think it's really a case of trying to pin down the problem.
I've noticed that even if I remove my onDraw and Logic updating methods completely from the equation, it's still taking sometimes up to 25ms just to lock and unlock/post the canvas! So it might be that the problem actually doesn't lie in onDraw method - give it a go, comment onDraw() out from your Run() method and see what speeds you get (Use logging to Logcat to see the figures, remember that ironically, displaying a frame / time count on the screen could affect the very thing you're measuring). :-)
What's your targetSdk set to?
If targetSdk is set to 8 (2.2), but you're running it on a 4.x device (15+), the 4.x device will run in compatibility mode, meaning that it will virtualize all the calls so that they return exactly the same as if they're running on a version 8 device.
This virtualization might explain some of the slowness. Try changing targetSdk to 17 (for your 4.2 device) and see if it makes a difference.
I has experienced the same thing on Kindle Fire, Sony Xperia Z and Samsung S4 (all with android 4.2).
The fix is: at App Manifest file remove "android:supportsRtl="true"".
Hope it will save your time. I spend 4 hours on tests and merging before i got it.
I'm currently developing a small Android application to familiarize myself with the API. However, I have come across a problem regarding sub-pixel data while using createBitmap. I currently have these blocks of code. (NOTE: Image being read in is a 128x128 RGB565 jpeg):
public class MainMenu extends View {
private int vWidth; // Width of our view
private int vHeight; // Height of our view
private Bitmap imgButtons;
private Rect rect;
private MenuItem testButton;
private MenuItem testButton2;
public MainMenu(Context context) {
super(context);
// Get our view dimensions
vWidth = getWidth();
vHeight = getHeight();
// Load our menu buttons' image
BitmapFactory.Options imgOptions = new BitmapFactory.Options();
imgButtons = BitmapFactory.decodeResource(getResources(), R.drawable.mainmenubuttons, imgOptions);
rect = new Rect(100, 400, 228, 528);
// Create our menu buttons and load their specific images
testButton = new MenuItem(100, 50);
testButton2 = new MenuItem(100, 200);
testButton.LoadImage(imgButtons, 128, 64, 0, 0);
testButton2.LoadImage(imgButtons, 128, 64, 0, 64);
}
#Override
public void onDraw(Canvas canvas) {
canvas.drawBitmap(imgButtons, null, rect, null);
testButton.Draw(canvas);
testButton2.Draw(canvas);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() != MotionEvent.ACTION_DOWN)
super.onTouchEvent(event);
return true;
}
}
class MenuItem {
private int xPos; // Center x-coordinate
private int yPos; // Center y-coordinate
private int width; // Button width
private int height; // Button height
private Rect rect;
private Bitmap buttonImage;
public MenuItem(int withXPos, int withYPos) {
xPos = withXPos;
yPos = withYPos;
}
public void LoadImage(Bitmap image, int imageWidth, int imageHeight, int xOffset, int yOffset) {
width = imageWidth;
height = imageHeight;
buttonImage = Bitmap.createBitmap(image, xOffset, yOffset, width, height);
rect = new Rect(xPos - (width >> 1), yPos - (height >> 1),
xPos + (width >> 1), yPos + (height >> 1));
}
public void Draw(Canvas canvas) {
canvas.drawBitmap(buttonImage, null, rect, null);
}
}
Below is an image displaying the issue:
I lied. I'm not allowed to post images yet, so here is a link to the image:
createBitmap issues
So, why is that I can display the original image correctly, but cannot correctly display images created from its sub-pixel data? I have run the application using versions 2.1 through 3.0, and 3.0 has no issues displaying the image correctly. Is it just an issue with devices before version 3.0, or am I missing something? I found an article on stacked describing issues between solid images used(RBG565), and images that contain alpha(ARGB8888). Where, my method wouldn't work if the image was of type ARGB8888, and if so I needed to physically handle the pixel data and copy the sub-pixel data myself and create my own pixel buffer for storing that data. I tried this on a whim and it lead me down to the same issue. I just figured why not try it? I have also tried other variations of Bitmap.createBitmap to no avail. At this point I'm a little stumped and figured I'd ask the community.