B4A Library [Lib] ScrollView2D

Hello,

Does it need a long explanation ? You can scroll in the two directions.

This lib does not work with Android versions < 2.

v1.01:
I fixed a big bug in the original code.
I added the function SmoothScrollTo.

v1.02:
I restarted from a fresh basis because of the many bugs in the original code. I fixed most of them (now, multitouch events and hardware keys are correctly handled) but a few are left and need more work (sometimes the wrong object get the focus and resizing is not perfectly handled). I noticed by the way that the stock scrollview is bugged and does not set the focus correctly if you move your finger very slowly.
Thanks to Erel, I solved the problem with the B4A documentation.
I added a new function: FullScroll.

v1.03:
I fixed all known bugs (including bugs found in ScrollView & HorizontalScrollView);
I added the scrollbars;
I added three new functions:
- ScrollbarsVisibility
- FadingEdges
- GiveFocusToFirstVisible.

v1.1:
I fixed a problem with events that were not fired if declared in a class;
I added two functions: ScrollingIsFinished and DisableTouchEventInterception.

v1.2:
SV2D appears now as a custom view in the designer with all its properties.

v1.3:
I fixed a bug (SV_2 = SV_1 did not work because the inner panel was declared in the wrong class).

Enjoy,
Fred
 

Attachments

  • ScrollView2D v1.3.zip
    120.5 KB · Views: 5,833
  • Java source - ScrollView2D.zip
    17.6 KB · Views: 1,748
Last edited:

johnaaronrose

Active Member
Licensed User
Longtime User
ScrollChanged event problem solved

Problem was nothing to do with the CatLog app, which works perfectly. Nor was it to do with storage. It was due to my sv2dMap_ScrollChanged Sub's code not being run due to my coding sv2dMap.Initialize(0, 0, "") rather than sv2dMap.Initialize(0, 0, "sv2dMap"). All too often I find that my sillier mistakes are the hardest to spot!
 

johnaaronrose

Active Member
Licensed User
Longtime User
Scrolling a map while keeping it aligned with a path

My ScrollView2D view allows scrolling of a map which is rather larger than my phone's screen: it is actually loaded from a .jpg file containing a scanned A4 image of a map. Now I want to draw on the displayed map a polyline path. I could do this by having a canvas (with the same dimensions as the Scrollview2D view) and using the canvas's DrawLine method to draw the path's lines. However, I presume that the path would not move in alignment with the map as the map is scrolled. Am I correct? If so, what is the way to achieve this?
 

Informatix

Expert
Licensed User
Longtime User
My ScrollView2D view allows scrolling of a map which is rather larger than my phone's screen: it is actually loaded from a .jpg file containing a scanned A4 image of a map. Now I want to draw on the displayed map a polyline path. I could do this by having a canvas (with the same dimensions as the Scrollview2D view) and using the canvas's DrawLine method to draw the path's lines. However, I presume that the path would not move in alignment with the map as the map is scrolled. Am I correct? If so, what is the way to achieve this?

Your method is not recommended to display and draw on a big bitmap, mainly because you waste a lot of memory. The right method should be to split your bitmap into parts and display these parts only when they are visible. That would imply many changes to your current code, of course. If you want minimal changes, then you may use an Accelerated Surface instead of an ImageView in your ScrollView2D. That would allow you to use the existing canvas of the view to draw onto it. This way, if you scroll your view, you scroll also what's drawn.
 

johnaaronrose

Active Member
Licensed User
Longtime User
Accelerated Surface

Your method is not recommended to display and draw on a big bitmap, mainly because you waste a lot of memory. The right method should be to split your bitmap into parts and display these parts only when they are visible. That would imply many changes to your current code, of course. If you want minimal changes, then you may use an Accelerated Surface instead of an ImageView in your ScrollView2D. That would allow you to use the existing canvas of the view to draw onto it. This way, if you scroll your view, you scroll also what's drawn.

I didn't use an ImageView but instead placed the 'contents' of the .jpg file into an sv2d by means of the following:
B4X:
sv2dMap.Initialize(0, 0, "sv2dMap")
      Activity.AddView(sv2dMap, pnlMap.Left, pnlMap.Top, pnlMap.Width, pnlMap.Height)
      MapBitmap.Initialize(GPSDir, MapFileName)
     sv2dMap.Left = pnlMap.Left
      sv2dMap.Top = pnlMap.Top
      sv2dMap.Width = pnlMap.Width
      sv2dMap.Height = pnlMap.Height
      sv2dMap.Panel.SetBackgroundImage(MapBitmap)
      sv2dMap.Panel.Width = MapBitmap.Width
      sv2dMap.Panel.Height = MapBitmap.Height
      sv2dMap.HorizontalScrollPosition = (sv2dMap.Panel.Width / 2) - MapBitmap.Width
      sv2dMap.VerticalScrollPosition = (sv2dMap.Panel.Height / 2) - MapBitmap.Height
      DoEvents

Currently, I monitor the scrolling of the map using the ScrollChanged event of sv2dMap. Does use of the Accelerated Surface (in the minimal changes method) imply dropping the sv2dMap view and doing scrolling of the map using the Touch event of the AcceleratedSurface? Or should the sv2dMap view be retained with the AcceleratedSurface's parent being sv2dMap and still doing scrolling of the map using the ScrollChanged event of sv2dMap (& having the AcceleratedSurface Touch event's Sub doing nothing)?
 

johnaaronrose

Active Member
Licensed User
Longtime User
Compile problems

Yes, add the AS to the panel of the SV2D and don't declare the touch event handler of the AS.

I've done as above. I now get a compile error on the DrawRect command shown below (sv2dMap is the ScrollView2D) even though I have the latest Accelerated Surface library loaded:
Dim acsfMap As AcceleratedSurface
Dim rectMapPos As Rect
Activity.AddView("acsfMap", sv2dMap.Left, sv2dMap.Top, sv2dMap.Width, sv2dMap.Height)
acsfMap.Initialize("", False)
acsfMap.DrawRect(rectMapPos, Colors.Red, False, 2dip, False)
acsfMap.DrawLine(x1, yc, x2, yc, Colors.LightGray, 1dip, False)
acsfMap.DrawLine(xc, y1, xc, y2, Colors.LightGray, 1dip, False)
acsfMap.DrawCircle(xc, yc, GPS_Radius, Colors.Red, False, 3dip, False)
acsfMap.DrawPoint(xc, yc, Colors.Red)
acsfMap.Invalidate2(rectMapPos)
DoEvents

PS I've noticed when compiling the example Accelerated Surface app, that compiling gives "AndroidManifest.xml:17: error: No resource identifier found for attribute 'hardwareAccelerated' in package 'android'". Could this be due to my having android-10's android.jar in menu item Option > Config Paths?
 

Informatix

Expert
Licensed User
Longtime User
I've done as above. I now get a compile error on the DrawRect command shown below (sv2dMap is the ScrollView2D) even though I have the latest Accelerated Surface library loaded:
Dim acsfMap As AcceleratedSurface
Dim rectMapPos As Rect
Activity.AddView("acsfMap", sv2dMap.Left, sv2dMap.Top, sv2dMap.Width, sv2dMap.Height)
acsfMap.Initialize("", False)
acsfMap.DrawRect(rectMapPos, Colors.Red, False, 2dip, False)
acsfMap.DrawLine(x1, yc, x2, yc, Colors.LightGray, 1dip, False)
acsfMap.DrawLine(xc, y1, xc, y2, Colors.LightGray, 1dip, False)
acsfMap.DrawCircle(xc, yc, GPS_Radius, Colors.Red, False, 3dip, False)
acsfMap.DrawPoint(xc, yc, Colors.Red)
acsfMap.Invalidate2(rectMapPos)
DoEvents

All your Draw calls have to be placed in the Draw event of the AS. This event is called whenever you invalidate the AS (or when the AS is displayed for the first time). The event has a parameter (the Canvas reference) that you will use to make your draws.

Example:
B4X:
Sub AcSf_Draw(AC As AS_Canvas)
   AC.DrawCircle(50dip, 50dip, 40dip, Colors.Green, False, 5dip, True)
   AC.DrawPoint(50dip, 50dip, Colors.Green)
   AC.DrawText("With filter", 53%x, 55dip, Typeface.DEFAULT_BOLD, 20, Colors.Yellow, AC.ALIGN_CENTER)

PS I've noticed when compiling the example Accelerated Surface app, that compiling gives "AndroidManifest.xml:17: error: No resource identifier found for attribute 'hardwareAccelerated' in package 'android'". Could this be due to my having android-10's android.jar in menu item Option > Config Paths?
Yes, this path should always point to the latest jar (that doesn't prevent the app from running on old devices).
 

johnaaronrose

Active Member
Licensed User
Longtime User
Scrolling the Canvas member of an AcceleratedSurface

Informatix,

Thanks for your helpful reply.

The geographical map is displayed as the background image of the Scrollview2D (named sv2dMap) view: this view has its left-top-width-top values set to the area on the screen (a Panel view named pnlMap) where it is required to display part of the A4 size geographical map, and its 'contained' Panel width-top values to the equivalent sizes of the Bitmap corresponding to the A4 geographical map. If the user scrolls, then the app calculates the new Horizontal & Vertical Scroll Positions of sv2d and then the changes in Horizontal & Vertical Scroll Positions of sv2d.

However, I do not see a way to scroll the points already drawn on the AcceleratedSurface (named acsfMap) as it doesn't have a Panel 'contained' within it. IMO the only other possibility is to erase the Canvas part of acsfMap - but there seems to be no method to do this - after storing all drawn element details so that they can be redrawn in their new positions on the Canvas.

So, unless you can suggest a way of 'scrolling' the Canvas part of acsfMap, would it be possible to make the necessary changes to the AcceleratedSurface code? Ideally, the first method of having a Panel (as per the Scrollview2D view) would be better. Another possibility would be to have a Canvas 'contained' within a ScrollView2D, which has a Panel member.
 

johnaaronrose

Active Member
Licensed User
Longtime User
The map should be the background of the Accelerated surface and the AS should be placed in the panel of your SV2D as the main (and only) view. You don't need anything else.

I think that I'm going round in circles. Previously, I had a Panel named pnlMap, which was positioned above a Panel view named pnlButtons (which is parent to a number of buttons. pnlMap acted as the 'place' for the ScrollView2D view named sv2dMap (with its panel property co-ordinates 'placed' on the top left of pnlMap and its height & width being the size of the geographical map). This worked fine in terms of displaying the geographical map in pnlMap (without overwriting pnlButtons). It also allowed scrolling of the geographical map using the sv2dMap_ScrollChanged event.

If I now introduce the Accelerated Surface named acsfMap placed in the panel of sv2dMap & with its background image as the geographical map, the pnlButtons are overwritten. The same happens if I replace pnlMap by an Image View named ivmap. So it seems that the best 'holder' for the geographical map is sv2dMap (i.e. set its background to the geographical map's bitmap).

I also have the problem of calibrating the geographical map with 2 points i.e. for each, user clicking the point & then inputting its longitude & latitude by means of the Dialog library modal InputNumber methods which works fine. I'm doing this with a pnlMap_Touch sub but there is interference between that & the sv2dMap.ScrollChanged (which is activated even though I only touch the screen) - I want to draw on acsfMap's canvas each point just after it is clicked by the user but still to allow the user to scroll the map in order to allow marking of a caibration point outside the displayed portion of the map. Any ideas?
 

Informatix

Expert
Licensed User
Longtime User
Without code, that becomes too complicated for me.
Here is an example. I draw a green circle in the upper left corner of the SV2D. When I scroll the view, the circle scrolls too. And I do nothing special apart adding my Accelerated Surface to the panel of the SV2D.
 

Attachments

  • Test.zip
    99.2 KB · Views: 349

johnaaronrose

Active Member
Licensed User
Longtime User
Without code, that becomes too complicated for me.
Here is an example. I draw a green circle in the upper left corner of the SV2D. When I scroll the view, the circle scrolls too. And I do nothing special apart adding my Accelerated Surface to the panel of the SV2D.

I've just been trying to compile the sv2d example extended with an accelerated Surface. I get various compile problems even though I'm using Accelerated Surface v1.12 & Scrollview2D v1.10 in my Additional Libraries. For instance:
1. sv2d 'objects' to the property DIR_HORIZONTAL which I replaced with FS_DIR_HORZ
2. sv2d 'objects' to the property GLOW_EFFECT_LEFT for which I found no replacement
3. sv2d 'objects' to the property StopScrolling which I replaced with ScrollingisFinished

after the above corrections, I still could not compile this acsf extended sv2d example as I got:
Generating R file. Error
AndroidManifest.xml:14: error: No resource identifier found for attribute 'hardwareAccelerated' in package 'android'

I think that I've seen this before but I've forgotten the workaround. Could you tell me what it is?

I wanted to put the line "AcSf.Enabled = False" before the "AcSf.Invalidate" line as I have a feeling that doing that still causes the green circle AcSF drawing. Is that the correct way of preventing the AcSf.Draw event Sub's coding being executed?
 

Informatix

Expert
Licensed User
Longtime User
I've just been trying to compile the sv2d example extended with an accelerated Surface. I get various compile problems even though I'm using Accelerated Surface v1.12 & Scrollview2D v1.10 in my Additional Libraries. For instance:
1. sv2d 'objects' to the property DIR_HORIZONTAL which I replaced with FS_DIR_HORZ
2. sv2d 'objects' to the property GLOW_EFFECT_LEFT for which I found no replacement
3. sv2d 'objects' to the property StopScrolling which I replaced with ScrollingisFinished

Ah sorry I took the source code of the example that is included with UltimateListView and not the source code of the free version. Here is the correct version (I removed unneeded parts). All my apologies.

To make it work, you have to select a more recent JAR version in your IDE (it's in the Paths menu).
 

Attachments

  • Test.zip
    98.5 KB · Views: 305

johnaaronrose

Active Member
Licensed User
Longtime User
Preventing green circle being drawn

Ah sorry I took the source code of the example that is included with UltimateListView and not the source code of the free version. Here is the correct version (I removed unneeded parts). All my apologies.

To make it work, you have to select a more recent JAR version in your IDE (it's in the Paths menu).

Thanks for the correct version of the Sv2d example enhanced by drawing of the green circle. It now compiles OK.

I've now put the line "AcSf.Enabled = False" before the "AcSf.Invalidate" line in order to prevent the green circle AcSF drawing. Is that the correct way of preventing the AcSf.Draw event Sub's coding being executed?
 
Last edited:

Informatix

Expert
Licensed User
Longtime User
I've now put the line "AcSf.Enabled = False" before the "AcSf.Invalidate" line as I have a feeling that doing that does not prevent the green circle AcSF drawing. Is that the correct way of preventing the AcSf.Draw event Sub's coding being executed?

The AS is automatically invalidated when it is added to the panel. The call to invalidate in the Resume is not really needed (it ensures that the AS is refreshed when the app resumes), you could remove it. Setting Enabled true or false should not have any effect. This property is an inheritance.
 

johnaaronrose

Active Member
Licensed User
Longtime User
AS Draw

The AS is automatically invalidated when it is added to the panel. The call to invalidate in the Resume is not really needed (it ensures that the AS is refreshed when the app resumes), you could remove it. Setting Enabled true or false should not have any effect. This property is an inheritance.

OK, I now understand the AS a little better e.g. in my app, when it (acsfMap) is added to the panel (pnlMap) in the Activity_Create event's Sub and by the Activity_Resume event's Sub, it causes the acsf_Draw event's Sub to execute.

My problem is:
sv2dMap contains the scrollable geographical map as its Background Image: currently I'm using its ScrollChange event for monitoring purposes but I don't really need it. pnlMap has a Touch event which I use for map calibration (i.e. the user 'sets' 2 marker calibration points which pnlMap's Touch Sub's code is called in order to input each point's Latitude & Longitude input by Dialog Library modal methods & these are drawn using acsf_Draw Sub's code) during Activity_Resume Sub. At that Sub's end, I do not want to allow the user to interact with pnlMap or acsfMap, except that the user may decide that the current GPS location is part of a path which is shown on the map by means of code in the acsf_Draw Sub. Thus, I cannot use the acsfMap's Touch event as its Sub would be called during calibration, when acsfMap's Draw event Sub code should be executed. So my concept was to Enable/Disable pnlMap & acsfMap events by means of the Enable property for each view. However, since it appears that I cannot do this, can you suggest a workaround. I've attached the app's zip file.

PS apologies if the above explanation is not clear.
 

Attachments

  • GPSMap.zip
    38.7 KB · Views: 255

Informatix

Expert
Licensed User
Longtime User
If your concern is just to enable or disable some parts of your code, why don't you use a boolean? For example, when it's true, you run the code in the Touch event handler and in the Draw sub. When it's false, you skip these parts. You can even use the Enabled property of the AS for that (it's a free variable !).
 

johnaaronrose

Active Member
Licensed User
Longtime User
Use of acsfMap.Enabled being = False

If your concern is just to enable or disable some parts of your code, why don't you use a boolean? For example, when it's true, you run the code in the Touch event handler and in the Draw sub. When it's false, you skip these parts. You can even use the Enabled property of the AS for that (it's a free variable !).

It just seems strange that with buttons, the setting of the Enabled property to Flase stops a button's Click event Sub code being called. This alos happens with panels for Touch events. Therefore, IMO it should happen with Accelerated Surfaces for Touch events.

Anyway, when I changed the acsfMap's Touch event coding to immediately exit the event's coding by:

B4X:
If Not(acsfMap.Enabled) Then
Msgbox("Not drawing "&"acsfMap.Enabled="&acsfMap.Enabled, "acsfMap_Draw")
      Return

it caused a loop whereby the event's Sub was re-entered repeatedly.

PS I've attached a .zip of my project.
 

Attachments

  • GPSMap.zip
    38.7 KB · Views: 252

Informatix

Expert
Licensed User
Longtime User
It just seems strange that with buttons, the setting of the Enabled property to Flase stops a button's Click event Sub code being called. This alos happens with panels for Touch events. Therefore, IMO it should happen with Accelerated Surfaces for Touch events.
Only if I decide that I should work that way. Maybe I could add in a future version that Enabled property disables the touch event. It's noted.

Anyway, when I changed the acsfMap's Touch event coding to immediately exit the event's coding by:
B4X:
If Not(acsfMap.Enabled) Then
Msgbox("Not drawing "&"acsfMap.Enabled="&acsfMap.Enabled, "acsfMap_Draw")
      Return
You should not call MsgBox directly in the touch event. Place it in another sub and call this sub will CallSubDelayed.
 
Top