Open Visual Studio and create new Cross Platform App with C# code as shown below,
Now you can see in solution explorer several projects for different platforms are created. One is Portal project that is common for all the platforms, another one is Android, this is specifically for Android OS, another one is iOS specifically for iOS OS.
Here I am going to explain how to create a drawing app for Android, so let's start.
To draw and create files in specific platforms, we have to do code in specific platforms and to access this from Portable project we need to use dependency service.
So here we will write those functions in Android project.
- First, we have to write code for drawing, for this add one class to android project and give name as ClassDrawing (To add class right click on android project from the menu select Add -> class)
- After creating class add namespaces as below,
- using Android.Views;
- using Android.Graphics;
- using Android.Content;
- using System;
- After adding namespces, make this class as public and inherit from view class and write one constructor for this class with context parameter and inherit from base(context) as shown below,
- public class ClassDrawing : View
- {
- public ClassDrawing(Context context): base(context)
- {
- }
-
- }
Now let’s add some global variables as below,
- public class ClassDrawing : View
- {
- public Color LineColor { get; set; }
- public string ImageFilePath { get; set; }
- public float PenWidth { get; set; }
-
- private Path DrawPath;
- private Paint DrawPaint;
- private Paint CanvasPaint;
- private Canvas DrawCanvas;
- private Bitmap CanvasBitmap;
-
- private int w, h;
- private Bitmap _image;
-
- public ClassDrawing(Context context): base(context)
- {
- }
- }
Then lets add some default color, styles to this drawing pad as shown below inside constructor,
- public ClassDrawing(Context context): base(context)
- {
-
- LineColor = Color.Black;
- PenWidth = 5.0f;
-
- DrawPath = new Path();
- DrawPaint = new Paint
- {
- Color = LineColor,
- AntiAlias = true,
- StrokeWidth = PenWidth
- };
-
- DrawPaint.SetStyle(Paint.Style.Stroke);
- DrawPaint.StrokeJoin = Paint.Join.Round;
- DrawPaint.StrokeCap = Paint.Cap.Round;
-
- CanvasPaint = new Paint
- {
- Dither = true
- };
- }
Next override some functions as shown below,
- protected override void OnSizeChanged(int w, int h, int oldw, int oldh)
- {
- base.OnSizeChanged(w, h, oldw, oldh);
- if (w > 0 && h > 0)
- {
- try
- {
- CanvasBitmap = Bitmap.CreateBitmap(w, h, Bitmap.Config.Argb8888);
- DrawCanvas = new Canvas(CanvasBitmap);
- this.w = w;
- this.h = h;
- }
- catch (Exception ex)
- {
- Console.WriteLine(ex.Message);
- }
- }
- }
-
- protected override void OnDraw(Canvas canvas)
- {
- base.OnDraw(canvas);
-
- DrawPaint.Color = LineColor;
- DrawPaint.StrokeWidth = PenWidth;
- canvas.DrawBitmap(CanvasBitmap, 0, 0, CanvasPaint);
- canvas.DrawPath(DrawPath, DrawPaint);
- }
- public override bool OnTouchEvent(MotionEvent e)
- {
- var touchX = e.GetX();
- var touchY = e.GetY();
-
- switch (e.Action)
- {
- case MotionEventActions.Down:
- DrawPath.MoveTo(touchX, touchY);
- break;
- case MotionEventActions.Move:
- DrawPath.LineTo(touchX, touchY);
- break;
- case MotionEventActions.Up:
- DrawCanvas.DrawPath(DrawPath, DrawPaint);
- DrawPath.Reset();
- break;
- default:
- return false;
- }
-
- Invalidate();
-
- return true;
- }
Then let’s add function for clearing the drawing pad as shown below,
- public void Clear()
- {
- try
- {
- DrawPath = new Path();
- CanvasBitmap = Bitmap.CreateBitmap(w, h, Bitmap.Config.Argb8888);
- DrawCanvas = new Canvas(CanvasBitmap);
-
- DrawCanvas.DrawColor(Color.White, PorterDuff.Mode.Multiply);
- CanvasBitmap.EraseColor(Color.Transparent);
- DrawPaint = new Paint
- {
- Color = LineColor,
- AntiAlias = true,
- StrokeWidth = PenWidth
- };
-
- DrawPaint.SetStyle(Paint.Style.Stroke);
- DrawPaint.StrokeJoin = Paint.Join.Round;
- DrawPaint.StrokeCap = Paint.Cap.Round;
- }
- catch (Exception e)
- {
- Console.WriteLine(e.Message);
- }
-
- Invalidate();
- }
Next we have to return this drawn image so that we can get the file so we have to create a function as below to return a drawn image,
- public Bitmap GetImageFromView()
- {
- Bitmap tempBitmap = null;
- try
- {
- tempBitmap = Bitmap.CreateBitmap(w, h, Bitmap.Config.Argb8888);
- DrawCanvas = new Canvas(tempBitmap);
-
- if (_image != null)
- {
- DrawPaint.SetStyle(Paint.Style.Fill);
- DrawPaint.Color = Color.White;
- DrawCanvas.DrawRect(new Rect(0, 0, w, h), DrawPaint);
-
- float scaleX = (float)_image.Width / w;
- float scaleY = (float)_image.Height / h;
- Rect outRect = new Rect();
-
- int outWidth, outHeight;
- if (scaleX > scaleY)
- {
- outWidth = w;
- outHeight = (int)(_image.Height / scaleX);
- }
- else
- {
- outWidth = (int)(_image.Width / scaleY);
- outHeight = h;
- }
-
- outRect.Left = w / 2 - outWidth / 2;
- outRect.Top = h / 2 - outHeight / 2;
- outRect.Right = w / 2 + outWidth / 2;
- outRect.Bottom = h / 2 + outHeight / 2;
-
- DrawCanvas.DrawBitmap(_image, new Rect(0, 0, _image.Width, _image.Height), outRect, DrawPaint);
- }
- else
- {
- DrawPaint.SetStyle(Paint.Style.Fill);
- DrawPaint.Color = Color.White;
- DrawCanvas.DrawRect(new Rect(0, 0, w, h), DrawPaint);
- }
-
- DrawPaint.Color =LineColor;
- DrawCanvas.DrawBitmap(CanvasBitmap, 0, 0, CanvasPaint);
- DrawCanvas.DrawPath(DrawPath, DrawPaint);
-
- }
- catch (Exception ex)
- {
- Console.WriteLine(ex.Message);
- }
-
- return tempBitmap;
- }
Next create a function to load file which is created, file path we will get it from variable ImageFilePath. Function as shown below,
- public void LoadImageFromFile()
- {
- if (ImageFilePath != null && ImageFilePath != "")
- {
- _image = BitmapFactory.DecodeFile(ImageFilePath);
- }
- }
Note
Here are the 3 variable I have mentioned as public to access from outside that are LineColor, ImageFilePath, PenWidth
Let's create another class to render this drawing into portable project, I have given name for this class as ClassImageRenderer, before writing functionality into this class let's create a class in portable project and add some properties to get and set from ClassImageRenderer from android project. I have given a name as ImageEditor and inherit this class from Image class, the class as shown below,
- public class ImageEditor : Image
- {
- public static readonly BindableProperty LineColorProperty =
- BindableProperty.Create((ImageEditor w) => w.LineColor, Color.Default);
-
- public static readonly BindableProperty LineWidthProperty =
- BindableProperty.Create((ImageEditor w) => w.LineWidth, 1);
-
- public static readonly BindableProperty ImageProperty =
- BindableProperty.Create((ImageEditor w) => w.ImagePath, "");
-
- public static readonly BindableProperty ClearImagePathProperty =
- BindableProperty.Create((ImageEditor w) => w.ClearPath, false);
-
- public static readonly BindableProperty SavedImagePathProperty =
- BindableProperty.Create((ImageEditor w) => w.SavedImagePath, "");
-
- public static readonly BindableProperty SavedImage64byteProperty =
- BindableProperty.Create((ImageEditor w) => w.SavedImage64byte, "");
-
- public Color LineColor
- {
- get
- {
- return (Color)GetValue(LineColorProperty);
- }
- set
- {
- SetValue(LineColorProperty, value);
- }
- }
-
-
-
-
- public int LineWidth
- {
- get
- {
- return (int)GetValue(LineWidthProperty);
- }
- set
- {
- SetValue(LineWidthProperty, value);
- }
- }
-
- public string ImagePath
- {
- get
- {
- return (string)GetValue(ImageProperty);
- }
- set
- {
- SetValue(ImageProperty, value);
- }
- }
-
- public bool ClearPath
- {
- get
- {
- return (bool)GetValue(ClearImagePathProperty);
- }
- set
- {
- SetValue(ClearImagePathProperty, value);
- }
- }
-
- public string SavedImagePath
- {
- get
- {
- return (string)GetValue(SavedImagePathProperty);
- }
- set
- {
- SetValue(SavedImagePathProperty, value);
- }
- }
-
- public string SavedImage64byte
- {
- get
- {
- return (string)GetValue(SavedImage64byteProperty);
- }
- set
- {
- SetValue(SavedImage64byteProperty, value);
- }
- }
- }
Next let’s add functionality to the class ClassImageRenderer which I created on Android project, first export those properties which we have added in previous class ImageEditor for that write the below code before namespace DrawingPad.Droid
[assembly: ExportRenderer(typeof(ImageEditor), typeof(ClassImageRenderer))]
Then inherit ClassImageRenderer from ViewRenderer and add name spaces as shown below,
- using Java.IO;
- using DrawingPad;
- using DrawingPad.Droid;
- using Xamarin.Forms;
- using System.ComponentModel;
- [assembly: ExportRenderer(typeof(ImageEditor), typeof(ClassImageRenderer))]
-
- namespace DrawingPad.Droid
- {
- public class ClassImageRenderer : ViewRenderer<ImageEditor, ClassDrawing>
- {
- }
- }
Then write the functions inside the class as shown below,
- protected override void OnElementChanged(ElementChangedEventArgs<ImageEditor> e)
- {
- base.OnElementChanged(e);
-
- if (e.OldElement == null)
- {
- SetNativeControl(new ClassDrawing(Context));
- }
- }
- public static byte[] BitmapToBytes(Android.Graphics.Bitmap bitmap)
- {
- byte[] data = new byte[0];
- using (System.IO.MemoryStream stream = new System.IO.MemoryStream())
- {
- bitmap.Compress(Android.Graphics.Bitmap.CompressFormat.Jpeg, 90, stream);
- stream.Close();
- data = stream.ToArray();
- }
- return data;
- }
-
- rotected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
- {
- base.OnElementPropertyChanged(sender, e);
-
- if (e.PropertyName == ImageEditor.ClearImagePathProperty.PropertyName)
- {
- Control.Clear();
- }
- else if (e.PropertyName == ImageEditor.SavedImagePathProperty.PropertyName)
- {
- Bitmap curDrawingImage = Control.GetImageFromView();
-
- Byte[] imgBytes = BitmapToBytes(curDrawingImage);
- Element.SavedImage64byte = Convert.ToBase64String(imgBytes);
-
- Java.IO.File f = new Java.IO.File(Element.SavedImagePath);
-
- f.CreateNewFile();
-
- FileOutputStream fo = new FileOutputStream(f);
- fo.Write(imgBytes);
-
- fo.Close();
- }
- else
- {
- UpdateControl(true);
- }
- }
-
- private void UpdateControl(bool bDisplayFlag)
- {
- Control.LineColor = Element.LineColor.ToAndroid();
- Control.PenWidth = Element.LineWidth * 3;
- Control.ImageFilePath = Element.ImagePath;
-
- if (bDisplayFlag)
- {
- Control.LoadImageFromFile();
- Control.Invalidate();
- }
- }
Lets create an interface and dependency service to create a image save path, for this in portable class create one interface as shown below,
- public interface ISaveImage
- {
- string SaveImage();
- }
And create one class in Android project to create this class, and inside class let's assign image path to Android device as shown below,
- using System;
- using Xamarin.Forms;
- using DrawingPad.Droid;
- [assembly: Dependency(typeof(ClassSaveImage))]
- namespace DrawingPad.Droid
- {
- public class ClassSaveImage : ISaveImage
- {
- public string SaveImage()
- {
- string savedFileName = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "/temp_" + DateTime.Now.ToString("yyyy_mm_dd_hh_mm_ss") + ".png";
- return savedFileName;
- }
- }
- }
That’s it, now control is ready to use lets create a content page the content page I have added xaml code as shown below,
In xaml namespace add below line to add control which we have created to access ImageEditor class
xmlns:local="clr-namespace:DrawingPad;assembly:DrawingPad"
Then add below line inside stacklayout
- <local:ImageEditor x:Name="imgeditor" Grid.Row="0" HeightRequest="400" BackgroundColor="White" LineColor="Black"/>
And add color buttons and two buttons for clear and save, the full code is as shown below,
- <?xml version="1.0" encoding="utf-8" ?>
- <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
- xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
- xmlns:local="clr-namespace:DrawingPad;assembly:DrawingPad"
- x:Class="DrawingPad.MainPage">
- <StackLayout x:Name="MainStack" VerticalOptions="Center" HorizontalOptions="Center" BackgroundColor="White">
- <ScrollView Orientation="Horizontal" >
- <StackLayout x:Name="redStripStack" Padding="0,0,0,0" HeightRequest="40" >
- <StackLayout Orientation="Horizontal" VerticalOptions="Center" HorizontalOptions="Center" Padding="5,5,5,5">
- <Button x:Name="btnGreen" BackgroundColor="Green" HeightRequest="30" BorderColor="White" BorderWidth="3" TextColor="White" Clicked="btnGreen_Clicked"/>
- <Button x:Name="btnGray" BackgroundColor="Gray" HeightRequest="30" BorderColor="White" BorderWidth="3" TextColor="White" Clicked="btnGray_Clicked" />
- <Button x:Name="btnDarkGray" BackgroundColor="DarkGray" HeightRequest="30" BorderColor="White" BorderWidth="3" TextColor="White" Clicked="btnDarkGray_Clicked"/>
- <Button x:Name="btnRed" BackgroundColor="Red" HeightRequest="30" BorderColor="White" BorderWidth="3" TextColor="White" Clicked="btnRed_Clicked" />
- <Button x:Name="btnDarkRed" BackgroundColor="DarkRed" HeightRequest="30" BorderColor="White" BorderWidth="3" TextColor="White" Clicked="btnDarkRed_Clicked"/>
- <Button x:Name="btnBlack" BackgroundColor="Black" HeightRequest="30" BorderColor="White" BorderWidth="0" TextColor="White" Clicked="btnBlack_Clicked" />
- <Button x:Name="btnBlue" BackgroundColor="Blue" HeightRequest="30" BorderColor="White" BorderWidth="3" TextColor="White" Clicked="btnBlue_Clicked"/>
- <Button x:Name="btnDarkBlue" BackgroundColor="DarkBlue" HeightRequest="30" BorderColor="White" BorderWidth="3" TextColor="White" Clicked="btnDarkBlue_Clicked"/>
- <Button x:Name="btnLightBlue" BackgroundColor="LightBlue" HeightRequest="30" BorderColor="White" BorderWidth="3" TextColor="White" Clicked="btnLightBlue_Clicked" />
- <Button x:Name="btnSkyBlue" BackgroundColor="SkyBlue" HeightRequest="30" BorderColor="White" BorderWidth="3" TextColor="White" Clicked="btnSkyBlue_Clicked"/>
- <Button x:Name="btnYellow" BackgroundColor="Yellow" HeightRequest="30" BorderColor="White" BorderWidth="3" TextColor="White" Clicked="btnYellow_Clicked" />
- <Button x:Name="btnOrange" BackgroundColor="Orange" HeightRequest="30" BorderColor="White" BorderWidth="3" TextColor="White" Clicked="btnOrange_Clicked"/>
- <Button x:Name="btnDarkOrange" BackgroundColor="DarkOrange" HeightRequest="30" BorderColor="White" BorderWidth="3" TextColor="White" Clicked="btnDarkOrange_Clicked"/>
- <Button x:Name="btnPurple" BackgroundColor="Purple" HeightRequest="30" BorderColor="White" BorderWidth="3" TextColor="White" Clicked="btnPurple_Clicked"/>
- <Button x:Name="btnPink" BackgroundColor="Pink" HeightRequest="30" BorderColor="White" BorderWidth="3" TextColor="White" Clicked="btnPink_Clicked"/>
- <Button x:Name="btnCyan" BackgroundColor="Cyan" HeightRequest="30" BorderColor="White" BorderWidth="3" TextColor="White" Clicked="btnCyan_Clicked"/>
- </StackLayout>
- </StackLayout>
- </ScrollView>
- <StackLayout>
- <Grid HorizontalOptions="Center" VerticalOptions="Center" Padding="5,5,5,5">
- <Grid.RowDefinitions>
- <RowDefinition Height="*"/>
- <RowDefinition Height="40"/>
- </Grid.RowDefinitions>
- <local:ImageEditor x:Name="imgeditor" Grid.Row="0" HeightRequest="400" BackgroundColor="White" LineColor="Black"/>
-
- <Grid Grid.Row="1" VerticalOptions="EndAndExpand">
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="60*"/>
- <ColumnDefinition Width="40*"/>
- </Grid.ColumnDefinitions>
- <Button x:Name="btnSave" Text="Save" Grid.Column="0" TextColor="White" BackgroundColor="Green" Clicked="btnSaveImage_Click"/>
- <Button x:Name="btnClear" Text="Clear" Grid.Column="1" TextColor="White" BackgroundColor="Gray" Clicked="btnClear_Click"/>
- </Grid>
- </Grid>
- </StackLayout>
- </StackLayout>
- </ContentPage>
Now let’s write functionality for these buttons, first write the code for save button as shown for this, first get the file path from Android device using dependency service and assign this path to this image editor property SavedImagePath and get 64 bye string from the control as shown below.
- private void btnSaveImage_Click(object sender, EventArgs e)
- {
- var imgPath = DependencyService.Get<ISaveImage>().SaveImage();
- imgeditor.SavedImagePath = imgPath;
- string _64byte = imgeditor.SavedImage64byte;
- DisplayAlert("64byteData", _64byte, "Ok");
- }
Next for clear button as shown below,
- private void btnClear_Click(object sender, EventArgs e)
- {
- imgeditor.ClearPath = !imgeditor.ClearPath;
For color buttons as shown below,
- private void setBorder()
- {
- btnGreen.BorderWidth = 3;
- btnRed.BorderWidth = 3;
- btnGray.BorderWidth = 3;
- btnDarkGray.BorderWidth = 3;
- btnDarkRed.BorderWidth = 3;
- btnBlack.BorderWidth = 3;
- btnBlue.BorderWidth = 3;
- btnDarkBlue.BorderWidth = 3;
- btnLightBlue.BorderWidth = 3;
- btnSkyBlue.BorderWidth = 3;
- btnYellow.BorderWidth = 3;
- btnOrange.BorderWidth = 3;
- btnDarkOrange.BorderWidth = 3;
- btnPurple.BorderWidth = 3;
- btnPink.BorderWidth = 3;
- btnCyan.BorderWidth = 3;
- }
- private void btnGreen_Clicked(object sender, EventArgs args)
- {
- setBorder();
- btnGreen.BorderWidth = 0;
- imgeditor.LineColor = Color.Green;
- }
- private void btnRed_Clicked(object sender, EventArgs args)
- {
- setBorder();
- btnRed.BorderWidth = 0;
- imgeditor.LineColor = Color.Red;
- }
- private void btnGray_Clicked(object sender, EventArgs args)
- {
- setBorder();
- btnGray.BorderWidth = 0;
- imgeditor.LineColor = Color.Gray;
- }
- private void btnDarkGray_Clicked(object sender, EventArgs args)
- {
- setBorder();
- btnDarkGray.BorderWidth = 0;
- imgeditor.LineColor = Color.DarkGray;
- }
- private void btnDarkRed_Clicked(object sender, EventArgs args)
- {
- setBorder();
- btnDarkRed.BorderWidth = 0;
- imgeditor.LineColor = Color.DarkRed;
- }
- private void btnBlack_Clicked(object sender, EventArgs args)
- {
- setBorder();
- btnBlack.BorderWidth = 0;
- imgeditor.LineColor = Color.Black;
- }
- private void btnBlue_Clicked(object sender, EventArgs args)
- {
- setBorder();
- btnBlue.BorderWidth = 0;
- imgeditor.LineColor = Color.Blue;
- }
- private void btnDarkBlue_Clicked(object sender, EventArgs args)
- {
- setBorder();
- btnDarkBlue.BorderWidth = 0;
- imgeditor.LineColor = Color.DarkBlue;
- }
- private void btnLightBlue_Clicked(object sender, EventArgs args)
- {
- setBorder();
- btnLightBlue.BorderWidth = 0;
- imgeditor.LineColor = Color.LightBlue;
- }
- private void btnSkyBlue_Clicked(object sender, EventArgs args)
- {
- setBorder();
- btnSkyBlue.BorderWidth = 0;
- imgeditor.LineColor = Color.SkyBlue;
- }
- private void btnYellow_Clicked(object sender, EventArgs args)
- {
- setBorder();
- btnYellow.BorderWidth = 0;
- imgeditor.LineColor = Color.Yellow;
- }
- private void btnOrange_Clicked(object sender, EventArgs args)
- {
- setBorder();
- btnOrange.BorderWidth = 0;
- imgeditor.LineColor = Color.Orange;
- }
- private void btnDarkOrange_Clicked(object sender, EventArgs args)
- {
- setBorder();
- btnDarkOrange.BorderWidth = 0;
- imgeditor.LineColor = Color.DarkOrange;
- }
- private void btnPurple_Clicked(object sender, EventArgs args)
- {
- setBorder();
- btnPurple.BorderWidth = 0;
- imgeditor.LineColor = Color.Purple;
- }
And last override the sizeallocated function for this content page as shown below,
- protected override void OnSizeAllocated(double width, double height)
- {
- base.OnSizeAllocated(width, height);
- if (this.Width > 0)
- {
- MainStack.WidthRequest = (this.Width * 90) / 100;
- imgeditor.WidthRequest = (this.Width * 90) / 100;
-
- }
- }
Now Drawing pad ready, just compile and see the output as shown below,