LowLatencyCanvasView
@RequiresApi(value = 29)
public final class LowLatencyCanvasView extends ViewGroup
| java.lang.Object | |||
| ↳ | android.view.View | ||
| ↳ | android.view.ViewGroup | ||
| ↳ | androidx.graphics.lowlatency.LowLatencyCanvasView |
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 |
|---|
public interface LowLatencyCanvasView.CallbackProvides callbacks for consumers to draw into the front buffered overlay as well as provide opportunities to synchronize |
Public constructors |
|---|
LowLatencyCanvasView( |
Public methods |
|
|---|---|
void |
|
void |
|
void |
addView(View child, ViewGroup.LayoutParams params) |
void |
addView(View child, int index, ViewGroup.LayoutParams params) |
void |
|
final void |
cancel()Cancels any in progress request to render to the front buffer and hides the front buffered overlay. |
final void |
clear()Clears the content of the buffer and hides the front buffered overlay. |
final void |
commit()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 |
Protected methods |
|
|---|---|
void |
|
void |
|
void |
onLayout(boolean changed, int l, int t, int r, int b) |
Inherited methods |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Public constructors
LowLatencyCanvasView
public LowLatencyCanvasView(
@NonNull Context context,
AttributeSet attrs,
int defStyle
)
Public methods
cancel
public final void cancel()
Cancels any in progress request to render to the front buffer and hides the front buffered overlay. Cancellation is a "best-effort" approach and any in progress rendering will still be applied.
clear
public final void clear()
Clears the content of the buffer and hides the front buffered overlay. This will cancel all pending requests to render. This is similar to cancel, however in addition to cancelling the pending render requests, this also clears the contents of the buffer. Similar to commit this will also hide the front buffered overlay.
commit
public final void commit()
Invalidates this View and draws the buffer within View#onDraw. This will synchronously hide the front buffered overlay when drawing the buffer to this View. Consumers are encouraged to invoke this method when a user gesture that requires low latency rendering is complete. For example in response to a MotionEvent.ACTION_UP event in an implementation of View.onTouchEvent.
execute
public final void execute(@NonNull Runnable runnable)
Dispatches a runnable to be executed on the background rendering thread. This is useful for updating data structures used to issue drawing instructions on the same thread that Callback.onDrawFrontBufferedLayer is invoked on.
renderFrontBufferedLayer
public final void renderFrontBufferedLayer()
Render content to the front buffered layer. This triggers a call to Callback.onDrawFrontBufferedLayer. Callback implementations can also configure the corresponding SurfaceControlCompat.Transaction that updates the contents on screen by implementing the optional Callback.onFrontBufferedLayerRenderComplete callback
setRenderCallback
public final void setRenderCallback(LowLatencyCanvasView.Callback callback)
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.