0

I am trying to create a list of cards representing categories and I opted to use a custom widget instead of the provided Card widget so I can use images and apply rounded borders to them and I found that using Container does just that, the Ink and InkWell widgets are used to show the ripple effect on tapped.

This is the code for the screen

class CategoriesScreen extends ConsumerWidget {
  const CategoriesScreen({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final categoriesNavigationKey = ref.read(categoriesNavigationKeyProvider);
    return PopScope(
      canPop: false,
      onPopInvokedWithResult: (route, result) =>
          categoriesNavigationKey.currentState!.maybePop(),
      child: Navigator(
        key: categoriesNavigationKey,
        initialRoute: '/',
        onGenerateRoute: (settings) {
          if (settings.name != null) {
            final String routeName = settings.name!;
            if (routeName == '/categoryDetails') {
              return CupertinoPageRoute<void>(
                settings: settings,
                builder: (context) => CategoryDetailsScreen(
                  category: Category.fromJson(
                    settings.arguments as Map<String, dynamic>,
                  ),
                ),
              );
            }
          }
          return CupertinoPageRoute<void>(
            settings: settings,
            builder: (context) => const _CategoriesList(),
          );
        },
      ),
    );
  }
}

Categories List:

class _CategoriesList extends ConsumerWidget {
  const _CategoriesList();
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final textTheme = Theme.of(context).textTheme;
    final categoriesScreenController = ref.watch(categoriesControllerProvider);

    return categoriesScreenController.maybeWhen(
      orElse: () {
        return Center(child: CircularProgressIndicator());
      },
      data: (categories) {
        return Scrollbar(
          child: SingleChildScrollView(
            child: Padding(
              padding: const EdgeInsets.all(24.0),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                spacing: 16,
                children: [
                  Text("Toutes nos catégories", style: textTheme.titleLarge),
                  for (final category in categories)
                    CategoryItem(category: category),
                ],
              ),
            ),
          ),
        );
      },
    );
  }
}

CategoryItem :

class CategoryItem extends ConsumerWidget {
  final Category category;
  final BorderRadius _borderRadius = const BorderRadius.all(
    Radius.circular(16),
  );
  const CategoryItem({super.key, required this.category});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Ink(
      decoration: BoxDecoration(
        borderRadius: _borderRadius,
        color: Theme.of(context).colorScheme.surface,
        boxShadow: kElevationToShadow[3],
      ),
      child: InkWell(
        onTap: () => ref
            .read(categoriesNavigationKeyProvider)
            .currentState
            ?.pushNamed('/categoryDetails', arguments: category.toJson()),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Container(
              clipBehavior: Clip.antiAlias,
              width: double.infinity,
              decoration: BoxDecoration(borderRadius: _borderRadius),
              child: Image.memory(
                category.image,
                fit: BoxFit.cover,
                height: 200,
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(22),
              child: Text(
                category.name,
                style: Theme.of(
                  context,
                ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Result (captured on a real device and on a release build) : https://streamable.com/et9scf

In the provided small video you can see that the list scrolling animation kind of decomposes the widget although it's altogether a one whole widget, this issue is clearly seen when tapping back to the main route on the Categories screen's navigator. Am I missing something here or is it an engine/framework issue?

1 Answer 1

0

Card-Component uses Material, so if you want to make your own CustomCard you need to wrap your whole card with the Material-Widget so that ink effects, clipping and shadows share the same surface.

With your approach, Ink can’t find such a surface and falls back to painting on the nearest ancestor Material, so the splash/image layer and your rounded-corner container end up in separate compositing layers that can drift apart during list or route animations.

So maybe try something like this:

class CategoryItem extends ConsumerWidget {
  const CategoryItem({super.key, required this.category});

  final Category category;
  static const _radius = BorderRadius.all(Radius.circular(16));

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Padding(                               
      padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
      child: Material(
        elevation: 3,                             
        borderRadius: _radius,
        color: Theme.of(context).colorScheme.surface,
        clipBehavior: Clip.antiAlias,             
        child: InkWell(
          onTap: () => ref
              .read(categoriesNavigationKeyProvider)
              .currentState
              ?.pushNamed(
                '/categoryDetails',
                arguments: category.toJson(),
              ),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Ink.image(
                image: MemoryImage(category.image),
                height: 200,
                width: double.infinity,
                fit: BoxFit.cover,
              ),
              Padding(
                padding: const EdgeInsets.all(22),
                child: Text(
                  category.name,
                  style: Theme.of(context)
                      .textTheme
                      .titleMedium
                      ?.copyWith(fontWeight: FontWeight.bold),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.