├── .gitattributes ├── .gitignore ├── Creando controles para Xamarin.Forms.pptx ├── Performance Comparison.xlsx ├── README.md ├── images └── dotnet2020.jpg └── src ├── DotNet2020.Android ├── Assets │ └── AboutAssets.txt ├── DotNet2020.Android.csproj ├── MainActivity.cs ├── Properties │ ├── AndroidManifest.xml │ └── AssemblyInfo.cs ├── Renderers │ └── CheckBoxRenderer.cs └── Resources │ ├── AboutResources.txt │ ├── Resource.designer.cs │ ├── layout │ ├── Tabbar.xml │ └── Toolbar.xml │ ├── mipmap-anydpi-v26 │ ├── icon.xml │ └── icon_round.xml │ ├── mipmap-hdpi │ ├── icon.png │ └── launcher_foreground.png │ ├── mipmap-mdpi │ ├── icon.png │ └── launcher_foreground.png │ ├── mipmap-xhdpi │ ├── icon.png │ └── launcher_foreground.png │ ├── mipmap-xxhdpi │ ├── icon.png │ └── launcher_foreground.png │ ├── mipmap-xxxhdpi │ ├── icon.png │ └── launcher_foreground.png │ └── values │ ├── colors.xml │ └── styles.xml ├── DotNet2020.iOS ├── AppDelegate.cs ├── Assets.xcassets │ └── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon1024.png │ │ ├── Icon120.png │ │ ├── Icon152.png │ │ ├── Icon167.png │ │ ├── Icon180.png │ │ ├── Icon20.png │ │ ├── Icon29.png │ │ ├── Icon40.png │ │ ├── Icon58.png │ │ ├── Icon60.png │ │ ├── Icon76.png │ │ ├── Icon80.png │ │ └── Icon87.png ├── Controls │ └── NativeCheckBox.cs ├── DotNet2020.iOS.csproj ├── Entitlements.plist ├── Info.plist ├── Main.cs ├── Properties │ └── AssemblyInfo.cs ├── Renderers │ └── CheckBoxRenderer.cs └── Resources │ ├── Default-568h@2x.png │ ├── Default-Portrait.png │ ├── Default-Portrait@2x.png │ ├── Default.png │ ├── Default@2x.png │ └── LaunchScreen.storyboard ├── DotNet2020.sln └── DotNet2020 ├── App.xaml ├── App.xaml.cs ├── AssemblyInfo.cs ├── Custom Controls └── CustomCheckBox.cs ├── Custom Renderers └── RendererCheckBox.cs ├── DotNet2020.csproj ├── SkiaSharp Controls └── SkiaCheckBox.cs ├── Styles ├── CheckBox.xaml └── CheckBox.xaml.cs ├── Templated Controls └── TemplatedCheckBox.cs ├── TypeConverters └── ContentTypeConverter.cs └── Views ├── MainView.xaml ├── MainView.xaml.cs ├── MeasureCustomCheckBoxView.xaml ├── MeasureCustomCheckBoxView.xaml.cs ├── MeasureRendererCheckBoxView.xaml ├── MeasureRendererCheckBoxView.xaml.cs ├── MeasureSkiaCheckBoxView.xaml ├── MeasureSkiaCheckBoxView.xaml.cs ├── MeasureTemplatedCheckBoxView.xaml └── MeasureTemplatedCheckBoxView.xaml.cs /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | *.VC.VC.opendb 85 | 86 | # Visual Studio profiler 87 | *.psess 88 | *.vsp 89 | *.vspx 90 | *.sap 91 | 92 | # TFS 2012 Local Workspace 93 | $tf/ 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | *.DotSettings.user 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | # TODO: Comment the next line if you want to checkin your web deploy settings 144 | # but database connection strings (with potential passwords) will be unencrypted 145 | *.pubxml 146 | *.publishproj 147 | 148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 149 | # checkin your Azure Web App publish settings, but sensitive information contained 150 | # in these scripts will be unencrypted 151 | PublishScripts/ 152 | 153 | # NuGet Packages 154 | *.nupkg 155 | # The packages folder can be ignored because of Package Restore 156 | **/packages/* 157 | # except build/, which is used as an MSBuild target. 158 | !**/packages/build/ 159 | # Uncomment if necessary however generally it will be regenerated when needed 160 | #!**/packages/repositories.config 161 | # NuGet v3's project.json files produces more ignoreable files 162 | *.nuget.props 163 | *.nuget.targets 164 | 165 | # Microsoft Azure Build Output 166 | csx/ 167 | *.build.csdef 168 | 169 | # Microsoft Azure Emulator 170 | ecf/ 171 | rcf/ 172 | 173 | # Windows Store app package directories and files 174 | AppPackages/ 175 | BundleArtifacts/ 176 | Package.StoreAssociation.xml 177 | _pkginfo.txt 178 | 179 | # Visual Studio cache files 180 | # files ending in .cache can be ignored 181 | *.[Cc]ache 182 | # but keep track of directories ending in .cache 183 | !*.[Cc]ache/ 184 | 185 | # Others 186 | ClientBin/ 187 | ~$* 188 | *~ 189 | *.dbmdl 190 | *.dbproj.schemaview 191 | *.pfx 192 | *.publishsettings 193 | node_modules/ 194 | orleans.codegen.cs 195 | 196 | # Since there are multiple workflows, uncomment next line to ignore bower_components 197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 198 | #bower_components/ 199 | 200 | # RIA/Silverlight projects 201 | Generated_Code/ 202 | 203 | # Backup & report files from converting an old project file 204 | # to a newer Visual Studio version. Backup files are not needed, 205 | # because we have git ;-) 206 | _UpgradeReport_Files/ 207 | Backup*/ 208 | UpgradeLog*.XML 209 | UpgradeLog*.htm 210 | 211 | # SQL Server files 212 | *.mdf 213 | *.ldf 214 | 215 | # Business Intelligence projects 216 | *.rdl.data 217 | *.bim.layout 218 | *.bim_*.settings 219 | 220 | # Microsoft Fakes 221 | FakesAssemblies/ 222 | 223 | # GhostDoc plugin setting file 224 | *.GhostDoc.xml 225 | 226 | # Node.js Tools for Visual Studio 227 | .ntvs_analysis.dat 228 | 229 | # Visual Studio 6 build log 230 | *.plg 231 | 232 | # Visual Studio 6 workspace options file 233 | *.opt 234 | 235 | # Visual Studio LightSwitch build output 236 | **/*.HTMLClient/GeneratedArtifacts 237 | **/*.DesktopClient/GeneratedArtifacts 238 | **/*.DesktopClient/ModelManifest.xml 239 | **/*.Server/GeneratedArtifacts 240 | **/*.Server/ModelManifest.xml 241 | _Pvt_Extensions 242 | 243 | # Paket dependency manager 244 | .paket/paket.exe 245 | paket-files/ 246 | 247 | # FAKE - F# Make 248 | .fake/ 249 | 250 | # JetBrains Rider 251 | .idea/ 252 | *.sln.iml 253 | -------------------------------------------------------------------------------- /Creando controles para Xamarin.Forms.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuarezruiz/DotNet2020/77c7e80b69f52e9aad09533456cd3dc70eb8140e/Creando controles para Xamarin.Forms.pptx -------------------------------------------------------------------------------- /Performance Comparison.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuarezruiz/DotNet2020/77c7e80b69f52e9aad09533456cd3dc70eb8140e/Performance Comparison.xlsx -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DotNet 2020 2 | 3 | Ways to create controls in Xamarin.Forms. 4 | 5 | ![dotnet2020](images/dotnet2020.jpg) -------------------------------------------------------------------------------- /images/dotnet2020.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuarezruiz/DotNet2020/77c7e80b69f52e9aad09533456cd3dc70eb8140e/images/dotnet2020.jpg -------------------------------------------------------------------------------- /src/DotNet2020.Android/Assets/AboutAssets.txt: -------------------------------------------------------------------------------- 1 | Any raw assets you want to be deployed with your application can be placed in 2 | this directory (and child directories) and given a Build Action of "AndroidAsset". 3 | 4 | These files will be deployed with your package and will be accessible using Android's 5 | AssetManager, like this: 6 | 7 | public class ReadAsset : Activity 8 | { 9 | protected override void OnCreate (Bundle bundle) 10 | { 11 | base.OnCreate (bundle); 12 | 13 | InputStream input = Assets.Open ("my_asset.txt"); 14 | } 15 | } 16 | 17 | Additionally, some Android functions will automatically load asset files: 18 | 19 | Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf"); 20 | -------------------------------------------------------------------------------- /src/DotNet2020.Android/DotNet2020.Android.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {32F5B953-A462-4A51-823C-97959A28DA32} 7 | {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 8 | {c9e5eea5-ca05-42a1-839b-61506e0a37df} 9 | Library 10 | DotNet2020.Droid 11 | DotNet2020.Android 12 | True 13 | True 14 | Resources\Resource.designer.cs 15 | Resource 16 | Properties\AndroidManifest.xml 17 | Resources 18 | Assets 19 | v9.0 20 | true 21 | true 22 | Xamarin.Android.Net.AndroidClientHandler 23 | 24 | 25 | 26 | 27 | true 28 | portable 29 | false 30 | bin\Debug 31 | DEBUG; 32 | prompt 33 | 4 34 | None 35 | 36 | 37 | true 38 | portable 39 | true 40 | bin\Release 41 | prompt 42 | 4 43 | true 44 | false 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 2.80.2 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | {2AB1E756-7B92-49C2-A542-D8B535259755} 98 | DotNet2020 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /src/DotNet2020.Android/MainActivity.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.Content.PM; 3 | using Android.Runtime; 4 | using Android.OS; 5 | using Xamarin.Forms; 6 | 7 | namespace DotNet2020.Droid 8 | { 9 | [Activity(Label = "DotNet2020", Icon = "@mipmap/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize)] 10 | public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity 11 | { 12 | protected override void OnCreate(Bundle savedInstanceState) 13 | { 14 | TabLayoutResource = Resource.Layout.Tabbar; 15 | ToolbarResource = Resource.Layout.Toolbar; 16 | 17 | base.OnCreate(savedInstanceState); 18 | 19 | Forms.SetFlags("Brushes_Experimental", "Shapes_Experimental"); 20 | Xamarin.Essentials.Platform.Init(this, savedInstanceState); 21 | Forms.Init(this, savedInstanceState); 22 | LoadApplication(new App()); 23 | } 24 | 25 | public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults) 26 | { 27 | Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults); 28 | 29 | base.OnRequestPermissionsResult(requestCode, permissions, grantResults); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/DotNet2020.Android/Properties/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/DotNet2020.Android/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | using Android.App; 5 | 6 | // General Information about an assembly is controlled through the following 7 | // set of attributes. Change these attribute values to modify the information 8 | // associated with an assembly. 9 | [assembly: AssemblyTitle("DotNet2020.Android")] 10 | [assembly: AssemblyDescription("")] 11 | [assembly: AssemblyConfiguration("")] 12 | [assembly: AssemblyCompany("")] 13 | [assembly: AssemblyProduct("DotNet2020.Android")] 14 | [assembly: AssemblyCopyright("Copyright © 2014")] 15 | [assembly: AssemblyTrademark("")] 16 | [assembly: AssemblyCulture("")] 17 | [assembly: ComVisible(false)] 18 | 19 | // Version information for an assembly consists of the following four values: 20 | // 21 | // Major Version 22 | // Minor Version 23 | // Build Number 24 | // Revision 25 | [assembly: AssemblyVersion("1.0.0.0")] 26 | [assembly: AssemblyFileVersion("1.0.0.0")] 27 | 28 | // Add some common permissions, these can be removed if not needed 29 | [assembly: UsesPermission(Android.Manifest.Permission.Internet)] 30 | [assembly: UsesPermission(Android.Manifest.Permission.WriteExternalStorage)] 31 | -------------------------------------------------------------------------------- /src/DotNet2020.Android/Renderers/CheckBoxRenderer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using Android.Content; 4 | using Android.Content.Res; 5 | using Android.Graphics; 6 | using Android.Support.V4.Widget; 7 | using Android.Support.V7.Widget; 8 | using Android.Views; 9 | using Android.Widget; 10 | using Xamarin.Forms; 11 | using Xamarin.Forms.Platform.Android; 12 | using Xamarin.Forms.Platform.Android.FastRenderers; 13 | using DotNet2020.Controls; 14 | using CheckBoxRenderer = DotNet2020.Droid.Renderers.CheckBoxRenderer; 15 | using Color = Xamarin.Forms.Color; 16 | using AAttribute = Android.Resource.Attribute; 17 | using AColor = Android.Graphics.Color; 18 | using AView = Android.Views.View; 19 | 20 | [assembly: ExportRenderer(typeof(RendererCheckBox), typeof(CheckBoxRenderer))] 21 | namespace DotNet2020.Droid.Renderers 22 | { 23 | public class CheckBoxRenderer : AppCompatCheckBox, 24 | IVisualElementRenderer, 25 | AView.IOnFocusChangeListener, 26 | CompoundButton.IOnCheckedChangeListener, 27 | ITabStop 28 | { 29 | bool _disposed; 30 | int? _defaultLabelFor; 31 | VisualElementTracker _tracker; 32 | VisualElementRenderer _visualElementRenderer; 33 | static int[][] _checkedStates = new int[][] 34 | { 35 | new int[] { AAttribute.StateEnabled, AAttribute.StateChecked }, 36 | new int[] { AAttribute.StateEnabled, -AAttribute.StateChecked }, 37 | new int[] { -AAttribute.StateEnabled, AAttribute.StateChecked }, 38 | new int[] { -AAttribute.StateEnabled, -AAttribute.StatePressed }, 39 | }; 40 | 41 | public event EventHandler ElementChanged; 42 | public event EventHandler ElementPropertyChanged; 43 | 44 | public CheckBoxRenderer(Context context) : base(context) => Init(); 45 | 46 | public CheckBoxRenderer(Context context, int defStyleAttr) : base(context, null, defStyleAttr) => Init(); 47 | 48 | void Init() 49 | { 50 | SoundEffectsEnabled = false; 51 | SetOnCheckedChangeListener(this); 52 | Tag = this; 53 | OnFocusChangeListener = this; 54 | 55 | this.SetClipToOutline(true); 56 | } 57 | 58 | void IVisualElementRenderer.SetElement(VisualElement element) 59 | { 60 | if (element == null) 61 | { 62 | throw new ArgumentNullException(nameof(element)); 63 | } 64 | 65 | if (!(element is RendererCheckBox checkBox)) 66 | { 67 | throw new ArgumentException("Element is not of type " + typeof(RendererCheckBox), nameof(element)); 68 | } 69 | 70 | RendererCheckBox oldElement = Element; 71 | Element = checkBox; 72 | 73 | if (oldElement != null) 74 | { 75 | oldElement.PropertyChanged -= OnElementPropertyChanged; 76 | } 77 | 78 | element.PropertyChanged += OnElementPropertyChanged; 79 | 80 | if (_tracker == null) 81 | { 82 | _tracker = new VisualElementTracker(this); 83 | } 84 | 85 | if (_visualElementRenderer == null) 86 | { 87 | _visualElementRenderer = new VisualElementRenderer(this); 88 | } 89 | 90 | OnElementChanged(new ElementChangedEventArgs(oldElement as RendererCheckBox, Element)); 91 | } 92 | 93 | protected virtual void OnElementChanged(ElementChangedEventArgs e) 94 | { 95 | if (e.NewElement != null && !_disposed) 96 | { 97 | this.EnsureId(); 98 | 99 | UpdateOnColor(); 100 | UpdateIsChecked(); 101 | UpdateBackgroundColor(); 102 | UpdateBackground(); 103 | } 104 | 105 | ElementChanged?.Invoke(this, new VisualElementChangedEventArgs(e.OldElement, e.NewElement)); 106 | } 107 | 108 | protected virtual void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) 109 | { 110 | if (e.PropertyName == RendererCheckBox.ColorProperty.PropertyName) 111 | { 112 | UpdateOnColor(); 113 | } 114 | else if (e.PropertyName == RendererCheckBox.IsCheckedProperty.PropertyName) 115 | { 116 | UpdateIsChecked(); 117 | } 118 | else if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName) 119 | { 120 | UpdateBackgroundColor(); 121 | } 122 | else if (e.PropertyName == VisualElement.BackgroundProperty.PropertyName) 123 | { 124 | UpdateBackground(); 125 | } 126 | 127 | ElementPropertyChanged?.Invoke(this, e); 128 | } 129 | 130 | protected override void Dispose(bool disposing) 131 | { 132 | if (_disposed) 133 | return; 134 | 135 | _disposed = true; 136 | 137 | if (disposing) 138 | { 139 | _disposed = true; 140 | _tracker?.Dispose(); 141 | _tracker = null; 142 | SetOnCheckedChangeListener(null); 143 | OnFocusChangeListener = null; 144 | 145 | if (Element != null) 146 | { 147 | Element.PropertyChanged -= OnElementPropertyChanged; 148 | 149 | Element = null; 150 | } 151 | } 152 | 153 | base.Dispose(disposing); 154 | } 155 | 156 | Size MinimumSize() 157 | { 158 | return Size.Zero; 159 | } 160 | 161 | SizeRequest IVisualElementRenderer.GetDesiredSize(int widthConstraint, int heightConstraint) 162 | { 163 | if (_disposed) 164 | { 165 | return new SizeRequest(); 166 | } 167 | 168 | Measure(widthConstraint, heightConstraint); 169 | return new SizeRequest(new Size(MeasuredWidth, MeasuredHeight), MinimumSize()); 170 | } 171 | 172 | void IOnCheckedChangeListener.OnCheckedChanged(CompoundButton buttonView, bool isChecked) 173 | { 174 | ((IElementController)Element).SetValueFromRenderer(RendererCheckBox.IsCheckedProperty, isChecked); 175 | } 176 | 177 | void UpdateIsChecked() 178 | { 179 | if (Element == null || Control == null) 180 | return; 181 | 182 | Checked = Element.IsChecked; 183 | } 184 | 185 | protected virtual ColorStateList GetColorStateList() 186 | { 187 | var tintColor = Element.Color == Color.Default ? Color.Accent.ToAndroid() : Element.Color.ToAndroid(); 188 | 189 | var list = new ColorStateList( 190 | _checkedStates, 191 | new int[] 192 | { 193 | tintColor, 194 | tintColor, 195 | tintColor, 196 | tintColor 197 | }); 198 | 199 | return list; 200 | } 201 | 202 | void UpdateBackgroundColor() 203 | { 204 | if (Element.BackgroundColor == Color.Default) 205 | SetBackgroundColor(AColor.Transparent); 206 | else 207 | SetBackgroundColor(Element.BackgroundColor.ToAndroid()); 208 | } 209 | 210 | void UpdateBackground() 211 | { 212 | Brush background = Element.Background; 213 | 214 | this.UpdateBackground(background); 215 | } 216 | 217 | void UpdateOnColor() 218 | { 219 | if (Element == null || Control == null) 220 | return; 221 | 222 | var mode = PorterDuff.Mode.SrcIn; 223 | 224 | CompoundButtonCompat.SetButtonTintList(Control, GetColorStateList()); 225 | CompoundButtonCompat.SetButtonTintMode(Control, mode); 226 | } 227 | 228 | void IOnFocusChangeListener.OnFocusChange(AView v, bool hasFocus) 229 | { 230 | ((IElementController)Element).SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, hasFocus); 231 | } 232 | 233 | public void SetLabelFor(int? id) 234 | { 235 | if (_defaultLabelFor == null) 236 | _defaultLabelFor = LabelFor; 237 | 238 | LabelFor = (int)(id ?? _defaultLabelFor); 239 | } 240 | 241 | void IVisualElementRenderer.UpdateLayout() => _tracker?.UpdateLayout(); 242 | VisualElement IVisualElementRenderer.Element => Element; 243 | AView IVisualElementRenderer.View => this; 244 | ViewGroup IVisualElementRenderer.ViewGroup => null; 245 | VisualElementTracker IVisualElementRenderer.Tracker => _tracker; 246 | 247 | protected RendererCheckBox Element { get; private set; } 248 | 249 | protected AppCompatCheckBox Control => this; 250 | 251 | AView ITabStop.TabStop => this; 252 | } 253 | } -------------------------------------------------------------------------------- /src/DotNet2020.Android/Resources/AboutResources.txt: -------------------------------------------------------------------------------- 1 | Images, layout descriptions, binary blobs and string dictionaries can be included 2 | in your application as resource files. Various Android APIs are designed to 3 | operate on the resource IDs instead of dealing with images, strings or binary blobs 4 | directly. 5 | 6 | For example, a sample Android app that contains a user interface layout (main.xml), 7 | an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png) 8 | would keep its resources in the "Resources" directory of the application: 9 | 10 | Resources/ 11 | drawable-hdpi/ 12 | icon.png 13 | 14 | drawable-ldpi/ 15 | icon.png 16 | 17 | drawable-mdpi/ 18 | icon.png 19 | 20 | layout/ 21 | main.xml 22 | 23 | values/ 24 | strings.xml 25 | 26 | In order to get the build system to recognize Android resources, set the build action to 27 | "AndroidResource". The native Android APIs do not operate directly with filenames, but 28 | instead operate on resource IDs. When you compile an Android application that uses resources, 29 | the build system will package the resources for distribution and generate a class called 30 | "Resource" that contains the tokens for each one of the resources included. For example, 31 | for the above Resources layout, this is what the Resource class would expose: 32 | 33 | public class Resource { 34 | public class drawable { 35 | public const int icon = 0x123; 36 | } 37 | 38 | public class layout { 39 | public const int main = 0x456; 40 | } 41 | 42 | public class strings { 43 | public const int first_string = 0xabc; 44 | public const int second_string = 0xbcd; 45 | } 46 | } 47 | 48 | You would then use R.drawable.icon to reference the drawable/icon.png file, or Resource.layout.main 49 | to reference the layout/main.xml file, or Resource.strings.first_string to reference the first 50 | string in the dictionary file values/strings.xml. 51 | -------------------------------------------------------------------------------- /src/DotNet2020.Android/Resources/layout/Tabbar.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/DotNet2020.Android/Resources/layout/Toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/DotNet2020.Android/Resources/mipmap-anydpi-v26/icon.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/DotNet2020.Android/Resources/mipmap-anydpi-v26/icon_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/DotNet2020.Android/Resources/mipmap-hdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuarezruiz/DotNet2020/77c7e80b69f52e9aad09533456cd3dc70eb8140e/src/DotNet2020.Android/Resources/mipmap-hdpi/icon.png -------------------------------------------------------------------------------- /src/DotNet2020.Android/Resources/mipmap-hdpi/launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuarezruiz/DotNet2020/77c7e80b69f52e9aad09533456cd3dc70eb8140e/src/DotNet2020.Android/Resources/mipmap-hdpi/launcher_foreground.png -------------------------------------------------------------------------------- /src/DotNet2020.Android/Resources/mipmap-mdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuarezruiz/DotNet2020/77c7e80b69f52e9aad09533456cd3dc70eb8140e/src/DotNet2020.Android/Resources/mipmap-mdpi/icon.png -------------------------------------------------------------------------------- /src/DotNet2020.Android/Resources/mipmap-mdpi/launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuarezruiz/DotNet2020/77c7e80b69f52e9aad09533456cd3dc70eb8140e/src/DotNet2020.Android/Resources/mipmap-mdpi/launcher_foreground.png -------------------------------------------------------------------------------- /src/DotNet2020.Android/Resources/mipmap-xhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuarezruiz/DotNet2020/77c7e80b69f52e9aad09533456cd3dc70eb8140e/src/DotNet2020.Android/Resources/mipmap-xhdpi/icon.png -------------------------------------------------------------------------------- /src/DotNet2020.Android/Resources/mipmap-xhdpi/launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuarezruiz/DotNet2020/77c7e80b69f52e9aad09533456cd3dc70eb8140e/src/DotNet2020.Android/Resources/mipmap-xhdpi/launcher_foreground.png -------------------------------------------------------------------------------- /src/DotNet2020.Android/Resources/mipmap-xxhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuarezruiz/DotNet2020/77c7e80b69f52e9aad09533456cd3dc70eb8140e/src/DotNet2020.Android/Resources/mipmap-xxhdpi/icon.png -------------------------------------------------------------------------------- /src/DotNet2020.Android/Resources/mipmap-xxhdpi/launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuarezruiz/DotNet2020/77c7e80b69f52e9aad09533456cd3dc70eb8140e/src/DotNet2020.Android/Resources/mipmap-xxhdpi/launcher_foreground.png -------------------------------------------------------------------------------- /src/DotNet2020.Android/Resources/mipmap-xxxhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuarezruiz/DotNet2020/77c7e80b69f52e9aad09533456cd3dc70eb8140e/src/DotNet2020.Android/Resources/mipmap-xxxhdpi/icon.png -------------------------------------------------------------------------------- /src/DotNet2020.Android/Resources/mipmap-xxxhdpi/launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuarezruiz/DotNet2020/77c7e80b69f52e9aad09533456cd3dc70eb8140e/src/DotNet2020.Android/Resources/mipmap-xxxhdpi/launcher_foreground.png -------------------------------------------------------------------------------- /src/DotNet2020.Android/Resources/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | #3F51B5 5 | #303F9F 6 | #FF4081 7 | -------------------------------------------------------------------------------- /src/DotNet2020.Android/Resources/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /src/DotNet2020.iOS/AppDelegate.cs: -------------------------------------------------------------------------------- 1 | using Foundation; 2 | using UIKit; 3 | using Xamarin.Forms; 4 | 5 | namespace DotNet2020.iOS 6 | { 7 | // The UIApplicationDelegate for the application. This class is responsible for launching the 8 | // User Interface of the application, as well as listening (and optionally responding) to 9 | // application events from iOS. 10 | [Register("AppDelegate")] 11 | public partial class AppDelegate : Xamarin.Forms.Platform.iOS.FormsApplicationDelegate 12 | { 13 | // 14 | // This method is invoked when the application has loaded and is ready to run. In this 15 | // method you should instantiate the window, load the UI into it and then make the window 16 | // visible. 17 | // 18 | // You have 17 seconds to return from this method, or iOS will terminate your application. 19 | // 20 | public override bool FinishedLaunching(UIApplication app, NSDictionary options) 21 | { 22 | Forms.SetFlags("Brushes_Experimental", "Shapes_Experimental"); 23 | Forms.Init(); 24 | LoadApplication(new App()); 25 | 26 | return base.FinishedLaunching(app, options); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/DotNet2020.iOS/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "scale": "2x", 5 | "size": "20x20", 6 | "idiom": "iphone", 7 | "filename": "Icon40.png" 8 | }, 9 | { 10 | "scale": "3x", 11 | "size": "20x20", 12 | "idiom": "iphone", 13 | "filename": "Icon60.png" 14 | }, 15 | { 16 | "scale": "2x", 17 | "size": "29x29", 18 | "idiom": "iphone", 19 | "filename": "Icon58.png" 20 | }, 21 | { 22 | "scale": "3x", 23 | "size": "29x29", 24 | "idiom": "iphone", 25 | "filename": "Icon87.png" 26 | }, 27 | { 28 | "scale": "2x", 29 | "size": "40x40", 30 | "idiom": "iphone", 31 | "filename": "Icon80.png" 32 | }, 33 | { 34 | "scale": "3x", 35 | "size": "40x40", 36 | "idiom": "iphone", 37 | "filename": "Icon120.png" 38 | }, 39 | { 40 | "scale": "2x", 41 | "size": "60x60", 42 | "idiom": "iphone", 43 | "filename": "Icon120.png" 44 | }, 45 | { 46 | "scale": "3x", 47 | "size": "60x60", 48 | "idiom": "iphone", 49 | "filename": "Icon180.png" 50 | }, 51 | { 52 | "scale": "1x", 53 | "size": "20x20", 54 | "idiom": "ipad", 55 | "filename": "Icon20.png" 56 | }, 57 | { 58 | "scale": "2x", 59 | "size": "20x20", 60 | "idiom": "ipad", 61 | "filename": "Icon40.png" 62 | }, 63 | { 64 | "scale": "1x", 65 | "size": "29x29", 66 | "idiom": "ipad", 67 | "filename": "Icon29.png" 68 | }, 69 | { 70 | "scale": "2x", 71 | "size": "29x29", 72 | "idiom": "ipad", 73 | "filename": "Icon58.png" 74 | }, 75 | { 76 | "scale": "1x", 77 | "size": "40x40", 78 | "idiom": "ipad", 79 | "filename": "Icon40.png" 80 | }, 81 | { 82 | "scale": "2x", 83 | "size": "40x40", 84 | "idiom": "ipad", 85 | "filename": "Icon80.png" 86 | }, 87 | { 88 | "scale": "1x", 89 | "size": "76x76", 90 | "idiom": "ipad", 91 | "filename": "Icon76.png" 92 | }, 93 | { 94 | "scale": "2x", 95 | "size": "76x76", 96 | "idiom": "ipad", 97 | "filename": "Icon152.png" 98 | }, 99 | { 100 | "scale": "2x", 101 | "size": "83.5x83.5", 102 | "idiom": "ipad", 103 | "filename": "Icon167.png" 104 | }, 105 | { 106 | "scale": "1x", 107 | "size": "1024x1024", 108 | "idiom": "ios-marketing", 109 | "filename": "Icon1024.png" 110 | } 111 | ], 112 | "properties": {}, 113 | "info": { 114 | "version": 1, 115 | "author": "xcode" 116 | } 117 | } -------------------------------------------------------------------------------- /src/DotNet2020.iOS/Assets.xcassets/AppIcon.appiconset/Icon1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuarezruiz/DotNet2020/77c7e80b69f52e9aad09533456cd3dc70eb8140e/src/DotNet2020.iOS/Assets.xcassets/AppIcon.appiconset/Icon1024.png -------------------------------------------------------------------------------- /src/DotNet2020.iOS/Assets.xcassets/AppIcon.appiconset/Icon120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuarezruiz/DotNet2020/77c7e80b69f52e9aad09533456cd3dc70eb8140e/src/DotNet2020.iOS/Assets.xcassets/AppIcon.appiconset/Icon120.png -------------------------------------------------------------------------------- /src/DotNet2020.iOS/Assets.xcassets/AppIcon.appiconset/Icon152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuarezruiz/DotNet2020/77c7e80b69f52e9aad09533456cd3dc70eb8140e/src/DotNet2020.iOS/Assets.xcassets/AppIcon.appiconset/Icon152.png -------------------------------------------------------------------------------- /src/DotNet2020.iOS/Assets.xcassets/AppIcon.appiconset/Icon167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuarezruiz/DotNet2020/77c7e80b69f52e9aad09533456cd3dc70eb8140e/src/DotNet2020.iOS/Assets.xcassets/AppIcon.appiconset/Icon167.png -------------------------------------------------------------------------------- /src/DotNet2020.iOS/Assets.xcassets/AppIcon.appiconset/Icon180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuarezruiz/DotNet2020/77c7e80b69f52e9aad09533456cd3dc70eb8140e/src/DotNet2020.iOS/Assets.xcassets/AppIcon.appiconset/Icon180.png -------------------------------------------------------------------------------- /src/DotNet2020.iOS/Assets.xcassets/AppIcon.appiconset/Icon20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuarezruiz/DotNet2020/77c7e80b69f52e9aad09533456cd3dc70eb8140e/src/DotNet2020.iOS/Assets.xcassets/AppIcon.appiconset/Icon20.png -------------------------------------------------------------------------------- /src/DotNet2020.iOS/Assets.xcassets/AppIcon.appiconset/Icon29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuarezruiz/DotNet2020/77c7e80b69f52e9aad09533456cd3dc70eb8140e/src/DotNet2020.iOS/Assets.xcassets/AppIcon.appiconset/Icon29.png -------------------------------------------------------------------------------- /src/DotNet2020.iOS/Assets.xcassets/AppIcon.appiconset/Icon40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuarezruiz/DotNet2020/77c7e80b69f52e9aad09533456cd3dc70eb8140e/src/DotNet2020.iOS/Assets.xcassets/AppIcon.appiconset/Icon40.png -------------------------------------------------------------------------------- /src/DotNet2020.iOS/Assets.xcassets/AppIcon.appiconset/Icon58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuarezruiz/DotNet2020/77c7e80b69f52e9aad09533456cd3dc70eb8140e/src/DotNet2020.iOS/Assets.xcassets/AppIcon.appiconset/Icon58.png -------------------------------------------------------------------------------- /src/DotNet2020.iOS/Assets.xcassets/AppIcon.appiconset/Icon60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuarezruiz/DotNet2020/77c7e80b69f52e9aad09533456cd3dc70eb8140e/src/DotNet2020.iOS/Assets.xcassets/AppIcon.appiconset/Icon60.png -------------------------------------------------------------------------------- /src/DotNet2020.iOS/Assets.xcassets/AppIcon.appiconset/Icon76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuarezruiz/DotNet2020/77c7e80b69f52e9aad09533456cd3dc70eb8140e/src/DotNet2020.iOS/Assets.xcassets/AppIcon.appiconset/Icon76.png -------------------------------------------------------------------------------- /src/DotNet2020.iOS/Assets.xcassets/AppIcon.appiconset/Icon80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuarezruiz/DotNet2020/77c7e80b69f52e9aad09533456cd3dc70eb8140e/src/DotNet2020.iOS/Assets.xcassets/AppIcon.appiconset/Icon80.png -------------------------------------------------------------------------------- /src/DotNet2020.iOS/Assets.xcassets/AppIcon.appiconset/Icon87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuarezruiz/DotNet2020/77c7e80b69f52e9aad09533456cd3dc70eb8140e/src/DotNet2020.iOS/Assets.xcassets/AppIcon.appiconset/Icon87.png -------------------------------------------------------------------------------- /src/DotNet2020.iOS/Controls/NativeCheckBox.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CoreGraphics; 3 | using UIKit; 4 | using Xamarin.Forms; 5 | using Xamarin.Forms.Platform.iOS; 6 | 7 | namespace DotNet2020.iOS.Controls 8 | { 9 | public class NativeCheckBox : UIButton 10 | { 11 | static UIImage _checked; 12 | static UIImage _unchecked; 13 | 14 | // All these values were chosen to just match the android drawables that are used 15 | const float _defaultSize = 18.0f; 16 | const float _lineWidth = 2.0f; 17 | Color _tintColor; 18 | bool _isChecked; 19 | bool _isEnabled; 20 | float _minimumViewSize; 21 | public EventHandler CheckedChanged; 22 | bool _disposed; 23 | 24 | public NativeCheckBox() 25 | { 26 | TouchUpInside += OnTouchUpInside; 27 | ContentMode = UIViewContentMode.Center; 28 | ImageView.ContentMode = UIViewContentMode.ScaleAspectFit; 29 | HorizontalAlignment = UIControlContentHorizontalAlignment.Left; 30 | VerticalAlignment = UIControlContentVerticalAlignment.Center; 31 | AdjustsImageWhenDisabled = false; 32 | AdjustsImageWhenHighlighted = false; 33 | } 34 | 35 | internal float MinimumViewSize 36 | { 37 | get { return _minimumViewSize; } 38 | set 39 | { 40 | _minimumViewSize = value; 41 | var xOffset = (value - _defaultSize + _lineWidth) / 4; 42 | ContentEdgeInsets = new UIEdgeInsets(0, xOffset, 0, 0); 43 | } 44 | } 45 | 46 | void OnTouchUpInside(object sender, EventArgs e) 47 | { 48 | IsChecked = !IsChecked; 49 | CheckedChanged?.Invoke(this, null); 50 | } 51 | 52 | public bool IsChecked 53 | { 54 | get => _isChecked; 55 | set 56 | { 57 | if (value == _isChecked) 58 | return; 59 | 60 | _isChecked = value; 61 | UpdateDisplay(); 62 | } 63 | } 64 | 65 | public bool IsEnabled 66 | { 67 | get => _isEnabled; 68 | set 69 | { 70 | if (value == _isEnabled) 71 | return; 72 | 73 | _isEnabled = value; 74 | UserInteractionEnabled = IsEnabled; 75 | UpdateDisplay(); 76 | } 77 | } 78 | 79 | public Color CheckBoxTintColor 80 | { 81 | get => _tintColor; 82 | set 83 | { 84 | if (_tintColor == value) 85 | return; 86 | 87 | _tintColor = value; 88 | CheckBoxTintUIColor = (CheckBoxTintColor.IsDefault ? null : CheckBoxTintColor.ToUIColor()); 89 | } 90 | } 91 | 92 | UIColor _checkBoxTintUIColor; 93 | UIColor CheckBoxTintUIColor 94 | { 95 | get 96 | { 97 | return _checkBoxTintUIColor ?? UIColor.White; 98 | } 99 | set 100 | { 101 | if (value == _checkBoxTintUIColor) 102 | return; 103 | 104 | _checkBoxTintUIColor = value; 105 | ImageView.TintColor = value; 106 | TintColor = value; 107 | 108 | if (Enabled) 109 | SetNeedsDisplay(); 110 | else 111 | UpdateDisplay(); 112 | } 113 | } 114 | 115 | public override bool Enabled 116 | { 117 | get 118 | { 119 | return base.Enabled; 120 | } 121 | 122 | set 123 | { 124 | bool changed = base.Enabled != value; 125 | base.Enabled = value; 126 | 127 | if (changed) 128 | UpdateDisplay(); 129 | } 130 | } 131 | 132 | protected virtual UIImage GetCheckBoximage() 133 | { 134 | // Ideally I would use the static images here but when disabled it always tints them grey 135 | // and I don't know how to make it not tint them gray 136 | if (!Enabled && CheckBoxTintColor != Color.Default) 137 | { 138 | if (IsChecked) 139 | return CreateCheckBox(CreateCheckMark()).ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal); 140 | 141 | return CreateCheckBox(null).ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal); 142 | } 143 | 144 | if (_checked == null) 145 | _checked = CreateCheckBox(CreateCheckMark()).ImageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate); 146 | 147 | if (_unchecked == null) 148 | _unchecked = CreateCheckBox(null).ImageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate); 149 | 150 | return IsChecked ? _checked : _unchecked; 151 | } 152 | 153 | internal void UpdateDisplay() 154 | { 155 | SetImage(GetCheckBoximage(), UIControlState.Normal); 156 | SetNeedsDisplay(); 157 | } 158 | 159 | internal virtual UIBezierPath CreateBoxPath(CGRect backgroundRect) => UIBezierPath.FromOval(backgroundRect); 160 | internal virtual UIBezierPath CreateCheckPath() => new UIBezierPath 161 | { 162 | LineWidth = (nfloat)0.077, 163 | LineCapStyle = CGLineCap.Round, 164 | LineJoinStyle = CGLineJoin.Round 165 | }; 166 | 167 | internal virtual void DrawCheckMark(UIBezierPath path) 168 | { 169 | path.MoveTo(new CGPoint(0.72f, 0.22f)); 170 | path.AddLineTo(new CGPoint(0.33f, 0.6f)); 171 | path.AddLineTo(new CGPoint(0.15f, 0.42f)); 172 | } 173 | 174 | internal virtual UIImage CreateCheckBox(UIImage check) 175 | { 176 | UIGraphics.BeginImageContextWithOptions(new CGSize(_defaultSize, _defaultSize), false, 0); 177 | var context = UIGraphics.GetCurrentContext(); 178 | context.SaveState(); 179 | 180 | var checkedColor = CheckBoxTintUIColor; 181 | checkedColor.SetFill(); 182 | checkedColor.SetStroke(); 183 | 184 | var vPadding = _lineWidth / 2; 185 | var hPadding = _lineWidth / 2; 186 | var diameter = _defaultSize - _lineWidth; 187 | 188 | var backgroundRect = new CGRect(hPadding, vPadding, diameter, diameter); 189 | var boxPath = CreateBoxPath(backgroundRect); 190 | boxPath.LineWidth = _lineWidth; 191 | boxPath.Stroke(); 192 | 193 | if (check != null) 194 | { 195 | boxPath.Fill(); 196 | check.Draw(new CGPoint(0, 0), CGBlendMode.DestinationOut, 1); 197 | } 198 | 199 | context.RestoreState(); 200 | var img = UIGraphics.GetImageFromCurrentImageContext(); 201 | UIGraphics.EndImageContext(); 202 | 203 | return img; 204 | } 205 | 206 | 207 | internal UIImage CreateCheckMark() 208 | { 209 | UIGraphics.BeginImageContextWithOptions(new CGSize(_defaultSize, _defaultSize), false, 0); 210 | var context = UIGraphics.GetCurrentContext(); 211 | context.SaveState(); 212 | 213 | var vPadding = _lineWidth / 2; 214 | var hPadding = _lineWidth / 2; 215 | var diameter = _defaultSize - _lineWidth; 216 | 217 | var checkPath = CreateCheckPath(); 218 | 219 | context.TranslateCTM(hPadding + (nfloat)(0.05 * diameter), vPadding + (nfloat)(0.1 * diameter)); 220 | context.ScaleCTM(diameter, diameter); 221 | DrawCheckMark(checkPath); 222 | UIColor.White.SetStroke(); 223 | checkPath.Stroke(); 224 | 225 | context.RestoreState(); 226 | var img = UIGraphics.GetImageFromCurrentImageContext(); 227 | UIGraphics.EndImageContext(); 228 | 229 | return img; 230 | } 231 | 232 | protected override void Dispose(bool disposing) 233 | { 234 | if (_disposed) 235 | return; 236 | 237 | _disposed = true; 238 | if (disposing) 239 | TouchUpInside -= OnTouchUpInside; 240 | 241 | base.Dispose(disposing); 242 | } 243 | } 244 | } -------------------------------------------------------------------------------- /src/DotNet2020.iOS/DotNet2020.iOS.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | iPhoneSimulator 6 | 8.0.30703 7 | 2.0 8 | {6E0C2D9E-36C2-4156-96F4-A777EF27063A} 9 | {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 10 | {6143fdea-f3c2-4a09-aafa-6e230626515e} 11 | Exe 12 | DotNet2020.iOS 13 | Resources 14 | DotNet2020.iOS 15 | true 16 | NSUrlSessionHandler 17 | automatic 18 | 19 | 20 | true 21 | full 22 | false 23 | bin\iPhoneSimulator\Debug 24 | DEBUG 25 | prompt 26 | 4 27 | x86_64 28 | None 29 | true 30 | 31 | 32 | none 33 | true 34 | bin\iPhoneSimulator\Release 35 | prompt 36 | 4 37 | None 38 | x86_64 39 | 40 | 41 | true 42 | full 43 | false 44 | bin\iPhone\Debug 45 | DEBUG 46 | prompt 47 | 4 48 | ARM64 49 | iPhone Developer 50 | true 51 | Entitlements.plist 52 | None 53 | -all 54 | 55 | 56 | none 57 | true 58 | bin\iPhone\Release 59 | prompt 60 | 4 61 | ARM64 62 | iPhone Developer 63 | Entitlements.plist 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | false 78 | 79 | 80 | false 81 | 82 | 83 | false 84 | 85 | 86 | false 87 | 88 | 89 | false 90 | 91 | 92 | false 93 | 94 | 95 | false 96 | 97 | 98 | false 99 | 100 | 101 | false 102 | 103 | 104 | false 105 | 106 | 107 | false 108 | 109 | 110 | false 111 | 112 | 113 | false 114 | 115 | 116 | false 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 2.80.2 132 | 133 | 134 | 135 | 136 | 137 | {2AB1E756-7B92-49C2-A542-D8B535259755} 138 | DotNet2020 139 | 140 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /src/DotNet2020.iOS/Entitlements.plist: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/DotNet2020.iOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIDeviceFamily 6 | 7 | 1 8 | 2 9 | 10 | UISupportedInterfaceOrientations 11 | 12 | UIInterfaceOrientationPortrait 13 | UIInterfaceOrientationLandscapeLeft 14 | UIInterfaceOrientationLandscapeRight 15 | 16 | UISupportedInterfaceOrientations~ipad 17 | 18 | UIInterfaceOrientationPortrait 19 | UIInterfaceOrientationPortraitUpsideDown 20 | UIInterfaceOrientationLandscapeLeft 21 | UIInterfaceOrientationLandscapeRight 22 | 23 | MinimumOSVersion 24 | 8.0 25 | CFBundleDisplayName 26 | DotNet2020 27 | CFBundleIdentifier 28 | com.companyname.DotNet2020 29 | CFBundleVersion 30 | 1.0 31 | UILaunchStoryboardName 32 | LaunchScreen 33 | CFBundleName 34 | DotNet2020 35 | XSAppIconAssets 36 | Assets.xcassets/AppIcon.appiconset 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/DotNet2020.iOS/Main.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | using Foundation; 6 | using UIKit; 7 | 8 | namespace DotNet2020.iOS 9 | { 10 | public class Application 11 | { 12 | // This is the main entry point of the application. 13 | static void Main(string[] args) 14 | { 15 | // if you want to use a different Application Delegate class from "AppDelegate" 16 | // you can specify it here. 17 | UIApplication.Main(args, null, "AppDelegate"); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/DotNet2020.iOS/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("DotNet2020.iOS")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("DotNet2020.iOS")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("72bdc44f-c588-44f3-b6df-9aace7daafdd")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/DotNet2020.iOS/Renderers/CheckBoxRenderer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using CoreGraphics; 4 | using DotNet2020.Controls; 5 | using DotNet2020.iOS.Controls; 6 | using UIKit; 7 | using Xamarin.Forms; 8 | using Xamarin.Forms.Platform.iOS; 9 | using CheckBoxRenderer = DotNet2020.iOS.Renderers.CheckBoxRenderer; 10 | 11 | [assembly: ExportRenderer(typeof(RendererCheckBox), typeof(CheckBoxRenderer))] 12 | namespace DotNet2020.iOS.Renderers 13 | { 14 | public class CheckBoxRenderer : ViewRenderer 15 | { 16 | protected virtual float MinimumSize => 44f; // Apple docs 17 | bool _disposed; 18 | 19 | protected override void OnElementChanged(ElementChangedEventArgs e) 20 | { 21 | if (e.OldElement != null) 22 | e.OldElement.CheckedChanged -= OnElementCheckedChanged; 23 | 24 | if (e.NewElement != null) 25 | { 26 | if (Control == null) 27 | { 28 | SetNativeControl(CreateNativeControl()); 29 | Control.CheckedChanged += OnControlCheckedChanged; 30 | } 31 | 32 | Control.MinimumViewSize = MinimumSize; 33 | Control.IsChecked = Element.IsChecked; 34 | Control.IsEnabled = Element.IsEnabled; 35 | 36 | e.NewElement.CheckedChanged += OnElementCheckedChanged; 37 | UpdateTintColor(); 38 | } 39 | 40 | base.OnElementChanged(e); 41 | } 42 | 43 | protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) 44 | { 45 | base.OnElementPropertyChanged(sender, e); 46 | 47 | if (e.PropertyName == CheckBox.ColorProperty.PropertyName) 48 | UpdateTintColor(); 49 | } 50 | 51 | protected override void Dispose(bool disposing) 52 | { 53 | if (_disposed) 54 | return; 55 | 56 | _disposed = true; 57 | 58 | if (disposing && Control != null) 59 | { 60 | Control.CheckedChanged -= OnControlCheckedChanged; 61 | } 62 | 63 | base.Dispose(disposing); 64 | } 65 | 66 | public override void LayoutSubviews() 67 | { 68 | base.LayoutSubviews(); 69 | Control.UpdateDisplay(); 70 | } 71 | 72 | protected override void SetAccessibilityLabel() 73 | { 74 | // If we have not specified an AccessibilityLabel and the AccessibiltyLabel is current bound to the Title, 75 | // exit this method so we don't set the AccessibilityLabel value and break the binding. 76 | // This may pose a problem for users who want to explicitly set the AccessibilityLabel to null, but this 77 | // will prevent us from inadvertently breaking UI Tests that are using Query.Marked to get the dynamic Title 78 | // of the ImageButton. 79 | 80 | var elemValue = (string)Element?.GetValue(AutomationProperties.NameProperty); 81 | if (string.IsNullOrWhiteSpace(elemValue) && Control?.AccessibilityLabel == Control?.Title(UIControlState.Normal)) 82 | return; 83 | 84 | base.SetAccessibilityLabel(); 85 | } 86 | 87 | public override CGSize SizeThatFits(CGSize size) 88 | { 89 | var result = base.SizeThatFits(size); 90 | var height = Math.Max(MinimumSize, result.Height); 91 | var width = Math.Max(MinimumSize, result.Width); 92 | var final = Math.Min(width, height); 93 | return new CGSize(final, final); 94 | } 95 | 96 | public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint) 97 | { 98 | var sizeConstraint = base.GetDesiredSize(widthConstraint, heightConstraint); 99 | 100 | var set = false; 101 | 102 | var width = widthConstraint; 103 | var height = heightConstraint; 104 | if (sizeConstraint.Request.Width == 0) 105 | { 106 | if (widthConstraint <= 0 || double.IsInfinity(widthConstraint)) 107 | { 108 | width = MinimumSize; 109 | set = true; 110 | } 111 | } 112 | 113 | if (sizeConstraint.Request.Height == 0) 114 | { 115 | if (heightConstraint <= 0 || double.IsInfinity(heightConstraint)) 116 | { 117 | height = MinimumSize; 118 | set = true; 119 | } 120 | } 121 | 122 | if (set) 123 | { 124 | sizeConstraint = new SizeRequest(new Size(width, height), new Size(MinimumSize, MinimumSize)); 125 | } 126 | 127 | return sizeConstraint; 128 | } 129 | 130 | protected virtual void UpdateTintColor() 131 | { 132 | if (Element == null) 133 | return; 134 | 135 | Control.CheckBoxTintColor = Element.Color; 136 | } 137 | 138 | void OnControlCheckedChanged(object sender, EventArgs e) 139 | { 140 | Element.IsChecked = Control.IsChecked; 141 | } 142 | 143 | void OnElementCheckedChanged(object sender, EventArgs e) 144 | { 145 | Control.IsChecked = Element.IsChecked; 146 | } 147 | } 148 | } -------------------------------------------------------------------------------- /src/DotNet2020.iOS/Resources/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuarezruiz/DotNet2020/77c7e80b69f52e9aad09533456cd3dc70eb8140e/src/DotNet2020.iOS/Resources/Default-568h@2x.png -------------------------------------------------------------------------------- /src/DotNet2020.iOS/Resources/Default-Portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuarezruiz/DotNet2020/77c7e80b69f52e9aad09533456cd3dc70eb8140e/src/DotNet2020.iOS/Resources/Default-Portrait.png -------------------------------------------------------------------------------- /src/DotNet2020.iOS/Resources/Default-Portrait@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuarezruiz/DotNet2020/77c7e80b69f52e9aad09533456cd3dc70eb8140e/src/DotNet2020.iOS/Resources/Default-Portrait@2x.png -------------------------------------------------------------------------------- /src/DotNet2020.iOS/Resources/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuarezruiz/DotNet2020/77c7e80b69f52e9aad09533456cd3dc70eb8140e/src/DotNet2020.iOS/Resources/Default.png -------------------------------------------------------------------------------- /src/DotNet2020.iOS/Resources/Default@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsuarezruiz/DotNet2020/77c7e80b69f52e9aad09533456cd3dc70eb8140e/src/DotNet2020.iOS/Resources/Default@2x.png -------------------------------------------------------------------------------- /src/DotNet2020.iOS/Resources/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/DotNet2020.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNet2020.Android", "DotNet2020.Android\DotNet2020.Android.csproj", "{32F5B953-A462-4A51-823C-97959A28DA32}" 5 | EndProject 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNet2020.iOS", "DotNet2020.iOS\DotNet2020.iOS.csproj", "{6E0C2D9E-36C2-4156-96F4-A777EF27063A}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNet2020", "DotNet2020\DotNet2020.csproj", "{2AB1E756-7B92-49C2-A542-D8B535259755}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | Debug|iPhoneSimulator = Debug|iPhoneSimulator 15 | Release|iPhoneSimulator = Release|iPhoneSimulator 16 | Debug|iPhone = Debug|iPhone 17 | Release|iPhone = Release|iPhone 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {32F5B953-A462-4A51-823C-97959A28DA32}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {32F5B953-A462-4A51-823C-97959A28DA32}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {32F5B953-A462-4A51-823C-97959A28DA32}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {32F5B953-A462-4A51-823C-97959A28DA32}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {32F5B953-A462-4A51-823C-97959A28DA32}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU 25 | {32F5B953-A462-4A51-823C-97959A28DA32}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU 26 | {32F5B953-A462-4A51-823C-97959A28DA32}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU 27 | {32F5B953-A462-4A51-823C-97959A28DA32}.Release|iPhoneSimulator.Build.0 = Release|Any CPU 28 | {32F5B953-A462-4A51-823C-97959A28DA32}.Debug|iPhone.ActiveCfg = Debug|Any CPU 29 | {32F5B953-A462-4A51-823C-97959A28DA32}.Debug|iPhone.Build.0 = Debug|Any CPU 30 | {32F5B953-A462-4A51-823C-97959A28DA32}.Release|iPhone.ActiveCfg = Release|Any CPU 31 | {32F5B953-A462-4A51-823C-97959A28DA32}.Release|iPhone.Build.0 = Release|Any CPU 32 | {6E0C2D9E-36C2-4156-96F4-A777EF27063A}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator 33 | {6E0C2D9E-36C2-4156-96F4-A777EF27063A}.Debug|Any CPU.Build.0 = Debug|iPhoneSimulator 34 | {6E0C2D9E-36C2-4156-96F4-A777EF27063A}.Release|Any CPU.ActiveCfg = Release|iPhoneSimulator 35 | {6E0C2D9E-36C2-4156-96F4-A777EF27063A}.Release|Any CPU.Build.0 = Release|iPhoneSimulator 36 | {6E0C2D9E-36C2-4156-96F4-A777EF27063A}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator 37 | {6E0C2D9E-36C2-4156-96F4-A777EF27063A}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator 38 | {6E0C2D9E-36C2-4156-96F4-A777EF27063A}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator 39 | {6E0C2D9E-36C2-4156-96F4-A777EF27063A}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator 40 | {6E0C2D9E-36C2-4156-96F4-A777EF27063A}.Debug|iPhone.ActiveCfg = Debug|iPhone 41 | {6E0C2D9E-36C2-4156-96F4-A777EF27063A}.Debug|iPhone.Build.0 = Debug|iPhone 42 | {6E0C2D9E-36C2-4156-96F4-A777EF27063A}.Release|iPhone.ActiveCfg = Release|iPhone 43 | {6E0C2D9E-36C2-4156-96F4-A777EF27063A}.Release|iPhone.Build.0 = Release|iPhone 44 | {2AB1E756-7B92-49C2-A542-D8B535259755}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {2AB1E756-7B92-49C2-A542-D8B535259755}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {2AB1E756-7B92-49C2-A542-D8B535259755}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {2AB1E756-7B92-49C2-A542-D8B535259755}.Release|Any CPU.Build.0 = Release|Any CPU 48 | {2AB1E756-7B92-49C2-A542-D8B535259755}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU 49 | {2AB1E756-7B92-49C2-A542-D8B535259755}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU 50 | {2AB1E756-7B92-49C2-A542-D8B535259755}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU 51 | {2AB1E756-7B92-49C2-A542-D8B535259755}.Release|iPhoneSimulator.Build.0 = Release|Any CPU 52 | {2AB1E756-7B92-49C2-A542-D8B535259755}.Debug|iPhone.ActiveCfg = Debug|Any CPU 53 | {2AB1E756-7B92-49C2-A542-D8B535259755}.Debug|iPhone.Build.0 = Debug|Any CPU 54 | {2AB1E756-7B92-49C2-A542-D8B535259755}.Release|iPhone.ActiveCfg = Release|Any CPU 55 | {2AB1E756-7B92-49C2-A542-D8B535259755}.Release|iPhone.Build.0 = Release|Any CPU 56 | EndGlobalSection 57 | EndGlobal 58 | -------------------------------------------------------------------------------- /src/DotNet2020/App.xaml: -------------------------------------------------------------------------------- 1 |  2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/DotNet2020/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using DotNet2020.Views; 2 | using Xamarin.Forms; 3 | 4 | namespace DotNet2020 5 | { 6 | public partial class App : Application 7 | { 8 | public App() 9 | { 10 | InitializeComponent(); 11 | 12 | MainPage = new NavigationPage(new MainView()); 13 | } 14 | 15 | protected override void OnStart() 16 | { 17 | } 18 | 19 | protected override void OnSleep() 20 | { 21 | } 22 | 23 | protected override void OnResume() 24 | { 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/DotNet2020/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using Xamarin.Forms.Xaml; 2 | 3 | [assembly: XamlCompilation(XamlCompilationOptions.Compile)] -------------------------------------------------------------------------------- /src/DotNet2020/Custom Controls/CustomCheckBox.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using System.Threading.Tasks; 4 | using Xamarin.Forms; 5 | using Xamarin.Forms.Shapes; 6 | using Rectangle = Xamarin.Forms.Shapes.Rectangle; 7 | 8 | namespace DotNet2020.Controls 9 | { 10 | public class CustomCheckBox : ContentView, IDisposable 11 | { 12 | TapGestureRecognizer _tapGestureRecognizer; 13 | Shape _background; 14 | Shape _glyph; 15 | 16 | public CustomCheckBox() 17 | { 18 | Initialize(); 19 | } 20 | 21 | void Initialize() 22 | { 23 | var container = new Grid 24 | { 25 | HorizontalOptions = LayoutOptions.Start, 26 | VerticalOptions = LayoutOptions.Start, 27 | RowSpacing = 0, 28 | Margin = new Thickness(6) 29 | }; 30 | 31 | _background = new Rectangle 32 | { 33 | Fill = Brush.Transparent, 34 | HeightRequest = 18, 35 | WidthRequest = 18, 36 | RadiusX = 2, 37 | RadiusY = 2, 38 | Stroke = Color, 39 | StrokeThickness = 2 40 | }; 41 | 42 | _glyph = new Path 43 | { 44 | Aspect = Stretch.Uniform, 45 | Stroke = Brush.White, 46 | StrokeThickness = 1, 47 | HorizontalOptions = LayoutOptions.Center, 48 | VerticalOptions = LayoutOptions.Center, 49 | HeightRequest = 14, 50 | WidthRequest = 14, 51 | Opacity = 0, 52 | Margin = new Thickness(2) 53 | }; 54 | 55 | var pathGeometryConverter = new PathGeometryConverter(); 56 | 57 | if (_glyph is Path path && pathGeometryConverter.ConvertFromInvariantString("M30.561941,0L31.997,1.393004 10.467954,23.597999 0,15.350999 1.2379759,13.780992 10.287961,20.909952z") is Geometry data) 58 | path.Data = data; 59 | 60 | container.Children.Add(_background); 61 | container.Children.Add(_glyph); 62 | 63 | Content = container; 64 | 65 | _tapGestureRecognizer = new TapGestureRecognizer(); 66 | 67 | UpdateIsEnabled(); 68 | } 69 | 70 | public void Dispose() 71 | { 72 | if (_tapGestureRecognizer != null) 73 | _tapGestureRecognizer.Tapped -= OnCheckBoxTapped; 74 | } 75 | 76 | public static readonly BindableProperty ColorProperty = 77 | BindableProperty.Create(nameof(Color), typeof(Brush), typeof(CustomCheckBox), Brush.DeepPink); 78 | 79 | public Brush Color 80 | { 81 | get => (Brush)GetValue(ColorProperty); 82 | set => SetValue(ColorProperty, value); 83 | } 84 | 85 | public static readonly BindableProperty IsCheckedProperty = 86 | BindableProperty.Create(nameof(IsChecked), typeof(bool), typeof(CustomCheckBox), false, 87 | propertyChanged: OnIsCheckedChanged); 88 | 89 | public bool IsChecked 90 | { 91 | get => (bool)GetValue(IsCheckedProperty); 92 | set => SetValue(IsCheckedProperty, value); 93 | } 94 | 95 | static async void OnIsCheckedChanged(BindableObject bindable, object oldValue, object newValue) 96 | { 97 | if (!(bindable is CustomCheckBox checkbox)) return; 98 | checkbox.CheckedChanged?.Invoke(checkbox, new CheckedChangedEventArgs((bool)newValue)); 99 | await checkbox.AnimateCheckedChanged(); 100 | } 101 | 102 | public event EventHandler CheckedChanged; 103 | 104 | protected override void OnPropertyChanged([CallerMemberName] string propertyName = null) 105 | { 106 | base.OnPropertyChanged(propertyName); 107 | 108 | if (propertyName == IsEnabledProperty.PropertyName) 109 | UpdateIsEnabled(); 110 | } 111 | 112 | void UpdateIsEnabled() 113 | { 114 | if (IsEnabled) 115 | { 116 | _tapGestureRecognizer.Tapped += OnCheckBoxTapped; 117 | GestureRecognizers.Add(_tapGestureRecognizer); 118 | } 119 | else 120 | { 121 | _tapGestureRecognizer.Tapped -= OnCheckBoxTapped; 122 | GestureRecognizers.Remove(_tapGestureRecognizer); 123 | } 124 | } 125 | 126 | void OnCheckBoxTapped(object sender, EventArgs e) 127 | { 128 | IsChecked = !IsChecked; 129 | 130 | if (IsChecked) 131 | { 132 | if (_background != null) 133 | _background.Fill = Color; 134 | 135 | if (_glyph != null) 136 | _glyph.Opacity = 1; 137 | } 138 | else 139 | { 140 | if (_background != null) 141 | _background.Fill = Brush.Default; 142 | 143 | if (_glyph != null) 144 | _glyph.Opacity = 0; 145 | } 146 | 147 | RaiseCheckedChanged(); 148 | } 149 | 150 | async Task AnimateCheckedChanged() 151 | { 152 | if (_background.Parent is View parent) 153 | { 154 | await parent.ScaleTo(0.85, 100); 155 | await parent.ScaleTo(1, 100, Easing.BounceOut); 156 | } 157 | } 158 | 159 | void RaiseCheckedChanged() 160 | { 161 | var checkedChangedEventArgs = new CheckedChangedEventArgs(IsChecked); 162 | CheckedChanged?.Invoke(this, checkedChangedEventArgs); 163 | } 164 | } 165 | } -------------------------------------------------------------------------------- /src/DotNet2020/Custom Renderers/RendererCheckBox.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xamarin.Forms; 3 | 4 | namespace DotNet2020.Controls 5 | { 6 | public class RendererCheckBox : View 7 | { 8 | public static readonly BindableProperty IsCheckedProperty = 9 | BindableProperty.Create(nameof(IsChecked), typeof(bool), typeof(RendererCheckBox), false, 10 | propertyChanged: (bindable, oldValue, newValue) => 11 | { 12 | ((RendererCheckBox)bindable).CheckedChanged?.Invoke(bindable, new CheckedChangedEventArgs((bool)newValue)); 13 | }, defaultBindingMode: BindingMode.TwoWay); 14 | 15 | public static readonly BindableProperty ColorProperty = 16 | BindableProperty.Create(nameof(Color), typeof(Color), typeof(RendererCheckBox), Color.Default); 17 | 18 | public Color Color 19 | { 20 | get => (Color)GetValue(ColorProperty); 21 | set => SetValue(ColorProperty, value); 22 | } 23 | 24 | public bool IsChecked 25 | { 26 | get => (bool)GetValue(IsCheckedProperty); 27 | set => SetValue(IsCheckedProperty, value); 28 | } 29 | 30 | public event EventHandler CheckedChanged; 31 | } 32 | } -------------------------------------------------------------------------------- /src/DotNet2020/DotNet2020.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | true 6 | 7 | 8 | 9 | portable 10 | true 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/DotNet2020/SkiaSharp Controls/SkiaCheckBox.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xamarin.Forms; 3 | using SkiaSharp.Views.Forms; 4 | using SkiaSharp; 5 | using System.Threading.Tasks; 6 | 7 | namespace DotNet2020.Controls 8 | { 9 | public class SkiaCheckBox : ContentView, IDisposable 10 | { 11 | const double DefaultSize = 20.0d; 12 | const float DefaultBorderWidth = 6.0f; 13 | 14 | SKCanvasView _skiaView; 15 | readonly TapGestureRecognizer _tapGestureRecognizer; 16 | 17 | public SkiaCheckBox() 18 | { 19 | InitializeCanvas(); 20 | 21 | WidthRequest = HeightRequest = DefaultSize; 22 | HorizontalOptions = VerticalOptions = new LayoutOptions(LayoutAlignment.Start, false); 23 | Margin = new Thickness(6); 24 | 25 | Content = _skiaView; 26 | 27 | _tapGestureRecognizer = new TapGestureRecognizer(); 28 | _tapGestureRecognizer.Tapped += OnTapped; 29 | GestureRecognizers.Add(_tapGestureRecognizer); 30 | } 31 | 32 | public static BindableProperty ColorProperty = 33 | BindableProperty.Create(nameof(Color), typeof(Color), typeof(SkiaCheckBox), Color.DeepPink); 34 | 35 | public Color Color 36 | { 37 | get { return (Color)GetValue(ColorProperty); } 38 | set { SetValue(ColorProperty, value); } 39 | } 40 | 41 | public static readonly BindableProperty IsCheckedProperty = 42 | BindableProperty.Create(nameof(IsChecked), typeof(bool), typeof(SkiaCheckBox), false, BindingMode.TwoWay, 43 | propertyChanged: OnIsCheckedChanged); 44 | 45 | public bool IsChecked 46 | { 47 | get { return (bool)GetValue(IsCheckedProperty); } 48 | set { SetValue(IsCheckedProperty, value); } 49 | } 50 | 51 | static async void OnIsCheckedChanged(BindableObject bindable, object oldValue, object newValue) 52 | { 53 | if (!(bindable is SkiaCheckBox checkbox)) return; 54 | checkbox.CheckedChanged?.Invoke(checkbox, new CheckedChangedEventArgs((bool)newValue)); 55 | await checkbox.AnimateCheckedChanged(); 56 | } 57 | 58 | public event EventHandler CheckedChanged; 59 | 60 | void InitializeCanvas() 61 | { 62 | _skiaView = new SKCanvasView(); 63 | _skiaView.PaintSurface += OnPaintSurface; 64 | _skiaView.WidthRequest = _skiaView.HeightRequest = DefaultSize; 65 | } 66 | 67 | void OnPaintSurface(object sender, SKPaintSurfaceEventArgs e) 68 | { 69 | e?.Surface?.Canvas?.Clear(); 70 | 71 | DrawStroke(e); 72 | 73 | if (IsChecked) 74 | DrawFill(e); 75 | } 76 | 77 | void DrawStroke(SKPaintSurfaceEventArgs e) 78 | { 79 | var imageInfo = e.Info; 80 | var canvas = e?.Surface?.Canvas; 81 | 82 | using (var stroke = new SKPaint 83 | { 84 | Style = SKPaintStyle.Stroke, 85 | Color = Color.ToSKColor(), 86 | StrokeWidth = DefaultBorderWidth, 87 | StrokeJoin = SKStrokeJoin.Round, 88 | IsAntialias = true 89 | }) 90 | { 91 | var cornerRadius = Device.RuntimePlatform == Device.UWP ? 0 : DefaultBorderWidth; 92 | canvas.DrawRoundRect(DefaultBorderWidth, DefaultBorderWidth, imageInfo.Width - (DefaultBorderWidth * 2), imageInfo.Height - (DefaultBorderWidth * 2), cornerRadius, cornerRadius, stroke); 93 | } 94 | } 95 | 96 | void DrawFill(SKPaintSurfaceEventArgs e) 97 | { 98 | var skImageInfo = e.Info; 99 | var canvas = e?.Surface?.Canvas; 100 | 101 | using (var fill = new SKPaint 102 | { 103 | Style = SKPaintStyle.Fill, 104 | Color = Color.ToSKColor(), 105 | StrokeJoin = SKStrokeJoin.Round, 106 | IsAntialias = true 107 | }) 108 | { 109 | var cornerRadius = Device.RuntimePlatform == Device.UWP ? 0 : DefaultBorderWidth; 110 | canvas.DrawRoundRect(DefaultBorderWidth, DefaultBorderWidth, skImageInfo.Width - (DefaultBorderWidth * 2), skImageInfo.Height - (DefaultBorderWidth * 2), cornerRadius, cornerRadius, fill); 111 | } 112 | 113 | using (var checkPath = new SKPath()) 114 | { 115 | checkPath.MoveTo(.2f * skImageInfo.Width, .5f * skImageInfo.Height); 116 | checkPath.LineTo(.425f * skImageInfo.Width, .7f * skImageInfo.Height); 117 | checkPath.LineTo(.8f * skImageInfo.Width, .275f * skImageInfo.Height); 118 | 119 | using (var glyph = new SKPaint 120 | { 121 | Style = SKPaintStyle.Stroke, 122 | Color = Color.White.ToSKColor(), 123 | StrokeWidth = DefaultBorderWidth, 124 | IsAntialias = true 125 | }) 126 | { 127 | glyph.StrokeCap = SKStrokeCap.Butt; 128 | canvas.DrawPath(checkPath, glyph); 129 | } 130 | } 131 | } 132 | 133 | void OnTapped(object sender, EventArgs e) 134 | { 135 | IsChecked = !IsChecked; 136 | } 137 | 138 | async Task AnimateCheckedChanged() 139 | { 140 | await _skiaView.ScaleTo(0.85, 100); 141 | _skiaView.InvalidateSurface(); 142 | await _skiaView.ScaleTo(1, 100, Easing.BounceOut); 143 | } 144 | 145 | public void Dispose() 146 | { 147 | if (_skiaView != null) 148 | _skiaView.PaintSurface -= OnPaintSurface; 149 | 150 | if (_tapGestureRecognizer != null) 151 | _tapGestureRecognizer.Tapped -= OnTapped; 152 | 153 | GestureRecognizers.Clear(); 154 | } 155 | } 156 | } -------------------------------------------------------------------------------- /src/DotNet2020/Styles/CheckBox.xaml: -------------------------------------------------------------------------------- 1 |  2 | 7 | 8 | 55 | 56 | -------------------------------------------------------------------------------- /src/DotNet2020/Styles/CheckBox.xaml.cs: -------------------------------------------------------------------------------- 1 | using Xamarin.Forms; 2 | 3 | namespace DotNet2020.Styles 4 | { 5 | public partial class CheckBox : ResourceDictionary 6 | { 7 | public CheckBox() 8 | { 9 | InitializeComponent(); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/DotNet2020/Templated Controls/TemplatedCheckBox.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using System.Threading.Tasks; 4 | using DotNet2020.TypeConverters; 5 | using Xamarin.Forms; 6 | 7 | namespace DotNet2020.Controls 8 | { 9 | public class TemplatedCheckBox : TemplatedView 10 | { 11 | const string ElementBackground = "PART_Background"; 12 | const string ElementGlyph = "PART_Glyph"; 13 | const string ElementContent = "PART_Content"; 14 | 15 | TapGestureRecognizer _tapGestureRecognizer; 16 | Xamarin.Forms.Shapes.Shape _background; 17 | Xamarin.Forms.Shapes.Shape _glyph; 18 | 19 | public TemplatedCheckBox() 20 | { 21 | Initialize(); 22 | } 23 | 24 | void Initialize() 25 | { 26 | _tapGestureRecognizer = new TapGestureRecognizer(); 27 | 28 | UpdateIsEnabled(); 29 | } 30 | 31 | public static readonly BindableProperty ColorProperty = 32 | BindableProperty.Create(nameof(Color), typeof(Brush), typeof(TemplatedCheckBox), Brush.DeepPink); 33 | 34 | public Brush Color 35 | { 36 | get => (Brush)GetValue(ColorProperty); 37 | set => SetValue(ColorProperty, value); 38 | } 39 | 40 | public static readonly BindableProperty IsCheckedProperty = 41 | BindableProperty.Create(nameof(IsChecked), typeof(bool), typeof(TemplatedCheckBox), false, 42 | propertyChanged: OnIsCheckedChanged); 43 | 44 | public bool IsChecked 45 | { 46 | get { return (bool)GetValue(IsCheckedProperty); } 47 | set { SetValue(IsCheckedProperty, value); } 48 | } 49 | 50 | static async void OnIsCheckedChanged(BindableObject bindable, object oldValue, object newValue) 51 | { 52 | if (!(bindable is TemplatedCheckBox checkbox)) return; 53 | checkbox.CheckedChanged?.Invoke(checkbox, new CheckedChangedEventArgs((bool)newValue)); 54 | await checkbox.AnimateCheckedChanged(); 55 | } 56 | 57 | public static readonly BindableProperty ContentProperty = 58 | BindableProperty.Create(nameof(Content), typeof(View), typeof(TemplatedCheckBox), null); 59 | 60 | [TypeConverter(typeof(ContentTypeConverter))] 61 | public View Content 62 | { 63 | get => (View)GetValue(ContentProperty); 64 | set => SetValue(ContentProperty, value); 65 | } 66 | 67 | public event EventHandler CheckedChanged; 68 | 69 | protected override void OnApplyTemplate() 70 | { 71 | base.OnApplyTemplate(); 72 | 73 | _background = GetTemplateChild(ElementBackground) as Xamarin.Forms.Shapes.Shape; 74 | _glyph = GetTemplateChild(ElementGlyph) as Xamarin.Forms.Shapes.Shape; 75 | } 76 | 77 | protected override void OnPropertyChanged([CallerMemberName] string propertyName = null) 78 | { 79 | base.OnPropertyChanged(propertyName); 80 | 81 | if (propertyName == IsEnabledProperty.PropertyName) 82 | UpdateIsEnabled(); 83 | } 84 | 85 | void UpdateIsEnabled() 86 | { 87 | if (IsEnabled) 88 | { 89 | _tapGestureRecognizer.Tapped += OnCheckBoxTapped; 90 | GestureRecognizers.Add(_tapGestureRecognizer); 91 | } 92 | else 93 | { 94 | _tapGestureRecognizer.Tapped -= OnCheckBoxTapped; 95 | GestureRecognizers.Remove(_tapGestureRecognizer); 96 | } 97 | } 98 | 99 | void OnCheckBoxTapped(object sender, EventArgs e) 100 | { 101 | IsChecked = !IsChecked; 102 | 103 | if (IsChecked) 104 | { 105 | if (_background != null) 106 | _background.Fill = Color; 107 | 108 | if (_glyph != null) 109 | _glyph.Opacity = 1; 110 | } 111 | else 112 | { 113 | if (_background != null) 114 | _background.Fill = Brush.Default; 115 | 116 | if (_glyph != null) 117 | _glyph.Opacity = 0; 118 | } 119 | 120 | RaiseCheckedChanged(); 121 | } 122 | 123 | async Task AnimateCheckedChanged() 124 | { 125 | if (_background.Parent is View parent) 126 | { 127 | await parent.ScaleTo(0.85, 100); 128 | await parent.ScaleTo(1, 100, Easing.BounceOut); 129 | } 130 | } 131 | 132 | void RaiseCheckedChanged() 133 | { 134 | var checkedChangedEventArgs = new CheckedChangedEventArgs(IsChecked); 135 | CheckedChanged?.Invoke(this, checkedChangedEventArgs); 136 | } 137 | } 138 | } -------------------------------------------------------------------------------- /src/DotNet2020/TypeConverters/ContentTypeConverter.cs: -------------------------------------------------------------------------------- 1 | using Xamarin.Forms; 2 | 3 | namespace DotNet2020.TypeConverters 4 | { 5 | public class ContentTypeConverter : TypeConverter 6 | { 7 | public override object ConvertFromInvariantString(string value) 8 | { 9 | Label label = new Label 10 | { 11 | Text = value 12 | }; 13 | 14 | return label; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/DotNet2020/Views/MainView.xaml: -------------------------------------------------------------------------------- 1 |  2 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 | 21 | 28 | 39 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 |