Skip to content

Commit

Permalink
[android] improve performance of Shell flyout opening
Browse files Browse the repository at this point in the history
Context: dotnet#10713
Context: https://github.com/supershopping/ShellFlyoutLagging

In reviewing the above customer sample, there is a noticeable delay on
some devices when you open a Shell flyout for the first time.

This was pretty close to a default Shell setup:

    <Shell Shell.FlyoutBehavior="Flyout">
        <FlyoutItem Title="Main Page">
            <ShellContent Title="Main Page"  Route="MainPage" ContentTemplate="{DataTemplate local:MainPage}" />
        </FlyoutItem>
        <FlyoutItem Title="Test Page">
            <ShellContent Title="Test Page"  Route="TestPage" ContentTemplate="{DataTemplate local:TestPage}" />
        </FlyoutItem>
    </Shell>

Reviewing the code, there was an oddity where we create & replace
a `RecyclerView`'s `LayoutManager`:

    SetLayoutManager(_layoutManager = new ScrollLayoutManager(context, (int)Orientation.Vertical, false));
    SetLayoutManager(new LinearLayoutManager(context, (int)Orientation.Vertical, false));

We think this is likely something that went wrong during a git merge, etc.

In addition to removing code, I create a new `ShellRecyclerView` in Java
that reduces the amount of interop calls from C# to Java. We can do
all the work now in the `ShellRecyclerView`'s constructor:

    var adapter = new ShellFlyoutRecyclerAdapter(ShellContext, OnElementSelected);
    var recyclerView = new ShellRecyclerView(context, adapter);

`dotnet-trace` output shows these changes have a reasonable impact on
a Pixel 5 device:

    Before:
    35.34ms microsoft.maui.controls!Microsoft.Maui.Controls.Platform.Compatibility.ShellFlyoutTemplatedContentRenderer.CreateFlyoutContent()
    After:
    17.64ms microsoft.maui.controls!Microsoft.Maui.Controls.Platform.Compatibility.ShellFlyoutTemplatedContentRenderer.CreateFlyoutContent()

Saving about ~17.7ms of time opening the Shell flyout on Android.
  • Loading branch information
jonathanpeppers committed Jul 8, 2023
1 parent 05b741d commit 7905db6
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,8 @@ protected virtual void UpdateFlyoutContent()
void DisconnectRecyclerView()
{
if (_flyoutContentView.IsAlive() &&
_flyoutContentView is RecyclerViewContainer rvc &&
rvc.GetAdapter() is ShellFlyoutRecyclerAdapter sfra)
_flyoutContentView is ShellRecyclerView srv &&
srv.GetAdapter() is ShellFlyoutRecyclerAdapter sfra)
{
sfra.Disconnect();
}
Expand All @@ -219,29 +219,17 @@ AView CreateFlyoutContent(ViewGroup rootView)

DisconnectRecyclerView();

var context = ShellContext.AndroidContext;
var content = ((IShellController)ShellContext.Shell).FlyoutContent;
if (content == null)
{
var lp = new CoordinatorLayout.LayoutParams(CoordinatorLayout.LayoutParams.MatchParent, CoordinatorLayout.LayoutParams.MatchParent);
lp.Behavior = new AppBarLayout.ScrollingViewBehavior();
var context = ShellContext.AndroidContext;
var recyclerView = new RecyclerViewContainer(context)
{
LayoutParameters = lp
};

recyclerView.SetAdapter(new ShellFlyoutRecyclerAdapter(ShellContext, OnElementSelected));

var adapter = new ShellFlyoutRecyclerAdapter(ShellContext, OnElementSelected);
var recyclerView = new ShellRecyclerView(context, adapter);
return recyclerView;
}

_contentView = new ShellViewRenderer(ShellContext.AndroidContext, content, MauiContext);

_contentView.PlatformView.LayoutParameters = new CoordinatorLayout.LayoutParams(LP.MatchParent, LP.MatchParent)
{
Behavior = new AppBarLayout.ScrollingViewBehavior()
};

_contentView = new ShellViewRenderer(context, content, MauiContext);
ShellRecyclerView.SetLayoutParameters(_contentView.PlatformView, scrollingEnabled: true);
return _contentView.PlatformView;
}

Expand Down Expand Up @@ -470,9 +458,9 @@ void OnFlyoutViewLayoutChanging()

void UpdateVerticalScrollMode()
{
if (_flyoutContentView is RecyclerView rv && rv.GetLayoutManager() is ScrollLayoutManager lm)
if (_flyoutContentView is ShellRecyclerView rv)
{
lm.ScrollVertically = _shellContext.Shell.FlyoutVerticalScrollMode;
ShellRecyclerView.SetLayoutParameters(rv, _shellContext.Shell.FlyoutVerticalScrollMode != ScrollMode.Disabled);
}
}

Expand Down Expand Up @@ -796,66 +784,4 @@ void UpdateMinimumHeight()
}
}
}

class RecyclerViewContainer : RecyclerView
{
bool _disposed;
ScrollLayoutManager _layoutManager;

public RecyclerViewContainer(Context context) : base(context)
{
SetClipToPadding(false);
SetLayoutManager(_layoutManager = new ScrollLayoutManager(context, (int)Orientation.Vertical, false));
SetLayoutManager(new LinearLayoutManager(context, (int)Orientation.Vertical, false));
}

protected override void Dispose(bool disposing)
{
if (_disposed)
return;

_disposed = true;
if (disposing)
{
SetLayoutManager(null);
var adapter = this.GetAdapter();
SetAdapter(null);
adapter?.Dispose();
_layoutManager?.Dispose();
_layoutManager = null;
}

base.Dispose(disposing);
}
}

internal class ScrollLayoutManager : LinearLayoutManager
{
public ScrollMode ScrollVertically { get; set; } = ScrollMode.Auto;

public ScrollLayoutManager(Context context, int orientation, bool reverseLayout) : base(context, orientation, reverseLayout)
{
}

int GetVisibleChildCount()
{
var firstVisibleIndex = FindFirstCompletelyVisibleItemPosition();
var lastVisibleIndex = FindLastCompletelyVisibleItemPosition();
return lastVisibleIndex - firstVisibleIndex + 1;
}

public override bool CanScrollVertically()
{
switch (ScrollVertically)
{
case ScrollMode.Disabled:
return false;
case ScrollMode.Enabled:
return true;
default:
case ScrollMode.Auto:
return ChildCount > GetVisibleChildCount();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -451,18 +451,18 @@ ShellFlyoutRenderer GetDrawerLayout(ShellRenderer shellRenderer)
return (ShellFlyoutRenderer)shellContext.CurrentDrawerLayout;
}

RecyclerViewContainer GetFlyoutMenuReyclerView(ShellRenderer shellRenderer)
ShellRecyclerView GetFlyoutMenuReyclerView(ShellRenderer shellRenderer)
{
IShellContext shellContext = shellRenderer;
DrawerLayout dl = shellContext.CurrentDrawerLayout;

var flyout = dl.GetChildAt(0);
RecyclerViewContainer flyoutContainer = null;
ShellRecyclerView flyoutContainer = null;

if (dl.GetChildAt(1) is ViewGroup vg1 &&
vg1.GetChildAt(0) is RecyclerViewContainer rvc)
vg1.GetChildAt(0) is ShellRecyclerView rv)
{
flyoutContainer = rvc;
flyoutContainer = rv;
}

return flyoutContainer ?? throw new Exception("RecyclerView not found");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.microsoft.maui;

import android.content.Context;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import com.google.android.material.appbar.AppBarLayout;

/**
* Used by Shell flyout menu
*/
public class ShellRecyclerView extends RecyclerView {
public ShellRecyclerView(@NonNull Context context, Adapter adapter)
{
super(context);

setClipToPadding(false);
setLayoutManager(new LinearLayoutManager(context, RecyclerView.VERTICAL, false));
setLayoutParameters(this, true);
setAdapter(adapter);
}

/**
* Configures a default, scrollable CoordinatorLayout.LayoutParams that matches its parent
* @param view
* @param scrollingEnabled
*/
public static void setLayoutParameters(View view, boolean scrollingEnabled)
{
CoordinatorLayout.LayoutParams layoutParams = new CoordinatorLayout.LayoutParams(CoordinatorLayout.LayoutParams.MATCH_PARENT, CoordinatorLayout.LayoutParams.MATCH_PARENT);
if (scrollingEnabled) {
layoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior());
}
view.setLayoutParams(layoutParams);
}
}
Binary file modified src/Core/src/maui.aar
Binary file not shown.

0 comments on commit 7905db6

Please sign in to comment.