安卓自定义画板

news2025/4/8 20:46:07

包含功能:

包含  获取当前画板的截图、设置画笔样式、获取画笔样式、设置画笔宽度、获取画笔宽度、设置画笔颜色、获取画笔颜色、加载图片、获取图片位图对象、设置图片位图对象,并在画布上绘制图片、撤销上一步操作、重做上一步撤销的操作、清空所有绘图路径,重新绘制位图

 自定义视图组件

package com.zx.drawing_board;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Stack;

public class DrawingBoard extends View {

    // 上下文对象,用于获取资源和应用程序信息
    public Context context;

    // 画布对象,用于绘制图形
    public Canvas canvas;

    // 画笔对象,用于设置绘制样式和颜色
    public Paint paint;

    // 位图对象,用于在其中进行绘制操作
    public Bitmap bitmap;

    // 绘制路径对象,记录用户绘制的路径
    public Path path;

    // 图片的URI地址
    public Uri uri;

    // 图片位图对象,用于加载图片
    private Bitmap mImageBitmap;

    // 保存用户绘制路径的栈结构
    private Stack<Path> paths = new Stack<>();

    /**
     * 构造函数,创建一个新的FingerPainterView对象
     * @param context 上下文对象,用于获取资源和应用程序信息
     */
    public DrawingBoard(Context context) {
        super(context);
        // 执行初始化方法
        init(context);
    }

    /**
     * 构造函数,创建一个新的FingerPainterView对象
     * @param context 上下文对象,用于获取资源和应用程序信息
     * @param attrs 属性集合对象,用于获取视图的自定义属性
     */
    public DrawingBoard(Context context, AttributeSet attrs) {
        super(context, attrs);

        // 执行初始化方法
        init(context);
    }

    /**
     * 构造函数,创建一个新的FingerPainterView对象
     * @param context 上下文对象,用于获取资源和应用程序信息
     * @param attrs 属性集合对象,用于获取视图的自定义属性
     * @param defStyle 样式属性,用于设置默认样式
     */
    public DrawingBoard(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        // 执行初始化方法
        init(context);
    }
    /**
     * 获取当前画板的截图
     * @return 画板的截图
     */
    public Bitmap getScreenshot() {
        return Bitmap.createBitmap(bitmap);
    }
    /**
     * 初始化方法,设置默认的画笔样式和颜色
     * @param context 上下文对象,用于获取资源和应用程序信息
     */
    private void init(Context context) {
        this.context = context;

        // 创建路径对象和画笔对象
        path = new Path();
        paint = new Paint();

        // 默认的画笔样式和颜色
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeJoin(Paint.Join.ROUND);
        paint.setStrokeWidth(20);
        paint.setStrokeCap(Paint.Cap.ROUND);
        paint.setARGB(255,0,0,0);
    }

    /**
     * 设置画笔样式
     * @param brush 画笔样式
     */
    public void setBrush(Paint.Cap brush) {
        paint.setStrokeCap(brush);
    }

    /**
     * 获取画笔样式
     * @return 画笔样式
     */
    public Paint.Cap getBrush() {
        return paint.getStrokeCap();
    }

    /**
     * 设置画笔宽度
     * @param width 画笔宽度
     */
    public void setBrushWidth(int width) {
        paint.setStrokeWidth(width);
    }

    /**
     * 获取画笔宽度
     * @return 画笔宽度
     */
    public int getBrushWidth() {
        return (int) paint.getStrokeWidth();
    }

    /**
     * 设置画笔颜色
     * @param colour 画笔颜色
     */
    public void setColour(int colour) {
        paint.setColor(colour);
    }

    /**
     * 获取画笔颜色
     * @return 画笔颜色
     */
    public int getColour() {
        return paint.getColor();
    }

    /**
     * 加载图片
     * @param uri 图片的URI地址
     */
    public void load(Uri uri) {
        this.uri = uri;
    }

    /**
     * 获取图片位图对象
     * @return 图片位图对象
     */
    public Bitmap getmImageBitmap() {
        return mImageBitmap;
    }

    /**
     * 设置图片位图对象,并在画布上绘制图片
     * @param mImageBitmap 图片位图对象
     */
    public void setmImageBitmap(Bitmap mImageBitmap) {
        this.mImageBitmap = mImageBitmap;
        canvas.drawColor(Color.WHITE);
        canvas.drawBitmap(mImageBitmap, 0, 0, paint);
    }


    /**
     * 撤销上一步操作
     */
    public void undo() {
        if (!paths.isEmpty()) {
            // 移除最近的路径,并重新绘制位图
            paths.pop();
            redrawBitmap();
        }
    }

    /**
     * 重做上一步撤销的操作
     */
    public void redo() {
        if (!paths.isEmpty()) {
            // 将最近撤销的路径重新添加到绘图路径中,并重新绘制位图
            Path lastPath = paths.peek();
            paths.push(new Path(lastPath));
            redrawBitmap();
        }
    }

    /**
     * 清空所有绘图路径,重新绘制位图
     */
    public void clear() {
        paths.clear();
        redrawBitmap();
    }
    @Override
    public Parcelable onSaveInstanceState() {
        Bundle bundle = new Bundle();
        // 保存父类视图状态
        bundle.putParcelable("superState", super.onSaveInstanceState());

        try {
            // 将位图保存到临时缓存文件中,以克服Binder事务大小限制
            File f = File.createTempFile("fingerpaint", ".png", context.getCacheDir());
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, new FileOutputStream(f));
            // 将临时文件名保存到bundle中
            bundle.putString("tempfile", f.getAbsolutePath());
        } catch(IOException e) {
            Log.e("FingerPainterView", e.toString());
        }
        return bundle;
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        if (state instanceof Bundle) {
            Bundle bundle = (Bundle) state;

            try {
                // 从bundle中获取缓存文件
                File f = new File(bundle.getString("tempfile"));
                Bitmap b = BitmapFactory.decodeStream(new FileInputStream(f));
                // 需要复制位图以创建可变版本
                bitmap = b.copy(b.getConfig(), true);
                b.recycle();
                f.delete();
            } catch(IOException e) {
                Log.e("FingerPainterView", e.toString());
            }

            state = bundle.getParcelable("superState");
        }
        super.onRestoreInstanceState(state);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        // 画布是白色的,并在顶部绘制带有alpha通道的位图
        canvas.drawColor(Color.WHITE);
        canvas.drawBitmap(bitmap, 0, 0, paint);
        // 显示当前的绘图路径
        for (Path p : paths) {
            canvas.drawPath(p, paint);
        }
        canvas.drawPath(path, paint);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        // 在Activity创建后,当视图被膨胀时调用
        if(bitmap==null) {
            if(uri!=null) {
                try {
                    // 尝试加载提供的uri,并进行缩放以适应我们的画布
                    InputStream stream = context.getContentResolver().openInputStream(uri);
                    Bitmap bm = BitmapFactory.decodeStream(stream);
                    bitmap  = Bitmap.createScaledBitmap(bm, Math.max(w, h), Math.max(w, h), false);
                    stream.close();
                    bm.recycle();
                } catch(IOException e) {
                    Log.e("FingerPainterView", e.toString());
                }
            }
            else {
                // 创建一个正方形位图,以便即使在旋转到横向时也可绘制
                bitmap = Bitmap.createBitmap(Math.max(w,h), Math.max(w,h), Bitmap.Config.ARGB_8888);
            }
        }
        canvas = new Canvas(bitmap);
    }

    /**
     * 触摸事件处理方法,用于绘制路径
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 按下手指时,重置路径并移动到指定位置
                path.reset();
                path.moveTo(x, y);
                path.lineTo(x, y);
                invalidate();
                break;
            case MotionEvent.ACTION_MOVE:
                // 手指移动时,连线到当前位置
                path.lineTo(x, y);
                invalidate();
                break;
            case MotionEvent.ACTION_UP:
                // 手指抬起时,将路径保存,并重置路径
                paths.push(new Path(path));
                path.reset();
                invalidate();
                break;
        }
        return true;
    }

    /**
     * 重新绘制位图,根据当前的绘图路径
     */
    private void redrawBitmap() {
        bitmap.eraseColor(Color.WHITE);
        for (Path p : paths) {
            canvas.drawPath(p, paint);
        }
        invalidate();
    }
}

用法

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.zx.drawing_board.DrawingBoard
        android:id="@+id/fp"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</RelativeLayout>

效果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1450215.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

.net和jar包windows服务部署

一.NetCore 1.创建启动脚本run_instal.bat,例如程序文件为ApiDoc.exe set serviceName"Apidoc Web 01" set serviceFilePath%~dp0ApiDoc.exe set serviceDescription"ApiDoc 动态接口服务 web 01"sc create %serviceName% BinPath%serviceFilePath% sc c…

TiDB 在医疗保障信息平台的应用实践

文章介绍了 TiDB 在医疗保障信息平台中的应用。东软医保云应用管理平台通过与 TiDB 联合&#xff0c;成功满足了医疗保障业务中高并发、实时性和复杂查询的要求。在某地市医疗保障信息平台的实践中&#xff0c;TiDB 分布式数据库有效实现了在线交易和实时分析服务&#xff0c;日…

5种风格非常经典的免费wordpress主题

免费wordpress主题下载 高端大气上档次的wordpress主题&#xff0c;也可以是免费的&#xff0c;可以在线免费下载。 https://www.wpniu.com/themes/288.html wordpress免费主题 高端大气的wordpress免费主题&#xff0c;LOGO在顶部左侧&#xff0c;导航菜单在顶部右侧。 ht…

2007-2021年上市公司内控信息披露指数/上市公司内部控制信息披露指数数据

2007-2021年上市公司内控信息披露指数/上市公司内部控制信息披露指数数据 1、时间&#xff1a;2007-2021年 2、范围&#xff1a;上市公司 3、指标&#xff1a;证券代码、证券简称、辖区、证监会行业、申万行业、内部环境、风险评估、控制活动、信息与沟通、内部监督、内部控…

使用人工智能增强人类能力的开源框架

主要特征 创建它是为了让人类能够轻松地通过人工智能增强自己。我认为目前人们使用人工智能太困难了。我认为工具太多&#xff0c;网站太多&#xff0c;而将问题与解决方案结合起来的实际用例太少。Fabric 是解决这些问题的一种方法。 它的最佳功能是它的模式&#xff0c;即使…

【MySQL/Redis】如何实现缓存一致

目录 不实用的方案 1. 先写 MySQL , 再写 Redis 2. 先写 Redis &#xff0c; 再写MySQL 3. 先删 Redis&#xff0c;再写 MySQL 实用的方案 1. 先删 Redis&#xff0c;再写 MySQL, 再删 Redis 2. 先写 MySQL , 再删 Redis 3. 先写MySQL&#xff0c;通过BinLog&#xff0…

Redis面试题整理(持续更新)

1. 缓存穿透&#xff1f; 缓存穿透是指查询一个一定不存在的数据&#xff0c;如果从存储层查不到数据则不写入缓存&#xff0c;这将导致这个不存在的数据每次请求都要到 DB 去查询&#xff0c;可能导致DB挂掉&#xff0c;这种情况大概率是遭到了攻击。 解决方案&#xff1a; …

MySQL 基础知识(三)之数据库操作

目录 1 显示当前时间、用户名、数据库版本 2 查看已有数据库 3 创建数据库 4 使用数据库 5 查看当前使用的数据库 6 查看当前数据库信息 7 查看数据库编码 8 修改数据库信息 9 删除数据库 10 查看最大连接数 11 查看数据库当前连接数&#xff0c;并发数 12 查看数据…

ch5-homework-基于LMDeploy的大模型量化部署实践

ch5-homework-基于LMDeploy的大模型量化部署实践 主要内容教程复现环境配置服务部署模型转换在线转换离线转换 TurboMind 推理命令行本地对话TurboMind推理API服务网页 Demo 演示TurboMind 服务作为后端TurboMind 推理作为后端 TurboMind 推理 Python 代码集成最佳实践方案实践…

第三十回 张都监血溅鸳鸯楼 武行者夜走蜈蚣岭-python可接受任意数量参数的函数

武松回到孟州城&#xff0c;来到张都监后花园墙外&#xff0c;这是一个马院&#xff0c;问清楚后槽张团练他们三人还在鸳鸯楼吃酒&#xff0c;直接一刀杀了。武松从后门这里爬过墙&#xff0c;来到了厨房&#xff0c;将两个还在服侍的丫环杀了。 武松认得路&#xff0c;蹑手蹑…

网络爬虫实战 | 上传以及下载处理后的文件

以实现爬虫一个简单的&#xff08;SimFIR (doctrp.top)&#xff09;网址为例&#xff0c;需要遵循几个步骤&#xff1a; 1. 分析网页结构 首先&#xff0c;需要分析该网页的结构&#xff0c;了解图片是如何存储和组织的。这通常涉及查看网页的HTML源代码&#xff0c;可能还包…

【剪辑必备】今天我教你如何手动去下载苹果官网4K预告片 完全免费

&#x1f680; 个人主页 极客小俊 ✍&#x1f3fb; 作者简介&#xff1a;web开发者、设计师、技术分享博主 &#x1f40b; 希望大家多多支持一下, 我们一起学习和进步&#xff01;&#x1f604; &#x1f3c5; 如果文章对你有帮助的话&#xff0c;欢迎评论 &#x1f4ac;点赞&a…

问题:用来表示证券收益的波动性,值越大说明()。 #媒体#经验分享

问题&#xff1a;用来表示证券收益的波动性&#xff0c;值越大说明()。 A.风险大 B.风险小 C.不确定 D.风险固定 参考答案如图所示

MySQL5.7升级到MySQL8.0的最佳实践分享

一、前言 事出必有因&#xff0c;在这个月的某个项目中&#xff0c;我们面临了一项重要任务&#xff0c;即每年一次的等保测评整改。这次测评的重点是Mysql的一些高危漏洞&#xff0c;客户要求我们无论如何必须解决这些漏洞。尽管我们感到无奈&#xff0c;但为了满足客户的要求…

命令行参数和环境变量

命令行参数 命令行参数是在用户在命令行中输入命令时&#xff0c;跟随命令一起输入的一些附加信息。这些参数可以用来配置命令的行为或传递一些数据给命令。 让同样的程序在不同的命令行参数下运行出不同的结果&#xff01; 将这些命令和参数可以传给 main 函数生&#xff0…

(15)Hive调优——数据倾斜的解决指南

目录 前言 一、什么是数据倾斜 二、发生数据倾斜的表现 2.1 MapReduce任务 2.2 Spark任务 三、如何定位发生数据倾斜的代码 四、发生数据倾斜的原因 3.1 key分布不均匀 3.1.1 某些key存在大量相同值 3.1.2 存在大量异常值或空值 3.2 业务数据本身的特性 3.3 SQL语句…

【更新】企业数字化转型-年度报告175个词频、文本统计

数据说明&#xff1a; 这份数据含数字化转型175个词频、各维度水平&#xff0c;保留2000-2021年数据。参考吴非、赵宸宇两位老师做法&#xff0c;根据上市公司年报文本&#xff0c;整理数字化转型175个词频数据&#xff0c;希望对大家有所帮助。 参考管理世界中吴非&#xff…

【正点原子STM32】TIMER 定时器(软件定时原理、定时器定时原理、分类和特性、基本定时器(影子寄存器和U事件和UI中断))

一、定时器概述 1.1、软件定时原理1.2、定时器定时原理1.3、STM32定时器分类1.4、STM32定时器特性表1.5、STM32基本、通用、高级定时器的功能整体区别 二、基本定时器 2.1、基本定时器简介2.2、基本定时器框图2.3、定时器计数模式及溢出条件2.4、定时器中断实验相关寄存器2.…

c语言(指针进阶)

指针 一.什么是字符指针二.使用指针数组模拟二维数组三.函数指针 一.什么是字符指针 字符指针&#xff1a;指向字符型数据的指针变量。每个字符串在内存中都占用一段连续的存储空间&#xff0c;并有唯一确定的首地址。即将字符串的首地址赋值给字符指针&#xff0c;可让字符指针…

用HTML Canvas和JavaScript创建美丽的花朵动画效果

目录 一、程序代码 二、代码原理 三、运行效果 一、程序代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>flower</title><style>* {margin: 0;padding: 0;overflow: hidden;backg…