Archives for posts with tag: Layout

I’ve seen a lot of confusion on how to correctly create headers and footers in Activities with a ScrollView root. There’s also a lot of bad advice lying around, mostly involving RelativeLayout and resulting in headers and footers overlapping other parts of the UI when space is scarce.

We’ll take a look at a better solution using the much more simple LinearLayout.

To be on the same page, let’s agree on how we want our footers and/or headers to behave. First, however, some definitions. Our content, hosted inside a fullscreen ScrollView, will consist of:

  • Header (optional): this View‘s top will match the ScrollView‘s.
  • Body: this View will always be right below the header, and somewhere above the footer.
  • Footer (optional): this View‘s bottom will match the ScrollView‘s

Good! Now, when the body’s content is smaller than the remaining space between the header and the footer, the layout will look like this:

Layout with no scrollbars

Layout with no scrollbars.

Unlike simple fixed headers and footers, however, ours will move out of the way if necessary. If the body’s size exceeds the available space, we want the layout to behave like this:

Layout with scrollbars

Layout with scrollbars. Left: scrolled to top; right: scrolled to bottom

Notice how, now that the screen’s size is not enough to display all of our content, the footer and the header are no longer anchored and respond to scrolling, without overlapping the body.

So, how do we get there? We use LinearLayout‘s layout_weight behavior to ensure that the body area will always expand to be at least as long as the remaining sandwiched space between the header and the footer. If the content’s shorter, it expands until it reaches the footer’s top; if it’s longer, it pushes the footer down.

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" 
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:fillViewport="true">

<LinearLayout
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <!-- HEADER -->
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
    />

    <!-- BODY -->
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="0dip"
        android:layout_weight="1"
        android:orientation="vertical"
    />

    <!-- FOOTER -->
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
    />
</LinearLayout>
</ScrollView>

VoilĂ !

Obviously, the header and the footer can be omitted. They are not both mandatory, the same technique will work for just a header, or just a footer.

If you’ve read this far, well, I hope this helped you. You are more than welcome to contact me with doubts, suggestions or corrections.

Saludos!

A few days ago, I had to create a list that allowed the user to drag elements and rearrange them. It was unexpectedly troublesome, and somewhat more of a challenge than I had initially anticipated, so I thought to document the experience.

If you just want to get your hands on the code, it’s freely available here. Any and all kinds of feedback are welcome!

Rearranging

The functionality can already be seen in dozens of popular applications, but there’s not really a standard, and implementations show minor differences both in behavior and presentation. For that reason, I think I should begin by clarifying what I was expecting to achieve.

I wanted the user to be able to activate an edit mode on a list, hold his finger down on an item, and drag it up or down with immediate visual feedback of each positional swap. The keyword here is immediate: I didn’t want to give the impression that the item was being “lifted” and then “dropped” — I wanted it to look like the item was pushing its neighbors as it moved.

As a side note, I actually find the classic lifted/dropped effect, with a transparent duplicate of the item hovering above the list as you drag it, much more beautiful and comfortable. Only, you know,… work, requirements and all that.

Design Considerations

Long before my hands were anywhere near the keyboard, I noticed the task at hand wasn’t so straightforward. If I wanted to keep it decoupled and independent of implementation details, there were at least three actors in my play:

  1. The adapter, holding access to the dataset and responsible for creating/recycling views.
  2. The views associated with the items, which may want to be grabbed only from certain areas, and/or redraw themselves to give visual feedback when moving around.
  3. The ListView itself, the one that could actually detect the user’s movements

I tried to organize them hierarchically, so that the mechanism would only rely on one of them and automatically handle the rest, but I found no way of doing it without having objects overstepping their boundaries or making dangerous assumptions. The responsibilities are simply too well-defined: the ListView handles the list presentation logic, each View takes care of its own presentation logic, and the Adapter provides the bridge with the dataset. I also felt that “hard-wiring” the components would heavily restrict the code user’s ability to extend or modify the behavior.

After some thinking, I ended up with three definitions: a RearrangeableListView class extending ListView, and two interfaces to externalize all the behavior that I thought should be external, namely RearrangeListener and MovableView.

The RearrangeListener interface is meant to represent the Adapter, and will most likely be implemented by it. Let’s take a look at it:

interface RearrangeListener {
    void onGrab(int index);
    boolean onRearrangeRequested(int fromIndex, int toIndex);
    void onDrop();
}

The object implementing it is notified when an item in the list is grabbed, dragged or dropped. More importantly, it is responsible for allowing or disallowing rearrangements, and for ensuring the backing dataset reflects the changes appropriately.

The notification behavior is as straightforward as any listener’s. The allowing/disallowing is also quite simple: if the listener returns false from onRearrangeRequested, the move is invalidated; while a return value of true indicates the rearrangement was successful and is already accounted for in the dataset. The reason behind asking the listener to validate moves may not be so intuitive, but remember I didn’t want the ListView to hold any kind of information about the data, so that it could be held and organized in any way. The less assumptions made, the better.

Let’s take a look at how an ArrayAdapter could implement this interface to simply allow and reflect all rearrangements within its data’s bounds:

@Override
public void onGrab(int index) {
    getItem(index).doSomething();
    notifyDataSetChanged();
}
 
@Override
public boolean onRearrangeRequested(int fromIndex, int toIndex) {
     
    if (toIndex >= 0 && toIndex < getCount()) {
        Object item = getItem(fromIndex);
         
        remove(item);
        insert(item, toIndex);
        notifyDataSetChanged();
         
        return true;
    }
     
    return false; 
}

@Override
public void onDrop() {
    doSomethingElse();
    notifyDataSetChanged();
}

This is just an example: the calls to notifyDataSetChanged may or may not be necessary, and both onGrab and onDrop() could be empty snippets.

The second interface I mentioned, MovableView, is defined as follows:

interface MovableView {
    public boolean onGrabAttempt(int x, int y);
    public void onRelease();
}

When the user attempts to grab a View for dragging, and it implements this interface, it can be notified of what’s happening and is allowed to reject a grab attempt, just as the listener is allowed to reject a rearrangement. Why? Well, the View might want to define a specific area from which it can be grabbed, or a certain set of circumstances under which that makes sense. Imagine the typical a list item with a grab handle on the right — the interface would be implemented like this:

@Override
public boolean onGrabAttempt(int x, int y) {
    Rect hitRect = new Rect();
    this.mMyHandleView.getHitRect(hitRect);
    
    return hitRect.contains(x, y);
}

@Override
public void onRelease() {}

Benefits of the design

What are the upsides of demanding the implementation of two interfaces? Well, as I see it, there are several assumptions that we don’t need to make, which allows for greater flexibility and reusability. To name a few:

  1. The dataset format, location and organization is completely irrelevant to the RearrangeableListView.
  2. The Adapter can decide on the fly whether a certain movement is valid, enacting changes in the dataset to add/remove/replace items besides the one being moved.
  3. The View children of the list can have any size, behavior and internal arrangement (even varying between items!), since they are fully responsible for their own piece of the mechanism’s logic.
  4. The ListView can handle UI events only when both the listener and the view approve, and leave room for other components to step in otherwise.

Implementation details

I think most of the code is pretty self-explanatory, so I won’t go into full detail, but there is something I think is worth mentioning: scrolling.

When the user drags an item to the visible bottom (or top) of the list, with the intention to keep dragging it down (or up), the list should automatically swap the held item with the first invisible one, scroll a bit, and keep doing that until the user releases his hold on the item. It took a lot of trial and error to get this behavior right, since several methods are available that sound tempting enough to use but end up introducing more problems than they solve.

The easiest way I found of forcing a scroll on the list was the setSelected() method. In pseudo-java-like code:

if (scrollingUp())
    setSelection(getFirstVisiblePosition() - 1);
else
if (scrollingDown())
    setSelection(getFirstVisiblePosition() + 1);

Now, invoking this code will make the list scroll appropriately once. Calling this from the MotionEvent handler would require the user (or us) to generate additional events just to keep the list scrolling. What I wanted was for the list to automatically scroll at a fixed rate until the user released the item, so instead I wrote a Runnable that executes the above logic and reposts itself with a delay. Again, in java-like pseudocode:

final Runnable autoScroll = new Runnable() {
    @Override
    public void run() {
        
        if (scrolling() && moveApproved()) {
            
            if (scrollingUp())
                setSelection(getFirstVisiblePosition() - 1);
            else
            if (scrollingDown())
                setSelection(getFirstVisiblePosition() + 1);
            
            postDelayed(this, AUTO_SCROLL_DELAY);
        }
    }
};

The advantage of this approach is that we don’t need any additional threads or countdown handlers, since we just rely on the UI’s event loop. To start an autoscroll, a simple call to run() is enough.

Final notes

I intended to give a much more in-depth description of the implementation, but as I said above, I think reading the code will do. Again, the full source code for RearrangeableListView is available in the blogActivity repository. I welcome all comments, bug reports, fixes or improvements — do contact me.

Saludos!