Android 画板小工具

工作之余,自己想着利用空闲时间做一些小工具出来,今天分享的是一个简单的画板工具,支持轨迹绘制、更换笔迹颜色等功能,并且可以把成品保存到系统相册。支持Android 13

先看一下效果,吐槽一下csdn的视频上传,质量压缩的比较厉害,然后比例也发生变化了,反正是大家凑合看吧,文末会放源码(我的所有demo的源码都是不需要积分的)

Android画板小工具测试视频

我主要放一下关键代码吧

 1.自定义画板SignatureView 

public class SignatureView extends View {private Context context;private Paint paint;private Bitmap bitmap;private Canvas canvas;private Path path;public SignatureView(Context context, AttributeSet attrs) {super(context, attrs);this.context = context;paint = new Paint();paint.setColor(Color.BLACK);paint.setStrokeWidth(10);paint.setStyle(Paint.Style.STROKE);// 获取屏幕尺寸DisplayMetrics displayMetrics = getResources().getDisplayMetrics();int screenWidth = displayMetrics.widthPixels;int screenHeight = displayMetrics.heightPixels;bitmap = Bitmap.createBitmap(screenWidth, screenHeight, Bitmap.Config.ARGB_8888);canvas = new Canvas(bitmap);canvas.drawColor(Color.WHITE);path = new Path();}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);canvas.drawBitmap(bitmap, 0, 0, paint);}@Overridepublic 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);break;case MotionEvent.ACTION_MOVE:path.lineTo(x, y);canvas.drawPath(path, paint);break;case MotionEvent.ACTION_UP:break;default:return false;}invalidate();return true;}public void setColor(int newColor) {paint.setColor(newColor);}public void clear() {canvas.drawColor(Color.WHITE);invalidate();}public Bitmap getSignatureBitmap() {return bitmap;}public int dpToPx(float dp) {float density = context.getResources().getDisplayMetrics().density;return Math.round(dp * density);}}

2.布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:context=".MainActivity"><com.swy.signdemo.SignatureViewandroid:id="@+id/signatureView"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"android:background="#ffffff" /><Viewandroid:layout_width="match_parent"android:layout_height="1dp"android:background="#dcdcdc" /><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="50dp"android:layout_gravity="bottom|end"android:layout_margin="10dp"android:orientation="horizontal"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="当前颜色:" /><FrameLayoutandroid:layout_width="32dp"android:layout_height="32dp"android:layout_gravity="center_vertical"android:background="#000"><Viewandroid:id="@+id/view_color"android:layout_width="40dp"android:layout_height="40dp"android:layout_gravity="center"android:background="@color/black" /></FrameLayout><Buttonandroid:id="@+id/pickColor"android:layout_width="wrap_content"android:layout_height="match_parent"android:layout_marginLeft="20dp"android:text="更换颜色" /><Buttonandroid:id="@+id/clearButton"android:layout_width="wrap_content"android:layout_height="match_parent"android:layout_marginLeft="10dp"android:text="清空" /><Buttonandroid:id="@+id/saveButton"android:layout_width="wrap_content"android:layout_height="match_parent"android:layout_marginLeft="10dp"android:text="保存" /></LinearLayout></LinearLayout>

3.选择颜色的弹窗

public class PickColorWindow extends PopupWindow {private WindowPickColorBinding binding;private CommonAdapter<ColorData> commonAdapter;private List<ColorData> colors = new ArrayList<>();private ColorData colorDataSelected = null;public PickColorWindow(Activity context, ColorData color, PickColorCallBack callBack) {super(context);binding = WindowPickColorBinding.inflate(context.getLayoutInflater());setWidth(WindowManager.LayoutParams.MATCH_PARENT);setHeight(WindowManager.LayoutParams.MATCH_PARENT);setContentView(binding.getRoot());initColors();binding.viewColor.setBackgroundColor(Color.parseColor(color.getColorValue()));binding.btnCancel.setOnClickListener(v -> {dismiss();});binding.btnConfirm.setOnClickListener(v -> {callBack.onPick(colorDataSelected);dismiss();});binding.recycler.setLayoutManager(new GridLayoutManager(context, 4));commonAdapter = new CommonAdapter<ColorData>(context,R.layout.item_color, colors) {@Overridepublic void convert(CommonViewHolder holder, ColorData bean, int position) {holder.setBackgroundColor(R.id.view_color, Color.parseColor(bean.getColorValue()));holder.setOnClickListener(R.id.view_color, v -> {colorDataSelected = bean;binding.viewColor.setBackgroundColor(Color.parseColor(colorDataSelected.getColorValue()));});}@Overridepublic void footConvert(CommonViewHolder holder, int size) {}};binding.recycler.setAdapter(commonAdapter);}private void initColors() {colors.clear();colors.add(new ColorData("#000000"));colors.add(new ColorData("#e6194B"));colors.add(new ColorData("#3cb44b"));colors.add(new ColorData("#ffe119"));colors.add(new ColorData("#4363d8"));colors.add(new ColorData("#f58231"));colors.add(new ColorData("#42d4f4"));colors.add(new ColorData("#f032e6"));colors.add(new ColorData("#fabed4"));colors.add(new ColorData("#469990"));colors.add(new ColorData("#dcbeff"));colors.add(new ColorData("#9A6324"));colors.add(new ColorData("#fffac8"));colors.add(new ColorData("#800000"));colors.add(new ColorData("#aaffc3"));colors.add(new ColorData("#a9a9a9"));}public interface PickColorCallBack {void onPick(ColorData colorData);}
}

4.主界面

public class MainActivity extends AppCompatActivity {private ActivityMainBinding binding;private AlertDialog dialog;private static final int REQUEST_EXTERNAL_STORAGE = 1;private static String[] PERMISSIONS_STORAGE = {Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE};private boolean havePermission = false;private PickColorWindow pickColorWindow;private ColorData currentColor = new ColorData("#000000");@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);binding = ActivityMainBinding.inflate(getLayoutInflater());Window window = getWindow();window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);window.setStatusBarColor(Color.TRANSPARENT); // 设置状态栏颜色为透明window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);setContentView(binding.getRoot());binding.pickColor.setOnClickListener(v -> {showColorPickerDialog();});binding.clearButton.setOnClickListener(v -> {binding.signatureView.clear();});binding.saveButton.setOnClickListener(v -> {if (havePermission) {saveBitmap(binding.signatureView.getSignatureBitmap());} else {checkPermission();}});}private void saveBitmap(Bitmap bitmap) {// 获取外部存储目录String folderName = Environment.DIRECTORY_PICTURES;File file = new File(Environment.getExternalStoragePublicDirectory(folderName), "signature.png");try {if (file.exists()) {file.delete();}// 创建目录(如果不存在)file.getParentFile().mkdirs();// 尝试创建文件if (file.createNewFile()) {OutputStream os = new FileOutputStream(file);bitmap.compress(Bitmap.CompressFormat.PNG, 100, os); // 保存为PNG格式os.close();// 发送广播通知相册刷新sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file)));Toast.makeText(MainActivity.this, "保存成功", Toast.LENGTH_SHORT).show();}} catch (IOException e) {e.printStackTrace();}}private void showColorPickerDialog() {if (pickColorWindow != null) {pickColorWindow.dismiss();pickColorWindow = null;}pickColorWindow = new PickColorWindow(this, currentColor, (ColorData color) -> {currentColor = color;binding.signatureView.setColor(Color.parseColor(currentColor.getColorValue()));binding.viewColor.setBackgroundColor(Color.parseColor(currentColor.getColorValue()));});pickColorWindow.showAsDropDown(binding.getRoot());}private void checkPermission() {//检查权限(NEED_PERMISSION)是否被授权 PackageManager.PERMISSION_GRANTED表示同意授权if (Build.VERSION.SDK_INT >= 30) {if (!Environment.isExternalStorageManager()) {if (dialog != null) {dialog.dismiss();dialog = null;}dialog = new AlertDialog.Builder(this).setTitle("提示")//设置标题.setMessage("请开启文件访问权限,否则无法正常使用本应用!").setNegativeButton("取消", (dialog, i) -> dialog.dismiss()).setPositiveButton("确定", (dialog, which) -> {dialog.dismiss();Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);startActivity(intent);}).create();dialog.show();} else {havePermission = true;saveBitmap(binding.signatureView.getSignatureBitmap());Log.i("swyLog", "Android 11以上,当前已有权限");}} else {if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {//申请权限if (dialog != null) {dialog.dismiss();dialog = null;}dialog = new AlertDialog.Builder(this).setTitle("提示")//设置标题.setMessage("请开启文件访问权限,否则无法正常使用本应用!").setPositiveButton("确定", (dialog, which) -> {dialog.dismiss();ActivityCompat.requestPermissions(MainActivity.this, PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE);}).create();dialog.show();} else {havePermission = true;saveBitmap(binding.signatureView.getSignatureBitmap());Log.i("swyLog", "Android 6.0以上,11以下,当前已有权限");}} else {havePermission = true;saveBitmap(binding.signatureView.getSignatureBitmap());Log.i("swyLog", "Android 6.0以下,已获取权限");}}}@Overridepublic void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);switch (requestCode) {case REQUEST_EXTERNAL_STORAGE: {if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {havePermission = true;saveBitmap(binding.signatureView.getSignatureBitmap());Toast.makeText(this, "授权成功!", Toast.LENGTH_SHORT).show();} else {havePermission = false;Toast.makeText(this, "授权被拒绝!", Toast.LENGTH_SHORT).show();}return;}}}}

这个demo的功能还是相对比较简单的,然后没有什么好讲的,只不过这个demo中有涉及到Android 的运行时权限申请,兼容Android13的,可以重点关注一下,其他的都是UI层的东西,基本上把代码复制过去,就可以用了,真的有什么问题了,评论区留言

demo源码

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

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

相关文章

音频抓取代码示例

以下是一个使用DefaultsKit库的简单爬虫程序&#xff0c;用于爬取音频。代码中使用了https://www.duoip.cn/get_proxy的API获取代理服务器。 import Foundation import DefaultsKit ​ let url "https://www.douban.com/music" // 目标网站URL let proxyUrl "…

npm ERR! node-sass@6.0.1 postinstall: `node scripts/build.js`

1.遇到的问题 vue npm install提示以下错误 2.首次尝试方法 尝试用下面的方式重新安装弄得-saas&#xff0c;结果不起作用 。 npm config set sass_binary_sitehttps://npm.taobao.org/mirrors/node-sass npm install node-sass 这时考虑降级node版本&#xff0c;node.js从…

从手动操作到自动化管理,如何实现企业身份业务全面自动化?

在数字化时代&#xff0c;身份管理已经成为了企业和组织不可或缺的一部分&#xff0c;企业对于管理员工、客户和合作伙伴的身份信息和访问权限的需求变得愈发复杂。身份管理不仅仅是一项必要的任务&#xff0c;更是确保业务流畅运营和数据安全的关键因素。然而&#xff0c;传统…

系统架构之微服务架构

微服务架构 一.传统的单体架构与微服务架构的区别1.1 单体架构1.1.1 优点1.1.2 缺点 1.2 微服务架构1.2.1 优点1.2.2 面临的问题与挑战 二. 微服务架构模式方案2.1 聚合器微服务2.2 链式微服务2.3 数据共享微服务2.4 异步消息传递微服务 三. SOA与微服务的区别 微服务&#xff…

Vue 中setup的特性

特性四&#xff1a;父传子组件传参【defineProps】&#xff1a; 父组件&#xff08;传递数据&#xff09;&#xff1a;利用自定义属性传递数据。 <template><h3>我是父组件</h3><hr /><Child :name"info.name" :age"info.age"…

vue图表引用使用

如果你选择Vue作为你的前端框架&#xff0c;并且需要使用图表插件&#xff0c;那么可以考虑以下几种方式&#xff1a; 使用Vue插件&#xff1a;Vue有许多专门为图表设计的插件&#xff0c;如Vue-chartjs、Vue-echarts等。这些插件提供了一些已经封装好的组件并支持常见的图表库…

前端渲染后端返回的HTML格式的数据

在日常开发中&#xff0c;经常有需要前端渲染后端返回页面的需求&#xff0c;对于不同数据结构&#xff0c;前端的渲染方式也不尽相同&#xff0c;本文旨在对各种情况进行总结。 后端返回纯html文件格式 数据包含html标签等元素&#xff0c;数据类型如下图&#xff1a; 前端通…

“编辑微信小程序与后台数据交互与微信小程序wxs的使用“

引言 在现代移动应用开发中&#xff0c;微信小程序已经成为了一个非常流行和广泛使用的平台。为了使小程序能够展示丰富的内容和实现复杂的功能&#xff0c;与后台数据的交互是至关重要的。同时&#xff0c;微信小程序还提供了一种特殊的脚本语言——wxs&#xff0c;用于增强小…

图论03-【无权无向】-图的深度优先遍历-路径问题/检测环/二分图

文章目录 1. 代码仓库2. 单源路径2.1 思路2.2 主要代码 3. 所有点对路径3.1 思路3.2 主要代码 4. 路径问题的优化-提前结束递归4.1 思路4.2 主要代码 5. 检测环5.1 思路5.2 主要代码 6. 二分图6.1 思路6.2 主要代码6.2.1 遍历每个联通分量6.2.2 递归判断相邻两点的颜色是否一致…

【手写数据库toadb】语言解析器,编程语言是这样被解析理解,解析器利器flex和bison,解析树与逆波兰式

flex与bsion使用介绍 ​专栏内容: 手写数据库toadb 本专栏主要介绍如何从零开发,开发的步骤,以及开发过程中的涉及的原理,遇到的问题等,让大家能跟上并且可以一起开发,让每个需要的人成为参与者。 本专栏会定期更新,对应的代码也会定期更新,每个阶段的代码会打上tag,方…

安装插件失败,getaddrinfo ENOENT raw.githubusercontent.com 报错

报错如图 解决方法&#xff1a; 查看raw.githubusercontent.com的真实IP地址 点开网址 https://www.ipaddress.com/ &#xff0c;输入raw.githubusercontent.com&#xff0c;点击查询&#xff1a; 复制以下的ip 修改hosts文件 找到这个文件夹&#xff1a; C:\Windows\System32\…

MIPS指令集摘要

目录 MIPS指令R I J三种格式 MIPS五种寻址方式 立即数寻址 寄存器寻址 基址寻址 PC相对寻址 伪直接寻址 WinMIPS64汇编指令 助记 从内存中加载数据 lb lbu lh lhu lw lwu ld l.d lui 存储数据到内存 sb sh sw sd s.d 算术运算 daddi daddui dadd…

Java中静态常量和枚举类的区别

在项目中我们有时候会使用常量、静态常量以及枚举&#xff0c;那么他们有什么区别呢&#xff1f;我们先看几个例子&#xff1a; 若依框架中使用的常量&#xff1a; /** 正常状态 */public static final String NORMAL "0";/** 异常状态 */public static final Stri…

绿盾控制台如何给未授权终端分配相应权限

环境&#xff1a; 绿盾控制台7.0 问题描述&#xff1a; 绿盾控制台如何给未授权终端分配相应权限 解决方案&#xff1a; 1.进入桌面管理系统 2.通过终端号&#xff0c;找到未授权终端下面&#xff0c;选择相应的未授权终端 3.点击鼠标右键&#xff0c;选择分配授权模块 4.下…

01认识微服务

一、微服务架构演变 1.单体架构 将所有的功能集中在一个项目开发&#xff0c;打成一个包部署。优点架构简单&#xff0c;部署成本低。缺点耦合度高&#xff0c;不利于大型项目的开发和维护 2.分布式架构 根据业务功能对系统进行拆分&#xff0c;每个业务模块作为独立的项目…

修改ConsoleApplication17_2项目实现oss上线

首先创建号oss&#xff0c;上传文件&#xff0c;复制临时链接 木马内写 可以看到能成功上线但是有个问题就是占用cpu大小为9%左右&#xff0c;这里我用的是腾讯云oss实现的&#xff0c;用阿里云oss实现也是9%左右 我再次进行url的aes加密 还是百分之9左右&#xff0c; 这里…

npm publish发布到在线仓库时,提示:Scope not found

当npm publish发布时&#xff0c;控制台提示&#xff1a;Scope not found&#xff0c;具体错误信息如下&#xff1a; npm notice npm ERR! code E404 npm ERR! 404 Not Found - PUT https://registry.npmjs.org/xxx%2fxxx - Scope not found npm ERR! 404 npm ERR! 404 xxx/xx…

Spring源码解析——事务的回滚和提交

正文 上一篇文章讲解了获取事务&#xff0c;并且通过获取的connection设置只读、隔离级别等&#xff0c;这篇文章讲解剩下的事务的回滚和提交。最全面的Java面试网站 回滚处理 之前已经完成了目标方法运行前的事务准备工作&#xff0c;而这些准备工作最大的目的无非是对于程…

[计算机提升] 快捷方式与硬链接

1.7 快捷方式与硬链接 1.7.1 快捷方式(符号链接) 在Windows系统中&#xff0c;快捷方式是指一个特殊的文件或图标&#xff0c;它提供了方便的访问其他文件、文件夹、应用程序或网站的方式。快捷方式可以视为一个指向其他位置的链接或引用。 快捷方式被创建时&#xff0c;会关…

Rust 中的String与所有权机制

文章目录 一、string二、所有权2.1 所有权与作用域2.2 对所有权的操作2.2.1 转移2.2.3 拷贝2.2.3 传递 2.3 引用2.3.1 借用2.3.2 可变引用 一、string 之前学习过 Rust 只有几种基础的数据类型&#xff0c;但是没有常用的字符串也就是String&#xff0c;今天来学习一下 String…