No uses CollectionView dentro de un ScrollView en Xamarin.Forms

Sobre el tema:

Contenido

En un articulo anterior hablé de como utilizar el CollectionView dentro de un ScrollView en Xamarin.Forms, esta vez te dire porque no debes hacerlo, sus consecuencias y una alternativa al ejemplo que planteamos la vez pasada.

En este articulo compartiré contigo un control personalizado del CollectionView que encapsula toda la lógica relacionada a nuestro caso de uso, que en esta ocasión es la Pagina de Perfil de Instagram donde se muestra una lista de imágenes en grilla con varios tabs para cambiar el contexto.

Nota: Este control lo puedes utilizar directamente en tus proyectos o simplemente adaptarlo a tus necesidades si no se ajusta a lo que buscas.

CollectionView dentro de un ScrollView

En el articulo anterior te hable un poco sobre el CollectionView dentro de un ScrollView, la experiencia de usuario de esta y su comportamiento. Si todavía no has leído el articulo, te recomiendo que lo hagas aquí.

En esta occasion, quiero hablarte de porque no utilizarlo en ningún contexto y te dare algunas alternativas que te pueden ayudar a conseguir el resultado que buscas.

¿Por que no utilizar CollectionView dentro de un ScrollView?

Una de las razones que mencione en el articulo pasado fue la Experiencia de usuario, al utilizar el CollectionView dentro de un ScrollView el comportamiento quizás no sea el que buscas.

Comportamiento no adecuado

A continuación te muestro un ejemplo de como se comporta un CollectionView dentro de un ScrollView:

CollectionView dentro de un ScrollVIew.
Nota: El header mostrado no pertenece al CollectionView.

Si te fijas bien, hasta que el CollectionView no llega hasta el fondo de la lista de items que maneja, el ScrollView no hace Scroll al menos que hagas scroll desde el header el cual no pertenece al CollectionView.

El rendimiento no es bueno con colecciones grandes de datos

Si optamos por crear un control personalizado, como el que muestro en el articulo anterior, donde deshabilitas el Scroll del CollectionView definiendo un alto fijo al control entonces nos encontraremos con problemas de memoria y nuestra aplicación se romperá.

La vez pasada, olvide hacer las pruebas de lugar, las pruebas para escribir artículos me dan pereza 😅. Gracias a Jorge Diego Crespo por la observación y por su ameno interés en querer ayudar a la comunidad. The best community ever!

Bien, lo que pasa es que no estamos reciclando las vistas en pantalla que es una de las funcionalidades mas interesantes del CollectionView, haciendo que tengamos problemas de memoria.

💡 Tip: El CollectionView es eficiente con colecciones grandes porque reutiliza elementos de vistas y requiere el uso de las vistas para almacenar las referencias de las vistas en caché.

Este es el principal problema por el cual no deberíamos utilizar un CollectionView con un alto definido dentro de un ScrollView.

Entonces, si la solución que presentamos antes no es la indicada, ¿que podemos hacer?

Alternativas a un CollectionView dentro de un ScrollView

En el articulo anterior les mostré un caso particular y muy popular donde podríamos necesitar cambiar de contextos o utilizar vistas que hagan scroll pero que compartan el mismo header. Esta es la pagina de usuario de Instagram. Vamos a verla:

Si se fijan, debajo de las historias, están los TabsView que nos permiten visualizar diferentes tipos de datos y contextos.

💡 Nota: Anteriormente, Instagram mostraba en los TabsView el tipo de vista que querías ver en la pagina ya sea Grid o Lista. Este comportamiento en Xamarin.Forms puede ser logrado con un TemplateSelector siempre y cuando el contexto de datos sea el mismo.

Vamos a ver algunas alternativas que nos pueden ayudar crear este tipo de interfaces de usuarios.

BindableLayout para colecciones de datos pequeñas

Bindable Layouts habilitan cualquier Layout para generar el contenido enlazado a una colección de elementos, con la opción de establecer la apariencia de cada elemento con DataTemplate

🚨 Nota: Cuando se utiliza este método y utilizamos grandes colecciones de datos también podríamos experimentar errores de memoria en nuestras aplicaciones porque las vistas no se reciclan.

Custom CollectionView para colleciones de datos grandes

A diferencia del control que mostré en el anterior articulo, este control lo que hace es simular el comportamiento del scroll de la pagina con un poco de animación en los diferentes elementos que se muestran en la pagina.

Para lograr esto hemos definido algunos parámetros por defecto como:

  • CustomHeader, de tipo View, recibe la instancia de la vista que estaremos utilizando como header y tiene un valor por defecto de null.
  • SecondContent, de tipo View, recibe la instancia de la vista que estaremos utilizando como contenido secundario y tiene un valor por defecto de null.


Con estos parámetros podemos tener un GoodCollactionView de la siguiente manera:

    public class GoodollectionView : CollectionView
    {
...
        [TypeConverter(typeof(ReferenceTypeConverter))]
        public View CustomHeader
        {
            set
            {
                _customHeader = value;
                _customHeader.SizeChanged += (o, e) => this.Header = new BoxView() { HeightRequest = _customHeader.Height };                
            }
        }

        [TypeConverter(typeof(ReferenceTypeConverter))]
        public View SecondContent
        {
            set => _secondContent = value;
        }

        private async void GoodCollectionView_Scrolled(object sender, ItemsViewScrolledEventArgs e)
        {
            double scrollY = e.VerticalOffset < 0 ? 0 : e.VerticalOffset;
            scrollY = scrollY > _customHeader.Height ? _customHeader.Height : scrollY;

            // Show or hide the header and scroll the second view
            await Task.WhenAll(_customHeader?.TranslateTo(0, -scrollY, 50), 
                _secondContent?.TranslateTo(0, -scrollY + _customHeader.Height, 50));
        }
    }

Cada vez que se haga scroll dentro de nuestro CollectionView calculados el scrollY y animamos la transición del header y el contenido secundario al mismo tiempo.

Nota: Hay una limitante. El control crea automáticamente un Header dentro del CollectionView, si creas un header sera sobre escrito por nuestro código y agregara un BoxView para simular el Margen del control. Si quieres usar un Header dentro del CollectionView solo establece un alto fijo a tu Header y a tu fila dentro Grid donde posicionas el header.

Una vez tenemos el control, lo podemos utilizar en nuestros proyectos de Xamarin Forms. Aquí un ejemplo en XAML:

...
    <Grid>

        <ctrls:GoodCollectionView
            x:Name="collectionView"
            Grid.RowSpan="2"
            CustomHeader="myCustomHeader"
            ItemSizingStrategy="MeasureFirstItem"
            ItemsLayout="VerticalGrid, 3"
            ItemsSource="{Binding Images}"
            SecondContent="mySecondView">

            <CollectionView.ItemTemplate>
                <DataTemplate>
                        ...
                </DataTemplate>
            </CollectionView.ItemTemplate>
        </ctrls:GoodCollectionView>

        <!-- YOUR VIEWS GOES HERE -->

        <StackLayout x:Name="myCustomHeader" VerticalOptions="Start">
            ...
        </StackLayout>

    </Grid>
...

Con esto podemos obtener un comportamiento similar al de la pagina de perfil de Instagram sin perder el reciclado de las vistas para evitar errores de memoria. Veamos un ejemplo:

CollectionView dentro de un ScrollVIew.
Nota: El header mostrado no pertenece al CollectionView.

Hay que tener en cuenta que este control funciona con la altura de nuestro Header, es decir, tienes que trabajar con altos definidos o en su defecto utilizar VerticalOptions=»Start» para definir el alto. Si no lo haces el alto sera -1.

Conclusión

Como pueden ver les he presentado dos alternativas para evitar el uso del CollectionView dentro del ScrollView. El control que se ha desarrollado esta muy adaptado a la problemática planteada, así que es muy probable que tengas que adaptar el mismo a tus necesidades si lo quieres utilizar.

Espero que este articulo de sea de utilidad, y nada déjenme saber en las redes que realmente les gusta este tipo de artículos y yo hare lo posible por darles lo mejor de lo mejor.

Ayuda a crecer nuestra comunidad y desbloquea el contenido debajo para acceder al repo de ejemplo. ⬇⬇⬇

Sin nada mas que agregar, ¡Nos vemos en la próxima!

¿Qué opinas de este contenido?
 
Luis Matos

Luis Matos

I help professionals and companies to create value solutions. I am a Systems Engineer, blockchain executive, and international mobile application speaker. Founder of the Malla Consulting Agency and several international technology communities.
Suscribirte
Notificar de
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x

Buscar en el sitio