Read symbols from mobile style

View inWPFUWPFormsiOSAndroid
View on GitHub

Combine multiple symbols from a mobile style file into a single symbol.

Image of read symbols from mobile style

Use case

You may choose to display individual elements of a dataset like a water infrastructure network (such as valves, nodes, or endpoints) with the same basic shape, but wish to modify characteristics of elements according to some technical specifications. Multilayer symbols lets you add or remove components or modify the colors to create advanced symbol styles.

How to use the sample

Select a symbol and a color from each of the category lists to create an emoji. A preview of the symbol is updated as selections are made. The size of the symbol can be set using the slider. Tap the map to create a point graphic using the customized emoji symbol, and tap "Reset" to clear all graphics from the display.

How it works

  1. On startup, read a mobile style file using SymbolStyle.OpenAsync.
  2. Get a list of all symbols in the style by calling SearchSymbolsAsync with the default search parameters.
  3. Iterate the list of SymbolStyleSearchResult and add symbols to list boxes according to their category. Display a preview of each symbol with CreateSwatchAsync.
  4. When symbol selections change, create a new multilayer symbol by passing the keys for the selected symbols into GetSymbolAsync. Color lock all symbol layers except the base layer and update the current symbol preview image.
  5. Create graphics symbolized with the current symbol when the user taps the map view.

Relevant API

  • MultilayerPointSymbol
  • MultilayerSymbol.CreateSwatchAsync
  • SymbolLayer
  • SymbolStyle
  • SymbolStyle.GetSymbolAsync
  • SymbolStyleSearchParameters

Offline Data

Emoji mobile style

About the data

The mobile style file used in this sample was created using ArcGIS Pro, and is hosted on ArcGIS Online. It contains symbol layers that can be combined to create emojis.

Additional information

While each of these symbols can be created from scratch, a more convenient workflow is to author them using ArcGIS Pro and store them in a mobile style file (.stylx). ArcGIS Runtime can read symbols from a mobile style, and you can modify and combine them as needed in your app.

Tags

advanced symbology, mobile style, multilayer, stylx

Sample Code

SymbolsFromMobileStyle.cs
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
// Copyright 2019 Esri.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
// You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
// language governing permissions and limitations under the License.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Android.App;
using Android.Graphics;
using Android.OS;
using Android.Views;
using Android.Widget;
using ArcGISRuntime.Samples.Managers;
using Esri.ArcGISRuntime.Mapping;
using Esri.ArcGISRuntime.Symbology;
using Esri.ArcGISRuntime.UI;
using Esri.ArcGISRuntime.UI.Controls;

namespace ArcGISRuntime.Samples.SymbolsFromMobileStyle
{
    [Activity(ConfigurationChanges = Android.Content.PM.ConfigChanges.Orientation | Android.Content.PM.ConfigChanges.ScreenSize)]
    [ArcGISRuntime.Samples.Shared.Attributes.Sample(
        name: "Read symbols from mobile style",
        category: "Symbology",
        description: "Combine multiple symbols from a mobile style file into a single symbol.",
        instructions: "Select a symbol and a color from each of the category lists to create an emoji. A preview of the symbol is updated as selections are made. The size of the symbol can be set using the slider. Tap the map to create a point graphic using the customized emoji symbol, and tap \"Reset\" to clear all graphics from the display.",
        tags: new[] { "advanced symbology", "mobile style", "multilayer", "stylx" })]
    [ArcGISRuntime.Samples.Shared.Attributes.OfflineData("1bd036f221f54a99abc9e46ff3511cbf")]
    public class SymbolsFromMobileStyle : Activity
    {
        // A map view to display a map and graphics.
        private MapView _myMapView;

        // A dialog for showing symbols from the stylx file.
        private SymbolDialogFragment _symbolDialog;

        // A multilayer symbol created by combining selected symbol layers from the style file.
        private MultilayerPointSymbol _faceSymbol;

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

            Title = "Read symbols from a style";

            // Create the UI.
            CreateLayout();

            // Initialize the app.
            Initialize();
        }

        private void CreateLayout()
        {
            // Create a new vertical layout for the entire page.
            LinearLayout layout = new LinearLayout(this) { Orientation = Orientation.Vertical };

            // Create a new horizontal layout for the toolbar (buttons).
            LinearLayout toolbar = new LinearLayout(this) { Orientation = Orientation.Horizontal };

            // Create a button to clear graphics from the map view, add it to the toolbar.
            Button clearButton = new Button(this) { Text = "Clear" };
            clearButton.Click += (sender, e) =>
            {
                _myMapView.GraphicsOverlays.FirstOrDefault()?.Graphics.Clear();
            };
            toolbar.AddView(clearButton);

            // Create a button to show the symbol selection dialog, add it to the toolbar.
            Button showSymbolDialog = new Button(this) { Text = "Choose symbol" };
            toolbar.AddView(showSymbolDialog);

            // Handle the button click event with a function that shows the dialog.
            showSymbolDialog.Click += ShowSymbolSelector;

            // Add the toolbar to the layout.
            layout.AddView(toolbar);

            // Add the map view to the layout.
            _myMapView = new MapView(this);
            layout.AddView(_myMapView);

            // Show the layout in the app.
            SetContentView(layout);
        }

        private void Initialize()
        {
            // Create new Map with a topographic basemap.
            Map myMap = new Map(BasemapStyle.ArcGISTopographic);

            // Display the map in the map view.
            _myMapView.Map = myMap;

            // Create an overlay to display point graphics and add it to the map view.
            GraphicsOverlay overlay = new GraphicsOverlay();
            _myMapView.GraphicsOverlays.Add(overlay);

            // Handle the tap event on the map view to allow the user to place graphics.
            _myMapView.GeoViewTapped += GeoViewTapped;

            // Get the full path to the downloaded mobile style file (.stylx).
            string mobileStyleFilePath = DataManager.GetDataFolder("1bd036f221f54a99abc9e46ff3511cbf", "emoji-mobile.stylx");

            // Create the dialog for selecting symbol layers from the style.
            _symbolDialog = new SymbolDialogFragment(mobileStyleFilePath);

            // Handle the OnSymbolComplete event on the dialog to get the symbol the user created.
            _symbolDialog.OnSymbolComplete += (sender, e) => { _faceSymbol = e.SelectedPointSymbol; };
        }

        // Handler for the map view tapped event.
        private void GeoViewTapped(object sender, GeoViewInputEventArgs e)
        {
            // Create a new graphic using the tap location and the current face symbol.
            Graphic graphic = new Graphic(e.Location, _faceSymbol);

            // Add the graphic to the first (only) graphics overlay in the map view.
            _myMapView.GraphicsOverlays.First().Graphics.Add(graphic);
        }

        // A button click handler to show the symbol dialog.
        private void ShowSymbolSelector(object sender, EventArgs e)
        {
            _symbolDialog.Show(FragmentManager, "");
        }
    }

    // A custom DialogFragment class to show symbols from a mobile style file.
    public class SymbolDialogFragment : DialogFragment
    {
        // The mobile style that contains the symbols to display.
        private SymbolStyle _emojiStyle;

        // List view controls to show categories of symbols: eyes, mouth, hat.
        private ListView _eyesList;
        private ListView _mouthList;
        private ListView _hatList;

        // Lists of a custom type (SymbolLayerInfo) to contain symbol choices for each category (eyes, mouth, hat).
        private List<SymbolLayerInfo> _eyeSymbolInfos = new List<SymbolLayerInfo> { new SymbolLayerInfo("", null, "") };
        private List<SymbolLayerInfo> _mouthSymbolInfos = new List<SymbolLayerInfo> { new SymbolLayerInfo("", null, "") };
        private List<SymbolLayerInfo> _hatSymbolInfos = new List<SymbolLayerInfo> { new SymbolLayerInfo("", null, "") };

        // The currently selected symbol layer key for each category (the key uniquely identifies symbols in the style).
        private string _selectedEyesKey = "";
        private string _selectedMouthKey = "";
        private string _selectedHatKey = "";

        // The key that identifies the base symbol (circle representing the face).
        private const string _baseSymbolKey = "Face1";

        // A variable to store the selected color for the face (defaults to yellow).
        private Color _faceColor = Color.Yellow;

        // A variable to store the desired symbol size.
        private int _symbolSize = 20;

        // An image view to show a preview of the current symbol (the selected symbol layers combined into a multilayer symbol).
        private ImageView _symbolPreviewImage;

        // An event so the listener can access the final symbol when the dialog is dismissed.
        public event EventHandler<OnSymbolCompleteEventArgs> OnSymbolComplete;

        public SymbolDialogFragment(string stylxFilepath)
        {
            // When the dialog is created, use the path to the stylx file to open the mobile style and display the symbols.
            ReadMobileStyle(stylxFilepath);
        }

        public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
        {
            base.OnCreateView(inflater, container, savedInstanceState);

            // Set a dialog title.
            Dialog.SetTitle("Define a symbol");

            // Get the context for creating the dialog controls.
            Android.Content.Context ctx = Activity.ApplicationContext;

            // The container for the dialog is a vertical linear layout.
            LinearLayout dialogView = new LinearLayout(ctx)
            {
                Orientation = Orientation.Vertical
            };
            dialogView.SetPadding(10, 0, 10, 10);

            try
            {
                // Create a list box for showing each category of symbol: eyes, mouth, hat.
                _eyesList = new ListView(ctx);
                _mouthList = new ListView(ctx);
                _hatList = new ListView(ctx);

                // Create a new list adapter to show the eye symbols in a list view.
                SymbolListAdapter eyesAdapter = new SymbolListAdapter(Activity, _eyeSymbolInfos);

                // Handle selection change events for the eyes symbol adapter.
                eyesAdapter.OnSymbolSelectionChanged += async (sender, e) =>
                {
                    // Get the key for the selected eyes (if any) then update the symbol.
                    if (eyesAdapter.SelectedSymbolInfo != null)
                    {
                        _selectedEyesKey = eyesAdapter.SelectedSymbolInfo?.Key;
                        await UpdateSymbol();
                    }

                };

                // Assign the adapter to the list view.
                _eyesList.Adapter = eyesAdapter;

                // Create a label to for the "eyes" symbol list.
                TextView eyesLabel = new TextView(ctx);
                eyesLabel.SetText("Eyes", TextView.BufferType.Normal);
                eyesLabel.Gravity = GravityFlags.Center;
                eyesLabel.TextSize = 15.0f;
                eyesLabel.SetTextColor(Android.Graphics.Color.Black);

                // Add the eyes label and list to the dialog.
                dialogView.AddView(eyesLabel);
                dialogView.AddView(_eyesList);

                // Create a new list adapter to show the mouth symbols in a list view.
                SymbolListAdapter mouthAdapter = new SymbolListAdapter(Activity, _mouthSymbolInfos);

                // Handle selection change events for the eyes symbol adapter.
                mouthAdapter.OnSymbolSelectionChanged += async (sender, e) =>
                {
                    // Get the key for the selected mouth (if any) then update the symbol.
                    if (mouthAdapter.SelectedSymbolInfo != null)
                    {
                        _selectedMouthKey = mouthAdapter.SelectedSymbolInfo?.Key;
                        await UpdateSymbol();
                    }
                };

                // Assign the adapter to the list view.
                _mouthList.Adapter = mouthAdapter;

                // Create a label to for the "mouth" symbol list.
                TextView mouthLabel = new TextView(ctx);
                mouthLabel.SetText("Mouth", TextView.BufferType.Normal);
                mouthLabel.Gravity = GravityFlags.Center;
                mouthLabel.TextSize = 15.0f;
                mouthLabel.SetTextColor(Android.Graphics.Color.Black);

                // Add the mouth label and list to the dialog.
                dialogView.AddView(mouthLabel);
                dialogView.AddView(_mouthList);

                // Create a new list adapter to show the hat symbols in a list view.
                SymbolListAdapter hatAdapter = new SymbolListAdapter(Activity, _hatSymbolInfos);

                // Handle selection change events for the hat symbol adapter.
                hatAdapter.OnSymbolSelectionChanged += async (sender, e) =>
                {
                    // Get the key for the selected hat (if any) then update the symbol.
                    if (hatAdapter.SelectedSymbolInfo != null)
                    {
                        _selectedHatKey = hatAdapter.SelectedSymbolInfo?.Key;
                        await UpdateSymbol();
                    }
                };

                // Assign the adapter to the list view.
                _hatList.Adapter = hatAdapter;

                // Create a label to for the "hat" symbol list.
                TextView hatLabel = new TextView(ctx);
                hatLabel.SetText("Hat", TextView.BufferType.Normal);
                hatLabel.Gravity = GravityFlags.Center;
                hatLabel.TextSize = 15.0f;
                hatLabel.SetTextColor(Android.Graphics.Color.Black);

                // Add the hat label and list to the dialog.
                dialogView.AddView(hatLabel);
                dialogView.AddView(_hatList);

                // Add a preview image for the symbol.
                _symbolPreviewImage = new ImageView(ctx);
                dialogView.AddView(_symbolPreviewImage);

                // A button to set a yellow symbol color.
                Button yellowButton = new Button(ctx);
                yellowButton.SetBackgroundColor(Color.Yellow);

                // Handle the button click event to set the current color and update the symbol.
                yellowButton.Click += async (sender, e) =>
                {
                    _faceColor = Color.Yellow; await UpdateSymbol();
                };

                // A button to set a green symbol color.
                Button greenButton = new Button(ctx);
                greenButton.SetBackgroundColor(Color.Green);

                // Handle the button click event to set the current color and update the symbol.
                greenButton.Click += async (sender, e) =>
                {
                    _faceColor = Color.Green; await UpdateSymbol();
                };

                // A button to set a pink symbol color.
                Button pinkButton = new Button(ctx);
                pinkButton.SetBackgroundColor(Color.Pink);

                // Handle the button click event to set the current color and update the symbol.
                pinkButton.Click += async (sender, e) =>
                {
                    _faceColor = Color.Pink; await UpdateSymbol();
                };

                // Add the color selection buttons to a horizontal layout.
                LinearLayout colorLayout = new LinearLayout(ctx)
                {
                    Orientation = Orientation.Horizontal
                };
                colorLayout.SetPadding(10, 20, 10, 20);
                colorLayout.AddView(yellowButton);
                colorLayout.AddView(greenButton);
                colorLayout.AddView(pinkButton);

                // Add the color selection buttons to the dialog.
                dialogView.AddView(colorLayout);

                // Create a slider (SeekBar) to adjust the symbol size.
                SeekBar sizeSlider = new SeekBar(ctx)
                {
                    // Set a maximum slider value of 60 and a current value of 20.
                    Max = 60,
                    Progress = 20
                };

                // Create a label to show the selected symbol size (preview size won't change).
                TextView sizeLabel = new TextView(ctx)
                {
                    Gravity = GravityFlags.Right,
                    TextSize = 10.0f,
                    Text = $"Size:{_symbolSize:0}"
                };
                sizeLabel.SetTextColor(Color.Black);

                // When the slider value (Progress) changes, update the symbol with the new size.
                sizeSlider.ProgressChanged += (s, e) =>
                {
                    // Read the slider value and limit the minimum size to 8.
                    _symbolSize = (e.Progress < 8) ? 8 : e.Progress;

                    // Show the selected size in the label.
                    sizeLabel.Text = $"Size:{_symbolSize:0}";
                };

                // Create a horizontal layout to show the slider and label.
                LinearLayout sliderLayout = new LinearLayout(ctx)
                {
                    Orientation = Orientation.Horizontal
                };
                sliderLayout.SetPadding(10, 10, 10, 10);
                sizeSlider.LayoutParameters = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.MatchParent, 1.0f);

                // Add the size slider and label to the layout.
                sliderLayout.AddView(sizeSlider);
                sliderLayout.AddView(sizeLabel);

                // Add the size controls to the dialog layout.
                dialogView.AddView(sliderLayout);

                // Add a button to close the dialog and use the current symbol.
                Button selectSymbolButton = new Button(ctx)
                {
                    Text = "OK"
                };

                // Handle the button click to dismiss the dialog and pass back the symbol.
                selectSymbolButton.Click += SelectSymbolButtonClick;

                // Add the button to the dialog.
                dialogView.AddView(selectSymbolButton);
            }
            catch (Exception ex)
            {
                // Show the exception message.
                AlertDialog.Builder alertBuilder = new AlertDialog.Builder(Activity);
                alertBuilder.SetTitle("Error");
                alertBuilder.SetMessage(ex.Message);
                alertBuilder.Show();
            }

            // Show a preview of the current symbol (created previously or the default face).
            UpdateSymbol();

            // Return the new view for display.
            return dialogView;
        }

        private async void ReadMobileStyle(string mobileStyleFilePath)
        {
            try
            {
                // Make sure the file exists.
                if (!System.IO.File.Exists(mobileStyleFilePath))
                {
                    throw new System.IO.FileNotFoundException("Mobile style file not found at " + mobileStyleFilePath);
                }

                // Open the mobile style file at the path provided.
                _emojiStyle = await SymbolStyle.OpenAsync(mobileStyleFilePath);

                // Get the default style search parameters.
                SymbolStyleSearchParameters searchParams = await _emojiStyle.GetDefaultSearchParametersAsync();

                // Search the style with the default parameters to return a list of all symbol results.
                IList<SymbolStyleSearchResult> styleResults = await _emojiStyle.SearchSymbolsAsync(searchParams);

                // Loop through the results and put symbols into the appropriate list according to category (eyes, mouth, hat).
                foreach (SymbolStyleSearchResult result in styleResults)
                {
                    // Get the result symbol as a multilayer point symbol.
                    MultilayerPointSymbol multiLayerSym = await result.GetSymbolAsync() as MultilayerPointSymbol;

                    // Create a swatch for the symbol and use it to create a bitmap image.
                    RuntimeImage swatch = await multiLayerSym.CreateSwatchAsync();
                    Bitmap symbolImage = await swatch.ToImageSourceAsync();

                    // Check the symbol category.
                    switch (result.Category)
                    {
                        // Add a new SymbolLayerInfo to represent the symbol and add it to its category list.
                        // SymbolLayerInfo is a custom class with properties for the symbol name, swatch image, and unique key.
                        case "Eyes":
                            {
                                _eyeSymbolInfos.Add(new SymbolLayerInfo(result.Name, symbolImage, result.Key));
                                break;
                            }
                        case "Mouth":
                            {
                                _mouthSymbolInfos.Add(new SymbolLayerInfo(result.Name, symbolImage, result.Key));
                                break;
                            }
                        case "Hat":
                            {
                                _hatSymbolInfos.Add(new SymbolLayerInfo(result.Name, symbolImage, result.Key));
                                break;
                            }
                        case "Face":
                            {
                                break;
                            }
                    }
                }
            }
            catch (Exception ex)
            {
                // Show the exception message.
                AlertDialog.Builder alertBuilder = new AlertDialog.Builder(Activity);
                alertBuilder.SetTitle("Error reading style");
                alertBuilder.SetMessage(ex.Message);
                alertBuilder.Show();
            }
        }

        // A function to get a multilayer point symbol that contains all selected symbol layers.
        private async Task<MultilayerPointSymbol> GetCurrentSymbol()
        {
            MultilayerPointSymbol faceSymbol = null;

            try
            {
                // Create a new list of strings with the key for the base (face) symbol and any additional selected layers.
                List<string> symbolKeys = new List<string>
                {
                    _baseSymbolKey, _selectedEyesKey, _selectedMouthKey, _selectedHatKey
                };

                // Use the keys to get the symbol layers as a multilayer symbol.
                faceSymbol = await _emojiStyle.GetSymbolAsync(symbolKeys) as MultilayerPointSymbol;

                // Loop through all symbol layers and lock the color.
                foreach (SymbolLayer lyr in faceSymbol.SymbolLayers)
                {
                    // Changing the color of the symbol will not affect this layer.
                    lyr.IsColorLocked = true;
                }

                // Unlock the color for the base (first) layer. Changing the symbol color will change this layer's color.
                faceSymbol.SymbolLayers.First().IsColorLocked = false;

                // Set the symbol color using the last selected color.
                System.Drawing.Color symbolColor = System.Drawing.Color.FromArgb(_faceColor.A, _faceColor.R, _faceColor.G, _faceColor.B);
                faceSymbol.Color = symbolColor;

                // Set the symbol size from the slider.
                faceSymbol.Size = _symbolSize;
            }
            catch (Exception ex)
            {
                // Show the exception message.
                AlertDialog.Builder alertBuilder = new AlertDialog.Builder(Activity);
                alertBuilder.SetTitle("Error creating symbol");
                alertBuilder.SetMessage(ex.Message);
                alertBuilder.Show();
            }

            // Return the constructed symbol.
            return faceSymbol;
        }

        // Get the current symbol and update the preview image.
        private async Task UpdateSymbol()
        {
            // Call a function to get the currrent multilayer point symbol from the selected layers.
            MultilayerPointSymbol emojiSymbol = await GetCurrentSymbol();

            try
            {
                // Use a swatch from the symbol to create an image.
                RuntimeImage swatch = await emojiSymbol.CreateSwatchAsync(100, 100, 96, System.Drawing.Color.White);
                Bitmap symbolImage = await swatch.ToImageSourceAsync();

                // Display the preview image.
                _symbolPreviewImage.SetImageBitmap(symbolImage);
            }
            catch (Exception ex)
            {
                // Show the exception message.
                AlertDialog.Builder alertBuilder = new AlertDialog.Builder(Activity);
                alertBuilder.SetTitle("Error creating preview");
                alertBuilder.SetMessage(ex.Message);
                alertBuilder.Show();
            }
        }

        // A click handler for the button that dismisses the dialog.
        private async void SelectSymbolButtonClick(object sender, EventArgs e)
        {
            try
            {
                // Get the current symbol.
                MultilayerPointSymbol selectedSymbol = await GetCurrentSymbol();

                // Create a new OnSymbolCompleteEventArgs object to store the selected symbol.
                OnSymbolCompleteEventArgs symbolSelectedArgs = new OnSymbolCompleteEventArgs(selectedSymbol);

                // Raise the OnSaveClicked event so the main activity can handle the event and use the symbol.
                OnSymbolComplete?.Invoke(this, symbolSelectedArgs);

                // Close the dialog
                Dismiss();
            }
            catch (Exception ex)
            {
                // Show the exception message (dialog will stay open so user can try again)
                AlertDialog.Builder alertBuilder = new AlertDialog.Builder(Activity);
                alertBuilder.SetTitle("Error");
                alertBuilder.SetMessage(ex.Message);
                alertBuilder.Show();
            }
        }
    }

    // A custom EventArgs object to store the multilayer point symbol.
    public class OnSymbolCompleteEventArgs
    {
        // A property that stores the multilayer point symbol composed of the selected style symbol layers.
        public MultilayerPointSymbol SelectedPointSymbol { get; private set; }

        // Pass the multilayer point symbol to the constructor.
        public OnSymbolCompleteEventArgs(MultilayerPointSymbol symbol)
        {
            SelectedPointSymbol = symbol;
        }
    }

    // A class that defines a custom list adapter to show symbol layers.
    public class SymbolListAdapter : BaseAdapter<SymbolLayerInfo>
    {
        // Store the current activity.
        private readonly Activity _ctx;

        // List of symbol layer infos.
        private readonly List<SymbolLayerInfo> _symbolLayerInfos;

        // The selected symbol layer info.
        public SymbolLayerInfo SelectedSymbolInfo { get; set; }

        // An event that notifies when the selected symbol has changed.
        public event EventHandler<EventArgs> OnSymbolSelectionChanged;

        // Constructor that takes the current activity and a list of symbol infos.
        public SymbolListAdapter(Activity context, List<SymbolLayerInfo> symbolInfos) : base()
        {
            _ctx = context;
            _symbolLayerInfos = symbolInfos;
        }

        // Get the symbol info at the specified position in the list.
        public override SymbolLayerInfo this[int position]
        {
            get
            {
                return _symbolLayerInfos.ElementAt(position);
            }
        }

        // Return the count of symbol infos in the list.
        public override int Count
        {
            get
            {
                return _symbolLayerInfos.Count;
            }
        }

        // Get an ID for the item at the specified position.
        public override long GetItemId(int position)
        {
            return position;
        }

        // Create a view to display an item in the list (SymbolLayerInfo).
        public override View GetView(int position, View convertView, ViewGroup parent)
        {
            // Create an ActivityListItem, since it contains an image to display the symbol swatch.
            View listItem = _ctx.LayoutInflater.Inflate(Android.Resource.Layout.ActivityListItem, null);

            // Display a symbol swatch if one is available, otherwise display the text "None".
            if (_symbolLayerInfos.ElementAt(position).Image != null)
            {
                // Set the list item icon with the symbol swatch image.
                listItem.FindViewById<ImageView>(Android.Resource.Id.Icon).SetImageBitmap(_symbolLayerInfos.ElementAt(position).Image);
            }
            else
            {
                // Display the text "None". Choosing this item will clear the symbol layer for that category.
                listItem.FindViewById<TextView>(Android.Resource.Id.Text1).SetText("None", TextView.BufferType.Normal);
            }

            // Handle the list item click event.
            listItem.Click += (sender, e) =>
            {
                // Get the clicked symbol layer info.
                SelectedSymbolInfo = _symbolLayerInfos.ElementAt(position);

                // Fire an event to notify clients that a symbol layer selection has changed.
                OnSymbolSelectionChanged?.Invoke(this, new EventArgs());
            };

            return listItem;
        }
    }

    // A class to contain information about a symbol from a style.
    public class SymbolLayerInfo
    {
        // A bitmap preview of the symbol.
        public Bitmap Image;

        // The name of the symbol in the style.
        public string Name;

        // A key that uniquely identifies the symbol in the style.
        public string Key;

        // Take the symbol info property values in the constructor.
        public SymbolLayerInfo(string name, Bitmap image, string key)
        {
            Name = name;
            Image = image;
            Key = key;
        }
    }
}

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.