34

I'm learning about using Custom Views from the following:

http://developer.android.com/guide/topics/ui/custom-components.html#modifying

The description says:

Class Initialization As always, the super is called first. Furthermore, this is not a default constructor, but a parameterized one. The EditText is created with these parameters when it is inflated from an XML layout file, thus, our constructor needs to both take them and pass them to the superclass constructor as well.

Is there a better description? I've been trying to figure out what the constructor(s) should look like and I've come up with 4 possible choices (see example at end of post). I'm not sure what these 4 choices do (or don't do), why I should implement them, or what the parameters mean. Is there a description of these?

public MyCustomView()
{
    super();
}

public MyCustomView(Context context)
{
    super(context);
}

public MyCustomView(Context context, AttributeSet attrs)
{
    super(context, attrs);
} 

public MyCustomView(Context context, AttributeSet attrs, Map params)
{
    super(context, attrs, params);
} 
dandan78
  • 12,242
  • 12
  • 61
  • 73
Mitch
  • 1,671
  • 3
  • 25
  • 38
  • Similar question here: http://stackoverflow.com/questions/9195713/do-i-need-all-three-constructors-for-an-android-custom-view – mbonnin Sep 01 '16 at 20:46

3 Answers3

66

You don't need the first one, as that just won't work.

The third one will mean your custom View will be usable from XML layout files. If you don't care about that, you don't need it.

The fourth one is just wrong, AFAIK. There is no View constructor that take a Map as the third parameter. There is one that takes an int as the third parameter, used to override the default style for the widget.

I tend to use the this() syntax to combine these:

public ColorMixer(Context context) {
    this(context, null);
}

public ColorMixer(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

public ColorMixer(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    // real work here
}

You can see the rest of this code in this book example.

CommonsWare
  • 910,778
  • 176
  • 2,215
  • 2,253
  • 7
    super(context, attrs) and super(context, attrs, 0) works differentially for me. First one is ok, but second removes original style from a view. Is it a bug in newer versions of Android? – broot Jan 26 '12 at 14:56
  • 9
    Yes, my `this()` approach had flaws. Just do a plain chain to the superclass (e.g., `super(context, attrs)`) and put a call to a common private method (e.g., `init();`) in each constructor. See https://github.com/commonsguy/cwac-colormixer/blob/master/src/com/commonsware/cwac/colormixer/ColorMixer.java for an example. – CommonsWare Jan 26 '12 at 15:01
  • Yes, I did exactly the same thing, but called this(context, null) in the first constructor. One flaw is that you have to initialize final fields in both constructors - you can't do that in init() method. – broot Jan 26 '12 at 16:19
  • @Brutall: True, though I tend to initialize final data members directly in the data member declaration, where possible. – CommonsWare Jan 26 '12 at 17:05
  • Is it possible an app would crash on linking the constructors? Not always, but I do get a crashreport of a galaxy s2, and I think it is related to this – vanleeuwenbram May 11 '12 at 06:27
  • 1
    @vanleeuwenbram Yes, using this approach the app will crash on any device running Gingerbread or lower. That is because `super(context, attrs, defStyle);` requires **API Level 11**. For backwards compatibility use a private `init()` method instead. – jenzz Mar 06 '13 at 15:38
  • happened to me on 4.0 so not only gingerbread when the user had a custom font installed (was extending a textview) – vanleeuwenbram Mar 07 '13 at 08:30
11

Here's a my pattern (creating a custom ViewGoup here, but still):

// CustomView.java

public class CustomView extends LinearLayout {

    public CustomView(Context context) {
        super(context);
        init(context);
    }

    public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public CustomView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context);
    }

    private void init(Context ctx) {
        LayoutInflater.from(ctx).inflate(R.layout.view_custom, this, true);
            // extra init
    }

}

and

// view_custom.xml

<merge xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- Views -->
</merge>
yanchenko
  • 53,981
  • 33
  • 142
  • 163
  • This is not as effective as the @CommonsWare solution because it does not let you have fields marked as 'final', whereas using a chain of this() calls will. – greg7gkb Aug 08 '12 at 22:49
  • 3
    @greg7gkb However @Commonsware solution is flawed. You have to call each separate super and use an `init()` – Blundell Mar 07 '13 at 11:51
  • No need to pass the Context to init(), you can just call getContext() – Ran Jul 25 '13 at 11:22
8

When you are adding your custom View from xml like :

 <com.mypack.MyView
      ...
      />

you will need the public constructor MyView(Context context, AttributeSet attrs), otherwise you will get an Exception when Android tries to inflate your View.

And when you add your View from xml and also specify the android:style attribute like :

 <com.mypack.MyView
      style="@styles/MyCustomStyle"
      ...
      />

you will also need the third public constructor MyView(Context context, AttributeSet attrs,int defStyle) .

The third constructor is usually used when you extend a style and customize it, and then you would like to set that style to a given View in your layouts

Edit Details

public MyView(Context context, AttributeSet attrs) {
            //Called by Android if <com.mypack.MyView/> is in layout xml file without style attribute.
            //So we need to call MyView(Context context, AttributeSet attrs, int defStyle) 
            // with R.attr.customViewStyle. Thus R.attr.customViewStyle is default style for MyView.
            this(context, attrs, R.attr.customViewStyle);
    }

See this

Community
  • 1
  • 1
AndroidGeek
  • 30,803
  • 14
  • 212
  • 262
  • I have few custom views in the same layout. How can I pass to the constructor an index which tells it which of the custom views it belongs to? – Zvi Apr 05 '16 at 05:45
  • You should read TypedArray and styleable attributes. http://stackoverflow.com/questions/11039829/how-to-pass-view-reference-to-android-custom-view – AndroidGeek Apr 05 '16 at 06:03
  • @Nepstar, I read that post and I did not understand how will it solve my problem, because styleable attributes, as far as I understood, are static properties defined before the app is running. Thus all the instances of my custom components will get the same data, whereas I need a unique id for each running one. – Zvi Apr 05 '16 at 10:14
  • To clarify, each of my components is part of a list and I need to know the position of the component in the list, as well as the data it contains. Moreover, onClick will not do because the custom component is a numberPicker which gets many clicks. Only after the user clicks a SAVE button do I need to collect the data in each numberPicker of each row. – Zvi Apr 05 '16 at 10:17
  • you should create a method like setIndex in your customView and then you can change it programatically like (textView.setText("DummyText"); http://www.101apps.co.za/index.php/articles/creating-custom-views.html this tutorial shows you how to change circleColor at runtime(programmatically) – AndroidGeek Apr 06 '16 at 04:59
  • Thanks but I need in the opposite direction - from the component to my fragment. Anyway, I have solved my problem by using a listener in my custom component. Thanks anyway. – Zvi Apr 06 '16 at 12:42
  • Zvi the listener in custom component is also called like yourCustomComponet.setListener(new OnClickOrWhatever). It is a method which I have told you to implement – AndroidGeek Apr 07 '16 at 06:38
  • This answer is __wrong__. The defStyle constructor has nothing to do with the style xml attribute. The LayoutInflater always calls `MyView(Context context, AttributeSet attrs)` – mbonnin Sep 01 '16 at 20:44
  • @mbonnin See edit. LayoutInflater calls MyView(Context context,Attributes) and in that function it calls the third constructor and it send the style to it. The thrid constructor is responsible for style. – AndroidGeek Sep 02 '16 at 04:38
  • What MyView(context,attr) does is up to your implementation. Technically speaking, the LayoutInflater never calls the 3rd constructor directly. You can call it if you want but you can also just can super(context, attrs) – mbonnin Sep 02 '16 at 14:36
  • The statement you said "The defStyle constructor has nothing to do with the style xml attribute" is wrong .Read customView – AndroidGeek Sep 03 '16 at 15:46