-1

EDIT: I was missing a key concept, the message queue, while creating my custom view and trying to update the view without using it turned out being the source of the issue I was having. This gets cleared up here:

Android: Writing Custom Views that Support The Message Queue

It turns out that you need to let the runtime/android call OnMeasure after OnCreate exits ("like normal"), and in the mean time put any updates to the view in the message queue via the .post method.

So, you can grab a handle to the view, via FindViewById, in OnCreate as usual, and then post any updates to it (either through overrides like setText, or your own set functions) within a runnable (see above link).


I've been working on a custom view in android for quite some time now and have throughly experimented with constructors, attributes, layout parameters, and drawing methods (OnMeasure, OnLayout, ...).

The parent-most viewgroup (the one declared in the layout file for rendering) inherits from RelativeLayout and contains three child views, all custom/inherited viewgroups as well, and so on.

I'm implementing a custom calendar (I know, very original and exciting), I'll call CustomCalendar. So, in my layout file I have the statment

<MonoDroid.CustomViews.CustomCalendar
  xmlns:calendar="http://schemas.android.com/apk/res/net.monocross.appname"
  android:id="+id/Calendar1"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  calendar:row_count="5"
  calendar:col_count="7"
 ...
/>

This view resides along side a few others that are defined in the layout file as well, and thus the source of my complication, which will be stated now.

I'm trying to figure out how to get this view's dimensions before OnMeasure is called (where I can get them no problem, however because the layout dimensions of the children views are determined from this views dimensions, I cannot instantiate the childeren views until the first time OnMeasure is called, and this proves to be after I need it; after OnCreate exits).

That is, after calling SetContentView in OnCreate, and then instantiating the custom view from it via FindViewById(Resource.Id.Calendar1), the child views do not get instantiated as well if its done from the OnMeasure override (again, b/c OnMeasure isn't called until after OnCreate returns).

If I read the attributes passed into the constructor, alls I get from layout_width and layout_height are the constant -1 for fill_parent. I cannot just use the entire screen either, because there are other views to compensate for.


Therefore, I'm looking for the best approach in determining the dimensions of a custom view from within its constructor so that I can instantiate all of the view's childern from it (the constructor) with the proper layout dimensions so that the views are available from OnCreate (for updating with content) after SetContentView is called and before returning from OnCreate.

Another means to the same end will be fine with me, though I'm kinda looking to do this the way the infrastructure intends it to be (if there is such a thing).

Community
  • 1
  • 1
samus
  • 6,102
  • 6
  • 31
  • 69
  • Similar post: http://stackoverflow.com/questions/4393612/when-can-i-first-measure-a-view – samus Oct 17 '12 at 13:26

3 Answers3

1

You can't get dimensions in the constructor. At best, if they are defined in XML, you could get the layout width and height from the AttributeSet.

What you should be doing is overriding onMeasure and onLayout. You can do whatever calculations and setup you need in onMeasure, then pass the calculated values to measureChild(view, widthSpec, heightSpec). and you can mess with layout stuff in onLayout then call childView.layout(left, top, right, bottom).

toadzky
  • 3,806
  • 1
  • 15
  • 26
  • Ok, you definitly just added a missing piece to my puzzle, and cleared some misconceptions up for me. However, I'm not sure if this will solve the issue the OP describes (which I will not recite here, lol). I'll experiment with this. Thanks. – samus Oct 16 '12 at 21:11
  • From this it sounds like OnMeasure is where I should initialize my child views (and call measureChild on them). If this is the case, then from what lifecycle method of my main Activity should I grab an instance of my calendar view (via FindViewById) and fill it with data ? – samus Oct 16 '12 at 21:32
  • you can create the whole view structure in onCreate - all the children and everything. onMeasure and onLayout are called when the views are put on screen. if the viewgroups have rules for how to measure and/or layout children, it doesn't matter WHEN you create the hierarchy. – toadzky Oct 16 '12 at 21:35
  • My custom viewgroup has child views (internal to the viewgroup, and not specified in the layout file) that it needs to instantiate. Since onMeasure (apparently) isn't called until after onCreate exits (and renders), the childern objects will not be instantiated within onCreate for me to populate them with data if I initialize them in onMeasure. I've tried using onResume with similar results. – samus Oct 17 '12 at 13:08
  • you shouldn't create them in onMeasure. That method is for measuring. Create them in onCreate and add them to the group. The onMeasure method should control the sizing of those child views. – toadzky Oct 17 '12 at 13:36
  • Have you ever created a custom view before ? I'm not re-writing my code so I can add everything from onCreate, that is prepostrous! I'm just going for the less "automated" route and using the option I presented at the end of the OP. Also, you don't have a full understanding of my problem despite me giving all the detials in the OP, and reciting them over and over in the above comments. You should work on your answering skills. – samus Oct 17 '12 at 13:43
  • yes, i have written custom views and view groups. i've actually done a custom calendar view before. and just because you don't like my answer doesn't mean it's wrong – toadzky Oct 17 '12 at 13:57
  • I meant what I said in the most professional way possible, and it was meant to be purely constructive criticism. I appreciate your help too, it provided some insight, nonetheless. – samus Oct 17 '12 at 14:42
1

I think I had this problem. I'm not sure of the timing myself, but I did the following to get the dimensions

ViewTreeObserver viewTreeObserver = mOverlayFrameLayout.getViewTreeObserver();
                if (viewTreeObserver.isAlive()) {
                  viewTreeObserver.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
                    public void onGlobalLayout() {
                        mOverlayFrameLayout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                        //do some things
                    }
                  });
                }
alistair
  • 1,164
  • 10
  • 27
  • Hmm, will have to give this a try. I'm not familiar with the ViewTreeObserver class. Thanks. – samus Oct 16 '12 at 21:17
  • Ohh, "ViewTreeObserver", as in the XML layout tree! Nice this might be what I've been looking for (if it works I'll give you the kudos)... What object is mOverlayFrameLayout a member of (I'd guess a FrameLayout ...) – samus Oct 17 '12 at 13:14
  • Well, you pointed me in the right direction and lead me to http://stackoverflow.com/questions/4393612/when-can-i-first-measure-a-view, so credits to you, thanks!. – samus Oct 17 '12 at 13:28
0

EDIT Dont do this (see OP's edit note).


It ends up being that onMeasure is where this problem is solved...

Ya just got to call Measure on the view from OnCreate, which requires constructing the width and height MeasureSpec parameters that Measure uses:

public class Activity1 : Activity
{
    ...

    private CustomCalendar _calendar;

    public override void OnCreate()
    {
        SetContentView(Resource.Layout.Calendar);

        _calendar = FindViewById<CustomCalendar>(Resource.Id.Calendar1);           

        int widthMeasureSpec = View.MeasureSpec.MakeMeasureSpec(Resources.DisplayMetrics.WidthPixels, MeasureSpecMode.Exactly);
        int heightMeasureSpec = View.MeasureSpec.MakeMeasureSpec(Resources.DisplayMetrics.HeightPixels, MeasureSpecMode.Exactly);

        _calendar.Measure(widthMeasureSpec, heightMeasureSpec);

        ...
    }
}

Then just recursively do the same for all nested/child views.

I have a static bool variable in each custom view that keeps track if the view has been initialized, and check it in onMeasure to determine if I should initialize and measure them.

    protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        base.OnMeasure(widthMeasureSpec, heightMeasureSpec);

        if (!_initialized)
        {
            _WIDTH = MeasureSpec.GetSize(widthMeasureSpec);
            _HEIGHT = MeasureSpec.GetSize(heightMeasureSpec);

            InitViews();

            MeasureSpecMode widthMode = MeasureSpec.GetMode(widthMeasureSpec);
            MeasureSpecMode heightMode = MeasureSpec.GetMode(heightMeasureSpec);

            widthMeasureSpec = MeasureSpec.MakeMeasureSpec(_childView.LayoutParameters.Width, widthMode);
            heightMeasureSpec = MeasureSpec.MakeMeasureSpec(_childView.LayoutParameters.Height, heightMode);

            _childView.Measure(widthMeasureSpec, heightMeasureSpec);

            _initialized = true;
        }
    }
samus
  • 6,102
  • 6
  • 31
  • 69