Xamarin.Android实现手写板的功能

目录

  • 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实现手写板和涂鸦功能

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【JDBC系列】- 扩展提升学习

扩展提升学习 &#x1f604;生命不息&#xff0c;写作不止 &#x1f525; 继续踏上学习之路&#xff0c;学之分享笔记 &#x1f44a; 总有一天我也能像各位大佬一样 &#x1f3c6; 博客首页 怒放吧德德 To记录领地 &#x1f31d;分享学习心得&#xff0c;欢迎指正&#xff0…

【前端】求职必备知识点4-CSS:flex、隐藏元素(7种方法)、单位

文章目录 flex隐藏元素&#xff08;7种方法&#xff09;不占位置占位置 单位思维导图 flex 【前端】CSS3弹性布局&#xff08;flex&#xff09;、媒体查询实现响应式布局和自适应布局_css媒体查询 自适应_karshey的博客-CSDN博客 flex缩写&#xff1a; flex-grow 和 flex-shr…

【Linux】网络基础2

文章目录 网络基础21. 应用层1.1 协议1.2 HTTP 协议1.2.1 URL1.2.2 urlencode和urldecode1.2.3 HTTP协议格式1.2.4 HTTP的方法1.2.5 HTTP的状态码1.2.6 HTTP 常见的header1.2.7 最简单的HTTP服务器 2. 传输层2.1 端口号2.1.1 端口号范围划分2.1.2 认识知名端口号2.1.3 netstat2…

浏览器无法连接网络问题

问题描述 电脑其他程序都能正常联网&#xff0c;但是所有的浏览器都无法联网&#xff0c;同时外部网站都能ping通 问题诊断 查看电脑Internet连接的问题报告显示&#xff1a;该设备或资源(Web 代理)未设置为接受端口"7890"上的连接。 解决方案 经过检查发现不是IP地址…

PHP Mysql查询全部全部返回字符串类型

设置pdo属性 $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);

中科亿海微浮点数转换定点数

引言 浮点数转换定点数是一种常见的数值转换技术&#xff0c;用于将浮点数表示转换为定点数表示。浮点数表示采用指数和尾数的形式&#xff0c;可以表示较大范围的数值&#xff0c;但存在精度有限的问题。而定点数表示则采用固定小数点位置的形式&#xff0c;具有固定的精度和范…

架构训练营学习笔记:6-1 微服务

序 这部分是了解的。传统企业使用soa较多。很多企业银行、电信对于Oracle 依赖大&#xff0c;强调稳定性。各个项目侧重外包&#xff0c;技术栈不统一。 soa 历史 这个之前电信的BOSS系统就是这种架构&#xff0c;不知道现在呢&#xff0c;核心计费系统billing是运行在tuxduo…

1.作用域

1.1局部作用域 局部作用域分为函数作用域和块作用域。 1.函数作用域: 在函数内部声明的变量只能在函数内部被访问&#xff0c;外部无法直接访问。 总结&#xff1a; (1)函数内部声明的变量&#xff0c;在函数外部无法被访问 (2)函数的参数也是函数内部的局部变量 (3)不同函数…

分布式协调组件Zookeeper

Zookeeper介绍 什么是Zookeeper ZooKeeper 是⼀种分布式协调组件&#xff0c;用于管理大型主机。在分布式环境中协调和管理服务是一个复杂的过程。ZooKeeper 通过其简单的架构和 API 解决了这个问题。ZooKeeper 允许开发人员专注于核心应用程序逻辑&#xff0c;而不必担心应用…

大数据扫盲(1): 数据仓库与ETL的关系及ETL工具推荐

在数字化时代&#xff0c;数据成为了企业决策的关键支持。然而&#xff0c;随着数据不断增长&#xff0c;有效地管理和利用这些数据变得至关重要。数据仓库和ETL工具作为数据管理和分析的核心&#xff0c;将帮助企业从庞杂的数据中提取有价值信息。 一、ETL是什么&#xff1f; …

【脚踢数据结构】内核链表

(꒪ꇴ꒪ )&#xff0c;Hello我是祐言QAQ我的博客主页&#xff1a;C/C语言,Linux基础,ARM开发板&#xff0c;软件配置等领域博主&#x1f30d;快上&#x1f698;&#xff0c;一起学习&#xff0c;让我们成为一个强大的攻城狮&#xff01;送给自己和读者的一句鸡汤&#x1f914;&…

Remote Sensing,2023 | 基于SBL的分布式毫米波相干雷达成像的高效实现

Remote Sensing,2023 | 基于SBL的分布式毫米波相干雷达成像的高效实现 注1&#xff1a;本文系“无线感知论文速递”系列之一&#xff0c;致力于简洁清晰完整地介绍、解读无线感知领域最新的顶会/顶刊论文(包括但不限于 Nature/Science及其子刊; MobiCom, Sigcom, MobiSys, NSDI…

WEB集群——LVS-DR 群集、nginx负载均衡

1、基于 CentOS 7 构建 LVS-DR 群集。 2、配置nginx负载均衡。 一、 LVS-DR 群集 1、LVS-DR工作原理 LVS-DR&#xff08;Linux Virtual Server Director Server&#xff09; 名称缩写说明 虚拟IP地址(Virtual IP Address) VIPDirector用于向客户端计算机提供服务的IP地址真实…

TCP网络服务器设计

最近设计了一个网络服务器程序&#xff0c;对于4C8G的机器配置&#xff0c;TPS可以达到5W。业务处理逻辑是简单的字符串处理。服务器接收请求后对下游进行类似广播的发送。在此分享一下设计方式&#xff0c;如果有改进思路欢迎大家交流分享。 程序运行在CentOS7.9操作系统上&a…

【uniapp】uniapp设置安全区域:

文章目录 一、效果图:二、实现代码: 一、效果图: 二、实现代码: {"path": "pages/index/index","style": {"navigationStyle": "custom","navigationBarTextStyle": "white","navigationBarTitle…

Unity之ShaderGraph 节点介绍 UV节点

UV节点 Flipbook&#xff08;翻页或纹理帧动画&#xff09; Polar Coordinates&#xff08;将输入 UV 的值转换为极坐标。&#xff09; Radial Shear&#xff08;径向剪切变形&#xff09; Rotate&#xff08;将UV 的值旋转&#xff09; Spherize&#xff08;鱼眼镜头的球形变…

CentOS7连接网络

1.下载centos7镜像文件 2.安装centos7 3.修改网卡,ens33. 注意: 这里使用的是dhcp,设置IPADDR192.168.31.64一方面是为了后面使用crt或者MobaXterm连接,另一方面它和windows电脑的网卡要一致.这样才可以连接到网络.win r,输入cmd,打开命令窗口输入ipconfig.可以看到IPv4: 102…

windows安装apache-jmeter-5.6.2教程

目录 一、下载安装包&#xff08;推荐第二种&#xff09; 二、安装jmeter 三、启动jmeter 一、下载安装包&#xff08;推荐第二种&#xff09; 1.官网下载&#xff1a;Apache JMeter - Download Apache JMeter 2.百度云下载&#xff1a;链接&#xff1a;https://pan.baidu.…

FLStudio21水果最新中文版升级下载

FLStudio21最新中文版是一款非常专业的后期编曲音频处理软件&#xff0c;对于音乐编辑处理的领域内的人而言&#xff0c;是非常能够满足需求的一款工具。FL Studio21拥有强大且专业的创作工具&#xff0c;这是先进的创作工具&#xff0c;让你的音乐突破想象力的限制。FL Studio…

数据结构-栈的实现(C语言版)

前言 栈是一种特殊的线性表&#xff0c;只允许在固定的一端进行插入和删除的操作&#xff0c;进行数据插入和删除的一端叫做栈顶&#xff0c;另一端叫做栈底。 栈中的数据元素遵循后进先出的的原则。 目录 1.压栈和出栈 2. 栈的实现 3.测试代码 1.压栈和出栈 压栈&#xff…