While a standard Subspace stays at a fixed position that's determined by
the system, sometimes you might want your app's spatial UI to move dynamically
with the user or stay attached to a specific location in its environment or in
the real world. You can pass a FollowTarget to the follow parameter of
Subspace to build experiences like head-locked HUDs or information panels
attached to real-world objects.
Add a subspace that follows the user's view
To add a subspace that's positioned relative to the user's field of view, use
FollowTarget.view. Depending on the follow mode you specify, this
lets you continuously track the user's head movements or place content
statically based on where they are looking. You could use this for things like
HUDs, persistent notifications, or quick-action controls.
Declare and request permissions
Before requesting permissions, you must declare them in your app's manifest
file using the <uses-permission> element.
<manifest ...>
<!-- Only declare permissions that your app actually needs. In this example,
we declare permissions for head tracking. -->
<uses-permission android:name="android.permission.HEAD_TRACKING"/>
<application ...>
...
</application>
</manifest>
Now request the permissions. For more information, review the permission principles and workflows to make sure you provide the best experience for your users, like checking whether the user has already granted the runtime permissions your app requires and whether your app should show a rationale to the user before requesting this permission.
Configure your session
Next, you'll configure your app's session that is created for you when using
Jetpack Compose for XR by setting DeviceTrackingMode.SPATIAL:
val session = checkNotNull(LocalSession.current)
session.configure(
config = Config.Builder(session.config).setDeviceTracking(DeviceTrackingMode.SPATIAL)
.build()
)
Create the subspace
Finally, you'll create a subspace that targets the user's view, together with a follow mode:
// Example: A subspace that softly follows the user's view (like a HUD)
Subspace(
follow = FollowTarget.view(
// Customizing the soft follow to move slightly faster and tracking all dimensions
mode = FollowMode.soft(
)
),
modifier = modifier,
) {
SpatialPanel {
// SpatialPanel Content
}
}
Follow modes for subspaces that target a user's view
When you use FollowTarget.view as your target, you can customize how the
content behaves by specifying a FollowMode:
FollowMode.soft: The content gradually catches up to the target's movements, creating a smooth floating effect.FollowMode.snap: The content is placed in front of the user once based on their initial view pose and stays stationary afterward.FollowMode.tight: The content tracks the user's view immediately with minimal delay, keeping content closely tethered to their movement.
All follow modes let you choose which dimensions of movement are tracked from the following options:
- X axis
- Y axis
- Pitch
- Roll
- Yaw
RotationOnly: Lets you specify a single value to track the dimensions that are related to rotation (pitch, yaw, and roll).
You can choose any combination of these dimensions, or use the default value of
TrackedDimensions.All that tracks them all.
Add a subspace that follows an environmental feature
To add a subspace that locks the spatial content strictly to a physical feature
in the user's environment, use FollowTarget.anchor(). Use this type of
target to follow a detected plane, an anchor, or a geospatial location.
Tracking real-world features requires additional permissions and session configurations depending on the feature you're following.
For detailed instructions on working with perception capabilities for ARCore
features, as well as setting up the necessary permissions and configuring
your session to use FollowTarget.anchor(), see the Jetpack XR SDK
documentation for Planes, Anchors, and Geospatial poses.
The following example creates a subspace that follows a real-world anchor:
// A subspace locked to a real-world anchor
Subspace(
follow = FollowTarget.anchor(anchor = myAnchor)
) {
// Rotate the panel to ensure it faces the user while remaining anchored
SpatialPanel(SubspaceModifier.rotateToLookAtUser()) {
// SpatialPanel Content
}
}
Avoid spatial overlap
Each Subspace you create manages its own independent 3D scene and has no
awareness of other subspaces that exist, so it doesn't automatically avoid
collisions.
To prevent your head-locked HUD from clipping through your anchored POI panels (Z-fighting), explicitly manage your layout volumes using these methods:
- Use offsets: Use
SubspaceModifier.offsetto push peripheral UI (like your HUD) closer or further away on the Z-axis, or shift it to the edges of the user's vision. - Restrict sizes: Apply modifiers like
requiredSizeInto constrain the bounding boxes of your spaces.
For more information about coordinate spaces, layout constraints, and all of the
available tracking configurations, see the Subspace reference
documentation.