androidx.ink.authoring.compose

Top-level functions summary

Unit
@Composable
<ShapeSpecT : Any, InProgressShapeT : InProgressShape<ShapeSpecT, CompletedShapeT>, CompletedShapeT : Any> InProgressShapes(
    customShapeWorkflow: ShapeWorkflow<ShapeSpecT, InProgressShapeT, CompletedShapeT>,
    defaultShapeSpec: ShapeSpecT?,
    nextShapeSpec: () -> ShapeSpecT?,
    pointerEventToWorldTransform: Matrix,
    shapeToWorldTransform: Matrix,
    maskPath: Path?,
    onShapesCompleted: (List<CompletedShapeT>) -> Unit
)

A Composable that displays in-progress ink shapes as it receives user pointer input.

Unit
@Composable
InProgressStrokes(
    defaultBrush: Brush?,
    nextBrush: () -> Brush?,
    pointerEventToWorldTransform: Matrix,
    strokeToWorldTransform: Matrix,
    maskPath: Path?,
    textureBitmapStore: TextureBitmapStore,
    onStrokesFinished: (List<Stroke>) -> Unit
)

A Composable that displays in-progress ink strokes as it receives user pointer input.

Top-level functions

InProgressShapes

@Composable
fun <ShapeSpecT : Any, InProgressShapeT : InProgressShape<ShapeSpecT, CompletedShapeT>, CompletedShapeT : Any> InProgressShapes(
    customShapeWorkflow: ShapeWorkflow<ShapeSpecT, InProgressShapeT, CompletedShapeT>,
    defaultShapeSpec: ShapeSpecT?,
    nextShapeSpec: () -> ShapeSpecT? = { defaultShapeSpec },
    pointerEventToWorldTransform: Matrix = IDENTITY_MATRIX,
    shapeToWorldTransform: Matrix = IDENTITY_MATRIX,
    maskPath: Path? = null,
    onShapesCompleted: (List<CompletedShapeT>) -> Unit
): Unit

A Composable that displays in-progress ink shapes as it receives user pointer input.

Multiple simultaneous shapes are supported. This implementation handles all best practices for the fastest, lowest latency rendering of real-time user input. For an alternative compatible with Android Views, see InProgressShapesView.

For performance reasons, it is recommended to only have one InProgressShapes on screen at a time, and to avoid unnecessarily resizing it or changing whether it is present on screen.

This will process pointers (and display the resulting shapes) that are not already consumed by an earlier touch listener - when androidx.compose.ui.input.pointer.PointerInputChange.isConsumed is false. If a pointer's down event arrives to this composable already consumed, it will not be drawn, even if later events for that pointer arrive unconsumed. If a pointer's initial events are not consumed by arrival, then it will be drawn, but if its later events have been consumed by arrival then the shape for that pointer will be canceled and removed from this composable's UI. If all of a pointer's events from down through moves through up arrive to this composable unconsumed, then the entire shape will be drawn and finished, and the completed shape will be made available by a call to onShapesCompleted. All shapes that were in progress simultaneously will be delivered in the same callback, running on the UI thread. To avoid visual flicker when transitioning rendering from this composable to another composable, during this callback the finished shapes must be added to some mutable state that triggers a recomposition that will render those shapes, typically using androidx.ink.rendering.android.canvas.CanvasStrokeRenderer.

Parameters
customShapeWorkflow: ShapeWorkflow<ShapeSpecT, InProgressShapeT, CompletedShapeT>

A ShapeWorkflow to be used as an alternative to the standard Ink behavior provided by InProgressStrokesView.

defaultShapeSpec: ShapeSpecT?

Specification for the shape being started. If this is null, nothing will be drawn, but the internal rendering resources will be preserved to avoid paying the cost for reinitialization when drawing is needed again. If different ShapeSpecT values are needed for different simultaneous shapes, use nextShapeSpec to return a different ShapeSpecT each time.

nextShapeSpec: () -> ShapeSpecT? = { defaultShapeSpec }

A way to override defaultShapeSpec, which will be called at the start of each pointer.

pointerEventToWorldTransform: Matrix = IDENTITY_MATRIX

The matrix that transforms androidx.compose.ui.input.pointer.PointerEvent coordinates into the caller's "world" coordinates, which typically is defined by how the caller's document is panned/zoomed/rotated. This defaults to the identity matrix, in which case the world coordinate space is the same as the input event coordinates, but the caller should pass in their own value reflecting a coordinate system that is independent of the device's pixel density (e.g. scaled by 1 / android.util.DisplayMetrics.density) and any pan/zoom/rotate gestures that have been applied to the "camera" which portrays the "world" on the device screen. This matrix must be invertible.

shapeToWorldTransform: Matrix = IDENTITY_MATRIX

Allows an object-specific (shape-specific) coordinate space to be defined in relation to the caller's "world" coordinate space. This defaults to the identity matrix, which is typical for many use cases at the time of shape construction. In typical use cases, shape coordinates and world coordinates may start to differ from one another only after shape authoring as a finished shape is manipulated within the world, e.g. it may be moved, scaled, or rotated relative to other content within an app's document. This matrix must be invertible.

maskPath: Path? = null

An area of this composable where no ink will be visible. A value of null indicates that shapes will be visible anywhere they are drawn. This is useful for UI elements that float on top of (in Z order) the drawing surface - without this, a user would be able to draw in-progress ("wet") shapes on top of those UI elements, but then when the shape is finished, it will appear as a dry shape underneath of the UI element. If this mask is set to the shape and position of the floating UI element, then the ink will never be rendered in that area, making it appear as if it's being drawn underneath the UI element. This technique is most convincing when the UI element is opaque. Often there are parts of the UI element that are translucent, such as drop shadows, or anti-aliasing along the edges. The result will look a little different between wet and dry shapes for those cases, but it can be a worthwhile tradeoff compared to the alternative of drawing wet shapes on top of that UI element. Note that this parameter does not affect the contents of the shapes at all, nor how they appear when drawn in a separate composable after onShapesCompleted is called - just how the shapes appear when they are still in progress in this composable.

onShapesCompleted: (List<CompletedShapeT>) -> Unit

Called when there are no longer any in-progress shapes for a short period. All shapes that were in progress simultaneously will be delivered in the same callback, running on the UI thread. The callback should pass the shapes to another Composable, triggering its recomposition within the same UI thread run loop as the callback being received, for that other Composable to render the shapes without any brief rendering errors (flickers). These flickers may come from a gap where a shape is drawn in neither this Composable nor the app's Composable during a frame, or a double draw where the shape is drawn twice and translucent shapes appear more opaque than they should. Be careful about passing shapes to asynchronous frameworks during this callback, as they may switch threads during the handoff from producer to consumer, and even if the final consumer is on the UI thread, it may not be in the same UI thread run loop and lead to a flicker.

InProgressStrokes

@Composable
fun InProgressStrokes(
    defaultBrush: Brush?,
    nextBrush: () -> Brush? = { defaultBrush },
    pointerEventToWorldTransform: Matrix = IDENTITY_MATRIX,
    strokeToWorldTransform: Matrix = IDENTITY_MATRIX,
    maskPath: Path? = null,
    textureBitmapStore: TextureBitmapStore = TextureBitmapStore { null },
    onStrokesFinished: (List<Stroke>) -> Unit
): Unit

A Composable that displays in-progress ink strokes as it receives user pointer input.

Multiple simultaneous strokes are supported. This implementation handles all best practices for the fastest, lowest latency rendering of real-time user input. For an alternative compatible with Android Views, see androidx.ink.authoring.InProgressStrokesView.

For performance reasons, it is recommended to only have one InProgressStrokes on screen at a time, and to avoid unnecessarily resizing it or changing whether it is present on screen.

This will process pointers (and display the resulting strokes) that are not already consumed by an earlier touch listener - when androidx.compose.ui.input.pointer.PointerInputChange.isConsumed is false. If a pointer's down event arrives to this composable already consumed, it will not be drawn, even if later events for that pointer arrive unconsumed. If a pointer's initial events are not consumed by arrival, then it will be drawn, but if its later events have been consumed by arrival then the stroke for that pointer will be canceled and removed from this composable's UI. If all of a pointer's events from down through moves through up arrive to this composable unconsumed, then the entire stroke will be drawn and finished, and the finished stroke will be made available by a call to onStrokesFinished. All strokes that were in progress simultaneously will be delivered in the same callback, running on the UI thread. To avoid visual flicker when transitioning rendering from this composable to another composable, during this callback the finished strokes must be added to some mutable state that triggers a recomposition that will render those strokes, typically using androidx.ink.rendering.android.canvas.CanvasStrokeRenderer.

Parameters
defaultBrush: Brush?

Brush specification for the stroke being started. If this is null, nothing will be drawn, but the internal rendering resources will be preserved to avoid paying the cost for reinitialization when drawing is needed again. If different Brush values are needed for different simultaneous strokes, use nextBrush to return a different Brush each time. Note that the overall scaling factor of pointerEventToWorldTransform and strokeToWorldTransform combined should be related to the value of Brush.epsilon - in general, the larger the combined pointerEventToStrokeTransform scaling factor is, the smaller on screen the stroke units are, so Brush.epsilon should be a larger quantity of stroke units to maintain a similar screen size.

nextBrush: () -> Brush? = { defaultBrush }

A way to override defaultBrush, which will be called at the start of each pointer.

pointerEventToWorldTransform: Matrix = IDENTITY_MATRIX

The matrix that transforms androidx.compose.ui.input.pointer.PointerEvent coordinates into the caller's "world" coordinates, which typically is defined by how the caller's document is panned/zoomed/rotated. This defaults to the identity matrix, in which case the world coordinate space is the same as the input event coordinates, but the caller should pass in their own value reflecting a coordinate system that is independent of the device's pixel density (e.g. scaled by 1 / android.util.DisplayMetrics.density) and any pan/zoom/rotate gestures that have been applied to the "camera" which portrays the "world" on the device screen. This matrix must be invertible.

strokeToWorldTransform: Matrix = IDENTITY_MATRIX

Allows an object-specific (stroke-specific) coordinate space to be defined in relation to the caller's "world" coordinate space. This defaults to the identity matrix, which is typical for many use cases at the time of stroke construction. In typical use cases, stroke coordinates and world coordinates may start to differ from one another only after stroke authoring as a finished stroke is manipulated within the world, e.g. it may be moved, scaled, or rotated relative to other content within an app's document. This matrix must be invertible.

maskPath: Path? = null

An area of this composable where no ink will be visible. A value of null indicates that strokes will be visible anywhere they are drawn. This is useful for UI elements that float on top of (in Z order) the drawing surface - without this, a user would be able to draw in-progress ("wet") strokes on top of those UI elements, but then when the stroke is finished, it will appear as a dry stroke underneath of the UI element. If this mask is set to the shape and position of the floating UI element, then the ink will never be rendered in that area, making it appear as if it's being drawn underneath the UI element. This technique is most convincing when the UI element is opaque. Often there are parts of the UI element that are translucent, such as drop shadows, or anti-aliasing along the edges. The result will look a little different between wet and dry strokes for those cases, but it can be a worthwhile tradeoff compared to the alternative of drawing wet strokes on top of that UI element. Note that this parameter does not affect the contents of the strokes at all, nor how they appear when drawn in a separate composable after onStrokesFinished is called - just how the strokes appear when they are still in progress in this composable.

textureBitmapStore: TextureBitmapStore = TextureBitmapStore { null }

Allows texture asset images to be loaded, corresponding to texture layers that may be specified by defaultBrush or nextBrush.

onStrokesFinished: (List<Stroke>) -> Unit

Called when there are no longer any in-progress strokes for a short period. All strokes that were in progress simultaneously will be delivered in the same callback, running on the UI thread. The callback should pass the strokes to another Composable, triggering its recomposition within the same UI thread run loop as the callback being received, for that other Composable to render the strokes without any brief rendering errors (flickers). These flickers may come from a gap where a stroke is drawn in neither this Composable nor the app's Composable during a frame, or a double draw where the stroke is drawn twice and translucent strokes appear more opaque than they should. Be careful about passing strokes to asynchronous frameworks during this callback, as they may switch threads during the handoff from producer to consumer, and even if the final consumer is on the UI thread, it may not be in the same UI thread run loop and lead to a flicker.