13 changed files with 402 additions and 9 deletions
@ -0,0 +1,12 @@ |
|||||
|
using Android.App; |
||||
|
using Android.Content.PM; |
||||
|
|
||||
|
using Avalonia; |
||||
|
using Avalonia.Android; |
||||
|
|
||||
|
namespace NativeEmbedSample.Android; |
||||
|
|
||||
|
[Activity(Label = "NativeEmbedSample", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleInstance, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)] |
||||
|
public class MainActivity : AvaloniaActivity<App> |
||||
|
{ |
||||
|
} |
||||
@ -0,0 +1,50 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net6.0-android</TargetFramework> |
||||
|
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion> |
||||
|
<OutputType>Exe</OutputType> |
||||
|
<Nullable>enable</Nullable> |
||||
|
<ApplicationId>com.Avalonia.NativeEmbedSample</ApplicationId> |
||||
|
<ApplicationVersion>1</ApplicationVersion> |
||||
|
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion> |
||||
|
<AndroidPackageFormat>apk</AndroidPackageFormat> |
||||
|
<MSBuildEnableWorkloadResolver>true</MSBuildEnableWorkloadResolver> |
||||
|
</PropertyGroup> |
||||
|
<ItemGroup> |
||||
|
<None Remove="Assets\AboutAssets.txt" /> |
||||
|
</ItemGroup> |
||||
|
<ItemGroup> |
||||
|
<AndroidResource Include="..\..\..\build\Assets\Icon.png"> |
||||
|
<Link>Resources\drawable\Icon.png</Link> |
||||
|
</AndroidResource> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<PropertyGroup Condition="'$(Configuration)'=='Release' and '$(TF_BUILD)' == ''"> |
||||
|
<DebugSymbols>True</DebugSymbols> |
||||
|
<RunAOTCompilation>True</RunAOTCompilation> |
||||
|
<EnableLLVM>True</EnableLLVM> |
||||
|
<AndroidEnableProfiledAot>True</AndroidEnableProfiledAot> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<PropertyGroup Condition="'$(Configuration)'=='Debug'"> |
||||
|
<EmbedAssembliesIntoApk>False</EmbedAssembliesIntoApk> |
||||
|
<RunAOTCompilation>False</RunAOTCompilation> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<PropertyGroup Condition="'$(Configuration)'=='Release'"> |
||||
|
<EmbedAssembliesIntoApk>True</EmbedAssembliesIntoApk> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.3.1.3" /> |
||||
|
<PackageReference Include="Xamarin.AndroidX.Lifecycle.ViewModel" Version="2.3.1.3" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\..\src\Android\Avalonia.Android\Avalonia.Android.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" /> |
||||
|
<ProjectReference Include="..\NativeEmbedSample\NativeEmbedSample.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<Import Project="..\..\..\build\BuildTargets.targets" /> |
||||
|
</Project> |
||||
@ -0,0 +1,4 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="auto"> |
||||
|
<application android:label="ControlCatalog.Android" android:icon="@drawable/Icon"></application> |
||||
|
</manifest> |
||||
@ -0,0 +1,44 @@ |
|||||
|
Images, layout descriptions, binary blobs and string dictionaries can be included |
||||
|
in your application as resource files. Various Android APIs are designed to |
||||
|
operate on the resource IDs instead of dealing with images, strings or binary blobs |
||||
|
directly. |
||||
|
|
||||
|
For example, a sample Android app that contains a user interface layout (main.axml), |
||||
|
an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png) |
||||
|
would keep its resources in the "Resources" directory of the application: |
||||
|
|
||||
|
Resources/ |
||||
|
drawable/ |
||||
|
icon.png |
||||
|
|
||||
|
layout/ |
||||
|
main.axml |
||||
|
|
||||
|
values/ |
||||
|
strings.xml |
||||
|
|
||||
|
In order to get the build system to recognize Android resources, set the build action to |
||||
|
"AndroidResource". The native Android APIs do not operate directly with filenames, but |
||||
|
instead operate on resource IDs. When you compile an Android application that uses resources, |
||||
|
the build system will package the resources for distribution and generate a class called "R" |
||||
|
(this is an Android convention) that contains the tokens for each one of the resources |
||||
|
included. For example, for the above Resources layout, this is what the R class would expose: |
||||
|
|
||||
|
public class R { |
||||
|
public class drawable { |
||||
|
public const int icon = 0x123; |
||||
|
} |
||||
|
|
||||
|
public class layout { |
||||
|
public const int main = 0x456; |
||||
|
} |
||||
|
|
||||
|
public class strings { |
||||
|
public const int first_string = 0xabc; |
||||
|
public const int second_string = 0xbcd; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
You would then use R.drawable.icon to reference the drawable/icon.png file, or R.layout.main |
||||
|
to reference the layout/main.axml file, or R.strings.first_string to reference the first |
||||
|
string in the dictionary file values/strings.xml. |
||||
@ -0,0 +1,13 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> |
||||
|
|
||||
|
<item> |
||||
|
<color android:color="@color/splash_background"/> |
||||
|
</item> |
||||
|
|
||||
|
<item android:drawable="@drawable/icon" |
||||
|
android:width="120dp" |
||||
|
android:height="120dp" |
||||
|
android:gravity="center" /> |
||||
|
|
||||
|
</layer-list> |
||||
@ -0,0 +1,4 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<resources> |
||||
|
<color name="splash_background">#FFFFFF</color> |
||||
|
</resources> |
||||
@ -0,0 +1,17 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8" ?> |
||||
|
<resources> |
||||
|
|
||||
|
<style name="MyTheme"> |
||||
|
</style> |
||||
|
|
||||
|
<style name="MyTheme.NoActionBar" parent="@style/Theme.AppCompat.NoActionBar"> |
||||
|
<item name="android:windowActionBar">false</item> |
||||
|
<item name="android:windowNoTitle">true</item> |
||||
|
</style> |
||||
|
|
||||
|
<style name="MyTheme.Splash" parent ="MyTheme.NoActionBar"> |
||||
|
<item name="android:windowBackground">@drawable/splash_screen</item> |
||||
|
<item name="android:windowContentOverlay">@null</item> |
||||
|
</style> |
||||
|
|
||||
|
</resources> |
||||
@ -0,0 +1,16 @@ |
|||||
|
using Android.App; |
||||
|
using Android.Content; |
||||
|
using Android.OS; |
||||
|
|
||||
|
namespace NativeEmbedSample.Android; |
||||
|
|
||||
|
[Activity(Theme = "@style/MyTheme.Splash", MainLauncher = true, NoHistory = true)] |
||||
|
public class SplashActivity : Activity |
||||
|
{ |
||||
|
protected override void OnResume() |
||||
|
{ |
||||
|
base.OnResume(); |
||||
|
|
||||
|
StartActivity(new Intent(Application.Context, typeof(MainActivity))); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,187 @@ |
|||||
|
#nullable enable |
||||
|
|
||||
|
using System; |
||||
|
using System.Diagnostics.CodeAnalysis; |
||||
|
using Android.Views; |
||||
|
using Android.Widget; |
||||
|
|
||||
|
using Avalonia.Controls.Platform; |
||||
|
using Avalonia.Platform; |
||||
|
|
||||
|
namespace Avalonia.Android.Platform |
||||
|
{ |
||||
|
internal class AndroidNativeControlHostImpl : INativeControlHostImpl |
||||
|
{ |
||||
|
private readonly AvaloniaView _avaloniaView; |
||||
|
|
||||
|
public AndroidNativeControlHostImpl(AvaloniaView avaloniaView) |
||||
|
{ |
||||
|
_avaloniaView = avaloniaView; |
||||
|
} |
||||
|
|
||||
|
public INativeControlHostDestroyableControlHandle CreateDefaultChild(IPlatformHandle parent) |
||||
|
{ |
||||
|
return new AndroidViewControlHandle(new FrameLayout(_avaloniaView.Context!), false); |
||||
|
} |
||||
|
|
||||
|
public INativeControlHostControlTopLevelAttachment CreateNewAttachment(Func<IPlatformHandle, IPlatformHandle> create) |
||||
|
{ |
||||
|
var holder = new AndroidViewControlHandle(_avaloniaView, false); |
||||
|
AndroidNativeControlAttachment? attachment = null; |
||||
|
try |
||||
|
{ |
||||
|
var child = create(holder); |
||||
|
// It has to be assigned to the variable before property setter is called so we dispose it on exception
|
||||
|
#pragma warning disable IDE0017 // Simplify object initialization
|
||||
|
attachment = new AndroidNativeControlAttachment(child); |
||||
|
#pragma warning restore IDE0017 // Simplify object initialization
|
||||
|
attachment.AttachedTo = this; |
||||
|
return attachment; |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
attachment?.Dispose(); |
||||
|
holder?.Destroy(); |
||||
|
throw; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public INativeControlHostControlTopLevelAttachment CreateNewAttachment(IPlatformHandle handle) |
||||
|
{ |
||||
|
return new AndroidNativeControlAttachment(handle) |
||||
|
{ |
||||
|
AttachedTo = this |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
public bool IsCompatibleWith(IPlatformHandle handle) => handle.HandleDescriptor == AndroidViewControlHandle.AndroidDescriptor; |
||||
|
|
||||
|
class AndroidNativeControlAttachment : INativeControlHostControlTopLevelAttachment |
||||
|
{ |
||||
|
// ReSharper disable once NotAccessedField.Local (keep GC reference)
|
||||
|
private IPlatformHandle? _child; |
||||
|
private View? _view; |
||||
|
private AndroidNativeControlHostImpl? _attachedTo; |
||||
|
|
||||
|
public AndroidNativeControlAttachment(IPlatformHandle child) |
||||
|
{ |
||||
|
_child = child; |
||||
|
|
||||
|
_view = (child as AndroidViewControlHandle)?.View |
||||
|
?? Java.Lang.Object.GetObject<View>(child.Handle, global::Android.Runtime.JniHandleOwnership.DoNotTransfer); |
||||
|
} |
||||
|
|
||||
|
[MemberNotNull(nameof(_view))] |
||||
|
private void CheckDisposed() |
||||
|
{ |
||||
|
if (_view == null) |
||||
|
throw new ObjectDisposedException(nameof(AndroidNativeControlAttachment)); |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
if (_view != null && _attachedTo?._avaloniaView is ViewGroup parent) |
||||
|
{ |
||||
|
parent.RemoveView(_view); |
||||
|
} |
||||
|
_child = null; |
||||
|
_attachedTo = null; |
||||
|
_view?.Dispose(); |
||||
|
_view = null; |
||||
|
} |
||||
|
|
||||
|
public INativeControlHostImpl? AttachedTo |
||||
|
{ |
||||
|
get => _attachedTo; |
||||
|
set |
||||
|
{ |
||||
|
CheckDisposed(); |
||||
|
|
||||
|
var oldAttachedTo = _attachedTo; |
||||
|
_attachedTo = (AndroidNativeControlHostImpl?)value; |
||||
|
if (_attachedTo == null) |
||||
|
{ |
||||
|
oldAttachedTo?._avaloniaView.RemoveView(_view); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
_attachedTo._avaloniaView.AddView(_view); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public bool IsCompatibleWith(INativeControlHostImpl host) => host is AndroidNativeControlHostImpl; |
||||
|
|
||||
|
public void HideWithSize(Size size) |
||||
|
{ |
||||
|
CheckDisposed(); |
||||
|
_view.Visibility = ViewStates.Gone; |
||||
|
} |
||||
|
|
||||
|
public void ShowInBounds(Rect bounds) |
||||
|
{ |
||||
|
CheckDisposed(); |
||||
|
if (_attachedTo == null) |
||||
|
throw new InvalidOperationException("The control isn't currently attached to a toplevel"); |
||||
|
|
||||
|
bounds *= _attachedTo._avaloniaView.TopLevelImpl.RenderScaling; |
||||
|
_view.Visibility = ViewStates.Visible; |
||||
|
_view.LayoutParameters = new FrameLayout.LayoutParams((int)bounds.Width, (int)bounds.Height) |
||||
|
{ |
||||
|
LeftMargin = (int)bounds.X, |
||||
|
TopMargin = (int)bounds.Y |
||||
|
}; |
||||
|
_view.RequestLayout(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public class AndroidViewControlHandle : INativeControlHostDestroyableControlHandle, IDisposable |
||||
|
{ |
||||
|
internal const string AndroidDescriptor = "JavaHandle"; |
||||
|
|
||||
|
private View? _view; |
||||
|
private bool _disposeView; |
||||
|
|
||||
|
public AndroidViewControlHandle(View view, bool disposeView) |
||||
|
{ |
||||
|
_view = view; |
||||
|
_disposeView = disposeView; |
||||
|
} |
||||
|
|
||||
|
public View View => _view ?? throw new ObjectDisposedException(nameof(AndroidViewControlHandle)); |
||||
|
|
||||
|
public string HandleDescriptor => AndroidDescriptor; |
||||
|
|
||||
|
IntPtr IPlatformHandle.Handle => _view?.Handle ?? default; |
||||
|
|
||||
|
public void Destroy() |
||||
|
{ |
||||
|
Dispose(true); |
||||
|
} |
||||
|
|
||||
|
void IDisposable.Dispose() |
||||
|
{ |
||||
|
Dispose(true); |
||||
|
} |
||||
|
|
||||
|
~AndroidViewControlHandle() |
||||
|
{ |
||||
|
Dispose(false); |
||||
|
} |
||||
|
|
||||
|
private void Dispose(bool disposing) |
||||
|
{ |
||||
|
if (_disposeView) |
||||
|
{ |
||||
|
_view?.Dispose(); |
||||
|
} |
||||
|
|
||||
|
_view = null; |
||||
|
if (disposing) |
||||
|
{ |
||||
|
GC.SuppressFinalize(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue