Skeletonizer

repository·main·Indexed 20 days ago

https://github.com/milad-akarie/skeletonizer

A Flutter package that automatically converts existing UI layouts into skeleton loaders by applying painting effects like shimmer. It allows developers to create loading states without building duplicate layouts, featuring tools like BoneMock for fake data, Skeleton annotations for behavior customization, and manual Bone widgets for custom skeletons. Supports various effects including ShimmerEffect, PulseEffect, and SolidColorEffect, with global configuration via SkeletonizerConfigData.

Tokens
4.9K
Snippets
20
Records
22
Agent score
69%

What's inside skeletonizer

  1. Use Skeleton annotations to customize behavior

    main

    Skeleton annotations allow you to change how specific widgets are treated when Skeletonizer is enabled. These annotations have no effect on the real layout when enabled is false.

    Common annotations include:

    • Skeleton.ignore: The widget will not be skeletonized.
    • Skeleton.leaf: Marks a container as a leaf, which is painted using a shader paint.
    • Skeleton.keep: The widget is not skeletonized but is painted exactly as is.
    • Skeleton.shade: The widget is not skeletonized but is shaded by a shader mask (required for CustomPainter widgets).
    • Skeleton.replace: Replaces the widget with a placeholder (useful for widgets that fail with fake data, like NetworkImage).
    • Skeleton.unite: Prevents multiple small bones from being drawn separately, treating them as one single bone.
    • Skeleton.ignorePointers: The widget will ignore pointer events when skeletonizer is enabled.
  2. How to provide fake data for Skeletonizer

    main

    Skeletonizer works by reducing an existing layout into a skeleton. Because the layout depends on data (e.g., the length of a list), you must provide fake data while loading so the layout has a shape to skeletonize.

    If you don't provide data, the widget tree might be empty, leaving nothing for Skeletonizer to process.

    Use BoneMock to easily generate realistic fake text for your mock data objects.

    // Example: Providing fake data using BoneMock
    final fakeUsers = List.filled(7, User(
        name: BoneMock.name,
        jobTitle: BoneMock.words(2),
        email: BoneMock.email,
        createdAt: BoneMock.date, 
      ),
    );
    
    return Skeletonizer(
      enabled: _loading,
      child: UserList(users: fakeUsers),
    );
  3. Customize iOS launch screen assets

    main

    To change the launch screen image for your iOS application, you can either replace the image files directly in the filesystem or use Xcode.

    Option 1: Filesystem replacement Replace the existing image files located in the example/ios/Runner/Assets.xcassets/LaunchImage.imageset/ directory with your own assets.

    Option 2: Using Xcode

    1. Open your Flutter project's iOS workspace using: open ios/Runner.xcworkspace.
    2. In the Xcode Project Navigator, navigate to Runner/Assets.xcassets.
    3. Drag and drop your desired images into the asset catalog.
    open ios/Runner.xcworkspace
  4. Configure global Skeletonizer settings via Theme or Provider

    main

    You can provide default configurations to all descendant Skeletonizer widgets using two methods:

    1. Using Theme Extensions

    Pass SkeletonizerConfigData as a theme extension in your MaterialApp. This is useful for setting different defaults for light and dark themes.

    MaterialApp(
      theme: ThemeData(
        extensions: const [
          SkeletonizerConfigData(), // light theme config
        ],
      ),
      darkTheme: ThemeData(
        brightness: Brightness.dark,
        extensions: const [
          SkeletonizerConfigData.dark(), // dark theme config
        ],
      ),
    );

    2. Using SkeletonizerConfig

    Wrap a widget tree with SkeletonizerConfig to provide inheritable configuration data to all descendant Skeletonizer widgets.

    SkeletonizerConfig(
        data: SkeletonizerConfigData(
          effect: const ShimmerEffect(),
          justifyMultiLineText: true,
          textBorderRadius: TextBoneBorderRadius(..),
          ignoreContainers: false,
        ),
        child: ...
    )
  5. Basic usage of Skeletonizer

    main

    To create skeleton loaders, wrap your existing layout with the Skeletonizer widget. You can control whether the skeleton effect is active using the enabled property. For sliver-based layouts, use SliverSkeletonizer or Skeletonizer.sliver.

    To prevent skeletonizing container widgets (like Cards or Containers) and only focus on the content inside them, set ignoreContainers: true.

    Skeletonizer(
      enabled: _loading,
      child: ListView.builder(
        itemCount: 7,
        itemBuilder: (context, index) {
          return Card(
            child: ListTile(
              title: Text('Item number $index as title'),
              subtitle: const Text('Subtitle here'),
              trailing: const Icon(Icons.ac_unit),
            ),
          );
        },
      ),
    )
  6. Customize loading effects

    main

    You can customize the shimmer effect by providing a custom effect to the Skeletonizer widget. The ShimmerEffect allows you to configure the baseColor, highlightColor, and the duration of the animation.

    Skeletonizer(
      effect: const ShimmerEffect(
        baseColor: Colors.grey[300],
        highlightColor: Colors.grey[100],
        duration: Duration(seconds: 1),
      ),
      child: ...
    )
  7. Replace problematic widgets with Skeleton.replace

    main

    When using fake data, some widgets (like NetworkImage) might throw errors if passed invalid or empty URLs. Use Skeleton.replace to swap these widgets with a placeholder during the loading state.

    Skeleton.replace(
      width: 50,
      height: 50,
      replacement: // defaults to a DecoratedBox,
      child: Icon(Icons.ac_unit, size: 40),
    )
  8. Configure text skeleton properties

    main

    You can provide global text configuration options to Skeletonizer widgets to control how text bones are rendered. Key options include:

    • justifyMultiLineText: Boolean to control text justification.
    • textBoneBorderRadius: Controls the border radius of text bones using TextBoneBorderRadius.
    Skeletonizer(
        justifyMultiLineText: false,
        textBoneBorderRadius: TextBoneBorderRadius.fromHeightFactor(.5),
        ...
    )
  9. Animate the transition between skeleton and content

    main

    To animate the switch from the skeleton state to the actual content, set enableSwitchAnimation: true on the Skeletonizer widget. You can customize this transition using SwitchAnimationConfig.

    SwitchAnimationConfig properties:

    • duration: Duration of the animation (default: 300ms).
    • switchInCurve: Curve for the incoming animation.
    • switchOutCurve: Curve for the outgoing animation.
    • transitionBuilder: Custom transition builder (defaults to AnimatedSwitcher.defaultTransitionBuilder).
    • layoutBuilder: Custom layout builder (defaults to AnimatedSwitcher.defaultLayoutBuilder).
    • reverseDuration: Duration for the reverse animation.
    SwitchAnimationConfig({
        this.duration = const Duration(milliseconds: 300),
        this.switchInCurve = Curves.linear,
        this.switchOutCurve = Curves.linear,
        this.transitionBuilder = AnimatedSwitcher.defaultTransitionBuilder,
        this.layoutBuilder = AnimatedSwitcher.defaultLayoutBuilder,
        this.reverseDuration,
    });
  10. Create skeletons manually using Bone widgets

    main

    If you want to bypass the automatic skeleton generation from real widgets, you can craft custom skeletons using Bone widgets. Bone widgets are designed to mimic common UI components and automatically use inherited theme data (like font size, line height, or icon sizing) to ensure the skeleton matches your actual UI layout.

    Available Bone types:

    • Bone(width, height): A generic shape.
    • Bone.circle(size): A circular shape.
    • Bone.square(size): A square shape.
    • Bone.text(words: int): Mimics text. Defaults to 3 words (5 letters each).
    • Bone.multiText(lines: int): Mimics multiline text.
    • Bone.icon(): Mimics an icon, reading size from the inherited theme.
    • Bone.button(): Mimics Material buttons.
    • Bone.iconButton(): Mimics icon buttons.

    When using the manual approach, only the Bone widgets are shaded/shimmered. This allows you to wrap other widgets like Card or Container without them being shaded, resulting in a seamless effect.

    Skeletonizer.zone(
        child: Card(
          child: ListTile(
            leading: Bone.circle(size: 48),
            title: Bone.text(words: 2),
            subtitle: Bone.text(),
            trailing: Bone.icon(),
          ),
        ),
    );
  11. Reference: Skeletonizer widget configuration options

    main

    The following configuration options are available on the Skeletonizer widget:

    • ignoreContainers: If true, all containers are ignored and only their children are skeletonized.
    • containersColor: If provided, all containers will be painted with this color. Otherwise, the actual color is used.