AndreiMisiukevich / CardView

CardsView | CarouselView | CoverflowView | CubeView for Xamarin.Forms
MIT License
710 stars 114 forks source link

Calculating height of non visible item #388

Closed dzebas closed 3 years ago

dzebas commented 3 years ago

Hi,

I've a complex situation, will try to describe as much as I can, sorry for a long explanation.

I'm trying to build something similar to Gmail/Outlook email apps, so user can swipe between emails and also scroll up/down inside email. You'll notice in my XAML below that I use custom WebView renderer (to set WebView height same as contents) inside another ScrollView so everything can be scrolled - subject, to/from, email body etc.

When ItemAppeared is triggered I'm retrieving email from the server and adding at then end of ItemsSource.

Problem 1:

If I scroll up/down on current carousel page, then OnPageFinished in WebView rendered is triggered for next page and height is not calculated correctly (probably because page it's not visible yet?). If instead of up/down I scroll to the left so next page gets visible then height is correct.

Problem 2:

If I comment await System.Threading.Tasks.Task.Delay( 100 ); in .ItemAppeared then page height is never calculated correctly.

Expected Results:

I think if I don't scroll to next page it's layout should not be calculated?

My XAML:

<cards:CarouselView 
        x:Name="carousel"
        ItemsSource="{Binding Emails}"
        IsVerticalSwipeEnabled="True"
        IsCyclical="False"
        IsViewReusingEnabled="False"
        DesiredMaxChildrenCount="3"
        VerticalSwipeThresholdDistance="60"
        ItemSwiped="CarouselView_ItemAppeared">

        <cards:CarouselView.ItemTemplate>
            <DataTemplate>
                <ScrollView>
                    <StackLayout VerticalOptions="FillAndExpand">
                        <Label Text="{Binding subject}" FontSize="Large" Margin="5,0,5,10"/>
                        <ctrl:CustomWebView x:Name="webView" VerticalOptions="FillAndExpand">
                            <WebView.Source>
                                <HtmlWebViewSource Html="{Binding html}"/>
                            </WebView.Source>
                        </ctrl:CustomWebView>
                    </StackLayout>
                </ScrollView>
            </DataTemplate>
        </cards:CarouselView.ItemTemplate>
    </cards:CarouselView >
        private async void CarouselView_ItemAppeared( PanCardView.CardsView view, PanCardView.EventArgs.ItemAppearedEventArgs args )
        {
            await System.Threading.Tasks.Task.Delay( 100 );
            viewModel.LoadNextCommand.Execute( args.Index );
        }

Custom WebView renderer:

using WebView = Android.Webkit.WebView;

[assembly: ExportRenderer( typeof( CustomWebView ), typeof( CustomWebViewRenderer ) )]
namespace Happen.Mobile.Droid.Renderers
{
    public class CustomWebViewRenderer : WebViewRenderer
    {
        static CustomWebView custWebView = null;
        WebView webView;

        public CustomWebViewRenderer( Context context ) : base( context )
        {
        }

        class CustomWebViewClient : Android.Webkit.WebViewClient
        {
            public override async void OnPageFinished( WebView view, string url )
            {
                if (custWebView != null)
                {
                    int i = 10;
                    while (view.ContentHeight == 0 && i-- > 0)
                        await System.Threading.Tasks.Task.Delay( 100 ); 

                    custWebView.HeightRequest = view.ContentHeight;
                }

                base.OnPageFinished( view, url );
            }
        }

        protected override void OnElementChanged( ElementChangedEventArgs<Xamarin.Forms.WebView> e )
        {
            base.OnElementChanged( e );

            custWebView = e.NewElement as CustomWebView;
            webView = Control;

            Control.Settings.BuiltInZoomControls = true;
            Control.Settings.DisplayZoomControls = false;
            Control.Settings.LoadWithOverviewMode = true;

            if (e.OldElement == null)
            {
                webView.SetWebViewClient( new CustomWebViewClient() );
            }
        }
    }
}
AndreiMisiukevich commented 3 years ago

Hi, Weird, if you scroll left/right all is good, but if up/down the issue appears? I don't think I can do much w/out reproduction.

Maybe try to remove DesiredMaxChildrenCount="3" That's not what you think, I guess)

AndreiMisiukevich commented 3 years ago

Also, change ItemSwiped="CarouselView_ItemAppeared" to ItemAppeared

dzebas commented 3 years ago

Hi Andrei,

Yes, it's a weird problem. It also happens with Xamarin CarouselView. For some reason when you first swipe up/down on page 1, then layout is triggered on page 2 even if not visible yet. If you swipe left then page 2 gets visible a little and then layout height is calculated correctly.

Changing DesiredMaxChildrenCount didn't fix it. I completely understand that it's very hard to reproduce without a demo, hopefully I've enough time at some point to produce one.

Love your CardView, it's fantastic! Greetings from Lithuania :)

AndreiMisiukevich commented 3 years ago

@dzebas hm, if the same issue exists on XF carousel, then it's not a CardsView bug. That's more WebView issue.

AndreiMisiukevich commented 3 years ago

Try to switch CarouselView to CoverFlowView (just replace one view with another. That's it)

dzebas commented 3 years ago

Thank you. Interesting, switching to CoverFlowView seems to be better, but still calculates heights incorrect in some cases, but this time too short. Also I get this strange zoom in/out bounce animation when you switch pages?

AndreiMisiukevich commented 3 years ago

Yes, you probably switch indices from VM? For example set index = 2 when it was 0?

If yes, then it's expected

But yeah, I think I need the reproduction sample

Also, I propose to load the page not when the card is built, but when the card is appearing

dzebas commented 3 years ago

It works this way:

  1. In VM Prism's InitializeAsync is called and I retrieve data from the sever
  2. Once I get email from server I set Html (bindable property of WebView as per my example in 1st post)
  3. This triggers ItemAppeared (see my code in 1st post) so I retrieve second email and add it to ItemsSource
  4. Then I manually swipe to second page (email) this again triggers ItemAppeared, so repeat steps 2-4 until end of emails list
  5. Also, once I load 4th email I delete 1st one from ItemsSource as I only keep 3 pages at any time. This also works when swiping back - I insert at the beginning and delete last one.

I'm deleting emails because they can be quite big, also WebView itself is probably heavy on resources?

Thanks again for your support, really appreciate it.

AndreiMisiukevich commented 3 years ago

@dzebas sorry, I don't think I can help much here w/out reproduction. if this issue exists with other carousels then it's not related to CardsView. That's more about your approach to achieving your design goals.

It can be a WebView issue then.

Closing this issue as I can't help