a presentation at AnDevCon from @chiuki

Here is the deck with out youtube

You can inherit from other views


saveInstanceState
restoreInstanceState

You can use xml attributes to define the component in the layout file


onLayout
onMeasure
onDraw
dispatchDraw

See what their responsibliities are


measure all children
layout all children

Tell the parent how big you are.

android api setMeasuredDimension

Search for: android api setMeasuredDimension

Implementing a custom view doc from android

Here is onMeasure api doc

Subclasses should override onMeasure(int, int) to provide better measurements of their content

Layout is a two pass process: a measure pass and a layout pass. The measuring pass is implemented in measure(int, int) and is a top-down traversal of the view tree. Each view pushes dimension specifications down the tree during the recursion. At the end of the measure pass, every view has stored its measurements. The second pass happens in layout(int, int, int, int) and is also top-down. During this pass each parent is responsible for positioning all of its children using the sizes computed in the measure pass.

When a view's measure() method returns, its getMeasuredWidth() and getMeasuredHeight() values must be set, along with those for all of that view's descendants. A view's measured width and measured height values must respect the constraints imposed by the view's parents. This guarantees that at the end of the measure pass, all parents accept all of their children's measurements. A parent view may call measure() more than once on its children. For example, the parent may measure each child once with unspecified dimensions to find out how big they want to be, then call measure() on them again with actual numbers if the sum of all the children's unconstrained sizes is too big or too small.

whats up with LayoutParams class

Understand layout parameters a bit more

A direct document on custom components from Android docs

Every ViewGroup class implements a nested class that extends ViewGroup.LayoutParams. This subclass contains property types that define the size and position for each child view, as appropriate for the view group. As you can see in figure 1, the parent view group defines layout parameters for each child view (including the child view group).


@Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    final int count = getChildCount();

    int maxHeight = 0;
    int maxWidth = 0;

    for (int i = 0; i < count; i++) {
      final View child = getChildAt(i);
      if (child.getVisibility() != GONE) {
        measureChild(child, widthMeasureSpec, heightMeasureSpec);
      }
    }

    maxWidth += getPaddingLeft() + getPaddingRight();
    maxHeight += getPaddingTop() + getPaddingBottom();

    Drawable drawable = getBackground();
    if (drawable != null) {
      maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
      maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
    }

    setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec),
        resolveSize(maxHeight, heightMeasureSpec));
  }

This code is taken from a larger example at java2s


onMeasure
measureChild
setMeasuredDimension
resolveSize

ViewGroup resolveSize

Search for: ViewGroup resolveSize

How to customize a ViewGroup

Search for: How to customize a ViewGroup


merge in layouts
requestLayout
MeasuredSpec
Calling onMeasure of parent for advantage
getMeasuredDimension
Taking into account padding
dispatchDraw
resolveSize
setMeasuredDimension

Here is the doc for dispatchDraw

Called by draw to draw the child views. This may be overridden by derived classes to gain control just before its children are drawn (but after its own view has been drawn).

android View.java

Search for: android View.java

Here is one of those links for source code of View.java

I want to see how resolveSize() works


/**
     * Utility to reconcile a desired size with constraints imposed by a MeasureSpec.
     * Will take the desired size, unless a different size is imposed by the constraints.
     *  
     * @param size How big the view wants to be
     * @param measureSpec Constraints imposed by the parent
     * @return The size this view should be.
     */
    public static int resolveSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize =  MeasureSpec.getSize(measureSpec);
        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
            result = Math.min(size, specSize);
            break;
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

// skip step 2 & 5 if possible (common case)

        final int viewFlags = mViewFlags;
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
        if (!verticalEdges && !horizontalEdges) {
            // Step 3, draw the content

            mPrivateFlags |= DRAWN;
            onDraw(canvas);

            // Step 4, draw the children

            dispatchDraw(canvas);

            // Step 6, draw decorations (scrollbars)

            onDrawScrollBars(canvas);

            // we're done...

            return;
        }

It is basically saying, I am done drawing myself and I might do some housecleaning, do you have something to draw??


protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize =  MeasureSpec.getSize(measureSpec);
        
        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

/**
     * Sets the minimum width of the view. It is not guaranteed the view will
     * be able to achieve this minimum width (for example, if its parent layout
     * constrains it with less available width).
     * 
     * @param minWidth The minimum width the view will try to be.
     */
    public void setMinimumWidth(int minWidth) {
        mMinWidth = minWidth;
    }

Here is the source code for ViewGroup


@Override
    protected void dispatchDraw(Canvas canvas) {
        final int count = mChildrenCount;
        final View[] children = mChildren;
        int flags = mGroupFlags;

        if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {
            final boolean cache = (mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE;

            for (int i = 0; i < count; i++) {
                final View child = children[i];
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
                    final LayoutParams params = child.getLayoutParams();
                    attachLayoutAnimationParameters(child, params, i, count);
                    bindLayoutAnimation(child);
                    if (cache) {
                        child.setDrawingCacheEnabled(true);
                        child.buildDrawingCache();
                    }
                }
            }

            final LayoutAnimationController controller = mLayoutAnimationController;
            if (controller.willOverlap()) {
                mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;
            }

            controller.start();

            mGroupFlags &= ~FLAG_RUN_ANIMATION;
            mGroupFlags &= ~FLAG_ANIMATION_DONE;

            if (cache) {
                mGroupFlags |= FLAG_CHILDREN_DRAWN_WITH_CACHE;
            }

            if (mAnimationListener != null) {
                mAnimationListener.onAnimationStart(controller.getAnimation());
            }
        }

        int saveCount = 0;
        final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
        if (clipToPadding) {
            saveCount = canvas.save();
            final int scrollX = mScrollX;
            final int scrollY = mScrollY;
            canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
                    scrollX + mRight - mLeft - mPaddingRight,
                    scrollY + mBottom - mTop - mPaddingBottom);

        }

        mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;

        boolean more = false;
        final long drawingTime = getDrawingTime();

        if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {
            for (int i = 0; i < count; i++) {
                final View child = children[i];
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                    more |= drawChild(canvas, child, drawingTime);
                }
            }
        } else {
            for (int i = 0; i < count; i++) {
                final View child = children[getChildDrawingOrder(count, i)];
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                    more |= drawChild(canvas, child, drawingTime);
                }
            }
        }

        // Draw any disappearing views that have animations
        if (mDisappearingChildren != null) {
            final ArrayList<View> disappearingChildren = mDisappearingChildren;
            final int disappearingCount = disappearingChildren.size() - 1;
            // Go backwards -- we may delete as animations finish
            for (int i = disappearingCount; i >= 0; i--) {
                final View child = disappearingChildren.get(i);
                more |= drawChild(canvas, child, drawingTime);
            }
        }

        if (clipToPadding) {
            canvas.restoreToCount(saveCount);
        }

        // mGroupFlags might have been updated by drawChild()
        flags = mGroupFlags;

        if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) {
            invalidate();
        }

        if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0 &&
                mLayoutAnimationController.isDone() && !more) {
            // We want to erase the drawing cache and notify the listener after the
            // next frame is drawn because one extra invalidate() is caused by
            // drawChild() after the animation is over
            mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;
            final Runnable end = new Runnable() {
               public void run() {
                   notifyAnimationListener();
               }
            };
            post(end);
        }
    }

android layouts merge element

Search for: android layouts merge element

View constructor inflating a layout

Search for: View constructor inflating a layout


include tag
self inflating view

a.xml
************
<merge>
(bunch of controls)
</merge>

b.xml
*****************
<LinearLayout>
(bunch of controls)
<include a.xml>
(other bunch)
</LinearLayout>

Doing it this way you avoid creating another linear layout just because you don't have a root node you can use to share!!

android requestLayout

Search for: android requestLayout

what is the difference between invalidate and requestlayout in android

Search for: what is the difference between invalidate and requestlayout in android

if you merely change the drawing and keep the relative postions of everything on the screen the same then invalidate() will call the onDraw() methods. The layout step that figures out the measurements and placements will not be called. (That is my initial understading. This is so early looking at this I could be wrong. But it makes sense).

If you call requestLayout() then the layouts and positions are recalculated. This seem to imply a redraw. If so I am not sure why folks are doing this

requestLayout()
invalidate()

If requestLayout also triggers redraw why call invalidate?

Do I need both requestLayout, forceLayout and invalidate?

Search for: Do I need both requestLayout, forceLayout and invalidate?

An interesting anti-pattern when these are called from onSizeChanged

Read this first: How android draws views


Views only in the invalid region are redrawn
You can force a view to draw (only) using invalidate
parents are drawn first
Measure Phases
  Phase 1: find out how big each view wants to be (UNSPECIFIED)
  Phase 2: impose real sizes (through EXACT or AT MOST)
  each view sets the setMeasuredWidth

android onLayout

Search for: android onLayout

onSizeChanged and onLayout

Search for: onSizeChanged and onLayout

Google doc on the layouts from the masters: Romain, Chet

you will need a google account password like your gmail to access this!

Layout phase ends up calling a view that the layout has changed by calling "layout". A view will then call onSizeChanged so that the derived view can note its coordinates. This is a self method invocation.

However the onLayout() is not meant for itself but for others, like children or tell the world out there "hey! I am a view. I have just adjusted my sizes through onSizechanged(). I am telling you that. Before I go back and return from the layout phase see if you have anything else to do in this callback from me called "onLayout()".

Here is Chet, Romain's presentations on pareleys.com

Ahah! use generateDefaultLayoutParams()

In android dp pixel what is the standard size?

Search for: In android dp pixel what is the standard size?

it can still cross parent boundaries if it is laid out offset

we know that: margins are outside a view

left and top includes the padding


bottom|left

Avoid deep nested linear layouts

width/height set to zero and use weights

TableLayout is an optimized linearlayout

I know you would call requestLayout. But do you also call invalidate? See the source code of TextView to figure this out!

When you specify a view to be exactly 10 pixels in the xml files then you will get a measure pass that tells you to use 10 pixels EXACT!

when you say match parent, you will get at most the width of the parent!!

which will take into account whether to use exactly or at most etc...

Code for implementing a layoutparams is pretty identical to what the layouts does in the framework

Android Flow layout

Search for: Android Flow layout

android TextView.java

Search for: android TextView.java


/**
     * Call this when something has changed which has invalidated the
     * layout of this view. This will schedule a layout pass of the view
     * tree.
     */
    public void requestLayout() {
        if (ViewDebug.TRACE_HIERARCHY) {
            ViewDebug.trace(this, ViewDebug.HierarchyTraceType.REQUEST_LAYOUT);
        }

        mPrivateFlags |= FORCE_LAYOUT;

        if (mParent != null && !mParent.isLayoutRequested()) {
            mParent.requestLayout();
        }
    }

requestLayout

Search Google for: requestLayout

Search Android Developers Group for: requestLayout

Search Android Beginers Group for: requestLayout

Search Google Code for: requestLayout

Search Android Issues Database for: requestLayout

requestLayout Romain

Search for: requestLayout Romain

ViewGroup is a ViewParent!!

How does invalidate trigger a draw in Android

Search for: How does invalidate trigger a draw in Android

android ViewRoot.java

Search for: android ViewRoot.java

Here is the source code for ViewRoot.java

Here is how invalidate will end up traversing


//In ViewRoot!!
public void requestLayout() {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }

ViewRoot performTraversals

Search for: ViewRoot performTraversals

android when do you use forceLayout?

Search for: android when do you use forceLayout?

forcelayout romain

Search for: forcelayout romain

forcelayout dianne

Search for: forcelayout dianne


public class CallLogListItemView extends LinearLayout {
    public CallLogListItemView(Context context) {
        super(context);
    }

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

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

    @Override
    public void requestLayout() {
        // We will assume that once measured this will not need to resize
        // itself, so there is no need to pass the layout request to the parent
        // view (ListView).
        forceLayout();
    }
}

/**
     * {@inheritDoc}
     */
    @Override
    public void requestLayout() {
        if (mInitialized) {
            int count = getChildCount();
            for (int i = 0; i < count; i++) {
                getChildAt(i).forceLayout();
            }
        }

        super.requestLayout();
    }

it has nothing else to request layout but her parent. However if you happen to have children they need to be told as well.

Bottom line is measure will not work and will not call onMeasure if the layout flag is not set.

If this view didn't cause the requestLayout, and is a sibling or a child of the view that caused it, then its layout flag is empty. This means it will return the old measures. If I have a view group that I delete a view, that doesn't change the measurement requirements of its siblings.

However if it does then the view group needs to "touch" the child views with forced layout and they will measure when the pass comes along!!!

It is easier to tell you what forceLayout() is first. It is like a "touch" command in build environments. Usually when a file doesn't change the build dependencies will ignore it. So you force that file to be compiled by "touch"ing and thereby updating its time stamp.

So when you forceLayout() on a view you are marking that view (only that). If a view is not marked then its onMeasure() will not be called. You can see this in the measure() method of the view. It checks to see if this view is marked.

The behavior of requestLayout() is slightly different. a requestlayout touches the current view just like forceLayout() but also walks up the chain touching every parent of this view until it reaches ViewRoot. ViewRoot overrides this method and schedules a layout pass. Because it is just a schedule to run it doesnt start the layout pass right away. It will wait for the main thread to complete its chores and attend the message queue.

Still, explain to me when I use forceLayout!! I understand the requestLayout because I end up scheduling a pass. What am I doing with forceLayout? Obviously if you are calling a requestLayout on a view there is no point in calling forceLayout on that view. What is "force" anyway?

Do you recall, that a "force" is like a "touch" for build!! So you are "forcing" the file to compile.

When a view gets its requestLayout called, all its siblings are untouched. They don't have this flag setup. Or if the view that is touched is a viewgroup (like when you delete a view or add a view) the viewgroup doesn't need to calculate teh sizes of the its children because their sizes haven't changed. No point in calling their onMeasure. But if for some reason if the view group decides that these children needs to be measured, it will call forceLayout on each of them followed by measure() which now correctly calls onmeasure().

what happens the very first time?? who touched all views to begin with. I reason that (I haven't verified this yet) the views start out with this flag being set!! Let me see I will check in a minute and report again.

the initial request to layout a new view happens when the view is added to a parent like a viewgroup!! So there is that mystery set aside.