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

Discussion in 'Libraries developers questions' started by Erel, May 7, 2013.

  1. Erel

    Erel Administrator Staff Member Licensed 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:
    Code:
    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();
       }
    }
     
  2. corwin42

    corwin42 Expert Licensed 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:
    So If I want to add such a view manually I think I have to call the Initialize() and DesignerCreateView() methods manually?
     
    Last edited: May 7, 2013
  3. Erel

    Erel Administrator Staff Member Licensed User

    You can choose whether to provide an additional method for manually adding the views or use DesignerCreateView for that purpose as well.

    DesignerCreateView will be called automatically when the layout file is loaded.
     
  4. icefairy333

    icefairy333 Active Member Licensed User

    Hi Erel:

    can you show me how to use "props"?
     
  5. Erel

    Erel Administrator Staff Member Licensed User

    props HashMap is currently empty and of no use. It might be used in the future.
     
  6. icefairy333

    icefairy333 Active Member Licensed User

    I see.does the
    Code:
    @designername
    some as this :confused:
     
  7. Erel

    Erel Administrator Staff Member Licensed User

    @DesignerName is not a new annotation. It is used internally in some special cases where the exposed method name needs to be different than the real name.
     
  8. Informatix

    Informatix Expert Licensed 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?
     
  9. Erel

    Erel Administrator Staff Member Licensed 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).
     
  10. Informatix

    Informatix Expert Licensed User

    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?
     

    Attached Files:

  11. Erel

    Erel Administrator Staff Member Licensed User

    How did you implement setLeft / setTop ?
     
  12. Informatix

    Informatix Expert Licensed User

    Sorry, I forgot to add the source code:
    Code:
    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();
        }
        
    }
     
  13. Erel

    Erel Administrator Staff Member Licensed User

    For Left and Top you should just use the base panel instead of switchView.

    For Width and Height you have two options:
    - Change both base and switchView.
    - Add switchView with the FILL_PARENT constant and only modify base.
     
  14. corwin42

    corwin42 Expert Licensed 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.
     
  15. Informatix

    Informatix Expert Licensed 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.
     
  16. agraham

    agraham Expert Licensed User

    It's not reflection that is failing it is this line in Globals. I can't immediately see why :confused:

    Code:
    //BA.debugLineNum = 24;BA.debugLine="Dim MySwitch As Switch";
    mostCurrent._myswitch = new anywheresoftware.b4a.objects.SwitchWrapper();
     
  17. agraham

    agraham Expert Licensed User

    Disregard above - Silly me :sign0161: I was going to install 2.70 this afternoon so although it compiles with 2.52 it doesn't find Common.DesignerCustomView at runtime.
     
  18. Erel

    Erel Administrator Staff Member Licensed User

    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):
    Code:
    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();
       }
    }
     
  19. Informatix

    Informatix Expert Licensed 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.
     
  20. Erel

    Erel Administrator Staff Member Licensed User

    What about the solution proposed above? Does it answer the concern about the panel?

    This is a bit of a hack, but you can let the user write the cache size in the Tag field (to override a reasonable default value).
     
Loading...
  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice