24

I'm new to Android.

I am drawing bitmaps, lines and shapes onto a Canvas inside the OnDraw(Canvas canvas) method of my view. I am looking for help on how to implement smooth scrolling in response to a drag by the user. I have searched but not found any tutorials to help me with this.

The reference for Canvas seems to say that if a Canvas is constructed from a Bitmap (called bmpBuffer, say) then anything drawn on the Canvas is also drawn on bmpBuffer. Would it be possible to use bmpBuffer to implement a scroll ... perhaps copy it back to the Canvas shifted by a few pixels at a time? But if I use Canvas.drawBitmap to draw bmpBuffer back to Canvas shifted by a few pixels, won't bmpBuffer be corrupted? Perhaps, therefore, I should copy bmpBuffer to bmpBuffer2 then draw bmpBuffer2 back to the Canvas.

A more straightforward approach would be to draw the lines, shapes, etc. straight into a buffer Bitmap then draw that buffer (with a shift) onto the Canvas but so far as I can see the various methods: drawLine(), drawShape() and so on are not available for drawing to a Bitmap ... only to a Canvas.

Could I have 2 Canvases? One of which would be constructed from the buffer bitmap and used simply for plotting the lines, shapes, etc. and then the buffer bitmap would be drawn onto the other Canvas for display in the View?

I should welcome any advice!

Answers to similar questions here (and on other websites) refer to "blitting". I understand the concept but can't find anything about "blit" or "bitblt" in the Android documentation. Are Canvas.drawBitmap and Bitmap.Copy Android's equivalents?

Loktar
  • 34,764
  • 7
  • 90
  • 104
prepbgg
  • 3,564
  • 10
  • 39
  • 51
  • Done a bit more googling this morning. According to this web page http://markmail.org/message/oedvjxi3dhokzq23 I can have a second canvas, so I'll explore that idea. – prepbgg Jan 17 '10 at 08:24
  • See more about this in a new "answer" below. – prepbgg Mar 26 '11 at 22:27

5 Answers5

13

I seem to have found an answer. I have put the bulk of the drawing code (which was previously in onDraw()) in a new doDrawing() method. This method starts by creating a new bitmap larger than the screen (large enough to hold the complete drawing). It then creates a second Canvas on which to do the detailed drawing:

    BufferBitmap = Bitmap.createBitmap(1000, 1000, Bitmap.Config.ARGB_8888);
    Canvas BufferCanvas = new Canvas(BufferBitmap);

The rest of the doDrawing() method is taken up with detailed drawing to BufferCanvas.

The entire onDraw() method now reads as follows:

    @Override protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawBitmap(BufferBitmap, (float) -posX, (float) -posY, null);
}

The position variables, posX and posY, are initialised at 0 in the application's onCreate()method. The application implements OnGestureListener and uses the distanceX and distanceY arguments returned in the OnScroll notification to increment posX and posY.

That seems to be about all that's needed to implement smooth scrolling. Or am I over-looking something!?

prepbgg
  • 3,564
  • 10
  • 39
  • 51
  • One thing that I have discovered since writing this "answer" is that extra code is needed to "recycle" the buffer bitmap (and any other bitmap objects) when the screen is rotated. This is because when the screen is rotated the app's main activity is ended and restarted but the memory used by Bitmap objects is not automatically recovered by the system. So, it seems to be important to call the recycle() method for every bitmap when it is no longer needed. In the case of the buffer bitmap the answer seems to be to override the Activity's onDestroy method and to call the bitmap's recycle() in it. – prepbgg Jan 27 '10 at 12:23
  • This code can be avoided simply by putting `android:configChanges="orientation"` in the Manifest. See Ribo's answer to this question dated 2011-01-04. – prepbgg Jan 05 '11 at 15:58
  • please help in this question http://stackoverflow.com/questions/11720702/canvas-zoom-in-partially/11721193#11721193 – kamal_tech_view Jul 30 '12 at 12:56
  • Sorry, kamal and WildBill, to be slow in responding. I haven't looked at this thread recently. I'm glad you've solved your problem, kamal. WildBill, I first call doDrawing() from the main Activity's onCreate() after the View has been created; and I call doDrawing() again whenever the image in the buffer bitmap needs to be changed. – prepbgg May 16 '13 at 20:05
1

I had this problem too,

I did the drawing like this:

Canvas BigCanvas = new Canvas();
Bitmap BigBitmap = new Bitmap(width,height);

int ScrollPosX , ScrollPosY  // (calculate these with the onScrollEvent handler)

void onCreate()
{
   BigCanvas.SetBitmap(BigBitmap);
}

onDraw(Canvas TargetCanvas)
{
   // do drawing stuff
   // ie.  BigCanvas.Draw.... line/bitmap/anything

   //draw to the screen with the scrolloffset

   //drawBitmap (Bitmap bitmap, Rect src, Rect dst, Paint paint)
   TargetCanvas.DrawBitmap(BigBitmap(new Rect(ScrollPosX,ScrollPosY,ScrollPosX + BigBitmap.getWidth(),ScrollPosY + BigBitmap.getHeight(),new Rect(0,0,ScreenWidth,ScreenHeight),null);
}

for smooth scrolling you'd need to make some sort of method that takes a few points after scrolling (i.e the first scroll point and the 10th) , subtract those and scroll by that number in a for each loop that makes it gradually slower ( ScrollAmount - turns - Friction ).

I Hope this gives some more insight.

Mervin
  • 1,103
  • 4
  • 18
  • 26
  • Thanks, Mervin, for your reply. I'm not sure I understand exactly how your scrolling works. Do you wait until 10 scroll events have been received before you start to move the bitmap? I had imagined that, in order to make the screen display feel fully responsive I would need to redraw bitmap onto the Canvas as soon as the first scroll event was received. And, because my "drawing stuff" is quite time-consuming, I decided to move this out of the onDraw method in an attempt to make the program as responsive as possible to the scroll. – prepbgg Jul 31 '10 at 04:43
1

No need for the activity to be restarted! (Per prepbgg's Jan 27 10 reply to his Jan 17 10 'answer') Rather than recycling the bitmap and incurring the overhead of having the activity reloaded, you can avoid having the application loaded by putting the 'android:configChanges' attribute shown below, in the 'activity' element of the AndroidManifest.xml file for the app. This tells the system the the app will handle orientation changes and that it doesn't need to restart the app.

<activity android:name=".ANote"
    android:label="@string/app_name"
    android:configChanges="orientation|screenLayout">
    <intent-filter>
       <action android:name="android.intent.action.MAIN" />
       <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

This method can be used to get a notification when the orienation is changed:

public void onConfigurationChanged(Configuration  newConfig) {
    super.onConfigurationChanged(newConfig);
    prt("onConfigurationChanged: "+newConfig);

    if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
      prt("  PORTRAIT");
    } else {
      prt("  LANDSCAPE");
    }
} // end of onConfigurationChanged
Sunil D.
  • 17,983
  • 6
  • 53
  • 65
Ribo
  • 3,363
  • 1
  • 29
  • 35
  • This is very interesting. (I did, in fact, at a fairly early stage stop the app from changing orientation by putting android:screenOrientation="portrait" in the Manifest. I did this to stop the memory problems.) However, I would like to handle rotations if possible. When you talk of the app handling orientation changes, what do I need to do to achieve this? Will the View's onDraw method still be called automatically? Or do I simply, perhaps, respond to the rotation by "invalidating" the view? – prepbgg Jan 04 '11 at 22:55
  • I've just replaced `android:screenOrientation="portrait"` in the Manifest with `android:configChanges="orientation"` and the app just works! (No need to have any code to check for an orientation change ... the screen gets redrawn without any intervention from me.) LogCat suggests that the phone spends about 250 ms in garbage collection each time the orientation is changed. I can't yet see any problems. Thank you very much for coming here with this suggestion. – prepbgg Jan 05 '11 at 15:56
1

Continuation of reply to Viktor ...

In fact, the situation is more complicated. Because the doDrawing process is quite slow (taking 2-3 seconds on my slow old HTC Hero phone) I found it desirable to pop up a Toast message to advise the user that it was happening and to indicate the reason. The obvious way to do this was to create a new method containing just 2 lines:

public void redrawBuffer(String strReason) {
    Toaster.Toast(strReason, "Short");`
    doDrawing();
}

and to call this method from other places in my program instead of doDrawing().

However, I found that the Toaster either never appeared or flashed up so briefly that it could not be read. My workaround has been to use a time check Handler to force the program to sleep for 200 milliseconds between displaying the Toast and calling doDrawing(). Although this slightly delays the start of a redraw I feel this is a price worth paying in terms of the program's usability because the user knows what is going on. reDrawBuffer() now reads:

public void redrawBuffer(String strReason) {
    Toaster.Toast(strReason, "Short");
    mTimeCheckHandler.sleep(200);    
}`

and the Handler code (which is nested within my View class) is:

private timeCheckHandler mTimeCheckHandler = new timeCheckHandler();

class timeCheckHandler extends Handler {
@Override
    public void handleMessage(Message msg) {
        doDrawing();
    }
    public void sleep(long delayMillis) {
        this.removeMessages(0);
        sendMessageDelayed(obtainMessage(0), delayMillis);
    }
}`
prepbgg
  • 3,564
  • 10
  • 39
  • 51
0

prepbgg: I don't think the code will work because canvas.drawBitmap does not draw into the bitmap but draws the bitmap on-to the canvas.

Correct me if I am wrong!

unwind
  • 391,730
  • 64
  • 469
  • 606
gvaish
  • 9,374
  • 3
  • 38
  • 43
  • Thanks, MasterGaurav. I agree that canvas.drawBitmap(BufferBitmap, ...) does not draw anything into BufferBitmap. However, I have a separate doDrawing() method where things (lines, bitmaps, etc.) are drawn into BufferCanvas and thereby into BufferBitmap. The code does seem to work (although it does occasionally stutter and/or crash ... I don't know whether that is because my drawing code is faulty or whether there are other errors.) – prepbgg Jul 31 '10 at 05:02