B4A Library CustomView and Lib Example - RatingBar Using Mainly Reflection

Create a Custom View from Android API using (mainly) Reflection - RatingBar

Suggested pre reading:

Reflection Documentation

CustomView with designer support

This is an example of what can be done using reflection and is intended mainly as a learning exercise (I always learn something when I build this sort of thing), although there is a lot of reflection code, it is not a time critical library so would probably be up to the task in general use. Time will tell.


In a bid to help those that want to better understand Reflection and the new CustomView, I have created this walkthrough that uses both to implement a Class that is not currently available which is RatingBar.

This is a simple Class, although I did find a couple of wrinkles as I tried to create this library and does not contain too many methods, although more could be added for inherited View methods if required.

To dive straight in we need to find the relevant Android API documentation for something interesting, in this case the RatingBar. Which is here : RatingBar | Android Developers.

Setting up the project

Then we need to create our outline project. To do that, we need a new standard B4A project to which we add a class (Project/Add New Module/Class) in this case we will call it RatingBarClass, then we need change the initialize method and add a DesignerCreateView method which are required to add the customview with the designer. This is all documented here : CustomView with designer support

Our starting class code should look something like this:
B4X:
'Class module
Sub Class_Globals
    Private mTarget As Object
    Private mEventName As String
End Sub

'Initializes the object. You can add parameters to this method if needed.
Public Sub Initialize (TargetModule As Object, EventName As String)
   mTarget = TargetModule
   mEventName = EventName
End Sub

Public Sub DesignerCreateView(Base As Panel, Lbl As Label, Props As Map)
End Sub

Now we want to add our custom view to the designer:
Open the designer and click AddView, select CustomView and change the name to RatingBar1, notice that the Event Name also changes. Then in the Custom Type field (below the + TextStyle field) select RatingBarClass. Save the Layout as 1.

Adding the RatingBar

Now for a bit of reflection. Add the reflection library to your project (download it and add it to your addl libs folder if you haven't already) click the Libs tab at the bottm right of the IDE, and select Reflection from the list, it should briefly say loading then display the version no and a tick in the box next to it.

Take a look at the Android Developers documentation (link above), we need to find a Public Constructor so that we can create an instance of the RatingBar. There are three, we want to be able to select the size of the displayed stars, to do this we need to provide a default style so we need a constructor that will accept the style.

B4X:
RatingBar(Context context, AttributeSet attrs, int defStyle)
The Context class holds information about the applications environment and needs to be passed to the constructor to enable creation, the Reflection Library gives us access to the Context via it's GetContext method. We don't need to pass anything for the attribute set so we can use null, the defStyle we use to define the size of the stars displayed which is an integer number defined in the Android API.

The Reflection library also provides two create object methods. As we need to pass parameters we need createobject2.

This is a similar process to the Dim of B4a, it returns a pointer to the created object with which we can call more methods on it.

Because Basic4Android doesn't know about all of the objects that could possibly be available from the Android API, we need to use a generic B4A type. In this case as our Object is a child of the View class we will use View so that we can use methods inherited from View as well as those specific to RatingBar.

So in Class_Globals we put:
B4X:
Dim RB As View
Then create the object in DesignerCreateViews:
B4X:
'Create RatingBar Object
Dim R As Reflector
Dim mSize As Int = 16842876
RB=R.CreateObject2("android.widget.RatingBar",Array As Object(R.GetContext,Null,mSize), Array As
String("android.content.Context","android.util.AttributeSet","java.lang.int"))
For reflection to be able to access the objects we need to provide the full qualified names which are available in the Android developers documentation and the Reflection documentation.

Base is the panel that the designer passes to DesignerCreateView for us to build our view on, we can now add our RatingBar object RB to the Base Panel:
B4X:
Base.AddView(RB,0,0,100%x,100%y)
Using methods inherited from View we can then set the Width and Height to a value that is an android constant (although we can't access the constants directly) that means Wrap Content which ensures that the View is just big enough to display then number of stars we ask it to.

B4X:
'-2 = Wrap Content, will make the correct number of starts appear
RB.Width=-2
RB.Height=-2

Using the CustomView

We can now set up our Main activity as we would normally to load a layout, i.e. Dim the view and load the layout as below:

B4X:
Sub Process_Globals
   'These global variables will be declared once when the application starts.
   'These variables can be accessed from all modules.
End Sub

Sub Globals
   'These global variables will be redeclared each time the activity is created.
   'These variables can only be accessed from this module.
   Dim RB As RatingBarClass
End Sub

Sub Activity_Create(FirstTime As Boolean)
   'Do not forget to load the layout file created with the visual designer. For example:
   'Activity.LoadLayout("Layout1")
   Activity.LoadLayout("1.bal")
End Sub

And our class code looks like:
B4X:
Sub Class_Globals
   Private mTarget As Object
      Private mEventName As String
   Dim RB As Object
End Sub
'Initializes the object. You can add parameters to this method if needed.
Public Sub Initialize (TargetModule As Object, EventName As String)
   mTarget = TargetModule
   mEventName = EventName
End Sub
Public Sub DesignerCreateView(Base As Panel, Lbl As Label, Props As Map)
   'Create RatingBar Object
   Dim R As Reflector
   Dim mSize As Int = 16842876
    RB=R.CreateObject2("android.widget.RatingBar",Array As Object(R.GetContext,Null,mSize), Array As String("android.content.Context", "android.util.AttributeSet", "java.lang.int"))
   Base.AddView(RB,0,0,100%x,100%y)
   '-2 = Wrap Content, will make the correct number of stars appear
   RB.Width=-2
   RB.Height=-2
End Sub
Compile and run this and you should see the rating stars in the panel on the screen. That's the hard work done.

Now we can go through the rest of the methods available in the RatingBar Class and add then to our CustomView taking care to use the correct type names in the reflection calls. These are in the complete project download.

As an example here is the get/setter code to get/set the number of stars note the lower case get and set at the beginning of the sub names:
B4X:
'Get or set the number of stars shown.
Sub getNumStars As Int
   Dim R As Reflector
   R.Target=RB
   Return R.RunMethod("getNumStars")
End Sub
Sub setNumStars(Num As Int)
   Dim R As Reflector
   R.Target=RB
   R.RunMethod2("setNumStars",Num,"java.lang.int")
End Sub
I have created one class specific get/setter for visibility:
B4X:
'Get or Set Rating Bar visibility
Sub setIsVisible(Visible As Boolean)
   mBase.Visible=Visible
End Sub
Sub getIsVisible As Boolean
   Return mBase.Visible
End Sub
If you look in the IDE IsVisible will be one method.
Set as:
B4X:
RB.IsVisible = True
And get as:
B4X:
If RB.IsVisible Then ...

Using the CustomView in code

To add a custom view to code, we need to add a second initialize path. This avoids problems negotiating the parameters passed to DesignerCreateView by the designer.
Here I have created a Sub called Setup, which mirrors the DesignerCreateSub and we can freely call it, after we have called initialize. I stress that this is only if you want to add this view manually, there is no need to run either if you add the View using the designer.

Additional functionality

Starsize

There are two default star sizes, selecting them is difficult with the designer as there are no optional fields. For now this code will use the Tag field. Enter 'Small' in the tag to display small stars. The designer passes the settings in the Label View that is passed to DesignerCreateView which can therefore be accessed as
B4X:
If Lbl.Tag = "Small" Then ...
etc.

Code for the selection has been added to the class.

If you add the CustomView manually you need to call Initialize and Setup, one of the parameters for setup is size.

RatingChanged CallBack

This library will now work as it is, the only thing that is missing is the onRatingBarChangeListener. It is not possible to set this using reflection, so for the sake of completeness, I have provided a single method Java Library for the purpose, all it does is tie the eventName provided to the onRatingBarChangeListener so that when the stars are changed on screen, the sub eventName + "_RatingChanged" is called. This is a separate file in this post and it's jar and xml files should be added to the additional library files in the normal manner if you want to use it.

Adding a callsub using mTarget and mEventName updated from the initialization subroutine which gives us the module from which the class instance was created and the eventname, we can redirect the callback to the relevant module and Subroutine.

B4X:
'Callback from java lib
Sub RB_RatingChanged(Rating As Float,UserChanged As Boolean)
   
   Log(Rating&" "&UserChanged)
   Dim SubToCall As String =mEventName&"_RatingChanged"
   CallSub3(mTarget,SubToCall,Rating,UserChanged)
End Sub

And to allow the events to be displayed in the designer add
B4X:
#Event: RatingChanged(Rating As Float,UserChanged As Boolean)
to the beginning of the class code.

Files attached are the full project (RB.zip), and a Java Library which implements a RatingChanged callback (RatingBar.zip).

Download and unzip the Java Library files to your addlLibs folder. if you don't want to install and use the callback library just comment out the lines:

B4X:
Dim RBLib As RatingBarLib

From Class_Globals and

B4X:
RBLib.setCallback(RB,"RB")

from the Setup and DesignerCreateView Subs

The project contains two activities, one with the View added with the designer and one added manually, click anywhere on the screen apart from the stars to switch activities.

I've enjoyed putting this together, I hope it's useful.

Requirements:


Versions:

V1.1 RB.Zip added Event function and call to initializing module.
RatingBar.Zip (Library) removed un-needed files from jar - reduced size

V1.2 RB.zip implemented methods as get/setters - a little tidier.
 

Attachments

  • RatingBar.zip
    2.4 KB · Views: 668
  • RB1.2.zip
    9.4 KB · Views: 672
Last edited:

moster67

Expert
Licensed User
@tds: yep, you're right. Steve has written a great tutorial. I will walk through it properly tomorrow.

Thank you Steve. You have a nice way of explaining stuff - I've seen that in many of your helpful posts here at the forum. I wish you could partnership with that user who is writing the B4A-book. I think you would be a great asset for him.
 

stevel05

Expert
Licensed User
Thanks All,

@Moster67 I hope it makes sense when you go through it.
 

m643

Member
Licensed User
Hi Stevel,

I have two questions: Is it possible to change the color of the stars? Other question is how I can disable the onclick event on the rating bar because I want the bar only to show the stars not to change.

Thank you
 

stevel05

Expert
Licensed User
To take the second question first, you just need to implement setIsIndicator(boolean isIndicator) with reflection (see the android documentation).

Changing the color is more problematic as it uses pre-drawn images, there is a solution here which I haven't tried and which I'm not sure how to implement on B4A, or if it can bee. I'll look into it a bit further.
 

stevel05

Expert
Licensed User
It's not possible to change the color of the stars as RatingBar expects the style do be defined in XML which B4A doesn't use.
 

Tom Christman

Active Member
Licensed User
Thanks very much for this tutorial..........as Moster67 said, you have a wonderful quality for structured tutorials. And many of us appreciate the candor and patience in your postings (ala Klaus) for those of us who struggle with the quirkiness of the Android OS and Erels great software and its applications.
 

stevel05

Expert
Licensed User

scsjc

Well-Known Member
Licensed User
hello, nice work.... can use that to vote my app on playstore-rate direct ???
 

stevel05

Expert
Licensed User
It's just an input method, you would need to write the rest of the code.
 
Top