LowLatencyCanvasView


@RequiresApi(value = 29)
public final class LowLatencyCanvasView extends ViewGroup


View implementation that leverages a "front buffered" rendering system. This allows for lower latency graphics by leveraging a combination of front buffered alongside multi-buffered content layers. This class provides similar functionality to CanvasFrontBufferedRenderer, however, leverages the traditional View system for implementing the multi buffered content instead of a separate SurfaceControlCompat instance and entirely abstracts all SurfaceView usage for simplicity.

Drawing of this View's content is handled by a consumer specified LowLatencyCanvasView.Callback implementation instead of View.onDraw. Rendering here is done with a Canvas into a single buffer that is presented on screen above the rest of the View hierarchy content. This overlay is transient and will only be visible after LowLatencyCanvasView.renderFrontBufferedLayer is called and hidden after LowLatencyCanvasView.commit is invoked. After LowLatencyCanvasView.commit is invoked, this same buffer is wrapped into a bitmap and drawn within this View's View.onDraw implementation.

Calls to LowLatencyCanvasView.renderFrontBufferedLayer will trigger LowLatencyCanvasView.Callback.onDrawFrontBufferedLayer to be invoked to handle drawing of content with the provided Canvas.

After LowLatencyCanvasView.commit is called, the overlay is hidden and the buffer is drawn within the View hierarchy, similar to traditional View implementations.

A common use case would be a drawing application that intends to minimize the amount of latency when content is drawn with a stylus. In this case, touch events between MotionEvent.ACTION_DOWN and MotionEvent.ACTION_MOVE can trigger calls to LowLatencyCanvasView.renderFrontBufferedLayer which will minimize the delay between then the content is visible on screen. Finally when the gesture is complete on MotionEvent.ACTION_UP, a call to LowLatencyCanvasView.commit would be invoked to hide the transient overlay and render the scene within the View hierarchy like a traditional View. This helps provide a balance of low latency guarantees while mitigating potential tearing artifacts.

This helps support low latency rendering for simpler use cases at the expensive of configuration customization of the multi buffered layer content.

import androidx.annotation.WorkerThread
import androidx.graphics.lowlatency.LowLatencyCanvasView

LowLatencyCanvasView(context).apply {
    setBackgroundColor(Color.WHITE)

    data class Line(
        val x1: Float,
        val y1: Float,
        val x2: Float,
        val y2: Float,
    )
    // Thread safe collection to support creation of new lines from the UI thread as well as
    // consumption of lines from the background drawing thread
    val lines = Collections.synchronizedList(ArrayList<Line>())
    setRenderCallback(object : LowLatencyCanvasView.Callback {

        val mAllLines = ArrayList<Line>()

        private val mPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
            strokeWidth = 15f
            color = Color.CYAN
            alpha = 128
        }

        @WorkerThread
        override fun onRedrawRequested(
            canvas: Canvas,
            width: Int,
            height: Int
        ) {
            for (line in mAllLines) {
                canvas.drawLine(line.x1, line.y1, line.x2, line.y2, mPaint)
            }
        }

        @WorkerThread
        override fun onDrawFrontBufferedLayer(
            canvas: Canvas,
            width: Int,
            height: Int
        ) {
            lines.removeFirstOrNull()?.let { line ->
                mAllLines.add(line)
                canvas.drawLine(line.x1, line.y1, line.x2, line.y2, mPaint)
            }
        }
    })
    setOnTouchListener(object : View.OnTouchListener {

        var mCurrentX = -1f
        var mCurrentY = -1f
        var mPreviousX = -1f
        var mPreviousY = -1f

        override fun onTouch(v: View?, event: MotionEvent?): Boolean {
            if (event == null) return false
            when (event.action) {
                MotionEvent.ACTION_DOWN -> {
                    requestUnbufferedDispatch(event)
                    mCurrentX = event.x
                    mCurrentY = event.y
                }
                MotionEvent.ACTION_MOVE -> {
                    mPreviousX = mCurrentX
                    mPreviousY = mCurrentY
                    mCurrentX = event.x
                    mCurrentY = event.y

                    val line = Line(mPreviousX, mPreviousY, mCurrentX, mCurrentY)
                    lines.add(line)
                    renderFrontBufferedLayer()
                }
                MotionEvent.ACTION_CANCEL -> {
                    cancel()
                }
                MotionEvent.ACTION_UP -> {
                    commit()
                }
            }
            return true
        }
    })
}

Summary

Nested types

Provides callbacks for consumers to draw into the front buffered overlay as well as provide opportunities to synchronize SurfaceControlCompat.Transactions to submit the layers to the hardware compositor

Public constructors

LowLatencyCanvasView(
    @NonNull Context context,
    AttributeSet attrs,
    int defStyle
)

Public methods

void
addView(View child)
void
addView(View child, int index)
void
void
addView(View child, int index, ViewGroup.LayoutParams params)
void
addView(View child, int width, int height)
final void

Cancels any in progress request to render to the front buffer and hides the front buffered overlay.

final void

Clears the content of the buffer and hides the front buffered overlay.

final void

Invalidates this View and draws the buffer within View#onDraw.

final void

Dispatches a runnable to be executed on the background rendering thread.

final void

Render content to the front buffered layer.

final void

Configures the Callback used to render contents to the front buffered overlay as well as optionally configuring the SurfaceControlCompat.Transaction used to update contents on screen.

Protected methods

void
void
void
onLayout(boolean changed, int l, int t, int r, int b)

Inherited methods

From android.view.View
void
void
void
void
@NonNull ViewPropertyAnimator
void

This method is deprecated. Deprecated in Java

void
void
boolean
boolean
boolean
awakenScrollBars(int p0, boolean p1)
void
void

This method is deprecated. Deprecated in Java

void
buildDrawingCache(boolean p0)

This method is deprecated. Deprecated in Java

void
boolean
boolean
boolean
boolean
boolean
boolean
final void
void
final void
boolean
void
void
void
int
int
int
void
@NonNull WindowInsets
int
int
int
@NonNull AccessibilityNodeInfo
void
void

This method is deprecated. Deprecated in Java

boolean
boolean
dispatchNestedFling(float p0, float p1, boolean p2)
boolean
dispatchNestedPreFling(float p0, float p1)
boolean
boolean
dispatchNestedPreScroll(
    int p0,
    int p1,
    @Nullable int[] p2,
    @Nullable int[] p3
)
boolean
dispatchNestedScroll(int p0, int p1, int p2, int p3, @Nullable int[] p4)
boolean
void
void
drawableHotspotChanged(float p0, float p1)
final @Nullable OnBackInvokedDispatcher
final @NonNull T
<T extends View> findViewById(int p0)
final @NonNull T
<T extends View> findViewWithTag(@NonNull Object p0)
boolean

This method is deprecated. Deprecated in Java

@NonNull View
focusSearch(int p0)
void
void
void
generateDisplayHash(
    @NonNull String p0,
    @Nullable Rect p1,
    @NonNull Executor p2,
    @NonNull DisplayHashResultCallback p3
)
@NonNull View.AccessibilityDelegate
int
@NonNull AccessibilityNodeProvider
@Nullable CharSequence
int
int
@Nullable String
@Nullable String
float
@ViewDebug.ExportedProperty(category = "drawing")
getAlpha()
@NonNull Animation
@Nullable Matrix
@NonNull IBinder
@NonNull int[]
@NonNull Map<@NonNull Integer, @NonNull Integer>
@Nullable String[]
final @NonNull AutofillId
int
@Nullable AutofillValue
@NonNull Drawable
@Nullable BlendMode
@Nullable ColorStateList
@Nullable PorterDuff.Mode
int
@ViewDebug.ExportedProperty(category = "layout")
getBaseline()
final int
float
int
float
@NonNull Rect
boolean
final boolean
final @Nullable ContentCaptureSession
@NonNull CharSequence
@ViewDebug.ExportedProperty(category = "accessibility")
getContentDescription()
final int
final @NonNull Context
@NonNull ContextMenu.ContextMenuInfo
final boolean
@NonNull Display
final @NonNull int[]
@NonNull Bitmap

This method is deprecated. Deprecated in Java

@NonNull Bitmap
getDrawingCache(boolean p0)

This method is deprecated. Deprecated in Java

int

This method is deprecated. Deprecated in Java

int

This method is deprecated. Deprecated in Java

void
long
float
@ViewDebug.ExportedProperty(category = "drawing")
getElevation()
int
boolean
boolean
int
@ViewDebug.ExportedProperty(mapping = [@ViewDebug.IntToString(from = 0, to = "NOT_FOCUSABLE"), @ViewDebug.IntToString(from = 1, to = "FOCUSABLE"), @ViewDebug.IntToString(from = 16, to = "FOCUSABLE_AUTO")], category = "focus")
getFocusable()
@NonNull ArrayList<@NonNull View>
getFocusables(int p0)
void
@NonNull Drawable
int
@Nullable BlendMode
@Nullable ColorStateList
@Nullable PorterDuff.Mode
float
final boolean
boolean
@NonNull Handler
float
float
float
float
int
@Nullable Runnable
final boolean
final int
@ViewDebug.ExportedProperty(category = "layout")
getHeight()
void
int
int
@Nullable Drawable
@Nullable Drawable
int
int
@ViewDebug.ExportedProperty(category = "accessibility", mapping = [@ViewDebug.IntToString(from = 0, to = "auto"), @ViewDebug.IntToString(from = 1, to = "yes"), @ViewDebug.IntToString(from = 2, to = "no"), @ViewDebug.IntToString(from = 4, to = "noHideDescendants")])
getImportantForAccessibility()
int
@ViewDebug.ExportedProperty(mapping = [@ViewDebug.IntToString(from = 0, to = "auto"), @ViewDebug.IntToString(from = 1, to = "yes"), @ViewDebug.IntToString(from = 2, to = "no"), @ViewDebug.IntToString(from = 4, to = "yesExcludeDescendants"), @ViewDebug.IntToString(from = 8, to = "noExcludeDescendants")])
getImportantForAutofill()
int
@ViewDebug.ExportedProperty(mapping = [@ViewDebug.IntToString(from = 0, to = "auto"), @ViewDebug.IntToString(from = 1, to = "yes"), @ViewDebug.IntToString(from = 2, to = "no"), @ViewDebug.IntToString(from = 4, to = "yesExcludeDescendants"), @ViewDebug.IntToString(from = 8, to = "noExcludeDescendants")])
getImportantForContentCapture()
boolean
@NonNull KeyEvent.DispatcherState
int
@ViewDebug.ExportedProperty(category = "accessibility")
getLabelFor()
int
@ViewDebug.ExportedProperty(category = "drawing", mapping = [@ViewDebug.IntToString(from = 0, to = "NONE"), @ViewDebug.IntToString(from = 1, to = "SOFTWARE"), @ViewDebug.IntToString(from = 2, to = "HARDWARE")])
getLayerType()
int
@ViewDebug.ExportedProperty(category = "layout", mapping = [@ViewDebug.IntToString(from = 0, to = "RESOLVED_DIRECTION_LTR"), @ViewDebug.IntToString(from = 1, to = "RESOLVED_DIRECTION_RTL")])
getLayoutDirection()
@NonNull ViewGroup.LayoutParams
@ViewDebug.ExportedProperty(deepExport = true, prefix = "layout_")
getLayoutParams()
final int
float
int
final boolean
void
void
void
@NonNull Matrix
final int
final int
@ViewDebug.ExportedProperty(category = "measurement", flagMapping = [@ViewDebug.FlagToString(mask = -16777216, equals = 16777216, name = "MEASURED_STATE_TOO_SMALL")])
getMeasuredHeightAndState()
final int
final int
final int
@ViewDebug.ExportedProperty(category = "measurement", flagMapping = [@ViewDebug.FlagToString(mask = -16777216, equals = 16777216, name = "MEASURED_STATE_TOO_SMALL")])
getMeasuredWidthAndState()
int
int
int
int
int
int
int
int
@NonNull View.OnFocusChangeListener
int
@NonNull ViewOutlineProvider
int
int
int
int
int
int
int
int
final @NonNull ViewParent
@NonNull ViewParent
final @Nullable OutcomeReceiver<@NonNull GetCredentialResponse, @NonNull GetCredentialException>
final @Nullable GetCredentialRequest
float
@ViewDebug.ExportedProperty(category = "drawing")
getPivotX()
float
@ViewDebug.ExportedProperty(category = "drawing")
getPivotY()
@NonNull PointerIcon
final @NonNull List<@NonNull Rect>
@Nullable String[]
float
@NonNull Resources
final boolean
final int
float
int
@Nullable AttachedSurfaceControl
@NonNull View
@NonNull WindowInsets
float
@ViewDebug.ExportedProperty(category = "drawing")
getRotation()
float
@ViewDebug.ExportedProperty(category = "drawing")
getRotationX()
float
@ViewDebug.ExportedProperty(category = "drawing")
getRotationY()
float
@ViewDebug.ExportedProperty(category = "drawing")
getScaleX()
float
@ViewDebug.ExportedProperty(category = "drawing")
getScaleY()
int
int
int
int
@ViewDebug.ExportedProperty(mapping = [@ViewDebug.IntToString(from = 0, to = "INSIDE_OVERLAY"), @ViewDebug.IntToString(from = 16777216, to = "INSIDE_INSET"), @ViewDebug.IntToString(from = 33554432, to = "OUTSIDE_OVERLAY"), @ViewDebug.IntToString(from = 50331648, to = "OUTSIDE_INSET")])
getScrollBarStyle()
int