目录
- 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 Resources
和Canvas
,前者可主要进行类似Html
的CSS
之类的功能,后者则实现比较负责的功能,本次主要用到了后者-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
,主要是想实现通过这个委托,将生成的图像传递出去。就是对话框中的event
(WriteDialogListener
)
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"><FrameLayoutandroid:id="@+id/tablet_view"android:layout_width="fill_parent"android:layout_height="300dp" ></FrameLayout><LinearLayoutandroid:layout_width="fill_parent"android:layout_height="wrap_content"android:orientation="horizontal"android:background="@android:drawable/bottom_bar"android:paddingTop="4dp" ><Buttonandroid:id="@+id/write_pad_ok"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_weight="1"android:text="确定" /><Buttonandroid:id="@+id/write_pad_clear"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_weight="1"android:text="清除" /><Buttonandroid: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"><ImageViewandroid:layout_width="match_parent"android:layout_height="400dp"android:id="@+id/signImageView" /><TextViewandroid: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
程序中主要使用了Path
类和Canvas
,具体的知识可以参考资料的第二篇文章,非常好
5、代码下载
代码下载
6、参考资料
1、Android实现手写板和涂鸦功能
2、Android知识总结——Path常用方法解析