Android Tutorial Custom View with Designer Support

Status
Not open for further replies.
Edit: please see the updated tutorial: https://www.b4x.com/android/forum/threads/62488/#content

Starting from v2.70, the visual designer supports custom views.

Note that the WYSIWYG designer (which runs on a real device / emulator) shows a place-holder instead of the custom view.

Adding the custom views with the designer is simpler and allows the developer to build and maintain the complete UI with the visual designer and designer script.

Custom views with designer support can be implemented as a class or inside a library.

Using custom views

1. Add the class or library to your project.
2. In the designer add a CustomView:

SS-2013-05-06_20.45.18.png


3. Set the custom type property:

SS-2013-05-06_20.45.50.png


4. You can now set the view properties and also treat it like any other view in the designer script.

5. Add the required declarations and events:

SS-2013-05-06_20.46.59.png


Note that the view's runtime appearance depends on the implementation. It might ignore some of the properties.

Implementing a custom view with designer support

There are two ways to implement a custom view. It can be implemented as a class (which can also be compiled to a library) or it can be implemented in Java. A tutorial about Java implementation will be available in the "libraries developers forum".

The following two subs are required in order to be supported by the designer:
B4X:
Sub Initialize (TargetModule As Object, EventName As String)

Sub DesignerCreateView(Base As Panel, Lbl As Label, Props As Map)
When a layout file is loaded the Initialize method will be called followed by a call to DesignerCreateView.

Initialize sub parameters:

TargetModule - references the module that loads the layout file.
EventName - the events name property.

These two parameters allow you to later raise events like all standard views.

DesignerCreateView parameters:

Base - a panel that will be the parent for your custom view. The panel background and layout will be based on the values from the designer. Note that you are free to do whatever you need with this panel.

Lbl - the purpose of the label is to hold all the text related properties. The label will not appear (unless you explicitly add it).

Props - a Map with additional entries. Currently the only entry is an "activity" key that holds a reference to the parent Activity object.

The following example creates a button that supports double click and single click events. Note that this example is a bit simplistic.
B4X:
'Events declaration
#Event: DoubleClick
#Event: SingleClick
'Class module
Sub Class_Globals
   Private mTarget As Object
   Private mEventName As String
   Private btn As Button
   Private topPanel As Panel
   Private downTime As Long
   Private timer1 As Timer
End Sub

Public Sub Initialize (TargetModule As Object, EventName As String)
   mTarget = TargetModule
   mEventName = EventName
   timer1.Initialize("timer1", 300)
End Sub


Public Sub DesignerCreateView(Base As Panel, Lbl As Label, Props As Map)
   btn.Initialize("")
   'create a button and add it to the Base panel.
   Base.AddView(btn, 0, 0, Base.Width, Base.Height)
   btn.TextSize = Lbl.TextSize
   btn.TextColor = Lbl.TextColor
   btn.Gravity = Lbl.Gravity
   btn.Text = Lbl.Text
   btn.Typeface = Lbl.Typeface
   topPanel.Initialize("topPanel")
   'Add a transparent panel over the button.
   'the panel will handle the touch event.
   Base.AddView(topPanel, 0, 0, Base.Width, Base.Height)
End Sub

Private Sub topPanel_Touch (Action As Int, X As Float, Y As Float)
   If Action = topPanel.ACTION_DOWN Then
      If downTime + timer1.Interval > DateTime.Now Then
         timer1.Enabled = False
         'raise the DoubleClick event
         CallSub(mTarget, mEventName & "_" & "DoubleClick")
      Else
         downTime = DateTime.Now
         timer1.Enabled = True
      End If
   End If
End Sub

Private Sub Timer1_Tick
   timer1.Enabled = False
   'raise the event
   CallSub(mTarget, mEventName & "_" & "SingleClick")
End Sub
 
Last edited:

laviniut

Active Member
Licensed User
Longtime User
I must create for my PhD an application for blind users. so i created an interface with buttons as customview. the blind user put a finger on screen and with TTS i can speak what button is. when he get wanted button he can doubleclick on it to start proper activity. till now all is fine.
but if the blind user is moving the finger on the screen without lifting the finger, when he get another customview button the method singleclick is not fired to use TTS, only if the finger is lift and put again on the screen.
can you create another method for customview like touch for a button (in addition to singleclick and doubleclick) ?
 

stevel05

Expert
Licensed User
Longtime User
@laviniut
This question is in the wrong forum, I have replied here.
 

juanomoran

Member
Licensed User
Longtime User
Question about customView and existing TableView class

First of all, congratulations for this excelent product! It only took me 1 week to decide about buying a license. :sign0098:
Now, I have a question. I want to use the Designer's support for custom views and don't really get what should I do to "register" the "tableView" class (here).

If I got it right, I just need to add the DesignerCreateView method, since the class already has an Initialize method with a reference to the parent object. Once I create the DesignerCreateView method (only leaving the reference to the Base panel), I can select Table as a custom type in the customView properties' page in the designer.

However, when I run the application it stops on the setHeaders sub within the class. It keeps saying that the Header panel should be first initialized.

Any clues on that?

Thank you very much.

Juan
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
It is not possible to make the Table class a "custom view" without breaking existing code. Currently custom views miss a feature which is support for custom fields. I believe that this feature will be added in the next version. For the Table view I think that it is better to wait for that feature and only then modify it to be a custom view.
 

derez

Expert
Licensed User
Longtime User
Last edited:

juanomoran

Member
Licensed User
Longtime User
Thank you Erel. The problem is how to handle different screen configurations and rotation with a tableView. However, I think that falls a little off-topic here. I will open the question in another thread. Thank you.
 

corwin42

Expert
Licensed User
Longtime User
Is it possible to call Base.LoadLayout("xxx") in DesignerCreateView sub?

I have a simple layout with a seekbar and a label I want to load. If I try to load the layout in DesignerCreateView I get a Classcastexception

** Activity (widgetconfig) Create, isFirst = true **
clsseekbar_designercreateview (java line: 59)
java.lang.RuntimeException: java.lang.ClassCastException: java.lang.String cannot be cast to android.widget.TextView
at anywheresoftware.b4a.keywords.LayoutBuilder.loadLayout(LayoutBuilder.java:151)
at anywheresoftware.b4a.objects.PanelWrapper.LoadLayout(PanelWrapper.java:110)
at de.amberhome.worldclock.clsseekbar._designercreateview(clsseekbar.java:59)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:169)
at anywheresoftware.b4a.objects.CustomViewWrapper.AfterDesignerScript(CustomViewWrapper.java:50)
at anywheresoftware.b4a.keywords.LayoutBuilder.loadLayout(LayoutBuilder.java:144)
at anywheresoftware.b4a.objects.PanelWrapper.LoadLayout(PanelWrapper.java:110)
at de.amberhome.worldclock.widgetconfig._activity_create(widgetconfig.java:318)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:169)
at de.amberhome.worldclock.widgetconfig.afterFirstLayout(widgetconfig.java:89)
at de.amberhome.worldclock.widgetconfig.access$100(widgetconfig.java:16)
at de.amberhome.worldclock.widgetconfig$WaitForLayout.run(widgetconfig.java:74)
at android.os.Handler.handleCallback(Handler.java:725)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:5074)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.ClassCastException: java.lang.String cannot be cast to android.widget.TextView
at anywheresoftware.b4a.objects.CustomViewWrapper.AfterDesignerScript(CustomViewWrapper.java:44)
at anywheresoftware.b4a.keywords.LayoutBuilder.loadLayout(LayoutBuilder.java:144)
... 24 more
java.lang.RuntimeException: java.lang.ClassCastException: java.lang.String cannot be cast to android.widget.TextView
** Service (advancedwidget) Start **
Action: android.appwidget.action.APPWIDGET_UPDATE
Extra: Bundle[{android.intent.extra.ALARM_COUNT=1}]
General: appWidgetIds: (ArrayList) [4, 8]
Update Widget: 4
 

stevel05

Expert
Licensed User
Longtime User
I had a similar problem, I don't think you can do it directly in the DesignerCreateView sub, presumably because when that is running, a layout is still being loaded so you would be nesting loadlayouts and all the required layout details may not have yet been calculated .

I put it in a separate sub and used callsubdelayed from within DesignerCreateView.
 
Last edited:

klaus

Expert
Licensed User
Longtime User
Hi Erel,
Could you please add in the Props - Map also the Parent view of a CustomView.
Props.Get("activity") returns the Activity.
When a CustomView is on a Panel I would like to get the Panel object with something like Props.Get("parent")

Best regards.
 

kiki78

Active Member
Licensed User
Longtime User
Hi Erel,

Is there a way to show and adjust properties of my CustomView in designer ?

Best Regards,
 

kiki78

Active Member
Licensed User
Longtime User
So, for now custom properties must be set in code.
Do you think you can in the future add something, for example #something marker, on property to expose it in designer ?

Best Regards,
 

andrewj

Active Member
Licensed User
Longtime User
Hi,
I have used this example to create my custom view class, but two things don't work as expected:
1. I don't seem to be able to add the view from code and add it straight to the parent view in the activity (the Designer approach isn't appropriate for my project). I want to be able to write code like:
B4X:
MyCustomView.Initialize("Main", "TabStrip")
pnlMain.AddView(MyCustomView.SlidingTabStripPanel, 0, 0, 100%x, 40dip)
That doesn't work because B4A doesn't recognise my class as a subtype of View. Instead I have to expose the base view from the class and use that instead.

2. B4A doesn't recognise my event signatures with the standard "sub<space><tab>" approach. I have to hand code them in the activity module.

Any ideas?
Thanks
Andrew
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
Note that if you don't intend to add the view with the designer then there is no need to use this feature. The only reason for adding the DesignerCreateView method is to make it compatible with the designer.

1. This is how it works. Another option is to create an "AddToPanel" sub in your class and then add the views to the panel from your class code. You can also pass an Activity instead of a Panel.

2. The events will only be recognized if you compile your class into a library (which is a very simple step) and reference the library.
 
Status
Not open for further replies.
Top