FrameLayout, Your Best UI Friend

FrameLayout, Your Best UI Friend

10 Oct 2013

You have probably already used FrameLayout (doc) for what it’s named, adding a decoration around other content element. It can actually be much more than that and is probably one of the most versatile container of all.

The secret of FrameLayout is how it layouts its children. Although normally designed to contain one item, it will happily stack up other element on top of each other. Thus FrameLayout is essentially a way to manipulate the Z-order of views on the screen.

This is super useful for a couple of UI tricks from HUD-like elements to sliding panels to more complex animated transitions. In this post we will see an example for each of those.

Overlay Elements

As I mentioned, FrameLayout will automatically stacks its children on top of each other. This makes it really easy to implement overlay or HUD element in your interface.

The idea is to wrap the part of the UI into an outer FrameLayout instance. Generally this will be the root element of your layout. You can then add below the main layout definition the other elements you want to overlay.

These elements will generally have a fixed size or be set as wrap_content. You can then place them at the right position on screen using the layout_gravity and layout_margin XML attributes.

In the following screenshot, my main content is a Google Map view. I have then added two overlays above it, one that replicate a toast message (center-bottom corner) and a TextView displaying the last time the data was loaded (upper-right corner).

This is simply achieved with the following layout:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <!-- The main content -->
    <com.google.android.gms.maps.MapView
        android:id="@+id/map"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    <!-- "Loaded" flash bar layout -->
    <FrameLayout
        android:id="@+id/FlashBarLayout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal|bottom"
        android:layout_marginBottom="72dp">
        <!-- flash bar content -->
    </FrameLayout>
    <!-- Last loaded time layout -->
    <TextView
        android:id="@+id/UpdateTimeText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="top|right"
        android:layout_marginTop="4dp"
        android:layout_marginRight="4dp" />
</FrameLayout>

Being just normal views, you can manipulate the overlays as usual. For instance you will probably want to implement some sort of animations for transition between visibility states if the overlay is transient.

Sliding Panels

Sliding panels (aka drawers, aka fly-out menus) are the rage these days. They mainly consist of a panel that is placed, depending on the desired spatial effect, above or below another piece of content. Again this falls back to placing elements using a FrameLayout object.

The trick then is to use the translationX and translationY properties of the panel View to move the element back and forth.

Alternatively, View also exposes the OffsetTopAndBottom and OffsetLeftAndRight methods (working on display list directly) but those are not as easy to animate. Indeed, with the translation properties you can directly use ObjectAnimator or ViewPropertyAnimator.

The panel is initially “hidden” by setting its translationY property to its height (i.e. top side forced to the bottom of the screen). We create the initial appearing effect with the following code piece inside the panel class:

// As its name imply, this interpolator will let the view go slightly overboard
// and then spring it back to its place
var intp = new Android.Views.Animations.OvershootInterpolator ();
var d = Context.Resources.GetInteger (Android.Resource.Integer.ConfigMediumAnimTime);
// Only the header part of the view should be visible, we thus just make
// the bottom of the view translated off-screen
var tY = Height - FindViewById (Resource.Id.PaneHeaderView).Height;

this.Animate ().TranslationY (tY).SetDuration (d).SetInterpolator (intp).Start ();

A common idiom of those panel is also to draw a shadow on the edge where they meet the content. This can be implemented very efficiently by overriding the DispatchDraw method of the sliding panel Viewand drawing a black linear GradientDrawable at the right place (use the Translate method on Canvasfor the right positioning).

protected override void DispatchDraw (Android.Graphics.Canvas canvas)
{
    base.DispatchDraw (canvas);
    if (shadowDrawable == null)
        shadowDrawable = new GradientDrawable (GradientDrawable.Orientation.BottomTop,
                                               new[] { Color.Argb (0x60, 0, 0, 0), Color.Argb (0, 0, 0, 0) });
    // The reserved area for the shadow is set with top padding on the view.
    shadowDrawable.SetBounds (0, 0, Width, PaddingTop);
    shadowDrawable.Draw (canvas);
}

Transitions

With the move to a Fragment world, everything becomes just another View to manage and thereby removing the need for Activity.

When you think about the different “screen” of you application as individual fragments it becomes very easy to manipulate them in fun ways. Notably, being just managed View objects, you can apply any of the animations techniques you are already familiar with to implement very nifty transition effects.

At the core of such system, you will find again the same idea of a central, expanded FrameLayout surface where your Fragment are added.

The idea is to pile the Fragment views on top of each other like a deck of card. The current screen is thus the first card of the pile and application transitions are then simply how you want the middle cards to be put on top.

I’m using this analogy of the card deck because the method we are going to use here isView.BringToFront. This method will move the view at the last position inside its parent array. The end result of that operation of course depends on the type of container but in the case of FrameLayout it will make the View be drawn on top.

Note that this method doesn’t force a relayout which is fine for FrameLayout since children individual bounds aren’t dependent on each other but in the case of e.g. LinearLayout you will have to callRequestLayout for the modification to happen.

We will assume the layout of our main Activity is a single full-size FrameLayout container. Our application is composed of two screens represented as two fragments.

Initially, we create and attach directly those fragments to our content frame, hiding the second one to get into an initial state:

FirstFragment fragment1;
SecondFragment fragment2;

Fragment currentFragment;

protected override void OnCreate (Bundle bundle)
{
	SetContentView (/* ... */);
	
	fragment1 = new FirstFragment (this);
	fragment2 = new SecondFragment (this);
	
	SupportFragmentManager.BeginTransation ()
		.Add (Resource.Id.content_frame, fragment2, SecondFragment.Name)
		.Hide (fragment2)
		.Add (Resource.Id.content_frame, fragment1, FirstFragment.Name)
		.Commit ();
	currentFragment = fragment1;
}

When we want to change the Fragment that is shown, we will make sure that both views are on top of the drawing pile with the current one being on top of the other using BringToFront. We then swap the two fragments in a FragmentTransaction:

void SwitchTo (Fragment fragment, string name)
{
	if (fragment.IsVisible)
		return;
	var t = SupportFragmentManager.BeginTransaction ();
	t.SetCustomAnimations (Resource.Animator.frag_slide_in,
	                       Resource.Animator.frag_slide_out);

	// Make sure the next view is below the current one
	fragment.View.BringToFront ();
	// And bring the current one to the very top
	currentFragment.View.BringToFront ();

	// Hide the current fragment
	t.Hide (currentFragment);
	t.Show (existingFragment);
	currentFragment = existingFragment;

	// You probably want to add the transaction to the backstack
	// so that user can use the back button
	t.AddToBackStack (null);
	t.Commit ();
}

To add a bit of woosh, we are using customs animations to mark the transition between the two fragments to get this result:

These animations need to be defined in an XML resource using either property animator (aka from Android.Animations) if you are using framework Fragment or using view animations (aka from Android.View.Animations) if you are using Fragment from the support package.

To register custom animations, we call the SetCustomAnimations method on FragmentTransactionlike so:

t.SetCustomAnimations (Resource.Animator.frag_slide_in,
                       Resource.Animator.frag_slide_out);

The first animation will be run on any fragment added/attached/shown while the second animation will be played on fragments hidden/detached/removed. Both are ran at the same time but you can use the animation startOffset attribute to delay one or the other.

For instance in my example, my frag_slide_out animation is defined like so:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
     android:interpolator="@android:interpolator/accelerate_cubic">
   <translate
      android:fromXDelta="0"
      android:toXDelta="100%"
      android:duration="300" />
   <alpha
        android:fromAlpha="1.0"
        android:toAlpha="0.3"
        android:duration="300" />
</set>

Conclusion

Everything I have presented here is part of a big refresh to Moyeu. You can view the full source code on the GitHub repository at garuma/Moyeu.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值