Android SurfaceView scrolling - android

I'm writing an android application that builds an offscreen bitmap that is (say) 640*480 pixels and displays that in a SurfaceView. Note that the size of the image is larger than the actual display on the handset.
The way that the application works is that it initially displays the upper left corner (0,0) of the image and then I would like to be able to scroll around the image.
My Surfaceview class implements the onMeasure callback and returns a size of 1024*768 which displays the area of the image that corresponds to the phone screen. I'd like to implement support for scrolling which I'm finding near impossible due to the very limited documentation (just bunches of classes and calls with no coherency). I've tried just calling scrollBy and that doesn't work.
If anyone has any pointers about how to proceed this would be much appreciated.

Once you start using a SurfaceView, you are moving yourself into a world where you have a blank area of pixels, and you are on your own to draw them how you want. You do everything yourself - and that means you need to implement scrolling yourself as well. It's not trivial to do, but neither is it too difficult.
One way to do it is the following:
- Extend SurfaceView and implement SurfaceHolder.Callback
- Use getHolder().addCallback( this ) to grab the callback
- Use OnTouch events to implement the scrolling behavior (i.e., when responding, pick up the x,y position from the MotionEvent and set the offset appropriately).
Implement a thread to handle the updating of the image. There are examples of this around on the web (sorry - don't have any to hand right now).
Alternatively, use an off-screen bitmap and have it drawn by a normal ImageView (size the ImageView to the size of the bitmap and place it in a scroll view).
Edit:
Seeing that this is still getting views, it might be worth pointing out that since this time, I've implemented and released as open-source a small library that implements a framework for the above functionality (inspired by many comments here on SO, among others). It can be found on Github: https://github.com/micabyte/android_game

I just solved this for my own game, my surfaceView is an adaptation of the LunarLander example by Google.
Everything is the same except I'm drawing on my own private Bitmap with my own private Canvas in the doDraw method, and then copying some of it to the canvas I'm given using the code below.
Rect src = new Rect(mScrollX, mScrollY, mScrollX+mSurfaceWidth, mScrollY+mSurfaceHeight);
Rect dst = new Rect(0, 0, mSurfaceWidth, mSurfaceHeight);
surfaceCanvas.drawBitmap (mLevelBitmap, src, dst, null);
The Rect objects define how drawBitmap scales/transforms pixels from the source Bitmap (my private bitmap) to the destination (the Canvas I was given to draw on in doDraw()
mScrollX and Y are the upper left corner of the rectangle that will be displayed (relative to the big private bitmap)
mSurfaceWidth/Height is the size of the rectangle that will be displayed
Just make sure the scroll values are within acceptable ranges [0,privateBitmapWidth-mSurfaceWidth]
update the scroll values yourself however you please, (in onTouchEvent() for example)

Related

How to update surfaceview partially?

I have a surfaceview in my activity. Initially some circles are drawn using
Canvas c = SurfaceView.getHolder().lockCanvas();
and
SurfaceView.getHolder().unlockCanvasAndPost(c);
Later on I want to draw on a very small part of the surfaceview. I do not want to redraw the whole surfaceview. Is it possible to update just the required part?
Use lockCanvas(Rect dirty) instead. Pass in the area you want to redraw.
Note that dirty is an in-out parameter. The Rect may be expanded to cover a larger area, and you need to set every pixel it covers. So if (for example) SurfaceView no longer has access to the previous pixels, it'll expand Rect to cover the entire surface.
Because SurfaceView is double- or triple-buffered, the implementation will copy chunks of pixels from the previous buffer. Since the Canvas you get from a SurfaceView is never hardware-accelerated, specifying a dirty rect is often a performance win.

Android draw transparency

I'm new to Android (and Java for that matter), and I'm making a simple game. I'm stuck.
My problem is this:
I have 2 png images (a background and foreground) that take up the whole screen on startup. I now want to be able to set the width of the "drawn portion" of the foreground to the x-coordinate of ON_DRAG. My control method works fine; it's used like this:
g.drawRect(0, 0, scene.getLine(), g.getHeight(), Color.GREEN);
where scene.getLine() returns the x-value of the touch. So at this point I can draw a green rectangle above my images. But what I actually want is for those rectangle dimensions to "punch a hole" in my top-layer png (so the background is revealed beneath). I do not want to scale the foreground.
I have tried clipRect, but it doesnt work because I have other images that need to be drawn on top of these two, and clipRect prevents this. I have looked at a bunch of "PorterDuffXfermode" code, but cannot understand how to apply it to my situation, and cannot make it work. i.e. I can make a Paint with the PorterDuff "SRC" mode set, but I dont know how to define "source" and "destination" images so the Paint will work its magic. A final consideration with this is that even if the PorterDuff would work, Im not sure it would be practical given that I want this thing to run at 60fps.
Any suggestions?
Thanks.
You can call canvas.save() before a clip and canvas.restore() after your drawing to return the canvas to the sate it was in before you stared. Then you can draw your additional images.

How to resize an ImageView dynamically by dragging from its corners just like a cropping frame on a picture?

In my android application I am stuck in a problem and nothing seems to work for me.
I have an ImageView on the top of another ImageView inside a relative layout.
Now I need to resize the imageview on top when user touches one of its corners and drags.
Just like a cropping frame we generally see. When we drag any one corner, then the diagonally opposite corner must remain fixed and the resizing must be done across the corner which is being dragged.
What I am doing is setting OnTouchListener and getting new/dragged coordinates on Action.MOVE then I tried to resize using Bitmap's createScaledBitmap. This does resize the image view but not across the corner which is being dragged. I am totally confused .
How I can use the coordinates to draw an Image View just like we do it while drawing a rectangle using Canvas.
Please help.
I wouldn't do this in an ImageView. I would subclass View and override onTouchEvent and onDraw to handle the input and draw all the various components. You have to break this down into it's components and manage a number of objects in this view.
You have a Rect that represents the size of the crop area. This probably defaults to the size of the control. In onTouchEvent, you need to test for an area around each corner and then keep track of which corner is being dragged to resize your Rect appropriately.
You don't have to call createScaledBitmap each time you draw it, and you probably shouldn't because you are flirting with an OutOfMemoryException at that point (clean up your Bitmaps too slowly and you'll find out the hard way what this is). Just decode the Bitmap when the control gets created and draw it to the canvas using a destination Rect.
Lots of code to write, but it sounds like a fun project. There's no easy way to drop in a control like this (if I'm understanding you correctly). You have to manage the touches, drags, and the destination rectangle inside the custom View.

How do I draw only part of a possibly large and/or zoomed SVG?

I would like to show a zoomable and scrollable map-like SVG in my app. The only way to do this without writing your own library etc. is to use an existing library all of which seem to render the SVG to a Bitmap, which can be assigned to an ImageView, for example.
The underlying bitmap quickly gets very large which may result in an out-of-memory exception. How do I draw only part of a possibly large and/or zoomed SVG to a Bitmap? Scaling up a small bitmap looks bad and is not an option.
I am going to answer this question myself because i have been looking for an answer for a while, found it scattered all over the web (but not here) and think that it may be helpful for others.
Can you not use Google's svg-android library? You can scale the Canvas to the correct size before rendering.
Would this be like how Google maps works? They appear to tile and increase/decrease detail as you zoom in and out.
I have done something similar in the past, not sure if it is how Google did it but my scheme worked exceptionally well for me. What I did was break my map into rectangular regions, I would then calculate which regions had any portion that was visible and pull in data only from those regions. Each rectangular region was sub-divided to provide more detail if the user zoomed in. I kept subdividing down to the level I wanted, I stopped at three, because I used a 10 X 10 grid so each level was effectively a magnification of 10 - at level three I was viewing 100 times the detail I was viewing at level one which was sufficient for my needs.
I was even able to animate this so that when you zoomed in you appeared to smoothly "fly" closer to the terrain.
First of all, I am using libsvg-android which requires the NDK but which seems to be very fast, especially if you have to redraw your SVG frequently. Actually, I am using this modified version of the library which enables you to query the width and height of the SVG as specified in the SVG document.
My first approach was this:
long svgID = SvgRaster.svgAndroidCreate();
SvgRaster.svgAndroidParseBuffer(svgID, svgString);
SvgRaster.svgAndroidSetAntialiasing(svgID, true);
Bitmap bitmap = Bitmap.createBitmap(500, 500, Bitmap.Config.RGB_565);
SvgRaster.svgAndroidRenderToAreaUniform(svgID, new Canvas(bitmap), -300, -200, 1500, 1500);
First, the SVG source is parsed. Then, a Bitmap is created that will be assigned to an ImageView later on. Next, the SVG is rendered (zoomed to 1500x1500 pixels in size) to the bitmap. Note the translation (-300, -200) applied because the user has scrolled the image view.
This does not work. It seems that negative offsets are not supported.
Instead, the canvas has to be translated like this:
Canvas canvas = new Canvas(bitmap);
canvas.translate(-300, -200);
SvgRaster.svgAndroidRenderToAreaUniform(svgID, canvas, 0, 0, 1500, 1500);
This works as expected. The translation of the SVG is applied to the canvas; the scale is applied by adjusting the size of the rendered SVG. In this way, you can have a large SVG document and render the proportion you are interested in to a much smaller bitmap.
I've written an ImageView subclass which is scrollable in both the x and the y directions and zoomable. Its image resource is a low resolution raster image of the SVG. When the user scrolls or zooms the image, this version of the SVG is shown. When the user stops interacting with the image, I render the currently visible part of the SVG to a Bitmap the size of my image view and draw this instead of the low resolution image.

Draw an image floating over another in Android

I want to be able to dynamically place image(s) over another image in my app.
Consider the first image as background and the other images to be on top level, I will also need to move those top level images (change their x and y on the screen) by code too.
Imagine, for example, a sea in which the user places fish and sea animals, then those sea animals start to move here and there on the screen: it will be like that.
How can I do this? If you don't know but remember any simple program or demo that does that, it will be also very welcome!
Thank you!
There is, of course, more than one way to do this, but I would say that the best way to do it would be to create a custom View (class that derives from View) and have this handle your bitmap drawing and all of your touch events.
There's a lot of code to write for loading the Bitmaps and keeping track of all of their positions (and then drawing them to the canvas in onDraw), but if you start really small by just allowing one image to be drawn and dragged around the screen, you can build on that and keep your code organized.
You would need to override onDraw(Canvas) and onTouchEvent(MotionEvent) in your custom View. You'll load your bitmaps with BitmapFactory (decodeResource method if you're including your images as resources in your project) and you'll need to remember to call recycle on your bitmaps when you're no longer using them.
In onDraw, you draw your bitmaps to the canvas at a specific location using Canvas.drawBitmap. There are two overloads of this method you can choose from, one that takes the top and left coordinates of the bitmap as floats (and therefore performs no scaling or stretching) and one that takes a destination and source rectangle to perform scaling, stretching and placement.
I always use the latter as it gives me finer tuned control. If you choose this route, you'll want to keep two Rect instances and a Bitmap instance for each image being drawn, update them in the touch events and draw them to the canvas in the draw event.
When something changes inside your view (as in the case of a touch event), call invalidate() method and the framework will know to redraw everything which triggers your onDraw method.

Categories

Resources