MOBILE

Android Application Development : Rolling Your Own Widgets (part 1) - Layout

11/19/2013 8:05:22 PM

“widget” is a just convenient term for a subclass of android.view.View, typically for a leaf node in the view tree. Many views are just containers for other views and are used for layout; we don’t consider them widgets, because they don’t directly interact with the user, handle events, etc. So the term “widget,” although informal, is useful for discussing the workhorse parts of the user interface that have the information and the behavior users care about.

Similarly, the MicroJobs application has a view that contains a list of names corresponding to locations on a map. As additional locations are added to the map, new name-displaying widgets are added dynamically to the list. Even this dynamically changing layout is just a use of pre-existing widgets; it does not create new ones.

A very complex widget, perhaps used as an interface tool implemented in several places (even by multiple applications), might even be an entire package of classes, only one of which is a descendant of View.

1. Layout

Most of the heavy lifting in the Android framework layout mechanism is implemented by container views. A container view is one that contains other views. It is an internal node in the view tree and subclasses of ViewGroup (which, in turn, subclasses View). The framework toolkit provides a variety of sophisticated container views that provide powerful and adaptable strategies for arranging a screen. AbsoluteLayout, LinearLayout , and RelativeLayout, to name some common ones, are container views that are both relatively easy to use and fairly hard to reimplement correctly. Since they are already available, fortunately you are unlikely to have to implement most of the algorithm discussed here. Understanding the big picture, though—how the framework manages the layout process—will help you build correct, robust widgets.

Example 1 shows a widget that is about as simple as it can be, while still working. If added to some Activity’s view tree, this widget will fill in the space allocated to it with the color cyan. Not very interesting, but before we move on to create anything more complex, let’s look carefully at how this example fulfills the two basic tasks of layout and drawing.

Example 1. A trivial widget
public class TrivialWidget extends View {

public TrivialWidget(Context context) {
super(context);
setMinimumWidth(100);
setMinimumHeight(20);
}

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

@Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.CYAN);
}
}

Dynamic layout is necessary because the space requirements for widgets change dynamically. Suppose, for instance, that a widget in a GPS-enabled application displays the name of the city in which you are currently driving. As you go from “Ely” to “Post Mills,” the widget receives notification of the change in location. When it prepares to redraw the city name, though, it notices that it doesn’t have enough room for the whole name of the new town. It needs to request that the screen be redrawn in a way that gives it more space, if that is possible.

Layout can be a surprisingly complex task and very difficult to get right. It is probably not very hard to make a particular leaf widget look right on a single device. On the other hand, it can be very tricky to get a widget that must arrange children to look right on multiple devices, even when the dimensions of the screen change.

Layout is initiated when the requestLayout method is invoked on some view in the view tree. Typically, a widget calls requestLayout on itself when it needs more space. The method could be invoked, though, from any place in an application, to indicate that some view in the current screen no longer has enough room to draw itself.

The requestLayout method causes the Android UI framework to enqueue an event on the UI event queue. When the event is processed, in order, the framework gives every container view an opportunity to ask each of its child widgets how much space each child would like for drawing. The process is separated into two phases: measuring the child views and then arranging them in their new positions. All views must implement the first phase, but the second is necessary only in the implementations of container views that must manage the layout of child views.

1.1. Measurement

The goal of the measurement phase is to provide each view with an opportunity to dynamically request the space it would ideally like for drawing. The UI framework starts the process by invoking the measure method of the view at the root of the view tree. Starting there, each container view asks each of its children how much space it would prefer. The call is propagated to all descendants, depth first, so that every child gets a chance to compute its size before its parent. The parent computes its own size based on the sizes of its children and reports that to its parent, and so on, up the tree.

The topmost LinearLayout asks each of the nested LinearLayout widgets for its preferred dimensions. They in turn ask the Buttons or EditText views they contain for theirs. Each child reports its desired size to its parent. The parents then add up the sizes of the children, along with any padding they insert themselves, and report the total to the topmost LinearLayout.

Because the framework must guarantee certain behaviors for all Views, during this process, the measure method is final and cannot be overridden. Instead, measure calls onMeasure, which widgets may override to claim their space. In other words, widgets cannot override measure, but they can override onMeasure.

The arguments to the onMeasure method describe the space the parent is willing to make available: a width specification and a height specification, measured in pixels. The framework assumes that no view will ever be smaller than 0 or bigger than 230 pixels in size and, therefore, it uses the high-order bits of the passed int parameter to encode the measurement specification mode. It is as if onMeasure were actually called with four arguments: the width specification mode, the width, the height specification mode, and the height. Do not be tempted to do your own bit-shifting to separate the pairs of arguments! Instead, use the static methods MeasureSpec.getMode and MeasureSpec.getSize.

The specification modes describe how the container view wants the child to interpret the associated size. There are three of them:


MeasureSpec.EXACTLY

The calling container view has already determined the exact size of the child view.


MeasureSpec.AT_MOST

The calling container view has set a maximum size for this dimension, but the child is free to request less.


MeasureSpec.UNSPECIFIED

The calling container view has not imposed any limits on the child, and so the child may request anything it chooses.

A widget is always responsible for telling its parent in the view tree how much space it needs. It does this by calling setMeasuredDimensions to set the properties that then become available to the parent, through the methods getMeasuredHeight and getMeasuredWidth. If your implementation overrides onMeasure but does not call setMeasuredDimensions, the measure method will throw IllegalStateException instead of completing normally.

The default implementation of onMeasure, inherited from View, calls setMea⁠sur⁠edDi⁠men⁠sions with one of two values, in each direction. If the parent specifies Meas⁠ureS⁠pec.UNSPECIFIED, it uses the default size of the view: the value supplied by either getSuggestedMinimumWidth or getSuggestedMinimumHeight. If the parent specifies either of the other two modes, the default implementation uses the size that was offered by the parent. This is a very reasonable strategy and allows a typical widget implementation to handle the measurement phase completely, simply by setting the values returned by getSuggestedMinimumWidth and getSuggestedMinimumHeight.

Your widget may not actually get the space it requests. Consider a view that is 100 pixels wide and has three children. It is probably obvious how the parent should arrange its children if the sum of the pixel widths requested by the children is 100 or less. If, however, each child requests 50 pixels, the parent container view is not going to be able to satisfy them all.

A container view has complete control of how it arranges its children. In the circumstance just described, it might decide to be “fair” and allocate 33 pixels to each child. Just as easily, it might decide to allocate 50 pixels to the leftmost child and 25 to each of the other two. In fact, it might decide to give one of the children the entire 100 pixels and nothing at all to the others. Whatever its method, though, in the end the parent determines a size and location for the bounding rectangle for each child.

Another example of a container view’s control of the space allocated to a widget comes from the example widget shown previously in Example 1. It always requests the amount of space it prefers, regardless of what it is offered (unlike the default implementation). This strategy is handy to remember for widgets that will be added to the toolkit containers, notably LinearLayout, that implement gravity. Gravity is a property that some views use to specify the alignment of their subelements. The first time you use one of these containers, you may be surprised to find that, by default, only the first of your custom widgets gets drawn! You can fix this either by using the setGravity method to change the property to Gravity.FILL or by making your widgets insistent about the amount of space they request.

It is also important to note that a container view may call a child’s measure method several times during a single measurement phase. As part of its implementation of onMeasure, a clever container view, attempting to lay out a horizontal row of widgets, might call each child widget’s measure method with mode MEASURE_SPEC.UNSPECIFIED and a width of 0 to find out what size the widget would prefer. Once it has collected the preferred widths for each of its children, it could compare the sum to the actual width available (which was specified in its parent’s call to its measure method). Now it might call each child widget’s measure method again, this time with the mode MeasureSpec.AT_MOST and a width that is an appropriate proportion of the space actually available. Because measure may be called multiple times, an implementation of onMeasure must be idempotent and must not change the application state.

A container view’s implementation of onMeasure is likely to be fairly complex. ViewGroup, the superclass of all container views, does not supply a default implementation. Each of the UI framework container views has its own. If you contemplate implementing a container view, you might consider basing it on one of them. If, instead, you implement measurement from scratch, you are still likely to need to call measure for each child and should consider using the ViewGroup helper methods: measureChild, measureChildren, and measureChildWithMargins. At the conclusion of the measurement phase, a container view, like any other widget, must report the space it needs by calling setMeasuredDimensions.

1.2. Arrangement

Once all the container views in the view tree have had a chance to negotiate the sizes of each of their children, the framework begins the second phase of layout, which consists of arranging the children. Again, unless you implement your own container view, you probably will never have to implement your own arrangement code. This section describes the underlying process so that you can better understand how it might affect your widgets. The default method, implemented in View, will work for typical leaf widgets, as demonstrated previously by Example 1.

Because a view’s onMeasure method might be called several times, the framework must use a different method to signal that the measurement phase is complete and that container views must fix the final locations of their children. Like the measurement phase, the arrangement phase is implemented with two methods. The framework invokes a final method, layout, at the top of the view tree. The layout method performs processing common to all views and then delegates to onLayout, which custom widgets override to implement their own behaviors. A custom implementation of onLayout must at least calculate the bounding rectangle that it will supply to each child when it is drawn and, in turn, invoke the layout method for each child (because it might also be a parent to other widgets).

It is worth reiterating that a widget is not guaranteed to receive the space it requests. It must be prepared to draw itself in whatever space is actually allocated to it. If it attempts to draw outside the space allocated to it by its parent, the drawing will be clipped by the clip rectangle. To exert fine control—to fill exactly the space allocated to it, for instance—a widget must either implement onLayout and record the dimensions of the allocated space or inspect the clip rectangle of the Canvas that is the parameter to onDraw.

Other  
  •  iPhone SDK 3 Programming : XML Processing - An RSS Reader Application
  •  iPhone SDK 3 Programming : XML Processing - Simple API for XML (SAX)
  •  iPhone SDK 3 Programming : XML Processing - Document Object Model (DOM)
  •  iPhone SDK 3 Programming : XML and RSS
  •  Windows Phone 8 : Making Money - Modifying Your Application, Dealing with Failed Submissions, Using Ads in Your Apps
  •  Windows Phone 8 : Making Money - Submitting Your App (part 3) - After the Submission
  •  Windows Phone 8 : Making Money - Submitting Your App (part 2) - The Submission Process
  •  Windows Phone 8 : Making Money - Submitting Your App (part 1) - Preparing Your Application
  •  Windows Phone 8 : Making Money - What Is the Store?
  •  BlackBerry Push APIs (part 3) - Building an Application that Uses the BlackBerry Push APIs - Checking the Status of a Push Request and Cancelling a Push Request
  •  
    Top 10
    Review : Sigma 24mm f/1.4 DG HSM Art
    Review : Canon EF11-24mm f/4L USM
    Review : Creative Sound Blaster Roar 2
    Review : Philips Fidelio M2L
    Review : Alienware 17 - Dell's Alienware laptops
    Review Smartwatch : Wellograph
    Review : Xiaomi Redmi 2
    Extending LINQ to Objects : Writing a Single Element Operator (part 2) - Building the RandomElement Operator
    Extending LINQ to Objects : Writing a Single Element Operator (part 1) - Building Our Own Last Operator
    3 Tips for Maintaining Your Cell Phone Battery (part 2) - Discharge Smart, Use Smart
    REVIEW
    - First look: Apple Watch

    - 3 Tips for Maintaining Your Cell Phone Battery (part 1)

    - 3 Tips for Maintaining Your Cell Phone Battery (part 2)
    VIDEO TUTORIAL
    - How to create your first Swimlane Diagram or Cross-Functional Flowchart Diagram by using Microsoft Visio 2010 (Part 1)

    - How to create your first Swimlane Diagram or Cross-Functional Flowchart Diagram by using Microsoft Visio 2010 (Part 2)

    - How to create your first Swimlane Diagram or Cross-Functional Flowchart Diagram by using Microsoft Visio 2010 (Part 3)
    Popular Tags
    Microsoft Access Microsoft Excel Microsoft OneNote Microsoft PowerPoint Microsoft Project Microsoft Visio Microsoft Word Active Directory Biztalk Exchange Server Microsoft LynC Server Microsoft Dynamic Sharepoint Sql Server Windows Server 2008 Windows Server 2012 Windows 7 Windows 8 Adobe Indesign Adobe Flash Professional Dreamweaver Adobe Illustrator Adobe After Effects Adobe Photoshop Adobe Fireworks Adobe Flash Catalyst Corel Painter X CorelDRAW X5 CorelDraw 10 QuarkXPress 8 windows Phone 7 windows Phone 8