Home > Devices > Fire Phone

Best Practices for Integrating Depth, Shadows, and Dynamic Perspective Sensor Effects

Introduction

This page describes the best practices for integrating depth, shadows, and Dynamic Perspective sensor effects into the Dynamic Perspective UI.

Control Placement

The Euclid package of the Dynamic Perspective UI includes two types of container classes:

  • Containers to help you lay out your controls
  • Containers that add 3D effects

Layout containers

Euclid provides a ZContainer class to help you position Euclid controls on the Z-axis. A ZContainer both places a control on the Z-axis at an optimum depth and adds basic depth effects. Note that you cannot place a 3D control into a 2D layout unless that control is wrapped in a ZContainer.

Place your control inside of a ZContainer to give the effect of a control "coming up from the glass" of the device. What does "coming up from the glass" mean? Since the "glass" is at a depth of 0 (for all units), the face of the control appears to be 2mm above the glass, while the base is at glass-level.

  • Note that controls are 2mm deep.
  • Set the layout_depth XML attribute to wrap_content for your controls.
  • Set autoPadding appropriately (see next section).
  • To place a text view or an image inside of a ZContainer, use ZTextView or ZImageView controls.
  • Place a ZTabBar or ZToolBar inside of a ZContainer with euclid:baseDepth set to "@euclid:dimen/globalLayerScreen".
  • Superficially, a ZHeaderNavigationBar might appear to be similar to a ZTabBar or a ZToolBar; however, a ZHeaderNavigationBar is a different type of control and should be placed in a ZContainer without explicitly setting the baseDepth.

3D effects containers

In addition to the layout-focused ZContainer, Euclid provides two containers for adding 3D effects to your app: ZShadowReceiver and ZParallaxBackground. ZShadowReceiver and ZParallaxBackground can each be placed at the root of your layout. These containers can take any number of children, and those children can be a mix of 2D and 3D controls. 3D controls placed inside of a ZShadowReceiver and ZParallaxBackground must be wrapped in a ZContainer.

ZShadowReceiver and ZParallaxBackground are not mutually exclusive containers; you can use one, both, or neither together in a single layout. If you were to use both ZParallaxBackground and a ZShadowReceiver in the same layout, you would typically place the ZParallaxBackground at the root of the layout, and then one or more ZShadowReceivers (depending on the shadows for your controls) inside of the ZParallaxBackground.

Padding and Auto Padding

Controls placed within a ZContainer can appear to be visually "clipped," especially when head tracking is activated. To mitigate the clipping effects, you can add padding to the ZContainer. ZContainer includes an Auto Padding attribute that, when enabled, adds padding inside of the ZContainer to mitigate visual clipping of the controls placed within that container.

You can enable Auto Padding through your XML layout or programmatically:

  • To enable Auto Padding in your layout XML, add the attribute euclid:autoPadding="true".
  • To enable Auto Padding programatically, pass a value of true to setAutoPadding(boolean)

Unless you are adhering to strict UX guidelines for your app where you need to set specific pixel values for padding, enabling Auto Padding is the easiest way to prevent the clipping effect in your UI.

You also have the option of manually overriding the amount of padding, including Auto Padding, on any specific side of a control in your layout XML. To override Auto Padding on just one side of a control but keep the Auto Padding values for the other three sides, enable Auto Padding and set the padding value explicitly for only the side that you want to modify. The following example sets the padding on the top of the control to 20px:

android:paddingTop=20px

With Auto Padding enabled, this control would retain the Auto Padding values for the remaining three sides. Alternatively, you could set this padding value to 0px to completely remove the padding from that side.

Shadows

ZShadowReceiver is a simple container class whose sole purpose is to act as a plane that receives the shadows cast by controls. In a typical use case for ZShadowReciever, you would wrap your controls in a ZContainer and then wrap selected ZContainers in a ZShadowReceiver to have the controls in those ZContainers cast shadows:

  • You can mix Euclid and non-Euclid controls inside of a ZShadowReceiver, but Euclid controls within a ZShadowReceiver must also be wrapped in a ZContainer within the ZShadowReceiver.
  • To have all Euclid controls in your layout cast shadows, place the ZShadowReceiverat the root of your layout.
  • If you only want certain controls to cast shadows, make sure that those controls (at some level of the hierarchy) are wrapped in a ZShadowReceiver.

Keep these tips in mind when planning which of your controls will be casting shadows:

  • Text should not usually cast shadows because shadows impede readability. See Best Practices for ZHeaderNavigationBar for more details.
  • Android and Foundation Controls do not cast shadows even if wrapped at some level in a ZShadowReceiver.

The following example places a ZShadowReceiver at the root of a LinearLayout with a ZHeaderNavigationBar as a sample control. As required, the ZHeaderNavigationBar is wrapped in a ZContainer. This layout will mitigate clipping, and the ZNavigationHeaderBar will cast shadows onto a plane that is slightly below screen level (the level represented by the constant globalLayerIn1, which is the default layer of the receiver).



<com.amazon.euclid.widget.ZShadowReceiver
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:euclid="http://schemas.android.com/apk/res/euclid"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.amazon.euclid.widget.ZContainer
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        euclid:autoPadding="true">

        <com.amazon.euclid.widget.ZLinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            euclid:layout_depth="wrap_content">

            <com.amazon.euclid.widget.ZSwitch
                android:id="@+id/zSwitch"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                euclid:layout_depth="wrap_content" />
        </com.amazon.euclid.widget.ZLinearLayout>
    </com.amazon.euclid.widget.ZContainer>
</com.amazon.euclid.widget.ZShadowReceiver>

Dynamic Perspective Sensors

Euclid provides several ways to have your controls react to head tracking and shortcut events. These events are captured by a listener when a user shifts the position of his or her head or shifts the position of the device.

Parallax

A parallax background visually shifts when a user's head moves, which makes the background appear to be "behind the screen".

Euclid provides a simple way to add a parallax background to your app with the ZParallaxBackground class, which takes a Drawable and applies it as a background. Because ZParallaxBackground fills its own container with the appropriate background, you can place this container at the root of your layout to apply the background to the full width/height of the screen.

Known limitation for ZParallaxBackground

ZParallaxBackground has a known limitation when applied to the Android SlidingDrawer control.

ZParallaxBackground has the following default backgrounds for each Euclid theme:

  • Euclid Light: Light mesh.
  • Euclid Dark: Dark mesh.

If you want a background other than the default for your theme, override the Drawable.

Add the parallax background by wrapping your layout in a ZParallaxBackground, as shown in the following sample code:

<?xml version="1.0" encoding="utf-8"?>

<com.amazon.euclid.widget.ZParallaxBackground
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:euclid="http://schemas.android.com/apk/res/euclid"
    android:orientation="vertical"
    android:id="@+id/background"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.amazon.euclid.widget.ZShadowReceiver
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.amazon.euclid.widget.ZContainer
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            euclid:autoPadding="true">

            <com.amazon.euclid.widget.ZLinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                euclid:layout_depth="wrap_content">

                <com.amazon.euclid.widget.ZButton
                    android:id="@+id/zbutton"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    euclid:layout_depth="match_parent"
                    android:layout_margin="10dp"
                    android:text="@string/click_me"
                    android:onClick="zButtonOnClick"/>
            </com.amazon.euclid.widget.ZLinearLayout>
        </com.amazon.euclid.widget.ZContainer>
    </com.amazon.euclid.widget.ZShadowReceiver>
</com.amazon.euclid.widget.ZParallaxBackground>

Continuous peek

Euclid provides a ZPeekAlphaController class for incorporating continuous peek into an app or control. Using alpha values, this utility class allows the provided view to automatically fade in or fade out in response to the continuous peek gesture from the Dynamic Perspective Sensors.

The primary constructor for ZPeekAlphaController creates an instance of ZPeekAlphaController, passing in the view to be controlled in the constructor and a boolean indicating if the view should fade in on peek (True) or fade out on peek (False). The constructor automatically registers with the MotionGesture manager. Immediately, the alpha values of the view start animating on a continuous peek. The values used by this constructor have been optimized for an improved user experience.

If your app has use cases that do not fit with this constructor, ZPeekAlphaController also includes additional constructors with more parameters that should likely address your needs.

  • The second constructor allows for customization of the internal configuration values of how continuous peek magnitude is translated into alpha animations on the provided view. Use this constructor if you have a specific use case that is not supported by the first constructor.

You can either use explicit methods or certain events to register or un-register a ZPeekAlphaController with the MotionGesture manager:

  • The controller includes methods to explicitly unregister and re-register with the MotionGesture manager.
  • A ZPeekAlphaController will automatically register or unregister with the MotionGesture manager when the view is attached or detached from its window.
  • If you are creating a custom control that will internally use a ZPeekAlphaController, override the view's onVisibilityChanged method and register/unregister the ZPeekAlphaController with MotionGesture, depending the view's visibility. Otherwise, your control will continue to run the cameras for MotionGesture even when they are not visible and do not need to be registered with MotionGesture for continuous peek updates.
  • The ZPeekAlphaController unregisters with the MotionGesture manager when the activity containing the view is paused (and re-registered when resumed).

The following Euclid controls automatically respond to a continuous peek:

  • ZTabBarItem
  • ZToolBarItem
  • ZHeaderNavigationBar
  • ZCaptionedIconButton

The following code snippet shows how to create a new instance of a ZPeekAlphaController and listen for continuous peek events:

    private ZPeekAlphaController mPeekController;

    @Override
    protected void onVisibilityChanged(View changedView, int visibility) {
        super.onVisibilityChanged(changedView, visibility);
        boolean isViewVisible = Utils.isViewVisible(this); // This crawls the view hierarchy to see if this view, or any of its parents are set to INVISIBLE or GONE.
        if (isViewVisible) {
            mPeekController.registerListener();
        }
        else {
            mPeekController.unregisterListener();
        }
    }

Note that you will need to make sure your view is actually attached to the view hierarchy for this snippet to work successfully. For example, the view could be visible all the way up to a root node, but if that root node is not actually attached, then the view you are checking will not be visible/rendered.