Xamarin.Android实现手写板的功能

news2024/7/6 19:46:53

目录

  • 1、背景说明
  • 2、实现效果
  • 3、代码实现
    • 3.1 整体思路
    • 3.2 核心绘画类-PaintView.cs
    • 3.3 对话框类-WritePadDialog.cs
    • 3.4 前端实现类-MainActivity
    • 3.5 布局文件
      • 3.5.1 write_pad.xml
      • 3.5.2 activity_main布局文件
  • 4、知识总结
  • 5、代码下载
  • 6、参考资料

1、背景说明

在实际使用过程中,可能会需要在APP中实现手写板的功能,网上比较多的是Android的实现,因此找了下资料,改了改,实现了Xamarin.Android手写板的功能

2、实现效果

实现的效果如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3、代码实现

3.1 整体思路

Xamarin.Android中实现绘图主要是两种方式Drawable ResourcesCanvas,前者可主要进行类似HtmlCSS之类的功能,后者则实现比较负责的功能,本次主要用到了后者-Canvas

整个思路是这样的:绘画核心部分通过继承View,重写相关方法,从而实现笔迹的追踪及记录。对话框主要是实现文件的保存等操作功能;前端的界面(即MainActivity)实现图像的展示,具体代码如下:

3.2 核心绘画类-PaintView.cs

绘画的核心方法

public class PaintView : View
{
    private Bitmap mBitmap; //用于存放展示的内容
    private Path mPath; //路径
    private Paint mPaint;//关键类
    private Canvas mCanvas; //画布

    private int screenWidth, screenHeight;
    private float currentX, currentY;

    public PaintView(Context context,int screenWidth,int screenHeight):base(context)
    {
        this.screenWidth = screenWidth;
        this.screenHeight = screenHeight;
        Initialize();
    }

    public PaintView(Context context, IAttributeSet attrs) :
        base(context, attrs)
    {
        Initialize();
    }

    public PaintView(Context context, IAttributeSet attrs, int defStyle) :
        base(context, attrs, defStyle)
    {
        Initialize();
    }

    //完成初始化的设置
    private void Initialize()
    {
        mPaint = new Paint();
        mPaint.AntiAlias = true;
        mPaint.Color = Color.Black;
        mPaint.StrokeWidth = 5;
        mPaint.SetStyle(Paint.Style.Stroke);

        mPath = new Path();

        mBitmap=Bitmap.CreateBitmap(screenWidth, screenHeight, Bitmap.Config.Argb8888);
        mCanvas = new Canvas(mBitmap);
    }


    //重写绘画方法
    protected override void OnDraw(Canvas canvas)
    {
        base.OnDraw(canvas);

        canvas.DrawBitmap(mBitmap, 0, 0, null);
        canvas.DrawPath(mPath, mPaint);
    }


    //重写监听的事件
    public override bool OnTouchEvent(MotionEvent e)
    {
        float x=e.GetX();
        float y=e.GetY();

        switch(e.Action)
        {
            case MotionEventActions.Down:
                currentX = x;
                currentY = y;
                mPath.MoveTo(currentX, currentY);
                break;
            case MotionEventActions.Move: 
                currentX = x;
                currentY = y;
                mPath.QuadTo(currentX, currentY,x,y);
                break;
            case MotionEventActions.Up:
                mCanvas.DrawPath(mPath, mPaint);
                break;
        }



        Invalidate();
        return true;
    }


    // 缩放
    public static Bitmap resizeImage(Bitmap bitmap, int width, int height)
    {
        int originWidth = bitmap.Width;
        int originHeight = bitmap.Height;

        float scaleWidth = ((float)width) / originWidth;
        float scaleHeight = ((float)height) / originHeight;

        Matrix matrix = new Matrix();
        matrix.PostScale(scaleWidth, scaleHeight);
        Bitmap resizedBitmap = Bitmap.CreateBitmap(bitmap, 0, 0, originWidth,
                originHeight, matrix, true);
        return resizedBitmap;
    }


    //清空
    public void clear()
    {
        if (mCanvas != null)
        {
            mPath.Reset();
            mCanvas.DrawColor(Color.Transparent, PorterDuff.Mode.Clear);
            Invalidate();
        }
    }

    public Bitmap getPaintBitmap()
    {
        return resizeImage(mBitmap, 320, 480);
    }

    public Path getPath()
    {
        return mPath;
    }

}

3.3 对话框类-WritePadDialog.cs

public delegate void Handler(object sender);

public class WritePadDialog : Dialog
{
    private Android.Content.Context mContext;
    private FrameLayout mFrameLayout;
    private PaintView mPaintView;
    private Button mBtnOK, mBtnClear, mBtnCancel;
    public event Handler WriteDialogListener;

    public WritePadDialog(Android.Content.Context context) : base(context)
    {
        mContext=context;
    }

    protected override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);

        RequestWindowFeature(1);
        //Window.SetFeatureInt(WindowFeatures.NoTitle,5);

        SetContentView(Resource.Layout.write_pad);

        mFrameLayout = FindViewById<FrameLayout>(Resource.Id.tablet_view);
        // 获取屏幕尺寸
        DisplayMetrics mDisplayMetrics = new DisplayMetrics();
        Window.WindowManager.DefaultDisplay.GetMetrics(mDisplayMetrics);
        int screenWidth = mDisplayMetrics.WidthPixels;
        int screenHeight = mDisplayMetrics.HeightPixels;
        mPaintView = new PaintView(mContext, screenWidth, screenHeight);
        mFrameLayout.AddView(mPaintView);
        mPaintView.RequestFocus();

        //保存按钮
        mBtnOK =FindViewById<Button>(Resource.Id.write_pad_ok);
        mBtnOK.Click += MBtnOK_Click;

        //清空按钮
        mBtnClear = FindViewById<Button>(Resource.Id.write_pad_clear);
        mBtnClear.Click += (o, e) => { mPaintView.clear(); };

        //取消按钮
        mBtnCancel = FindViewById<Button>(Resource.Id.write_pad_cancel);
        mBtnCancel.Click += (o, e) => { Cancel(); };

    }


    private void MBtnOK_Click(object sender, EventArgs e)
    {
        if (mPaintView.getPath().IsEmpty)
        {
            Toast.MakeText(mContext, "请写下你的大名", ToastLength.Short).Show();
            return;
        }

        WriteDialogListener(mPaintView.getPaintBitmap());
        Dismiss();
    }
}

这儿声明了一个委托delegate,主要是想实现通过这个委托,将生成的图像传递出去。就是对话框中的eventWriteDialogListener

3.4 前端实现类-MainActivity

protected override void OnCreate(Bundle savedInstanceState)
 {
     base.OnCreate(savedInstanceState);
     Xamarin.Essentials.Platform.Init(this, savedInstanceState);
     SetContentView(Resource.Layout.activity_main);

     AndroidX.AppCompat.Widget.Toolbar toolbar = FindViewById<AndroidX.AppCompat.Widget.Toolbar>(Resource.Id.toolbar);
     SetSupportActionBar(toolbar);

     FloatingActionButton fab = FindViewById<FloatingActionButton>(Resource.Id.fab);
     fab.Click += FabOnClick;

     mIVSign = FindViewById<ImageView>(Resource.Id.signImageView);
     mTVSign = FindViewById<TextView>(Resource.Id.signBtn);

     mTVSign.Click += MTVSign_Click;

 }

 private void MTVSign_Click(object sender, EventArgs e)
 {
     WritePadDialog mWritePadDialog = new WritePadDialog(this);
     mWritePadDialog.WriteDialogListener += MWritePadDialog_WriteDialogListener;
     mWritePadDialog.Show();
 }

 private void MWritePadDialog_WriteDialogListener(object sender)
 {
     mSignBitmap = (Bitmap)sender;
     createSignFile();
     mIVSign.SetImageBitmap(mSignBitmap);
     mTVSign.Visibility = ViewStates.Gone; 
 }

//创建文件
private void createSignFile()
{
    //ByteArrayOutputStream baos = null;
    MemoryStream baos = null;

    FileOutputStream fos = null;
    String path = null;
    Java.IO.File file = null;
    try
    {
        path = System.IO.Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal),DateTime.Now.ToString("yyyyMMddHHmmss")+ ".jpg");

        file = new Java.IO.File(path);
        fos = new FileOutputStream(file);
        baos = new MemoryStream();
        //如果设置成Bitmap.compress(CompressFormat.JPEG, 100, fos) 图片的背景都是黑色的
        mSignBitmap.Compress(Bitmap.CompressFormat.Png, 100, baos);
        byte[] b = StreamToBytes(baos);
        if (b != null)
        {
            fos.Write(b);
        }
    }
    catch (Java.IO.IOException e)
    {
        e.PrintStackTrace();
    }
    finally
    {
        try
        {
            if (fos != null)
            {
                fos.Close();
            }
            if (baos != null)
            {
                baos.Close();
            }
        }
        catch (Java.IO.IOException e)
        {
            e.PrintStackTrace();
        }
    }
}


private  byte[] StreamToBytes(Stream stream)
{
    byte[] bytes = new byte[stream.Length];
    stream.Read(bytes, 0, bytes.Length);
    // 设置当前流的位置为流的开始
    stream.Seek(0, SeekOrigin.Begin);
    return bytes;
}

这儿有个点,在Java中会存在ByteArrayOutputStream 类,但是在Xamarin中不存在,因此需要进行一个转换。

3.5 布局文件

3.5.1 write_pad.xml

write_pad.xml布局文件如下

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <FrameLayout
        android:id="@+id/tablet_view"
        android:layout_width="fill_parent"
        android:layout_height="300dp" >
    </FrameLayout>
 
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:background="@android:drawable/bottom_bar"
        android:paddingTop="4dp" >
 
        <Button
            android:id="@+id/write_pad_ok"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="确定" />
 
        <Button
            android:id="@+id/write_pad_clear"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="清除" />
 
        <Button
            android:id="@+id/write_pad_cancel"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="取消" />
    </LinearLayout>


</LinearLayout>

3.5.2 activity_main布局文件

<?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"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:showIn="@layout/activity_main">

    <ImageView
        
        android:layout_width="match_parent"
        android:layout_height="400dp"
        android:id="@+id/signImageView" />

    <TextView
        android:id="@+id/signBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="请点击我,进行签名~" />

</RelativeLayout>

4、知识总结

里面大量的涉及了Canvas的方法,可以参考官网的这篇文章Android Graphics and Animation

5、代码下载

代码下载

6、参考资料

主要参考Android实现手写板和涂鸦功能

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

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

相关文章

【动态规划刷题 6】 买卖股票的最佳时机含冷冻期 买卖股票的最佳时机含手续费

买卖股票的最佳时机含冷冻期 链接: 买卖股票的最佳时机含冷冻期 给定一个整数数组prices&#xff0c;其中第 prices[i] 表示第 i 天的股票价格 。​ 设计一个算法计算出最大利润。在满足以下约束条件下&#xff0c;你可以尽可能地完成更多的交易&#xff08;多次买卖一支股票…

【赠书活动|第四期《互联网广告系统:架构、算法与智能化》】

文章目录 内容简介作者简介读者对象大咖推荐抽奖方式 广告平台的建设和完善是一项长期工程。例如&#xff0c;谷歌早于2003年通过收购Applied Semantics开展Google AdSense 项目&#xff0c;而直到20年后的今天&#xff0c;谷歌展示广告平台仍在持续创新和提升。广告平台是负有…

2024软考系统架构设计师论文写作要点

一、写作注意事项 系统架构设计师的论文题目对于考生来说&#xff0c;是相对较难的题目。一方面&#xff0c;考生需要掌握论文题目中的系统架构设计的专业知识;另一方面&#xff0c;论文的撰写需要结合考生自身的项目经历。因此&#xff0c;如何将自己的项目经历和专业知识有机…

内网穿透:实现公网访问内网群晖NAS的方法

公网远程访问内网群晖NAS 7.X版 【内网穿透】 文章目录 公网远程访问内网群晖NAS 7.X版 【内网穿透】前言1. 在群晖控制面板找到“终端机和SNMP”2. 建立一条连接公网数据隧道3. 获取公网访问内网群晖NAS的数据隧道入口 前言 群晖NAS作为应用较为广泛的小型数据存储中心&#…

RabbitMQ学习——发布订阅/fanout模式 topic模式 rabbitmq回调确认 延迟队列(死信)设计

目录 引出点对点(simple)Work queues 一对多发布订阅/fanout模式以登陆验证码为例pom文件导包application.yml文件rabbitmq的配置生产者生成验证码&#xff0c;发送给交换机消费者消费验证码 topic模式配置类增加配置生产者发送信息进行发送控制台查看 rabbitmq回调确认配置类验…

Shopee虾皮买家号注册时需要注意什么问题

虾皮是一家在线购物平台&#xff0c;如果您打算在虾皮上注册一个买家账号&#xff0c;以下是一些需要注意的问题&#xff1a; 账号安全&#xff1a;确保您选择一个安全的密码&#xff0c;并定期更改密码&#xff0c;以保护您的账号免受未经授权的访问。 个人信息&#xff1a;…

idea报“Could not autowire. No beans of ‘UserMapper‘ type found. ”错解决办法

原因和解决办法 1.原因 idea具有检测功能&#xff0c;接口不能直接创建bean的&#xff0c;需要用动态代理技术来解决。 2.解决办法 1.修改idea的配置 1.点击file,选择setting 2.搜索inspections,找到Spring 3.找到Spring子目录下的Springcore 4.在Springcore的子目录下…

uni-app使用vue语法进行开发注意事项

目录 uni-app 项目目录结构 生命周期 路由 路由跳转 页面栈 条件编译 文本渲染 样式渲染 条件渲染 遍历渲染 事件处理 事件修饰符 uni-app 项目目录结构 组件/标签 使用&#xff08;类似&#xff09;小程序 语法/结构 使用vue 具体项目目录如下&#xff1a; 生命…

三步免费接入 Claude 2.0,支持多账号轮询!

Claude 2.0 已经发布了一段时间&#xff0c;经过我的非暴力测试&#xff0c;比 ChatGPT 3.5 的能力是要强的&#xff0c;有更强大的上下文 100k&#xff0c;相当于 10 万字的上下文记忆,非常适合处理长文档和大的代码段&#xff0c;虽说有些方面略逊色 ChatGPT 4.0 &#xff0c…

日常工具 之 一些 / 方便好用 / 免费 / 在线 / 工具整理

日常工具 之 一些 / 方便好用 / 免费 / 在线 / 工具整理 目录 日常工具 之 一些 / 方便好用 / 免费 / 在线 / 工具整理 1、在线Json &#xff0c;可以在线进行json 格式验证&#xff0c;解析转义等操作 2、Gif动图分解&#xff0c;在线把 gif 图分解成一张张单图 3、在线P…

仓储10、20代电子标签接口文档

标签注册 仓储10代注册 右下角左下角组合按键触发注册 注册成功&#xff1a;右上角绿灯变红灯&#xff0c;并显示信号强度的数值 ​ 仓储20代注册 右下角左下角组合按键触发注册 注册成功&#xff1a;右上角绿灯变红灯&#xff0c;并显示信号强度的数值 ​ 查询电子标签信息…

dinput8.dll导致游戏打不开的解决方法,快速修复dinput8.dll文件

当你尝试启动某个游戏时&#xff0c;如果遇到dinput8.dll文件缺失或损坏的错误提示&#xff0c;可能会导致游戏无法正常运行。dinput8.dll是DirectInput API的一部分&#xff0c;它提供了游戏手柄、键盘和鼠标等输入设备的支持。本文将详细介绍dinput8.dll的作用、导致游戏无法…

核心板如何选择合适的封装?

▍引言 核心板如何选择合适的封装&#xff1f; 核心板是一种集成了CPU、内存、存储、网络等功能的微型计算机模块&#xff0c;可以作为嵌入式系统的核心部件&#xff0c;或者作为开发板的扩展模块。核心板的封装方式决定了它与底板或者开发板的连接方式&#xff0c;影响着核心…

PP-ChatOCR:基于文心大模型的通用图像关键信息抽取利器,开发提效50%!

在日常生活中&#xff0c;大家经常会遇到图像关键信息自动抽取的场景&#xff0c;比如身份证拍照上传自动识别、发票拍照上传自动报销等。 在这个领域&#xff0c;现有的AI技术方案已经能解决一部分需求&#xff0c;但是依然存在一些痛点&#xff0c;比如发票的种类样式极其繁多…

低代码、逻辑、规则、数据分析、协同工具集合,解决企业不同需求

大家好&#xff0c;我是为IT部门兄弟操碎了心的“软件部长”&#xff0c;随着企业IT建设的不断发展&#xff0c;软开企服也在经历了数十年的项目中积累了丰富的经验&#xff0c;为此开始了IT软件的研发之路&#xff0c;之后就一发不可收拾。。。才有了现在出现在市面上的JVS。 …

CVPR 2023 | Attention-Based Point Cloud Edge Sampling

注1:本文系“计算机视觉/三维重建论文速递”系列之一,致力于简洁清晰完整地介绍、解读计算机视觉,特别是三维重建领域最新的顶会/顶刊论文(包括但不限于 CVPR, ICCV, ECCV, NeurIPS等)。本次介绍的论文是: CVPR 2023 | Attention-Based Point Cloud Edge Sampling CVPR 2023 | …

Vue2嵌入HTML页面空白、互相传参、延迟加载等问题解决方案

一、需求分析 最近做的一个用H5加原生开发的html项目&#xff0c;现需要集成到Vue2.0项目里面来。遇到的相关问题做个记录和总结&#xff0c;以便能帮到大家避免踩坑。 二、问题记录 1、页面空白问题 将html页面通过iframe的方式嵌入进来之后&#xff0c;发现页面是空白的&am…

Why Test?

Why Test&#xff1f; 可测试性&#xff08;testability&#xff09;是一种设计的属性&#xff0c;衡量了全面测试制造设计的正确性的难易程度。原始的设计和测试过程是分开的&#xff0c;只有在设计周期的结尾考虑测试。而DFT将测试和设计融合在一起。 来自foundary的device…

Java面向对象++

面向对象编程(高级部分) 类变量和类方法 类变量 类变量也叫静态变量/静态属性&#xff0c;是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。 定义语法: 访问修饰符static数据类型变量…

加速招标流程:校对软件优化文件处理效率

校对软件在优化招标流程中可以提高文件处理效率&#xff0c;从而加速整个招标过程。以下是校对软件如何实现这一目标的方式&#xff1a; 1.自动化化校对&#xff1a;校对软件可以自动进行拼写、语法和格式等基本校对&#xff0c;减少人工校对的时间和工作量。这样可以快速检查和…