4

Cheers,

I have an app that receives user input (2 numbers, width and height) and in theory depending on that input I have a custom view that should draw a grid (width and height).

Note:

  • These 2 values should be received before view attempts to draw itself.
  • These 2 values aren't constant and therefore I don't think XML approach can help.
  • I was told that adding another parameter to the View constructor is evil.
  • Do not confuse my 2 values with canvas.getWidth or etc.. these are values needed simply to draw something, nothing else.
  • My View is also a ViewGroup.
  • Main issue arises with Views declared in XML files.

I have temporarily solved this issue by making an SchemeContext class which contains those 2 static values and I simply set them in onCreate (before onCreateView) then use them in custom View onDraw when needed (SchemeContext.width). This is not really what people would call OOP I'm forcing global variables upon java and those are set on time because of the fragment lifecycle.

I've seen this answer How to pass variables to custom View before onDraw() is called?.

But it's more of a workaround than a solution (and probably not the fastest one). There has to be a sensible solution I don't think 3D games on android resort to these workarounds (SurfaceView with OpenGL is still a View right? :d).

If there is an obvious solution and this is an obvious double I'll remove the question.

Community
  • 1
  • 1
Spidey
  • 641
  • 12
  • 25
  • What's wrong with having a method `setValues(int width, int height)` in your custom view? After assigning these values inside `setValues(...)`, call invalidate()`. – Vikram Mar 11 '15 at 00:42
  • Why is adding a constructor evil? If you don't want to do that then why not add set the values before adding the view to its container? – Will Richardson Mar 11 '15 at 00:42
  • @Vikram Every object should be valid when its constructor returns. – Kevin Krumwiede Mar 11 '15 at 00:46
  • vikram: because I think it's an extra pass through viewgroup tree. Perhaps it's nitpicking in small apps but I figure it could cause an issue down the road. – Spidey Mar 11 '15 at 00:46
  • @KevinKrumwiede Please, do explain. – Vikram Mar 11 '15 at 00:50
  • @JavaNut13 One of the stackoverflow answers I've been reading (can't find post right now) suggested it steps away from practice and others developers reading it would be "wtf", also I'm not sure where the constructor gets called since we create object Views by inflating them so where would I pass my values. – Spidey Mar 11 '15 at 00:51
  • @JavaNut13 Regarding the second part. I didn't mention my custom view is also a viewgroup (his parent is ... screen) I will change that in my question. Imagining that I my example was View not ViewGroup I figure that solution wouldn't work for views that were added as children in XML. – Spidey Mar 11 '15 at 00:54
  • @Spidey How do you get a hold of these values? – Vikram Mar 11 '15 at 00:59
  • @Vikram There is a fragment (with dialog) dedicated to take user input then I pass these to fragment that inflates the view. – Spidey Mar 11 '15 at 01:00
  • @Vikram This is such a fundamental principle of OO programming that it's rarely discussed; I had some trouble finding a reference. But see [here](http://www.cis.upenn.edu/~matuszek/General/JavaSyntax/constructors.html): "The job of the constructor is..." EJ item 38 also touches on it: "It is critical to check the validity of constructor parameters to **prevent the construction of an object that violates its class invariants**." – Kevin Krumwiede Mar 11 '15 at 01:17
  • @KevinKrumwiede Okay. Let me explain what I suggested to user Spidey. Your custom view will have two members: `mWidth` & `mHeight` initialized to say `0` inside the constructor. These values are used inside the overridden `onDraw(Canvas)` method. There's also a method `setValues(int, int)` defined in your custom view which updates `mWidth` & `mHeight` and calls `invalidate()`. Upon inflation, we call `setValues(someWidth, someHeight)`. I do understand the concern about multiple passes. But, how does my suggestion `_allow_ the construction of an object that violates its class invariants` – Vikram Mar 11 '15 at 01:41
  • @Vikram Whether or not that would be acceptable depends on whether or not the class invariants allow `mWidth` and `mHeight` to be zero. That depends on what the class does (and what its contract says it does) with these values. If they're just arguments for `setMeasuredDimension(...)`, then zero is probably okay. If they're used for something else, then it depends. – Kevin Krumwiede Mar 11 '15 at 03:11
  • @KevinKrumwiede `Whether or not that would be acceptable depends on whether or not the class invariants allow mWidth and mHeight to be zero.` I'm sorry but what are you talking about? I said: _... mWidth & mHeight initialized to **say** 0 inside the constructor_. The implementation is @ Spidey's discretion. Of course, the initialization values will depend on _some_ criterion. But that isn't my concern. Its Spidey's. – Vikram Mar 11 '15 at 04:34
  • @Vikram Right. But the criteria may be such that it's impossible to assume any particular value in the constructor. – Kevin Krumwiede Mar 11 '15 at 04:36

2 Answers2

4

I haven't tried this, but I think it would be possible to do this fairly cleanly by overriding the LayoutInflater.Factory. That way, you can intercept the creation of the views that need additional parameters passed to their constructors, and let the rest of them fall through to default inflation.

For example, in your activity, before you inflate the view hierarchy:

LayoutInflater inflater = (LayoutInflater)getSystemService(LAYOUT_INFLATER_SERVICE);
MyInflaterFactory factory = new MyInflaterFactory();
// Pass information needed for custom view inflation to factory.
factory.setCustomValue(42);
inflater.setFactory(factory);

For your implementation of the factory:

class MyInflaterFactory implements LayoutInflater.Factory {
    public void setCustomValue(int val) {
        mCustomVal = val;
    }

    @Override
    public View onCreateView (String name, Context context, AttributeSet attrs) {
        if (name.equals("com.package.ViewWithCustomCreation")) {
            return new ViewWithCustomCreation(context, attrs, mCustomVal);
        }
        return null;
    }

    private int mCustomVal;
}
Zon
  • 12,838
  • 4
  • 69
  • 82
Reto Koradi
  • 49,246
  • 7
  • 76
  • 116
0

I was told that adding another parameter to the View constructor is evil.

Nonsense.

There are three (and in the newest APIs, four) different View constructors, each used in a different situation. (See this thread.) If you wanted to be able to declare your view in XML, for example, then you'd have to provide a constructor with exactly the right parameters, and have it call the corresponding superclass constructor. But there's nothing wrong with defining your own constructor (or even several of them) that call the superclass constructor intended for creating views programmatically.

The overriding principle is that every object must be valid when its constructor returns. So unless you can provide reasonable default values in your constructor, you have little choice but to accept the object's properties as constructor parameters.

Community
  • 1
  • 1
Kevin Krumwiede
  • 8,904
  • 4
  • 30
  • 70
  • I would likely agree with you for views that are created in java. But the issue for views or viewgroups declared in XML remains and I can't say declaring custom elements in XML should not be done because it's made possible. – Spidey Mar 11 '15 at 01:08
  • @Spidey That answer fails to justify the rather bold claim that developers expect subclass constructors to have exactly the same parameters as superclass constructors. And I am talking about views created in code. – Kevin Krumwiede Mar 11 '15 at 01:10
  • For java coded Views you are right. I still have issues with XML parts. – Spidey Mar 11 '15 at 01:20
  • @Spidey I thought you said you couldn't use XML because the parameters come from the user at runtime. If you want to allow declaration in XML, you must provide a constructor that takes a `Context`, an `AttributeSet`, and nothing else. See [this answer](http://stackoverflow.com/a/2695649/1953590) for how to create custom attributes. – Kevin Krumwiede Mar 11 '15 at 01:29
  • My parameters do come from the user in runtime and my specific situation is that I made a custom viewgroup which I declared in XML to wrap it with DrawerLayout. Is that bad practice because I don't see why (other than the value passing inconvenience). – Spidey Mar 11 '15 at 01:38
  • @Spidey We're miscommunicating somewhere. The parameters can't be supplied by the user *and* declared in the XML, unless you mean that defaults are supplied in the XML and later changed by the user. – Kevin Krumwiede Mar 11 '15 at 04:37