当前位置: 首页 > news >正文

Avalonia:开发Android应用

我把成功开发Android应用的经过记录下来,在开发过程中,模拟器经常出问题,将Java Development Kit的位置和Android SDK的位置改动一下,就解决了模拟器报错的问题,这是在Github上看到的解决办法。
先建Models文件夹,创建模型ColorItem.cs文件。

using Avalonia.Media;namespace AvaloniaMobileApp.Models
{public class ColorItem{public Color? Color { get; set; }public string? ColorName {  get; set; }}
}

再创建ColorItemMessage记录,用于在viewmodel间传递参数。

namespace AvaloniaMobileApp.Models
{public  record ColorItemMessage(string Sender,ColorItem Item);    }

先前用ReactiveUI写移动应用,结果闪退了,换用Communitytoolkit.mvvm社区工具,所以将ViewModelBase.cs继承 ObservableRecipient。

using CommunityToolkit.Mvvm.ComponentModel;namespace AvaloniaMobileApp.ViewModels;public abstract class ViewModelBase : ObservableRecipient
{
}

在ViewModels文件夹下先创建要展示的页面ViewModel,ColorsViewModel.cs,AboutViewModel.cs,PalletteViewModel.cs。
ColorsViewModel.cs

using Avalonia.Data.Converters;
using Avalonia.Media;
using AvaloniaMobileApp.Models;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using System;
using System.Collections.ObjectModel;
using System.Reflection;
using System.Threading.Tasks;namespace AvaloniaMobileApp.ViewModels
{public partial class ColorsViewModel : ViewModelBase{public ObservableCollection<ColorItem> Colors { get; }public ColorsViewModel(){Colors = [];}[ObservableProperty]private ColorItem _selectedColorItem = new();partial void OnSelectedColorItemChanged(ColorItem value){IsActive = true;WeakReferenceMessenger.Default.Send(new ColorItemMessage("colors", value));}[RelayCommand]private Task Init(){if (Colors.Count > 0){return Task.CompletedTask;}var properties = typeof(Colors).GetProperties(BindingFlags.Public | BindingFlags.Static);foreach (var property in properties){if (property.GetValue(null) is Color color){Colors.Add(new ColorItem{Color = color,ColorName = property.Name});}}return Task.CompletedTask;}public static FuncValueConverter<Color, string> ColorToHex => new(color =>{return $"#{color.R:X2}{color.G:X2}{color.B:X2}";});public static FuncValueConverter<Color, string> ColorToCMYK => new(value =>{if (value is Color color){double r = color.R / 255.0;double g = color.G / 255.0;double b = color.B / 255.0;double k = 1 - Math.Max(Math.Max(r, g), b);double c = k < 1 ? (1 - r - k) / (1 - k) : 0;double m = k < 1 ? (1 - g - k) / (1 - k) : 0;double y = k < 1 ? (1 - b - k) / (1 - k) : 0;return $"CMYK = ({Math.Round(c*100,1)}% {Math.Round(m*100,1)}% {Math.Round(y*100,1)}% {Math.Round(k*100,1)}%)";}else{return "";}});}
}

AboutViewModel.cs

using System.Reflection;namespace AvaloniaMobileApp.ViewModels
{public class AboutViewModel : ViewModelBase{public string? AppName => Assembly.GetExecutingAssembly().GetName().Name;public string? Version => Assembly.GetExecutingAssembly().GetName().Version!.ToString();public string? Message => $"该应用使用 Avalonia框架,Avalonia 是跨平台的优秀框架,这是 Android App,使用 Avalonia 基础导航功能,应用 CommunityToolKit.Mvvm 工具开发";}
}

PalletteViewModel.cs

using Avalonia.Media;
using AvaloniaMobileApp.Models;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;namespace AvaloniaMobileApp.ViewModels
{public partial class PalletteViewModel : ViewModelBase, IRecipient<ColorItemMessage>{[ObservableProperty]private Color? _colorType;[ObservableProperty]private ColorItem? _colorItem;public void Receive(ColorItemMessage message){            if(message.Sender == "main"){ColorType = message.Item.Color;ColorItem = message.Item;Red = message.Item.Color!.Value.R;Green = message.Item.Color.Value.G;Blue = message.Item.Color.Value.B;}}public PalletteViewModel(){IsActive = true;}[ObservableProperty]private byte red;[ObservableProperty]private byte green;[ObservableProperty]private byte blue;private void UpdateColorType(){ColorType = Color.FromRgb((byte)Red, (byte)Green, (byte)Blue);}partial void OnRedChanged(byte value) => UpdateColorType(); partial void OnGreenChanged(byte value) => UpdateColorType();partial void OnBlueChanged(byte value) => UpdateColorType();}
}

更改MainViewModel.cs

using AvaloniaMobileApp.Models;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Extensions.DependencyInjection;
using System.Threading.Tasks;namespace AvaloniaMobileApp.ViewModels;public partial class MainViewModel : ViewModelBase, IRecipient<ColorItemMessage>
{[ObservableProperty]   private ViewModelBase? _currentPage;[ObservableProperty]private ColorItem _selectedColorItem = new();public MainViewModel(){CurrentPage = App.Current.Services?.GetService<ColorsViewModel>();IsActive = true;}public void Receive(ColorItemMessage message){if(message.Sender == "colors"){SelectedColorItem = message.Item;GotoPalletteCommand.Execute(null);}      }[RelayCommand]private Task GotoAbout(){if(CurrentPage is not AboutViewModel){CurrentPage = App.Current.Services?.GetService<AboutViewModel>();}return Task.CompletedTask;}[RelayCommand]private Task GotoHome(){if(CurrentPage is not ColorsViewModel){CurrentPage = App.Current.Services?.GetService<ColorsViewModel>();}return Task.CompletedTask;}[RelayCommand]private Task GotoPallette(){if (CurrentPage is not PalletteViewModel){CurrentPage = App.Current.Services!.GetService<PalletteViewModel>();WeakReferenceMessenger.Default.Send(new ColorItemMessage("main", SelectedColorItem));}return Task.CompletedTask;}
}

创建要展示的Views,ColorsView.axaml。

<UserControl xmlns="https://github.com/avaloniaui"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:vm="using:AvaloniaMobileApp.ViewModels"xmlns:models="using:AvaloniaMobileApp.Models"xmlns:b="using:AvaloniaMobileApp.Behaviors"x:DataType="vm:ColorsViewModel"b:LoadedBehavior.ExecuteCommand="{Binding InitCommand}"mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"x:Class="AvaloniaMobileApp.Views.ColorsView">	<Grid RowDefinitions="Auto,*,auto"><TextBlock Text="{Binding Colors.Count,StringFormat='Avalonia.Media Colors = {0}'}" Grid.Row="0"/><ListBox x:Name="ColorsListBox" Grid.Row="1" ItemsSource="{Binding Colors}" SelectedItem="{Binding SelectedColorItem}"><ListBox.ItemTemplate><DataTemplate x:DataType="models:ColorItem"><StackPanel Orientation="Horizontal"><Rectangle Height="80" Width="80"><Rectangle.Fill><SolidColorBrush Color="{Binding Color}"/></Rectangle.Fill></Rectangle><StackPanel><TextBlock Text="{Binding ColorName}"/><TextBlock Text="{Binding Color,Converter={x:Static vm:ColorsViewModel.ColorToHex}}"/><TextBlock Text="{Binding Color,Converter={x:Static vm:ColorsViewModel.ColorToCMYK}}"/></StackPanel></StackPanel></DataTemplate></ListBox.ItemTemplate></ListBox>	  </Grid>
</UserControl>

由于在代码中应用了这二行代码,导致xaml设计器报错。

<TextBlock Text="{Binding Color,Converter={x:Static vm:ColorsViewModel.ColorToHex}}"/>
<TextBlock Text="{Binding Color,Converter={x:Static vm:ColorsViewModel.ColorToCMYK}}"/>

如果不介意的话,可以忽略,不影响运行,介意就改成IValueConverter。
AboutView.axaml

<UserControl xmlns="https://github.com/avaloniaui"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:vm="using:AvaloniaMobileApp.ViewModels"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"x:DataType="vm:AboutViewModel"x:Class="AvaloniaMobileApp.Views.AboutView"><Grid RowDefinitions="Auto,Auto"><StackPanel Orientation="Horizontal" Spacing="5" Grid.Row="0"><TextBlock x:Name="AppNameTextBlock" Text="{Binding AppName}" FontSize="18" FontWeight="Bold"/><TextBlock x:Name="VersionTextBlock" FontSize="16" Text="{Binding Version}"/></StackPanel><TextBlock x:Name="MessageTextBlock" Grid.Row="1" TextWrapping="Wrap" Text="{Binding Message}"/></Grid>
</UserControl>

Pallette.axaml

<UserControl xmlns="https://github.com/avaloniaui"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"xmlns:vm="using:AvaloniaMobileApp.ViewModels"x:DataType="vm:PalletteViewModel"xmlns:conv="using:AvaloniaMobileApp.Converter"x:Class="AvaloniaMobileApp.Views.PalletteView"><Grid RowDefinitions="Auto,Auto,Auto"><TextBlock Text="{Binding ColorItem.ColorName,StringFormat='传过来的颜色名: {0}'}" Grid.Row="0"/><Border Grid.Row="1" Background="White" Margin="5"><Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Height="200"><Rectangle.Fill><SolidColorBrush Color="{Binding ColorType}"/></Rectangle.Fill></Rectangle></Border><UniformGrid Columns="6" Grid.Row="2"><TextBlock Text="Red:" Grid.Column="0" HorizontalAlignment="Right"/><TextBox Watermark="16进制值" Text="{Binding Red,Converter={x:Static conv:ByteToString.Instance},UpdateSourceTrigger=LostFocus}"  Grid.Column="1"/><TextBlock Text="Green:" Grid.Column="2" HorizontalAlignment="Right"/><TextBox Watermark="16进制值" Text="{Binding Green,Converter={x:Static conv:ByteToString.Instance},UpdateSourceTrigger=LostFocus}" Grid.Column="3"/><TextBlock Text="Blue:" Grid.Column="4" HorizontalAlignment="Right"/><TextBox Watermark="16进制值" Text="{Binding Blue,Converter={x:Static conv:ByteToString.Instance},UpdateSourceTrigger=LostFocus}" Grid.Column="5"/></UniformGrid></Grid>
</UserControl>

MainView.axaml

<UserControl xmlns="https://github.com/avaloniaui"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:vm="clr-namespace:AvaloniaMobileApp.ViewModels"mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"x:Class="AvaloniaMobileApp.Views.MainView"x:DataType="vm:MainViewModel"><Design.DataContext><!-- This only sets the DataContext for the previewer in an IDE,to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) --><vm:MainViewModel /></Design.DataContext><Border x:Name="MainBorder"><Grid RowDefinitions="Auto,*,Auto"><Grid ColumnDefinitions="*,Auto" Grid.Row="0"><TextBlock Text="Colors View" x:Name="TitleTextBlock" Grid.Column="0"/><StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="10"><Button Command="{Binding GotoHomeCommand}" Content="&lt;&#8211;" FontSize="18"/><Button Command="{Binding GotoAboutCommand}" Content="About"/></StackPanel></Grid>			<TransitioningContentControl Grid.Row="1" Content="{Binding CurrentPage}"><TransitioningContentControl.PageTransition><CrossFade Duration="0:0:0.500"/></TransitioningContentControl.PageTransition></TransitioningContentControl>			</Grid></Border>
</UserControl>

MainWindow.axaml文件不用动。
由于不能用ReactiveUI,就得写附加属性,将命令绑定到事件。

using Avalonia;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Interactivity;
using System.Windows.Input;namespace AvaloniaMobileApp.Behaviors
{public class LoadedBehavior : AvaloniaObject{static LoadedBehavior(){ExecuteCommandProperty.Changed.AddClassHandler<Interactive>(OnExecuteCommandChanged);}private static void OnExecuteCommandChanged(Interactive interactive, AvaloniaPropertyChangedEventArgs args){if (args.NewValue is ICommand command){interactive.AddHandler(Control.LoadedEvent, Handler);}else{interactive.RemoveHandler(Control.LoadedEvent, Handler);}}private static void Handler(object? sender, RoutedEventArgs e){if (sender is Interactive interactive){var command = interactive.GetValue(ExecuteCommandProperty);if (command?.CanExecute(null) == true){command.Execute(null);}}}public static readonly AttachedProperty<ICommand> ExecuteCommandProperty = AvaloniaProperty.RegisterAttached<LoadedBehavior, Interactive, ICommand>("ExecuteCommand", default, false, BindingMode.OneWay);public static ICommand GetExecuteCommand(AvaloniaObject obj){return obj.GetValue(ExecuteCommandProperty);}public static void SetExecuteCommand(AvaloniaObject obj, ICommand value){obj.SetValue(ExecuteCommandProperty, value);}}
}

在Converter文件夹下创建ByteToString.cs,将byte转换成string。

using Avalonia.Data;
using Avalonia.Data.Converters;
using System;
using System.Globalization;namespace AvaloniaMobileApp.Converter
{public class ByteToString : IValueConverter{public static readonly ByteToString Instance = new();public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture){if (value is byte b && targetType.IsAssignableTo(typeof(string))){return b.ToString("X2");}return new BindingNotification(new InvalidCastException(), BindingErrorType.Error);}public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture){if(value is string str && targetType.IsAssignableTo(typeof(byte))){if(byte.TryParse(str,NumberStyles.HexNumber,CultureInfo.InvariantCulture,out var b)){return b;}return byte.MinValue;}return new BindingNotification(new InvalidCastException(), BindingErrorType.Error);}}
}

在App.axaml中编写样式。

<Application xmlns="https://github.com/avaloniaui"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:local="using:AvaloniaMobileApp"x:Class="AvaloniaMobileApp.App"RequestedThemeVariant="Default"><!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. --><Application.DataTemplates><local:ViewLocator/></Application.DataTemplates><Application.Resources><SolidColorBrush x:Key="PrimaryBackground">#4A598F</SolidColorBrush><SolidColorBrush x:Key="PrimaryForeground">#E8EFF7</SolidColorBrush>		</Application.Resources><Application.Styles><FluentTheme /><!--style--><Style Selector="Border#MainBorder"><Setter Property="Background" Value="{StaticResource PrimaryBackground}"/></Style><Style Selector="Border"><Setter Property="Padding" Value="10"/></Style><Style Selector="TextBlock"><Setter Property="Foreground" Value="{StaticResource PrimaryForeground}"/><Setter Property="Margin" Value="5"/><Setter Property="VerticalAlignment" Value="Center"/></Style><Style Selector="ListBox"><Setter Property="Margin" Value="5"/></Style><Style Selector="ListBox TextBlock"><Setter Property="Foreground" Value="Black"/></Style><Style Selector="TextBlock#TitleTextBlock"><Setter Property="FontSize" Value="18"/><Setter Property="FontWeight" Value="Bold"/></Style><Style Selector="Button"><Setter Property="Background" Value="Transparent"/><Setter Property="VerticalAlignment" Value="Center"/><Setter Property="Padding" Value="5"/></Style>		<Style Selector="Button /template/ ContentPresenter"><Setter Property="Foreground" Value="{StaticResource PrimaryForeground}"/></Style><Style Selector="Rectangle"><Setter Property="Margin" Value="5"/></Style></Application.Styles>
</Application>

在App.axaml.cs文件中使用ioc。

using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Data.Core.Plugins;
using Avalonia.Markup.Xaml;
using AvaloniaMobileApp.ViewModels;
using AvaloniaMobileApp.Views;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Linq;namespace AvaloniaMobileApp;public partial class App : Application
{public override void Initialize(){AvaloniaXamlLoader.Load(this);}public IServiceProvider? Services { get; private set; }public new static App Current => (App)Application.Current!;private IServiceProvider ConfigureServices(){var services = new ServiceCollection();services.AddTransient<AboutViewModel>();services.AddTransient<ColorsViewModel>();services.AddTransient<PalletteViewModel>();return services.BuildServiceProvider();}public override void OnFrameworkInitializationCompleted(){BindingPlugins.DataValidators.RemoveAt(0);Services = ConfigureServices();if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop){// Avoid duplicate validations from both Avalonia and the CommunityToolkit. // More info: https://docs.avaloniaui.net/docs/guides/development-guides/data-validation#manage-validationpluginsDisableAvaloniaDataAnnotationValidation();desktop.MainWindow = new MainWindow{DataContext = new MainViewModel()};}else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform){singleViewPlatform.MainView = new MainView{DataContext = new MainViewModel()};}base.OnFrameworkInitializationCompleted();}private void DisableAvaloniaDataAnnotationValidation(){// Get an array of plugins to removevar dataValidationPluginsToRemove =BindingPlugins.DataValidators.OfType<DataAnnotationsValidationPlugin>().ToArray();// remove each entry foundforeach (var plugin in dataValidationPluginsToRemove){BindingPlugins.DataValidators.Remove(plugin);}}
}

将项目设为Android启动,存档,分发。
在我的荣耀手机Magic5 pro上成功运行。
效果展示。

Screenshot_20250925_211533_com_CompanyName_AvaloniaMobileApp_MainActivity

Screenshot_20250925_211547_com_CompanyName_AvaloniaMobileApp_MainActivity

Screenshot_20250925_211556_com_CompanyName_AvaloniaMobileApp_MainActivity

http://www.hskmm.com/?act=detail&tid=17362

相关文章:

  • MIT s6.828环境搭建
  • kubernetes事件监控工具--Kube-Event
  • 实用指南:【 GUI自动化测试】GUI自动化测试(一) 环境安装与测试
  • 喵喵大王の新日记
  • 多GPU本地布署Wan2.2-T2V-A14B文本转视频模型 - yi
  • NOI 模拟赛五
  • 什么是Delphi4Python?
  • 实用指南:Python的大杀器:Jupyter Notebook处理.ipynb文件
  • flask认证机制logging模块实战
  • 25.9.25随笔联考总结
  • 软工9.25
  • 2025/9/25 模拟赛总结
  • 代码随想录算法训练营第九天 |151.翻转字符串里的单词、 LCR 182. 动态口令、28. 实现 strStr()、459.重复的子字符串
  • 当日总结(课后作业2)
  • Codeforces Global Round 29 (Div. 1 + Div. 2) A~E
  • AI 低代码平台:不止于 “快”,解码技术融合的深层逻辑
  • 实用指南:【知识拓展Trip Five】寄存器
  • 计算机视觉(opencv)实战二十七——目标跟踪 - 教程
  • P8367 [LNOI2022] 盒
  • 蓝桥杯 2025 省 B 题:画展布置 - 题解笔记
  • 二维坐标下的运算
  • Polar2025秋季个人挑战赛web-writeup
  • Day4
  • 题解:P12751 [POI 2017 R2] 集装箱 Shipping containers
  • 弱网配置
  • 绘制金融集团监控大屏的地图demo
  • 实用指南:《原神助手》开源神器:游戏体验大升级
  • 9-25
  • AT_agc021_d [AGC021D] Reversed LCS
  • adb shell 常用文件命令