Android Tutorial [java] Custom View with Designer Support (Java library)

Erel

B4X founder
Staff member
Licensed User
Longtime User
Please start with this tutorial: Custom View with Designer Support

Implementing custom views in a Java library is similar to custom views created in B4A.

The class must implement the DesignerCustomView interface.
This interface includes two methods:

void _initialize(BA ba, Object activityClass, String EventName)
void DesignerCreateView(PanelWrapper base, LabelWrapper lw, anywheresoftware.b4a.objects.collections.Map props)

Note that the _initialize method can be hidden by adding the @Hide annotation. The second method cannot be hidden.

For example, the following code creates a Switch view. This is a new view added in Android 4:
B4X:
package anywheresoftware.b4a.objects;

import android.widget.CompoundButton;
import android.widget.Switch;
import android.widget.CompoundButton.OnCheckedChangeListener;
import anywheresoftware.b4a.BA;
import anywheresoftware.b4a.BA.Events;
import anywheresoftware.b4a.BA.Hide;
import anywheresoftware.b4a.BA.ShortName;
import anywheresoftware.b4a.keywords.Common.DesignerCustomView;
import anywheresoftware.b4a.keywords.constants.Colors;

@ShortName("Switch")
@Events(values={"CheckedChange (Value As Boolean)"})
public class SwitchWrapper implements DesignerCustomView {
   private BA ba;
   private String eventName;
   private Switch switchView;
   @Hide
   public void _initialize(BA ba, Object activityClass, String EventName) {
      this.eventName = EventName.toLowerCase(BA.cul);
      this.ba = ba;
   }
   //this method cannot be hidden.
   public void DesignerCreateView(PanelWrapper base, LabelWrapper lw, anywheresoftware.b4a.objects.collections.Map props) {
      switchView = new Switch(ba.context);
      switchView.setText(lw.getText());
      switchView.setTypeface(lw.getTypeface());
      switchView.setTextSize(lw.getTextSize());
      switchView.setTextColor(lw.getTextColor());
      switchView.setOnCheckedChangeListener(new OnCheckedChangeListener() {

         @Override
         public void onCheckedChanged(CompoundButton arg0, boolean arg1) {
            ba.raiseEvent(this, eventName + "_checkedchange", arg1);
         }
         
      });
      base.AddView(switchView, 0, 0, base.getWidth(), base.getHeight());
      base.setColor(Colors.Transparent);
   }
   public void setChecked(boolean value) {
      switchView.setChecked(value);
   }
   public boolean getChecked() {
      return switchView.isChecked();
   }
}
 

corwin42

Expert
Licensed User
Longtime User
I still don't fully understand how it works.

Is the DesignerCreateView() method (or Sub in the class implementation way) only called by the designer? Or is it called by xxx.LoadLayout(), too? If yes, how can I still add such a view manually with AddView?

I think a complete working example would be very helpful (for both implementations (Java Library and B4A Class)).

Edit: Just have seen this quote in the other tutorial:
When a layout file is loaded the Initialize method will be called followed by a call to DesignerCreateView.

So If I want to add such a view manually I think I have to call the Initialize() and DesignerCreateView() methods manually?
 
Last edited:

Informatix

Expert
Licensed User
Longtime User
If I understand well how that's done, we cannot add a custom view directly to the activity. There's a panel in all cases. Right? I can understand that the panel acts as a placeholder in the designer, but what's the usefulness of this panel when the layout is loaded by LoadLayout?
 

Informatix

Expert
Licensed User
Longtime User
If you like you can ignore the base panel by removing it from the activity and adding the view directly to the activity, though it is not recommended in most cases.

The overhead of a few panels is insignificant (you will not be able to measure the difference).

Oh I agree. Performance is not what bothers me. It's the use.
I attached to this post the Switch library (copied from post #1, with two properties added: Left and Top) and a test project.
My steps:
1) I create a layout with the switch in the middle.
2) I open the layout with LoadLayout. Ok, my switch is loaded and it appears where I placed it.
3) I set its left and top properties to move it to the top left corner, but it does not move. It's logical: it's embedded inside a panel, so its coordinates are not relative to the activity but to this panel. I have to move the panel (cumbersome!). The problem is that I have no access to this panel. And getParent, with the Reflection library, raises an error.

So how can I move the view dynamically?
 

Attachments

  • Switch.zip
    11.2 KB · Views: 1,314

Informatix

Expert
Licensed User
Longtime User
Sorry, I forgot to add the source code:
B4X:
package anywheresoftware.b4a.objects;

import android.widget.CompoundButton;
import android.widget.Switch;
import android.widget.CompoundButton.OnCheckedChangeListener;
import anywheresoftware.b4a.BA;
import anywheresoftware.b4a.BA.Events;
import anywheresoftware.b4a.BA.Hide;
import anywheresoftware.b4a.BA.ShortName;
import anywheresoftware.b4a.keywords.Common.DesignerCustomView;
import anywheresoftware.b4a.keywords.constants.Colors;

@ShortName("Switch")
@Events(values={"CheckedChange (Value As Boolean)"})
public class SwitchWrapper implements DesignerCustomView {
    private BA ba;
    private String eventName;
    private Switch switchView;
    @Hide
    public void _initialize(BA ba, Object activityClass, String EventName) {
        this.eventName = EventName.toLowerCase(BA.cul);
        this.ba = ba;
    }
    //this method cannot be hidden.
    public void DesignerCreateView(PanelWrapper base, LabelWrapper lw, anywheresoftware.b4a.objects.collections.Map props) {
        switchView = new Switch(ba.context);
        switchView.setText(lw.getText());
        switchView.setTypeface(lw.getTypeface());
        switchView.setTextSize(lw.getTextSize());
        switchView.setTextColor(lw.getTextColor());
        switchView.setOnCheckedChangeListener(new OnCheckedChangeListener() {

            @Override
            public void onCheckedChanged(CompoundButton arg0, boolean arg1) {
                ba.raiseEvent(this, eventName + "_checkedchange", arg1);
            }
            
        });
        base.AddView(switchView, 0, 0, base.getWidth(), base.getHeight());
        base.setColor(Colors.Transparent);
    }
    public void setChecked(boolean value) {
        switchView.setChecked(value);
    }
    public boolean getChecked() {
        return switchView.isChecked();
    }
    
    public void setLeft(int value) {
        switchView.setLeft(value);
    }
    public int getLeft() {
        return switchView.getLeft();
    }
    public void setTop(int value) {
        switchView.setTop(value);
    }
    public int getTop() {
        return switchView.getTop();
    }
    
}
 

corwin42

Expert
Licensed User
Longtime User
I think you can store the panel in a class member variable in the DesignerCreateView() method. Then you will have access to it in the set/getLeft set/getTop methods.
 

Informatix

Expert
Licensed User
Longtime User
Sorry, but it's too cumbersome for me. I would have to distinguish if the user created the view manually (so there's no panel) or with the designer, and I'd have to handle a panel perfectly useless. Moreover, I have many calls to getParent in my classes and libraries, and I don't want to find another way to make it work. It's really disappointing.
The previous method is more convenient: placing a panel as a placeholder in the layout, then replacing it by the view after LoadLayout. Simple and effective.
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
Sorry, but it's too cumbersome for me. I would have to distinguish if the user created the view manually (so there's no panel) or with the designer, and I'd have to handle a panel perfectly useless. Moreover, I have many calls to getParent in my classes and libraries, and I don't want to find another way to make it work. It's really disappointing.
The previous method is more convenient: placing a panel as a placeholder in the layout, then replacing it by the view after LoadLayout. Simple and effective.

So don't use the panel. In the Java code, get the base panel parent, add your custom view (based on 'base' layout) and add your view to the parent.

Here is an example that uses the same code to create a view programmatically or with the designer (without a base panel):
B4X:
public class SwitchWrapper implements DesignerCustomView {
   private BA ba;
   private String eventName;
   private Switch switchView;
   
   public void Initliaze(BA ba, String EventName) {
      _initialize(ba, null, EventName);
   }
   
   @Hide
   public void _initialize(BA ba, Object activityClass, String EventName) {
      this.eventName = EventName.toLowerCase(BA.cul);
      this.ba = ba;
   }
   
   //programmatically add view
   public void AddToParent(ViewGroup Parent, @Pixel int left, @Pixel int top, @Pixel int width, @Pixel int height) {
      switchView = new Switch(ba.context);
      switchView.setOnCheckedChangeListener(new OnCheckedChangeListener() {

         @Override
         public void onCheckedChanged(CompoundButton arg0, boolean arg1) {
            ba.raiseEvent(this, eventName + "_checkedchange", arg1);
         }
         
      });
      Parent.addView(switchView, new BALayout.LayoutParams(left, top, width, height));
   }
   //this method cannot be hidden.
   public void DesignerCreateView(PanelWrapper base, LabelWrapper lw, anywheresoftware.b4a.objects.collections.Map props) {
      ViewGroup vg = (ViewGroup) base.getObject().getParent();
      base.RemoveView();
      AddToParent(vg, base.getLeft(), base.getTop(), base.getWidth(), base.getHeight());
      //set text properties
      switchView.setText(lw.getText());
      switchView.setTypeface(lw.getTypeface());
      switchView.setTextSize(lw.getTextSize());
      switchView.setTextColor(lw.getTextColor());
      
   }
   public void setChecked(boolean value) {
      switchView.setChecked(value);
   }
   public boolean getChecked() {
      return switchView.isChecked();
   }
}
 

Informatix

Expert
Licensed User
Longtime User
While I was thinking about how to improve your system, I discovered another problem. Some of my classes or libraries (and probably not only mine) have no default value for some of their parameters (for example, the memory cache size in UltimateListView). Once the object is initialized, this setting cannot be changed. Therefore, I do not see how to use your system in this case without changing my code and create potential risks (there's a reason, sometimes, to not allow changes for these parameters).
I imagined that we could communicate to the designer the initialization parameters (their name, their type and an arbitrary value which would serve as a default value) and these settings could be changed in the designer. Initialization would be done at the time of the LoadLayout, with the chosen settings.
Currently, we can set a text or a drawable for the custom view in the designer even if the custom view has no such properties. It's confusing.
 
Top