Change theme
Help
Press space for more information.
Show links for this issue (Shortcut: i, l)
Copy issue ID
Previous Issue (Shortcut: k)
Next Issue (Shortcut: j)
Sign in to use full features.
Vote: I am impacted
Notification menu
Refresh (Shortcut: Shift+r)
Go home (Shortcut: u)
Pending code changes (auto-populated)
View issue level access limits(Press Alt + Right arrow for more information)
Attachment actions
Unintended behavior
View staffing
Description
Version used:
android {
namespace 'com.unriddle.paper'
compileSdk 34
defaultConfig {
applicationId "com.unriddle.paper"
minSdk 24
targetSdk 34
versionCode 6
versionName "1.1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary true
}
}
buildTypes {
release {
debuggable false
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
debuggable true
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = '17'
}
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion '1.5.14'
}
packagingOptions {
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}
}
Gradle Version: 8.6
Kotlin Android Version: 1.9.24
Devices/Android versions reproduced on: Samsung galaxy s7 fe, Samsung galaxy s6 lite and xiomi pad 5.
Problem:
The LowLatencySurfaceView in Android View is not receiving onTouchEvent, that is the touch event is not consuming. When writing on the LowLatencySurfaceView in AndroidView the touch event is not captured in onTouchEvent and make other jetpack compose component like ModalNavigationDrawer to react (slide). Infact Modifier.pointerInteropFilter is not working as it not consuming the touch, causing other UI component to react.
Code:
class LowLatencySurfaceView(context: Context, private val fastRenderer: FastRenderer) :
SurfaceView(context) {
init {
setOnTouchListener(fastRenderer.onTouchListener)
setZOrderOnTop(true)
holder.addCallback(object : SurfaceHolder.Callback {
override fun surfaceCreated(holder: SurfaceHolder) {
// Get the global position on the screen
}
override fun surfaceChanged(
holder: SurfaceHolder,
format: Int,
width: Int,
height: Int
) {
// Surface size changed, you can get the width and height here
fastRenderer.canvasSize = Offset(width.toFloat(), height.toFloat())
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
// Surface is destroyed
}
})
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
fastRenderer.attachSurfaceView(this)
}
override fun onDetachedFromWindow() {
fastRenderer.release()
super.onDetachedFromWindow()
}
}
class FastRenderer(
val consumeTouch: Boolean = true,
val enableTouch: Boolean = false,
ink: Ink = Ink(),
val touchListener: (Offset, Offset, MotionEvent) -> Unit
) : CanvasFrontBufferedRenderer.Callback<Point>, ClearLineCallback {
private var frontBufferRenderer: CanvasFrontBufferedRenderer<Point>? = null
var canvasSize: Offset = Offset.Zero
var globalPostion: Offset = Offset.Zero
var previousPointer: Offset? = null
var currentInk = ink
val paint = Paint().apply {
color = Color.argb(255, 51, 102, 204)
style = Paint.Style.STROKE
strokeJoin = Paint.Join.ROUND
strokeCap = Paint.Cap.ROUND
}
@RequiresApi(Build.VERSION_CODES.Q)
fun attachSurfaceView(surfaceView: SurfaceView) {
frontBufferRenderer = CanvasFrontBufferedRenderer(surfaceView, this)
}
@RequiresApi(Build.VERSION_CODES.Q)
fun release() {
frontBufferRenderer?.release(true)
}
@RequiresApi(Build.VERSION_CODES.Q)
@SuppressLint("ClickableViewAccessibility")
val onTouchListener = View.OnTouchListener { view, motionEvent ->
if (frontBufferRenderer?.isValid() == true) {
if (enableTouch) {
if (motionEvent.isStylus()) {
when (motionEvent.actionMasked) {
MotionEvent.ACTION_DOWN -> {
view.requestUnbufferedDispatch(motionEvent)
val point = Point(
offset = Offset(motionEvent.x, motionEvent.y),
pressure = motionEvent.pressure
)
frontBufferRenderer?.renderFrontBufferedLayer(point)
}
MotionEvent.ACTION_MOVE -> {
val point = Point(
offset = Offset(motionEvent.x, motionEvent.y),
pressure = motionEvent.pressure
)
frontBufferRenderer?.renderFrontBufferedLayer(point)
}
MotionEvent.ACTION_UP -> {
val point = Point(
offset = Offset(motionEvent.x, motionEvent.y),
pressure = motionEvent.pressure
)
frontBufferRenderer?.renderFrontBufferedLayer(point)
frontBufferRenderer?.commit()
}
}
touchListener(globalPostion, canvasSize, motionEvent)
}
}
}
consumeTouch
}
override fun onDrawFrontBufferedLayer(
canvas: Canvas,
bufferWidth: Int,
bufferHeight: Int,
point: Point
) {
paint.strokeWidth = min(point.pressure * 15, MAX_STROKE_WIDTH)
canvas.drawLine(
previousPointer?.x ?: point.offset.x,
previousPointer?.y ?: point.offset.y,
point.offset.x,
point.offset.y,
paint
)
previousPointer = point.offset
}
override fun onDrawMultiBufferedLayer(
canvas: Canvas,
bufferWidth: Int,
bufferHeight: Int,
params: Collection<Point>
) {
currentInk.strokes.add(Stroke(points = params.toMutableList()))
previousPointer = null
currentInk.strokes.forEach { stroke ->
stroke.points.forEach { point ->
paint.strokeWidth = min(point.pressure * 15, MAX_STROKE_WIDTH)
canvas.drawLine(
previousPointer?.x ?: point.offset.x,
previousPointer?.y ?: point.offset.y,
point.offset.x,
point.offset.y,
paint
)
previousPointer = point.offset
}
previousPointer = null
}
}
@RequiresApi(Build.VERSION_CODES.Q)
override fun onClearLine() {
currentInk.strokes.clear()
frontBufferRenderer?.clear()
}
companion object {
const val MAX_STROKE_WIDTH = 5f
const val TAG = "FastRenderer"
}
}
@Composable
internal fun CommonDrawAreaFastRenderer(
modifier: Modifier = Modifier
) {
var isFastRendererRegistered by remember {
mutableStateOf(false)
}
val fastRendering = remember {
FastRenderer(
enableTouch = true,
){position, size, motionEvent ->
if (motionEvent.isStylus()){
when(motionEvent.actionMasked){
MotionEvent.ACTION_DOWN -> {
// viewModel.checkNewWritingArea(
// motionEvent,
// drawAreaType,
// size.x
// )
//
// if (isFastRendererRegistered.not()) {
// isFastRendererRegistered = true
// }
}
}
}
}
}
LaunchedEffect(isFastRendererRegistered) {
if(isFastRendererRegistered){
// viewModel.registerFastRenderer(fastRendering)
isFastRendererRegistered = false
}
}
AndroidView(
factory = { context ->
LowLatencySurfaceView(context, fastRenderer = fastRendering)
},
modifier = modifier
.onGloballyPositioned {
fastRendering.globalPostion = it.positionInRoot()
}
)
}
In the attached Video, the gray surface is the LowLatencySurface, and I am writing over it using stylus, when trying to write, it is not behaving as expected.
I am trying to use LowLatencySurface because draw line in traditional jetpack compose Canvas is showing UI performance issue when large number of lines drawn over the canvas.