1
0
Форкнуть 0

[all] Window Manager (alpha09) (#16)

* [TwoPage] test AndroidX.Window NuGet alpha01

quick hack - not ready for release

* change theme for WindowManager migrated samples

to help recognize/differentiate during testing

* Update to alpha05

* Update to alpha05.1 with updated binding

Still hacky code paths but Window Manager seems to be working

* remove test/debug code

* [DualView] AndroidX.Window alpha05

* [CompanionPane] AndroidX.Window alpha05

* [ListDetail] AndroidX.Window alpha05

* [IntentToSecondScreen] remove DuoSDK (not used)

* [DragAndDrop] AndroidX.Window alpha05

* [WindowManager] new sample

Based on the Kotlin version

* add reference to original source

https://github.com/googlecodelabs/android-foldable-codelab/tree/main/window-manager

* Update to WM alpha06, add isSeparating method call test

* fix layouts when spanned

* additional data display

* [TwoPage] alpha06

* [ListDetail] alpha06

* [DualView] alpha06

* [CompanionPane] alpha06

* update DisplayFeature parsing

* formatting fixes

* [WindowManager] add README and screenshot

* update all to alpha07

* update all to alpha09

API surface changed in this binding update https://github.com/xamarin/AndroidX/issues/325#issuecomment-882718035

* new alphas can be registered in OnStart

previously crashed if registered before OnAttachedToWindow
This commit is contained in:
Craig Dunn 2021-08-25 08:05:34 -07:00 коммит произвёл GitHub
Родитель fee97de434
Коммит 7392a65726
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
55 изменённых файлов: 1223 добавлений и 187 удалений

Просмотреть файл

@ -135,19 +135,19 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Xamarin.AndroidX.AppCompat">
<Version>1.1.0</Version>
<Version>1.2.0.5</Version>
</PackageReference>
<PackageReference Include="Xamarin.AndroidX.CardView">
<Version>1.0.0</Version>
</PackageReference>
<PackageReference Include="Xamarin.AndroidX.ConstraintLayout">
<Version>1.1.3</Version>
<Version>2.0.4.2</Version>
</PackageReference>
<PackageReference Include="Xamarin.AndroidX.RecyclerView">
<Version>1.1.0</Version>
</PackageReference>
<PackageReference Include="Xamarin.DuoSdk">
<Version>0.0.3.4</Version>
<PackageReference Include="Xamarin.AndroidX.Window">
<Version>1.0.0-alpha09</Version>
</PackageReference>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />

Просмотреть файл

@ -1,13 +1,15 @@

using Android.Content.Res;
using Android.OS;
using Android.Runtime;
using Android.Util;
using Android.Views;
using Android.Widget;
using AndroidX.AppCompat.App;
using AndroidX.Core.Util;
using AndroidX.Fragment.App;
using AndroidX.Window;
using CompanionPane.Fragments;
using Microsoft.Device.Display;
using Java.Lang;
using Java.Util.Concurrent;
using System.Linq;
namespace CompanionPane
@ -18,14 +20,17 @@ namespace CompanionPane
RoundIcon = "@mipmap/ic_launcher_round",
Theme = "@style/AppTheme",
MainLauncher = true,
ConfigurationChanges = Android.Content.PM.ConfigChanges.ScreenSize | Android.Content.PM.ConfigChanges.ScreenLayout | Android.Content.PM.ConfigChanges.Orientation | Android.Content.PM.ConfigChanges.SmallestScreenSize)]
public class MainActivity : AppCompatActivity, BaseFragment.IOnItemSelectedListener
ConfigurationChanges = Android.Content.PM.ConfigChanges.ScreenSize | Android.Content.PM.ConfigChanges.ScreenLayout | Android.Content.PM.ConfigChanges.Orientation | Android.Content.PM.ConfigChanges.SmallestScreenSize | Android.Content.PM.ConfigChanges.UiMode)]
public class MainActivity : AppCompatActivity, BaseFragment.IOnItemSelectedListener, IConsumer
{
const string TAG = "JWM"; // Jetpack Window Manager
WindowManager wm;
FoldingFeature.Orientation hingeOrientation = FoldingFeature.Orientation.Vertical;
bool isDuo, isDualMode;
SinglePortrait singlePortrait;
DualPortrait dualPortrait;
DualLandscape dualLandscape;
ScreenHelper screenHelper;
bool isDuo;
int currentPosition;
protected override void OnCreate(Bundle savedInstanceState)
@ -34,10 +39,6 @@ namespace CompanionPane
SetContentView(Resource.Layout.activity_main);
screenHelper = new ScreenHelper();
isDuo = screenHelper.Initialize(this);
var slides = Slide.Slides.ToList();
currentPosition = 0;
@ -47,9 +48,68 @@ namespace CompanionPane
dualPortrait.RegisterOnItemSelectedListener(this);
dualLandscape = DualLandscape.NewInstance(slides);
dualLandscape.RegisterOnItemSelectedListener(this);
wm = new WindowManager(this);
SetupLayout();
}
IExecutor runOnUiThreadExecutor()
{
return new MyExecutor();
}
class MyExecutor : Java.Lang.Object, IExecutor
{
Handler handler = new Handler(Looper.MainLooper);
public void Execute(IRunnable r)
{
handler.Post(r);
}
}
public void Accept(Java.Lang.Object newLayoutInfo) // Object will be WindowLayoutInfo
{
Log.Info(TAG, "===LayoutStateChangeCallback.Accept");
var wli = newLayoutInfo as WindowLayoutInfo;
if (wli.DisplayFeatures.Count == 0)
{ // no hinge found
isDualMode = false;
}
else
{
foreach (var df in wli.DisplayFeatures)
{
Log.Info(TAG, "Bounds:" + df.Bounds);
var ff = df as FoldingFeature;
if (!(ff is null))
{ // a hinge exists
Log.Info(TAG, "Orientation: " + ff.GetOrientation());
isDualMode = true;
hingeOrientation = ff.GetOrientation();
isDuo = true; //HACK: set first time we see the hinge, never un-set
}
else
{ // no hinge found
isDualMode = false;
}
}
}
SetupLayout();
}
protected override void OnStart()
{
base.OnStart();
wm.RegisterLayoutChangeCallback(runOnUiThreadExecutor(), this);
}
protected override void OnStop()
{
base.OnStop();
wm.UnregisterLayoutChangeCallback(this);
}
void ShowFragment(Fragment fragment)
{
var fragmentTransaction = SupportFragmentManager.BeginTransaction();
@ -69,19 +129,9 @@ namespace CompanionPane
}
}
public override void OnConfigurationChanged(Configuration newConfig)
void UseDualMode(FoldingFeature.Orientation hingeOrientation)
{
base.OnConfigurationChanged(newConfig);
if(ScreenHelper.IsDualScreenDevice(this))
screenHelper.Update();
SetupLayout();
}
void UseDualMode(SurfaceOrientation rotation)
{
if (rotation == SurfaceOrientation.Rotation90 || rotation == SurfaceOrientation.Rotation270)
if (hingeOrientation == FoldingFeature.Orientation.Horizontal)
{
dualLandscape.setCurrentPosition(currentPosition);
ShowFragment(dualLandscape);
@ -107,12 +157,10 @@ namespace CompanionPane
void SetupLayout()
{
var rotation = ScreenHelper.GetRotation(this);
if (isDuo)
{
if (screenHelper.IsDualMode)
UseDualMode(rotation);
if (isDualMode)
UseDualMode(hingeOrientation);
else
UseSingleMode();
}

Просмотреть файл

@ -5,7 +5,7 @@
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#008577"
android:fillColor="#1976d2"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"

Просмотреть файл

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#008577</color>
<color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#D81B60</color>
<color name="lightGray">#e3e3e3</color>
<color name="colorPrimary">#1976d2</color>
<color name="colorPrimaryDark">#004ba0</color>
<color name="colorAccent">#ffb300</color>
<color name="lightGray">#e3e3e3</color>
</resources>

Просмотреть файл

@ -101,15 +101,15 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Xamarin.AndroidX.AppCompat">
<Version>1.1.0</Version>
<Version>1.2.0.5</Version>
</PackageReference>
<PackageReference Include="Xamarin.AndroidX.ConstraintLayout">
<Version>1.1.3</Version>
<Version>2.0.4.2</Version>
</PackageReference>
<PackageReference Include="Xamarin.DuoSdk">
<Version>0.0.3.4</Version>
<PackageReference Include="Xamarin.AndroidX.Window">
<Version>1.0.0-alpha09</Version>
</PackageReference>
<PackageReference Include="Xamarin.Google.Android.Material" Version="1.1.0-rc2" />
<PackageReference Include="Xamarin.Google.Android.Material" Version="1.4.0" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\surface_duo.png" />

Просмотреть файл

@ -1,21 +1,26 @@
using Android.App;
using Android.Content.Res;
using Android.OS;
using Android.Runtime;
using Android.Widget;
using AndroidX.AppCompat.App;
using AndroidX.Fragment.App;
using Google.Android.Material.FloatingActionButton;
using Microsoft.Device.Display;
using Fragment = AndroidX.Fragment.App.Fragment;
using AndroidX.Window;
using AndroidX.Core.Util;
using Java.Util.Concurrent;
using Java.Lang;
using Android.Util;
using Android.Views;
using Android.Content;
using Java.Interop;
namespace DragAndDrop
{
[Activity(Label = "@string/app_name", Theme = "@style/AppTheme", MainLauncher = true)]
public class MainActivity : AppCompatActivity
public class MainActivity : AppCompatActivity, IConsumer
{
ScreenHelper screenHelper;
bool isDuo = false;
const string TAG = "JWM"; // Jetpack Window Manager
WindowManager wm;
bool isDuo, isDualMode;
protected override void OnCreate(Bundle savedInstanceState)
{
@ -24,8 +29,7 @@ namespace DragAndDrop
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.activity_main);
screenHelper = new ScreenHelper();
isDuo = screenHelper.Initialize(this);
wm = new WindowManager(this);
SetupLayout();
@ -33,12 +37,70 @@ namespace DragAndDrop
fab.Click += (s, e) => Recreate();
}
IExecutor runOnUiThreadExecutor()
{
return new MyExecutor();
}
class MyExecutor : Java.Lang.Object, IExecutor
{
Handler handler = new Handler(Looper.MainLooper);
public void Execute(IRunnable r)
{
handler.Post(r);
}
}
public void Accept(Java.Lang.Object newLayoutInfo) // Object will be WindowLayoutInfo
{
Log.Info(TAG, "===LayoutStateChangeCallback.Accept");
var wli = newLayoutInfo as WindowLayoutInfo;
if (wli.DisplayFeatures.Count == 0)
{ // no hinge found
isDualMode = false;
}
else
{
foreach (var df in wli.DisplayFeatures)
{
Log.Info(TAG, "Bounds:" + df.Bounds);
var ff = df as FoldingFeature;
if (!(ff is null))
{ // a hinge exists
Log.Info(TAG, "Orientation: " + ff.GetOrientation());
isDualMode = true;
isDuo = true; //HACK: set first time we see the hinge, never un-set
}
else
{ // no hinge found
isDualMode = false;
}
}
}
SetupLayout();
}
protected override void OnStart()
{
base.OnStart();
wm.RegisterLayoutChangeCallback(runOnUiThreadExecutor(), this);
}
protected override void OnStop()
{
base.OnStop();
wm.UnregisterLayoutChangeCallback(this);
}
void SetupLayout()
{
var bundle = new Bundle();
var rotation = ScreenHelper.GetRotation(this);
if (isDuo && screenHelper.IsDualMode)
var wm = this.GetSystemService(Context.WindowService).JavaCast<IWindowManager>();
var rotation = SurfaceOrientation.Rotation0;
if (wm != null)
rotation = wm.DefaultDisplay.Rotation;
if (isDuo && isDualMode)
{
switch (rotation)
{

Просмотреть файл

@ -5,7 +5,7 @@
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#008577"
android:fillColor="#1976d2"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"

Просмотреть файл

@ -4,8 +4,8 @@
~ Licensed under the MIT License.
-->
<resources>
<color name="colorPrimary">#008577</color>
<color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#D81B60</color>
<color name="light_gray">#ededed</color>
<color name="colorPrimary">#1976d2</color>
<color name="colorPrimaryDark">#004ba0</color>
<color name="colorAccent">#ffb300</color>
<color name="light_gray">#ededed</color>
</resources>

Просмотреть файл

@ -121,13 +121,13 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Xamarin.AndroidX.AppCompat">
<Version>1.1.0</Version>
<Version>1.2.0.5</Version>
</PackageReference>
<PackageReference Include="Xamarin.AndroidX.ConstraintLayout">
<Version>1.1.3</Version>
<Version>2.0.4.2</Version>
</PackageReference>
<PackageReference Include="Xamarin.DuoSdk">
<Version>0.0.3.4</Version>
<PackageReference Include="Xamarin.AndroidX.Window">
<Version>1.0.0-alpha09</Version>
</PackageReference>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />

Просмотреть файл

@ -5,8 +5,12 @@ using Android.Views;
using Android.Widget;
using AndroidX.AppCompat.App;
using DualView.Fragments;
using Microsoft.Device.Display;
using System.Collections.Generic;
using AndroidX.Window;
using AndroidX.Core.Util;
using Java.Util.Concurrent;
using Java.Lang;
using Android.Util;
namespace DualView
{
@ -16,12 +20,15 @@ namespace DualView
RoundIcon = "@mipmap/ic_launcher_round",
Theme = "@style/AppTheme",
MainLauncher = true,
ConfigurationChanges = Android.Content.PM.ConfigChanges.ScreenSize | Android.Content.PM.ConfigChanges.ScreenLayout | Android.Content.PM.ConfigChanges.Orientation | Android.Content.PM.ConfigChanges.SmallestScreenSize)]
ConfigurationChanges = Android.Content.PM.ConfigChanges.ScreenSize | Android.Content.PM.ConfigChanges.ScreenLayout | Android.Content.PM.ConfigChanges.Orientation | Android.Content.PM.ConfigChanges.SmallestScreenSize | Android.Content.PM.ConfigChanges.UiMode)]
public class MainActivity : AppCompatActivity, BaseFragment.IOnItemSelectedListener
public class MainActivity : AppCompatActivity, BaseFragment.IOnItemSelectedListener, IConsumer
{
ScreenHelper screenHelper;
bool isDuo;
const string TAG = "JWM"; // Jetpack Window Manager
WindowManager wm;
FoldingFeature.Orientation hingeOrientation = FoldingFeature.Orientation.Vertical;
bool isDuo, isDualMode;
Dictionary<string, BaseFragment> fragmentMap;
int currentSelectedPosition = -1;
@ -32,8 +39,6 @@ namespace DualView
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.activity_main);
screenHelper = new ScreenHelper();
isDuo = screenHelper.Initialize(this);
var items = Item.Items;
fragmentMap = new Dictionary<string, BaseFragment>();
@ -49,9 +54,66 @@ namespace DualView
dualLandscape.RegisterOnItemSelectedListener(this);
fragmentMap[GetSimpleName<DualLandscape>()] = dualLandscape;
wm = new WindowManager(this);
SetupLayout();
}
IExecutor runOnUiThreadExecutor()
{
return new MyExecutor();
}
class MyExecutor : Java.Lang.Object, IExecutor
{
Handler handler = new Handler(Looper.MainLooper);
public void Execute(IRunnable r)
{
handler.Post(r);
}
}
public void Accept(Java.Lang.Object newLayoutInfo) // Object will be WindowLayoutInfo
{
Log.Info(TAG, "===LayoutStateChangeCallback.Accept");
var wli = newLayoutInfo as WindowLayoutInfo;
if (wli.DisplayFeatures.Count == 0)
{ // no hinge found
isDualMode = false;
}
else
{
foreach (var df in wli.DisplayFeatures)
{
Log.Info(TAG, "Bounds:" + df.Bounds);
var ff = df as FoldingFeature;
if (!(ff is null))
{ // a hinge exists
Log.Info(TAG, "Orientation: " + ff.GetOrientation());
isDualMode = true;
hingeOrientation = ff.GetOrientation();
isDuo = true; //HACK: set first time we see the hinge, never un-set
}
else
{ // no hinge found
isDualMode = false;
}
}
}
SetupLayout();
}
protected override void OnStart()
{
base.OnStart();
wm.RegisterLayoutChangeCallback(runOnUiThreadExecutor(), this);
}
protected override void OnStop()
{
base.OnStop();
wm.UnregisterLayoutChangeCallback(this);
}
void UseSingleMode()
{
var baseFragment = fragmentMap[GetSimpleName<SinglePortrait>()];
@ -59,32 +121,33 @@ namespace DualView
ShowFragment(baseFragment);
}
void UseDualMode(SurfaceOrientation rotation)
void UseDualMode(FoldingFeature.Orientation hingeOrientation)
{
switch (rotation)
{
case SurfaceOrientation.Rotation90:
case SurfaceOrientation.Rotation270:
// Setting layout for double landscape
var baseFragment = fragmentMap[GetSimpleName<DualLandscape>()];
if (baseFragment != null)
ShowFragment(baseFragment);
break;
default:
var baseFragment1 = fragmentMap[GetSimpleName<DualPortrait>()];
if (baseFragment1 != null)
ShowFragment(baseFragment1);
break;
if (hingeOrientation == FoldingFeature.Orientation.Horizontal)
{ // hinge horizontal - setting layout for double landscape
var baseFragment = fragmentMap[GetSimpleName<DualLandscape>()];
if (baseFragment != null)
{
ShowFragment(baseFragment);
}
}
else
{ //includes FoldingFeature.Orientation.Vertical
// hinge vertical - setting layout for double portrait
var baseFragment1 = fragmentMap[GetSimpleName<DualPortrait>()];
if (baseFragment1 != null)
{
ShowFragment(baseFragment1);
}
}
}
void SetupLayout()
{
var rotation = ScreenHelper.GetRotation(this);
if (isDuo)
{
if (screenHelper.IsDualMode)
UseDualMode(rotation);
if (isDualMode)
UseDualMode(hingeOrientation);
else
UseSingleMode();
}
@ -94,16 +157,6 @@ namespace DualView
}
}
public override void OnConfigurationChanged(Configuration newConfig)
{
base.OnConfigurationChanged(newConfig);
if (ScreenHelper.IsDualScreenDevice(this))
screenHelper.Update();
SetupLayout();
}
void ShowFragment(BaseFragment fragment)
{
var fragmentTransaction = SupportFragmentManager.BeginTransaction();

Просмотреть файл

@ -5,7 +5,7 @@
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#008577"
android:fillColor="#1976d2"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"

Просмотреть файл

@ -4,7 +4,7 @@
-->
<resources>
<color name="colorPrimary">#008577</color>
<color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#5ed8af</color>
<color name="colorPrimary">#1976d2</color>
<color name="colorPrimaryDark">#004ba0</color>
<color name="colorAccent">#ffb300</color>
</resources>

Просмотреть файл

@ -92,10 +92,10 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Xamarin.AndroidX.AppCompat">
<Version>1.1.0</Version>
<Version>1.2.0.5</Version>
</PackageReference>
<PackageReference Include="Xamarin.AndroidX.ConstraintLayout">
<Version>1.1.3</Version>
<Version>2.0.4.2</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>

Просмотреть файл

@ -99,13 +99,10 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Xamarin.AndroidX.AppCompat">
<Version>1.1.0</Version>
<Version>1.2.0.5</Version>
</PackageReference>
<PackageReference Include="Xamarin.AndroidX.ConstraintLayout">
<Version>1.1.3</Version>
</PackageReference>
<PackageReference Include="Xamarin.DuoSdk">
<Version>0.0.3.4</Version>
<Version>2.0.4.2</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>

Просмотреть файл

@ -98,16 +98,16 @@
<Version>12.0.3</Version>
</PackageReference>
<PackageReference Include="Xamarin.AndroidX.AppCompat">
<Version>1.1.0</Version>
<Version>1.2.0.5</Version>
</PackageReference>
<PackageReference Include="Xamarin.AndroidX.CardView">
<Version>1.0.0</Version>
</PackageReference>
<PackageReference Include="Xamarin.AndroidX.ConstraintLayout">
<Version>1.1.3</Version>
<Version>2.0.4.2</Version>
</PackageReference>
<PackageReference Include="Xamarin.DuoSdk">
<Version>0.0.3.4</Version>
<PackageReference Include="Xamarin.AndroidX.Window">
<Version>1.0.0-alpha09</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>

Просмотреть файл

@ -3,11 +3,14 @@ using Android.Content.Res;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using AndroidX.AppCompat.App;
using AndroidX.Fragment.App;
using ListDetail.Fragments;
using Microsoft.Device.Display;
using AndroidX.Window;
using AndroidX.Core.Util;
using Java.Util.Concurrent;
using Java.Lang;
using Android.Util;
namespace ListDetail
{
@ -18,10 +21,13 @@ namespace ListDetail
Theme = "@style/AppTheme",
MainLauncher = true,
ConfigurationChanges = Android.Content.PM.ConfigChanges.ScreenSize | Android.Content.PM.ConfigChanges.ScreenLayout | Android.Content.PM.ConfigChanges.Orientation | Android.Content.PM.ConfigChanges.SmallestScreenSize)]
public class MainActivity : AppCompatActivity
public class MainActivity : AppCompatActivity, IConsumer
{
ScreenHelper screenHelper;
bool isDuo;
const string TAG = "JWM"; // Jetpack Window Manager
WindowManager wm;
FoldingFeature.Orientation hingeOrientation = FoldingFeature.Orientation.Vertical;
bool isDuo, isDualMode;
SinglePortrait singlePortrait;
DualPortrait dualPortrait;
@ -29,39 +35,92 @@ namespace ListDetail
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.activity_main);
screenHelper = new ScreenHelper();
isDuo = screenHelper.Initialize(this);
var items = Item.Items;
singlePortrait = SinglePortrait.NewInstance(items);
dualPortrait = DualPortrait.NewInstance(items);
wm = new WindowManager(this);
SetupLayout();
}
IExecutor runOnUiThreadExecutor()
{
return new MyExecutor();
}
class MyExecutor : Java.Lang.Object, IExecutor
{
Handler handler = new Handler(Looper.MainLooper);
public void Execute(IRunnable r)
{
handler.Post(r);
}
}
public void Accept(Java.Lang.Object newLayoutInfo) // Object will be WindowLayoutInfo
{
Log.Info(TAG, "===LayoutStateChangeCallback.Accept");
var wli = newLayoutInfo as WindowLayoutInfo;
if (wli.DisplayFeatures.Count == 0)
{ // no hinge found
isDualMode = false;
}
else
{
foreach (var df in wli.DisplayFeatures)
{
Log.Info(TAG, "Bounds:" + df.Bounds);
var ff = df as FoldingFeature;
if (!(ff is null))
{ // a hinge exists
Log.Info(TAG, "Orientation: " + ff.GetOrientation());
isDualMode = true;
hingeOrientation = ff.GetOrientation();
isDuo = true; //HACK: set first time we see the hinge, never un-set
}
else
{ // no hinge found
isDualMode = false;
}
}
}
SetupLayout();
}
protected override void OnStart()
{
base.OnStart();
wm.RegisterLayoutChangeCallback(runOnUiThreadExecutor(), this);
}
protected override void OnStop()
{
base.OnStop();
wm.UnregisterLayoutChangeCallback(this);
}
void UseSingleMode()
=> ShowFragment(singlePortrait);
void UseDualMode(SurfaceOrientation rotation)
void UseDualMode(FoldingFeature.Orientation hingeOrientation)
{
switch (rotation)
if (hingeOrientation == FoldingFeature.Orientation.Horizontal)
{
case SurfaceOrientation.Rotation90:
case SurfaceOrientation.Rotation270:
// Setting layout for double landscape
UseSingleMode();
break;
default:
ShowFragment(dualPortrait);
break;
// hinge horizontal - setting layout for double landscape
UseSingleMode();
} else {
//includes FoldingFeature.Orientation.Vertical
// hinge vertical - setting layout for double portrait
ShowFragment(dualPortrait);
}
}
private void SetupLayout()
{
var rotation = ScreenHelper.GetRotation(this);
if (isDuo)
{
if (screenHelper.IsDualMode)
UseDualMode(rotation);
if (isDualMode)
UseDualMode(hingeOrientation);
else
UseSingleMode();
}
@ -71,16 +130,6 @@ namespace ListDetail
}
}
public override void OnConfigurationChanged(Configuration newConfig)
{
base.OnConfigurationChanged(newConfig);
if (ScreenHelper.IsDualScreenDevice(this))
screenHelper.Update();
SetupLayout();
}
void ShowFragment(Fragment fragment)
{
var fragmentTransaction = SupportFragmentManager.BeginTransaction();

Просмотреть файл

@ -5,7 +5,7 @@
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#008577"
android:fillColor="#1976d2"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"

Просмотреть файл

@ -4,8 +4,8 @@
-->
<resources>
<color name="colorPrimary">#008577</color>
<color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#008577</color>
<color name="colorPrimary">#1976d2</color>
<color name="colorPrimaryDark">#004ba0</color>
<color name="colorAccent">#ffb300</color>
<color name="yellow">#FFD54F</color>
</resources>

Двоичные данные
Screenshots/xamarin-window-manager.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 43 KiB

Просмотреть файл

@ -5,8 +5,31 @@ using Android.Views;
using Android.Widget;
using AndroidX.AppCompat.App;
using AndroidX.ViewPager.Widget;
using Microsoft.Device.Display;
using AndroidX.Window;
using AndroidX.Core;
using AndroidX.Core.Util;
using AndroidX.Annotations;
using AndroidX.Collection;
using Java.Lang;
using Java.Util.Concurrent;
using Android.Util;
/*
15-Apr-21 Use androidx.window-1.0.0-alpha01
This is a terrible hack that just aims to get the basics of Window Manager working
It doesn't properly handle rotation - just single-portrait/dual-portrait
19-Apr-21 Update to androidx.window-1.0.0-alpha05
Discovered methods are missing from the binding
20-Apr-21 Update to androidx.window-1.0.0.1-alpha05 (adds RegisterLayoutChangeCallback method)
Registering and receiving events works, but the orientation value seems off
IConsumer.Accept is added to the Activity so that it can receive method calls on layout state changed
21-Apr-21 Refactor out test code, seems to work...
19-Jul-21 Update to androidx.window-1.0.0-apha09
FoldingFeature API changes - some properties became methods (GetOrientation, GetState, GetOcclusionType) and their types became "enums" (static class fields)
Use OnStart/Stop instead of OnAttachedToWindow/OnDetached
*/
namespace TwoPage
{
[Android.App.Activity(
@ -16,16 +39,23 @@ namespace TwoPage
Theme = "@style/AppTheme",
MainLauncher = true,
ConfigurationChanges = Android.Content.PM.ConfigChanges.ScreenSize | Android.Content.PM.ConfigChanges.ScreenLayout | Android.Content.PM.ConfigChanges.Orientation | Android.Content.PM.ConfigChanges.SmallestScreenSize)]
public class MainActivity : AppCompatActivity, ViewPager.IOnPageChangeListener
public class MainActivity : AppCompatActivity, ViewPager.IOnPageChangeListener, IConsumer
{
const string TAG = "JWM"; // Jetpack Window Manager
WindowManager wm;
FoldingFeature.Orientation hingeOrientation = FoldingFeature.Orientation.Vertical;
bool isDuo, isDualMode;
ViewPager viewPager;
PagerAdapter pagerAdapter;
ScreenHelper screenHelper;
/// <summary>Page number</summary>
int position = 0;
bool isDuo;
View single;
View dual;
//LayoutStateChangeCallback layoutStateChangeCallback = new LayoutStateChangeCallback();
public bool ShowTwoPages { get; set; } = false;
protected override void OnCreate(Bundle savedInstanceState)
@ -33,13 +63,94 @@ namespace TwoPage
base.OnCreate(savedInstanceState);
var fragments = TestFragment.Fragments;
pagerAdapter = new PagerAdapter(SupportFragmentManager, fragments);
screenHelper = new ScreenHelper();
isDuo = screenHelper.Initialize(this);
wm = new WindowManager(this);
single = LayoutInflater.Inflate(Resource.Layout.activity_main, null);
dual = LayoutInflater.Inflate(Resource.Layout.double_landscape_layout, null);
SetupLayout();
}
IExecutor runOnUiThreadExecutor() {
return new MyExecutor();
}
class MyExecutor : Java.Lang.Object, IExecutor {
Handler handler = new Handler(Looper.MainLooper);
public void Execute(IRunnable r)
{
handler.Post(r);
}
}
[System.Obsolete("Sample uses the activity as the callback method host - keeping this for demonstration purposes only")]
class LayoutStateChangeCallback : Java.Lang.Object, IConsumer
{
public void Accept(Java.Lang.Object newLayoutInfo) // Object will be WindowLayoutInfo
{
Log.Info(TAG, newLayoutInfo.ToString());
var wli = newLayoutInfo as WindowLayoutInfo; // cast to Type
foreach (var df in wli.DisplayFeatures)
{
Log.Info(TAG, "Bounds:" + df.Bounds);
//var ff = df as FoldingFeature;
if ((df is FoldingFeature ff))
{
Log.Info(TAG, "IsSeparating: " + ff.IsSeparating);
Log.Info(TAG, "OcclusionMode: " + ff.GetOcclusionType());
Log.Info(TAG, "Orientation: " + ff.GetOrientation());
Log.Info(TAG, "State: " + ff.GetState());
}
}
}
}
public void Accept(Java.Lang.Object newLayoutInfo) // Object will be WindowLayoutInfo
{
Log.Info(TAG, "===LayoutStateChangeCallback.Accept");
Log.Info(TAG, newLayoutInfo.ToString());
var wli = newLayoutInfo as WindowLayoutInfo;
if (wli.DisplayFeatures.Count == 0)
{ // no hinge found
isDualMode = false;
}
else
{
foreach (var df in wli.DisplayFeatures)
{
Log.Info(TAG, "Bounds:" + df.Bounds);
Log.Info(TAG, "(deprecated)Type: {df.Type} (FOLD or HINGE)");
var ff = df as FoldingFeature;
if (!(ff is null))
{ // a hinge exists
Log.Info(TAG, "IsSeparating: " + ff.IsSeparating);
Log.Info(TAG, "OcclusionMode: " + ff.GetOcclusionType());
Log.Info(TAG, "Orientation: " + ff.GetOrientation());
Log.Info(TAG, "State: " + ff.GetState());
isDualMode = true;
hingeOrientation = ff.GetOrientation();
isDuo = true; //HACK: set first time we see the hinge, never un-set
}
else
{ // no hinge found
isDualMode = false;
}
}
}
SetupLayout();
}
protected override void OnStart()
{
base.OnStart();
wm.RegisterLayoutChangeCallback(runOnUiThreadExecutor(), this);
}
protected override void OnStop()
{
base.OnStop();
wm.UnregisterLayoutChangeCallback(this);
}
void UseSingleMode()
{
//Setting layout for single portrait
@ -48,32 +159,28 @@ namespace TwoPage
SetupViewPager();
}
void UseDualMode(SurfaceOrientation rotation)
void UseDualMode(FoldingFeature.Orientation hingeOrientation)
{
switch (rotation)
{
case SurfaceOrientation.Rotation90:
case SurfaceOrientation.Rotation270:
// Setting layout for double landscape
SetContentView(dual);
ShowTwoPages = false;
break;
default:
// Setting layout for double portrait
SetContentView(single);
ShowTwoPages = true;
break;
if (hingeOrientation == FoldingFeature.Orientation.Horizontal)
{ // hinge horizontal - setting layout for double landscape
SetContentView(dual);
ShowTwoPages = false;
}
else
{ //includes FoldingFeature.OrientationVertical
// hinge vertical - setting layout for double portrait
SetContentView(single);
ShowTwoPages = true;
}
SetupViewPager();
}
void SetupLayout()
{
var rotation = ScreenHelper.GetRotation(this);
if (isDuo)
{
if (screenHelper.IsDualMode)
UseDualMode(rotation);
if (isDualMode)
UseDualMode(hingeOrientation);
else
UseSingleMode();
}
@ -83,17 +190,7 @@ namespace TwoPage
}
}
public override void OnConfigurationChanged(Configuration newConfig)
{
base.OnConfigurationChanged(newConfig);
if (ScreenHelper.IsDualScreenDevice(this))
screenHelper.Update();
SetupLayout();
}
void SetupViewPager()
void SetupViewPager()
{
pagerAdapter.ShowTwoPages = ShowTwoPages;
if (viewPager != null)

Просмотреть файл

@ -5,7 +5,7 @@
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#008577"
android:fillColor="#1976d2"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"

Просмотреть файл

@ -4,7 +4,7 @@
-->
<resources>
<color name="colorPrimary">#008577</color>
<color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#D81B60</color>
<color name="colorPrimary">#1976d2</color>
<color name="colorPrimaryDark">#004ba0</color>
<color name="colorAccent">#ffb300</color>
</resources>

Просмотреть файл

@ -93,13 +93,13 @@
<ItemGroup />
<ItemGroup>
<PackageReference Include="Xamarin.AndroidX.AppCompat">
<Version>1.1.0</Version>
<Version>1.2.0.5</Version>
</PackageReference>
<PackageReference Include="Xamarin.AndroidX.ConstraintLayout">
<Version>1.1.3</Version>
<Version>2.0.4.2</Version>
</PackageReference>
<PackageReference Include="Xamarin.DuoSdk">
<Version>0.0.3.4</Version>
<PackageReference Include="Xamarin.AndroidX.Window">
<Version>1.0.0-alpha09</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>

Просмотреть файл

@ -0,0 +1,19 @@
Any raw assets you want to be deployed with your application can be placed in
this directory (and child directories) and given a Build Action of "AndroidAsset".
These files will be deployed with your package and will be accessible using Android's
AssetManager, like this:
public class ReadAsset : Activity
{
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
InputStream input = Assets.Open ("my_asset.txt");
}
}
Additionally, some Android functions will automatically load asset files:
Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf");

Просмотреть файл

@ -0,0 +1,190 @@
using Android.App;
using Android.Graphics;
using Android.OS;
using Android.Util;
using Android.Views;
using Android.Widget;
using AndroidX.AppCompat.App;
using AndroidX.ConstraintLayout.Widget;
using AndroidX.Core.Util;
using AndroidX.Window;
using Java.Lang;
using Java.Util.Concurrent;
/*
This sample is a C# port of this Kotlin code
https://github.com/googlecodelabs/android-foldable-codelab/tree/main/window-manager
which is part of a Google Codelab that explains how to use Window Manager
19-Jul-21 Update to androidx.window-1.0.0-apha09
FoldingFeature API changes - some properties became methods (GetOrientation, GetState, GetOcclusionType) and their types became "enums" (static class fields)
Use OnStart/Stop instead of OnAttachedToWindow/OnDetached
*/
namespace WindowManagerDemo
{
[Activity(Label = "@string/app_name",
Theme = "@style/AppTheme",
MainLauncher = true)]//, // HACK: for some reason the Window Manager doesn't work when configuration changes are being handled
//ConfigurationChanges = Android.Content.PM.ConfigChanges.ScreenSize | Android.Content.PM.ConfigChanges.ScreenLayout | Android.Content.PM.ConfigChanges.Orientation | Android.Content.PM.ConfigChanges.SmallestScreenSize)]
public class MainActivity : AppCompatActivity, IConsumer
{
const string TAG = "JWM"; // Jetpack Window Manager
WindowManager wm;
ConstraintLayout constraintLayout;
TextView windowMetrics, layoutChange, configurationChanged;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
wm = new WindowManager(this);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.activity_main);
constraintLayout = FindViewById<ConstraintLayout>(Resource.Id.constraint_layout);
windowMetrics = FindViewById<TextView>(Resource.Id.window_metrics);
layoutChange = FindViewById<TextView>(Resource.Id.layout_change);
configurationChanged = FindViewById<TextView>(Resource.Id.configuration_changed);
}
void printLayoutStateChange(WindowLayoutInfo newLayoutInfo)
{
Log.Info(TAG, wm.CurrentWindowMetrics.Bounds.ToString());
Log.Info(TAG, wm.MaximumWindowMetrics.Bounds.ToString());
windowMetrics.Text = $"CurrentWindowMetrics: {wm.CurrentWindowMetrics.Bounds}\n" +
$"MaximumWindowMetrics: {wm.MaximumWindowMetrics.Bounds}";
layoutChange.Text = newLayoutInfo.ToString();
configurationChanged.Text = "One logic/physical display - unspanned";
foreach (var displayFeature in newLayoutInfo.DisplayFeatures)
{
if (displayFeature is FoldingFeature foldingFeature)
{
alignViewToDeviceFeatureBoundaries(newLayoutInfo);
if (foldingFeature.GetOcclusionType() == FoldingFeature.OcclusionType.None)
{
configurationChanged.Text = "App is spanned across a fold";
}
if (foldingFeature.GetOcclusionType() == FoldingFeature.OcclusionType.Full)
{
configurationChanged.Text = "App is spanned across a hinge";
}
configurationChanged.Text += "\nIsSeparating: " + foldingFeature.IsSeparating
+ "\nOrientation: " + foldingFeature.GetOrientation() // FoldingFeature.Orientation.Vertical or Horizontal
+ "\nState: " + foldingFeature.GetState(); // FoldingFeature.StateFlat or StateHalfOpened
}
else
{
Log.Info(TAG, "DisplayFeature is not a fold or hinge");
}
}
}
void alignViewToDeviceFeatureBoundaries(WindowLayoutInfo newLayoutInfo)
{
var set = new ConstraintSet();
set.Clone(constraintLayout); // existing constraints baseline
var foldFeature = newLayoutInfo.DisplayFeatures[0] as FoldingFeature;
//We get the display feature bounds.
var rect = foldFeature.Bounds;
//Set the red hinge indicator's width and height using the Bounds
set.ConstrainHeight(Resource.Id.device_feature, rect.Bottom - rect.Top);
set.ConstrainWidth(Resource.Id.device_feature, rect.Right - rect.Left);
set.Connect(
Resource.Id.device_feature, ConstraintSet.Start,
ConstraintSet.ParentId, ConstraintSet.Start, 0
);
set.Connect(
Resource.Id.device_feature, ConstraintSet.Top,
ConstraintSet.ParentId, ConstraintSet.Top, 0
);
if (foldFeature.GetOrientation() == FoldingFeature.Orientation.Vertical)
{
// Device feature is placed vertically
set.SetMargin(Resource.Id.device_feature, ConstraintSet.Start, rect.Left);
set.Connect(
Resource.Id.layout_change, ConstraintSet.End,
Resource.Id.device_feature, ConstraintSet.Start, 0
);
}
else
{
//Device feature is placed horizontally
var statusBarHeight = calculateStatusBarHeight();
var toolBarHeight = calculateToolbarHeight();
set.SetMargin(
Resource.Id.device_feature, ConstraintSet.Top,
rect.Top - statusBarHeight - toolBarHeight
);
set.Connect(
Resource.Id.layout_change, ConstraintSet.Top,
Resource.Id.device_feature, ConstraintSet.Bottom, 0
);
}
//Set the view to visible and apply constraints
set.SetVisibility(Resource.Id.device_feature, (int)SystemUiFlags.Visible); // public static final int VISIBLE = 0x00000000;
set.ApplyTo(constraintLayout);
}
int calculateToolbarHeight()
{
var typedValue = new TypedValue();
if (Theme.ResolveAttribute(Android.Resource.Attribute.ActionBarSize, typedValue, true))
{
return TypedValue.ComplexToDimensionPixelSize(typedValue.Data, Resources.DisplayMetrics);
}
else
{
return 0;
}
}
int calculateStatusBarHeight()
{
var rect = new Rect();
Window.DecorView.GetWindowVisibleDisplayFrame(rect);
return rect.Top;
}
IExecutor runOnUiThreadExecutor()
{
return new MyExecutor();
}
class MyExecutor : Java.Lang.Object, IExecutor
{
Handler handler = new Handler(Looper.MainLooper);
public void Execute(IRunnable r)
{
handler.Post(r);
}
}
public void Accept(Java.Lang.Object newLayoutInfo) // Object will be WindowLayoutInfo
{
Log.Info(TAG, "===LayoutStateChangeCallback.Accept");
Log.Info(TAG, newLayoutInfo.ToString());
printLayoutStateChange(newLayoutInfo as WindowLayoutInfo);
}
protected override void OnStart()
{
base.OnStart();
wm.RegisterLayoutChangeCallback(runOnUiThreadExecutor(), this);
}
protected override void OnStop()
{
base.OnStop();
wm.UnregisterLayoutChangeCallback(this);
}
}
}

Просмотреть файл

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.companyname.windowmanager" android:installLocation="auto">
<uses-sdk android:minSdkVersion="29" android:targetSdkVersion="30" />
<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"></application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>

Просмотреть файл

@ -0,0 +1,26 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Android.App;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("WindowManager")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("WindowManager")]
[assembly: AssemblyCopyright("Copyright © 2018")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

11
WindowManager/README.md Normal file
Просмотреть файл

@ -0,0 +1,11 @@
# Window Manager sample for Surface Duo with Xamarin.Android
Visit the [Window Manager for Xamarin docs](https://docs.microsoft.com/dual-screen/xamarin/) for more information on using Window Manager in your apps.
![Window Manager example spanned across two screens](Screenshots/xamarin-window-manager.png)
## Related links
- [Introduction to dual-screen devices](https://docs.microsoft.com/dual-screen/introduction)
- [Get the Surface Duo emulator](https://docs.microsoft.com/dual-screen/android/emulator/)
- [Xamarin.Android documentation](https://docs.microsoft.com/xamarin/android/)

Просмотреть файл

@ -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.xml),
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.xml
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.xml file, or R.strings.first_string to reference the first
string in the dictionary file values/strings.xml.

Просмотреть файл

@ -0,0 +1,34 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeWidth="1"
android:strokeColor="#00000000">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

Просмотреть файл

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#1976d2"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

Просмотреть файл

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/constraint_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/window_metrics"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="20dp"
android:text="@string/window_metrics"
android:textSize="20sp"
app:layout_constraintBottom_toTopOf="@+id/layout_change"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<TextView
android:id="@+id/layout_change"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="20dp"
android:text="@string/layout_change_text"
android:textSize="20sp"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toTopOf="@+id/configuration_changed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/window_metrics" />
<TextView
android:id="@+id/configuration_changed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="20dp"
android:text="@string/configuration_changed"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/layout_change" />
<!-- It's not important where this view is placed by default, it will be adjusted dynamically in runtime -->
<View
android:id="@+id/device_feature"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@android:color/holo_red_dark"
android:visibility="gone"
tools:ignore="MissingConstraints" />
</androidx.constraintlayout.widget.ConstraintLayout>

Просмотреть файл

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Просмотреть файл

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Двоичные данные
WindowManager/Resources/mipmap-hdpi/ic_launcher.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.9 KiB

Двоичные данные
WindowManager/Resources/mipmap-hdpi/ic_launcher_foreground.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.4 KiB

Двоичные данные
WindowManager/Resources/mipmap-hdpi/ic_launcher_round.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 4.8 KiB

Двоичные данные
WindowManager/Resources/mipmap-mdpi/ic_launcher.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.0 KiB

Двоичные данные
WindowManager/Resources/mipmap-mdpi/ic_launcher_foreground.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 958 B

Двоичные данные
WindowManager/Resources/mipmap-mdpi/ic_launcher_round.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.7 KiB

Двоичные данные
WindowManager/Resources/mipmap-xhdpi/ic_launcher.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 4.4 KiB

Двоичные данные
WindowManager/Resources/mipmap-xhdpi/ic_launcher_foreground.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.0 KiB

Двоичные данные
WindowManager/Resources/mipmap-xhdpi/ic_launcher_round.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 6.7 KiB

Двоичные данные
WindowManager/Resources/mipmap-xxhdpi/ic_launcher.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 6.2 KiB

Двоичные данные
WindowManager/Resources/mipmap-xxhdpi/ic_launcher_foreground.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 3.3 KiB

Двоичные данные
WindowManager/Resources/mipmap-xxhdpi/ic_launcher_round.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 10 KiB

Двоичные данные
WindowManager/Resources/mipmap-xxxhdpi/ic_launcher.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 8.9 KiB

Двоичные данные
WindowManager/Resources/mipmap-xxxhdpi/ic_launcher_foreground.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 4.8 KiB

Двоичные данные
WindowManager/Resources/mipmap-xxxhdpi/ic_launcher_round.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 15 KiB

Просмотреть файл

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#1976d2</color>
<color name="colorPrimaryDark">#004ba0</color>
<color name="colorAccent">#ffb300</color>
</resources>

Просмотреть файл

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#2C3E50</color>
</resources>

Просмотреть файл

@ -0,0 +1,6 @@
<resources>
<string name="app_name">WindowManager09</string>
<string name="window_metrics">window metrics</string>
<string name="layout_change_text">layout change text</string>
<string name="configuration_changed">Using one logic/physical display - unspanned</string>
</resources>

Просмотреть файл

@ -0,0 +1,11 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>

Просмотреть файл

@ -0,0 +1,130 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{244F483B-70E4-4D11-A0E9-355C9FBF9748}</ProjectGuid>
<ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<TemplateGuid>{122416d6-6b49-4ee2-a1e8-b825f31c79fe}</TemplateGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>WindowManagerDemo</RootNamespace>
<AssemblyName>WindowManagerDemo</AssemblyName>
<FileAlignment>512</FileAlignment>
<Deterministic>True</Deterministic>
<AndroidApplication>True</AndroidApplication>
<AndroidResgenFile>Resources\Resource.designer.cs</AndroidResgenFile>
<AndroidResgenClass>Resource</AndroidResgenClass>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<AndroidUseLatestPlatformSdk>false</AndroidUseLatestPlatformSdk>
<TargetFrameworkVersion>v11.0</TargetFrameworkVersion>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
<MonoAndroidResourcePrefix>Resources</MonoAndroidResourcePrefix>
<MonoAndroidAssetsPrefix>Assets</MonoAndroidAssetsPrefix>
<AndroidEnableSGenConcurrent>true</AndroidEnableSGenConcurrent>
<AndroidUseAapt2>true</AndroidUseAapt2>
<AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidClientHandler</AndroidHttpClientHandlerType>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>True</DebugSymbols>
<DebugType>portable</DebugType>
<Optimize>False</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AndroidUseSharedRuntime>True</AndroidUseSharedRuntime>
<AndroidLinkMode>None</AndroidLinkMode>
<EmbedAssembliesIntoApk>False</EmbedAssembliesIntoApk>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugSymbols>True</DebugSymbols>
<DebugType>portable</DebugType>
<Optimize>True</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AndroidManagedSymbols>true</AndroidManagedSymbols>
<AndroidUseSharedRuntime>False</AndroidUseSharedRuntime>
<AndroidLinkMode>SdkOnly</AndroidLinkMode>
<EmbedAssembliesIntoApk>True</EmbedAssembliesIntoApk>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Xml" />
<Reference Include="System.Core" />
<Reference Include="Mono.Android" />
<Reference Include="System.Numerics" />
<Reference Include="System.Numerics.Vectors" />
</ItemGroup>
<ItemGroup>
<Compile Include="MainActivity.cs" />
<Compile Include="Resources\Resource.designer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="Resources\AboutResources.txt" />
<None Include="Properties\AndroidManifest.xml" />
<None Include="Assets\AboutAssets.txt" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\layout\activity_main.xml">
<SubType>Designer</SubType>
</AndroidResource>
<AndroidResource Include="Resources\values\colors.xml" />
<AndroidResource Include="Resources\values\ic_launcher_background.xml" />
<AndroidResource Include="Resources\values\strings.xml" />
<AndroidResource Include="Resources\values\styles.xml" />
<AndroidResource Include="Resources\mipmap-anydpi-v26\ic_launcher.xml" />
<AndroidResource Include="Resources\mipmap-anydpi-v26\ic_launcher_round.xml" />
<AndroidResource Include="Resources\mipmap-hdpi\ic_launcher.png" />
<AndroidResource Include="Resources\mipmap-hdpi\ic_launcher_foreground.png" />
<AndroidResource Include="Resources\mipmap-hdpi\ic_launcher_round.png" />
<AndroidResource Include="Resources\mipmap-mdpi\ic_launcher.png" />
<AndroidResource Include="Resources\mipmap-mdpi\ic_launcher_foreground.png" />
<AndroidResource Include="Resources\mipmap-mdpi\ic_launcher_round.png" />
<AndroidResource Include="Resources\mipmap-xhdpi\ic_launcher.png" />
<AndroidResource Include="Resources\mipmap-xhdpi\ic_launcher_foreground.png" />
<AndroidResource Include="Resources\mipmap-xhdpi\ic_launcher_round.png" />
<AndroidResource Include="Resources\mipmap-xxhdpi\ic_launcher.png" />
<AndroidResource Include="Resources\mipmap-xxhdpi\ic_launcher_foreground.png" />
<AndroidResource Include="Resources\mipmap-xxhdpi\ic_launcher_round.png" />
<AndroidResource Include="Resources\mipmap-xxxhdpi\ic_launcher.png" />
<AndroidResource Include="Resources\mipmap-xxxhdpi\ic_launcher_foreground.png" />
<AndroidResource Include="Resources\mipmap-xxxhdpi\ic_launcher_round.png" />
</ItemGroup>
<ItemGroup />
<ItemGroup>
<PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.2.0.5" />
<PackageReference Include="Xamarin.AndroidX.ConstraintLayout">
<Version>2.0.4.2</Version>
</PackageReference>
<PackageReference Include="Xamarin.AndroidX.Window">
<Version>1.0.0-alpha09</Version>
</PackageReference>
<PackageReference Include="Xamarin.Google.Android.Material" Version="1.4.0" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\ic_launcher_background.xml">
<Generator>MSBuild:UpdateGeneratedFiles</Generator>
<SubType>Designer</SubType>
</AndroidResource>
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-v24\ic_launcher_foreground.xml">
<Generator>MSBuild:UpdateGeneratedFiles</Generator>
<SubType>Designer</SubType>
</AndroidResource>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

Просмотреть файл

@ -25,6 +25,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DragAndDrop", "DragAndDrop\
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ListDetail", "ListDetail\ListDetail.csproj", "{F8DB8953-1A58-4EC1-A971-4976036B4BBE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowManager", "WindowManager\WindowManager.csproj", "{244F483B-70E4-4D11-A0E9-355C9FBF9748}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -82,6 +84,12 @@ Global
{F8DB8953-1A58-4EC1-A971-4976036B4BBE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F8DB8953-1A58-4EC1-A971-4976036B4BBE}.Release|Any CPU.Build.0 = Release|Any CPU
{F8DB8953-1A58-4EC1-A971-4976036B4BBE}.Release|Any CPU.Deploy.0 = Release|Any CPU
{244F483B-70E4-4D11-A0E9-355C9FBF9748}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{244F483B-70E4-4D11-A0E9-355C9FBF9748}.Debug|Any CPU.Build.0 = Debug|Any CPU
{244F483B-70E4-4D11-A0E9-355C9FBF9748}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{244F483B-70E4-4D11-A0E9-355C9FBF9748}.Release|Any CPU.ActiveCfg = Release|Any CPU
{244F483B-70E4-4D11-A0E9-355C9FBF9748}.Release|Any CPU.Build.0 = Release|Any CPU
{244F483B-70E4-4D11-A0E9-355C9FBF9748}.Release|Any CPU.Deploy.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -96,6 +104,7 @@ Global
{4D77AA39-048C-4733-BE7F-BD41C8EE278B} = {CB956396-560E-4974-9CE4-0EBC33AB1E12}
{9B727405-CB01-4FCD-9134-9040F230F741} = {CB956396-560E-4974-9CE4-0EBC33AB1E12}
{F8DB8953-1A58-4EC1-A971-4976036B4BBE} = {CB956396-560E-4974-9CE4-0EBC33AB1E12}
{244F483B-70E4-4D11-A0E9-355C9FBF9748} = {CB956396-560E-4974-9CE4-0EBC33AB1E12}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3B8A9A9E-B7A6-4318-BA6C-715546DFFFB6}