• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

使用compose绘制可以保存成Bitmap的写字板

武飞扬头像
小九__
帮助2

前言

公司的项目使用了MVI架构,V层使用compose进行UI绘制,本着探索与学习,闲暇时间写了个记事本的app,现需要一个写字板功能,遂记录,本文不涉及compose 重组机制介绍,和绘制原理,只记录功能。

功能拆分

  • 写字板
    根据手势检测移动路径,使用Canvas组件绘制路径

  • 保存Compose中的UI成图片
    暂时没有找到Compose 控件直接转换成的方法,转换思路
    1、使用Modifier.onGloballyPositioned()监听组件在全局的位置
    2、使用Bitmap Canvas PixelCopy 等类,将指定区域的View 保存成Bitmap,如果是传统的View 可以通过drawToBitmap() 扩展函数操作
    3、使用contentResolver插入到相册

手势检测

Compose 中的Modifier提供pointerInput扩展方法用来监测手势输入 由于pointerInput传入的方法是挂起函数 并是PointerInputScope的扩展函数,所以pointerInput处理手势时,并不会阻塞当前线程 在PointerInputScope.awaitEachGesture()方法中 ,我们可以通过传入一个函数实时等待手指输入,获取当前位置和手指事件

绘制Path

和传统xml布局相似,可以通过Canvas 绘制一些点、线、路径等
差别是,XML是Canvas类,而Compose提供了一个方法(声明式UI特性)

   Canvas(modifier = Modifier.fillMaxSize()) {
       val path = Path()
       path.moveTo(0f, 0f)
       path.lineTo(size.width / 2f, size.height / 2f)
       path.lineTo(size.width, 0f)
       path.close()
       drawPath(path, Color.Magenta, style = Stroke(width = 10f))
   }
Compose组件转换成bitmap
DisposableEffect(Unit) {
    screenshotState.callback = {
       runCatching {
            composeView?.let {
                if (it.width == 0f || it.height == 0f){
                    return@let
                }
                view.screenshot(it){
                    screenshotState.imageState.value = it
                    if (it is ImageResult.Success){
                        println("screenshot imageState Success")
                        screenshotState.bitmapState.value = it.data
                    }
                }
            }
        }.onFailure {
            screenshotState.imageState.value = ImageResult.Error(Exception(it.message))
        }
    }
    onDispose {
        println("screenshot onDispose ")
        val bmp = screenshotState.bitmapState.value
        bmp?.apply {
            if (!isRecycled) {
                recycle()
            }
        }
        screenshotState.bitmapState.value = null
        screenshotState.callback = null
    }
}

//根据区域保存bitmap
fun View.screenshot(
    bounds: Rect,
    bitmapCallback: (ImageResult) -> Unit
) {
    try {

        val bitmap = Bitmap.createBitmap(
            bounds.width.toInt(),
            bounds.height.toInt(),
            Bitmap.Config.ARGB_8888,
        )

        PixelCopy.request(
            (this.context as Activity).window,
            bounds.toAndroidRect(),
            bitmap,
            {
                when (it) {
                    PixelCopy.SUCCESS -> {
                        bitmapCallback.invoke(ImageResult.Success(bitmap))
                    }
                    PixelCopy.ERROR_DESTINATION_INVALID -> {
                        
                    }
                    PixelCopy.ERROR_SOURCE_INVALID -> {
                        bitmapCallback.invoke(
                         
                        )
                    }
                    PixelCopy.ERROR_TIMEOUT -> {
                        bitmapCallback.invoke(
                            ImageResult.Error(
                                Exception(
                                    "A timeout occurred while trying to acquire a buffer "  
                                            "from the source to copy from."
                                )
                            )
                        )
                    }
                    PixelCopy.ERROR_SOURCE_NO_DATA -> {
                        
                    }
                    else -> {
                        bitmapCallback.invoke(
                           
                        )
                    }
                }
            },
            Handler(Looper.getMainLooper())
        )
    } catch (e: Exception) {
        bitmapCallback.invoke(ImageResult.Error(e))
    }
}

具体实现

@Composable
fun DrawTablet() {
    // 记录每一次move的路线
    var linepath by remember { mutableStateOf(Offset.Zero) }
    // 记录path对象
    val path by remember { mutableStateOf(Path()) }
    val screenshotState = rememberScreenshotState()
    val context =  LocalContext.current
    val scope = rememberCoroutineScope()
    ScreenshotBox(screenshotState = screenshotState,
        modifier =   Modifier
            .statusBarsPadding().
            navigationBarsPadding()
            .fillMaxSize()
            .pointerInput(Unit) {
            awaitEachGesture{
                while (true) {
                    val event = awaitPointerEvent()
                    when (event.type) {
                        //按住时,更新起始点
                        Press -> {
                            path.moveTo(event.changes.first().position.x, event.changes.first().position.y)
                        }
                        //移动时,更新起始点 移动时,记录路径path
                        Move->{
                            linepath = event.changes.first().position
                        }
                    }
                }
            }
        }){
            Canvas(modifier = Modifier.fillMaxSize()) {
                //重组新路线
                path.lineTo(linepath.x, linepath.y)
                drawPath(  color = Color.Red,
                    path = path,
                    style = Stroke(width = 10F))
            }
            Row(modifier = Modifier.align(Alignment.BottomCenter)) {
                Button(modifier = Modifier.weight(1f).padding(8.dp), onClick = {
                    linepath = Offset.Zero
                    path.reset()
                }){
                    Text(text = "Clear")
                }
                Button(modifier = Modifier.weight(1f).padding(8.dp), onClick = {
                    scope.launch {
                        screenshotState.capture()
                        screenshotState.bitmap?.insertImageToImage(context)
                    }
                }){
                    Text(text = "Screenshot")
                }
            }
        }
    }

参考资料

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhcffhjf
系列文章
更多 icon
同类精品
更多 icon
继续加载