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:
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.
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:
Then create the object in DesignerCreateViews:
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:
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.
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:
And our class code looks like:
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:
I have created one class specific get/setter for visibility:
If you look in the IDE IsVisible will be one method.
Set as:
And get as:
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
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.
And to allow the events to be displayed in the designer add
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:
From Class_Globals and
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.
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 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
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"))
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)
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
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
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
Set as:
B4X:
RB.IsVisible = True
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 ...
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)
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:
- B4A 2.70
- Reflection Library Written by Andrew Graham
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
Last edited: