IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 制作一个最简易的画板(自定义view绘制+媒体库适配保存内容) -> 正文阅读

[移动开发]制作一个最简易的画板(自定义view绘制+媒体库适配保存内容)

制作一个最简易的画板

分析需求:既然是最简易的,那么只要实现最基本的功能就可以了

  • 画画(这个一定要的)
  • 橡皮擦
  • 能保存图片
  • 撤销和复原

画画

所谓画画,不过是记录下手指移动的痕迹而已

那么刚好在View的onTouchEvent方法中,可以实时跟随手指的移动坐标

 override fun onTouchEvent(event: MotionEvent?): Boolean {
        event?.let {
            val x = it.x
            val y = it.y
            when(it.action){
                MotionEvent.ACTION_DOWN->{
                    path.moveTo(x, y)
                    preX = x
                    preY = y
                    return falsezz
                }
                MotionEvent.ACTION_MOVE->{
                    //在这里实时刷新
                    path.quadTo(preX, preY, x, y)
//                    path.lineTo(x,y)
                    mBufferCanvas.drawPath(path, paint)
                    invalidate()
                    preX = x
                    preY = y
                }
                MotionEvent.ACTION_UP->{
                //在这里保存路径
                    val drawPath = DrawPath()
                    val oldPath = Path(path)
                    val oldPaint = Paint(paint)
                    drawPath.path = oldPath
                    drawPath.paint = oldPaint
                    undoStack.push(drawPath) //入栈
                    cancelStack.clear() //清空下取消撤回栈的缓存
                    path.reset() //清除路径内容
                }
            }
        }
        return true
  }
    

这里需要用到一个Path对象,来保存我们每次绘制的路径

可以只用一个path对象储存,但是这样会导致后续的撤销功能不好做,于是将每次dowm-move-up事件,都保存在一个新的path对象中,然后将该path绘制到一个bitmap里面,在draw方法中,只要调用drawBitmap也能实现实时绘制功能

    override fun onDraw(canvas: Canvas){
        super.onDraw(canvas)
        //直接绘制位图
        canvas.drawBitmap(mBufferBitmap, 0f,0f,null)
//        canvas.drawPath(path, paint) //这样会导致不好撤销
    }

橡皮擦

提到橡皮擦,就不得不提一下Android里面绘制的图形混合模式

这里只要用到clear这一种模式-清除模式,在我们需要使用橡皮擦功能时,只需要将混合模式改为Clear即可

    /**
     * 设置画笔模式
     */
    fun setModel(model:Long){
        mMode = model
        when(model){
            EDIT_MODE_PEN -> {
                paint.xfermode = null //空就是普通画笔
            }
            EDIT_MODE_ERASER ->{
                paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)  //橡皮擦模式
            }
        }
    }

撤销与复原

撤销与复原,很适合用stack来实现,当我们写完一笔时,将保存该路径的path入栈,在点击撤销时,我们只需要对stack执行出栈操作,然后用复原栈来接收出来的path即可。最后将栈内剩下的所有path绘制即可。

    /***
     * 撤销功能
     * 预期实现,使用一个path的stack去保存每次绘制的一个路径
     * 在使用撤销后,就移除前面的path。并且可以维护一个反撤销的一个栈
     */
    fun undo(){
        if(!undoStack.empty()){
            cancelStack.push(undoStack.pop())
            clear()
            for (pa in undoStack){
                mBufferCanvas.drawPath(pa.path, pa.paint)
            }
            invalidate()
        }   }
    /***
     * 取消撤回
     * 每次撤回操作,都会在反撤回栈入栈一个drawPath
     * 当需要取消撤回时,就将栈中出栈一个drawPath
     */
    fun cancelUndo(){
        if (!cancelStack.empty()){
            undoStack.push(cancelStack.pop())
            for (pa in undoStack){
                mBufferCanvas.drawPath(pa.path, pa.paint)
            }
            invalidate()
        }
    }

有点需要注意的是,我们在保存path时,也需要保存当时使用的paint信息,因此需要一个类来对双方都进行保存

/***
 * 一个保存绘制路径的类
 * 主要是保存绘制路径以及所采用的paint
 */
public class DrawPath {
    private Path path;
    private Paint paint;
    
    public Path getPath() {
        return path;
    }
    public void setPath(Path path) {
        this.path = path;
    }
    public Paint getPaint() {
        return paint;
    }
    public void setPaint(Paint paint) {
        this.paint = paint;
    }
}

保存图片

图片保存方面和很多适配相关

首先在Android sdk23(6.0.1)版本之后,想要对读写文件都需要动态进行权限获取,不能仅仅在Manifest里面声明

然后是在Android 29 (10, Q)之后,文件操作要用媒体库来实现了,不能直接对路径文件进行操作

先看看权限申请相关
    /***
     * 动态获取权限
     */
    private fun requestPermissions() {
        if (ActivityCompat.checkSelfPermission(
                this,
                android.Manifest.permission.WRITE_EXTERNAL_STORAGE
            )
            != PackageManager.PERMISSION_GRANTED  //检测是否有权限,无则申请,有则执行需要权限的操作
            
        ) {
            ActivityCompat.requestPermissions(
                this,
                arrayOf(
                    android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
                    android.Manifest.permission.READ_EXTERNAL_STORAGE
                ), REQUEST_STATE_CODE
            ) //调用权限申请方法
        } else {
            mBitmap?.let { insertImages(it) }
        }
    }

在申请后,我们要在onRequestPermissionsResult方法中,获得申请结果

   /***
     * 权限申请回调
     */
    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)

        when (requestCode) {
            REQUEST_STATE_CODE -> {
                if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    if(Build.VERSION.SDK_INT > Build.VERSION_CODES.Q){
                        mBitmap?.let { insertImageQ(it) }
                    }else{
                        mBitmap?.let { insertImages(it) }
                    }
                } else {
                    Toast.makeText(this, "权限授予失败,请重试", Toast.LENGTH_SHORT).show()
                }
            }
        }
    }
再看看文件保存相关

首先,在保存图片前,要知道我们怎么将view上绘制出来的东西,去变成图片去保存。

我们只需要获得一个bitmap对象,就能将bitmap位图进行保存,要获得bitmap对象,利用view的draw方法,将view的所有内容画在我们新建的白板画布上,那么这个白板画布就会变成我们想要的bitmap对象了

    /***
     * 获取bitmap对象
     */
    private fun getBitmap(view: View): Bitmap {
        //创建白板画布
        val bitmap: Bitmap = Bitmap.createBitmap(
            view.measuredWidth, view.measuredHeight,
            Bitmap.Config.ARGB_8888
        )
        val canvas: Canvas = Canvas(bitmap)
        canvas.drawColor(Color.WHITE)
        view.draw(canvas)
        return bitmap
    }

在Android10之前,我们可以通过File类来直接对文件系统进行修改,因此只需要将图片保存在某个路径当中,然后通过广播通知系统去刷新图库即可。也可以采用简易版本的图片插入,不过貌似是一个被弃用的方法

    /***
     * 直接用mediaStore的insertImage插入到picture目录
     */
    private fun insertImages(bitmap: Bitmap){
        val resolver = contentResolver
        MediaStore.Images.Media.insertImage(resolver, bitmap, "YMD${System.currentTimeMillis()}.jpg", "op")
    }

在Android10之后,需要利用媒体库,插入媒体信息,获得uri,再通过uri打开输出流,在bitmap的compress方法中,传入输出流,将图片保存到系统的媒体库中

    /***
     * Android 10以上插入图片
     */
    @RequiresApi(Build.VERSION_CODES.Q)
    private fun insertImageQ(bitmap: Bitmap){
        val fileName: String = "YMD${System.currentTimeMillis()}.jpg"
        var outputStream: OutputStream?
        var imageUri: Uri?
        val contentValues = ContentValues().apply {
            put(MediaStore.Images.ImageColumns.DISPLAY_NAME, fileName)
            put(MediaStore.Images.ImageColumns.MIME_TYPE, "image/jpg")
            put(MediaStore.Images.ImageColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES)
            // 设置独占锁:耗时操作,独占访问权限,完成操作需复位
            put(MediaStore.Video.Media.IS_PENDING, 1)
        }
        val contentResolver = App.instance.contentResolver
        contentResolver.also { resolver->
            imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
            outputStream = imageUri?.let {
                resolver.openOutputStream(it)
      
  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-09-24 21:08:25  更:2022-09-24 21:11:59 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 21:04:57-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码