Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 9 additions & 13 deletions .github/workflows/flutter.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
name: unit test

on: [push]
on: [push, pull_request]

jobs:
build:

test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v1
- uses: actions/setup-java@v1
with:
java-version: '12.x'
- uses: subosito/flutter-action@v1
with:
channel: beta
- run: flutter pub get
- run: flutter test
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
channel: stable
- run: flutter pub get
- run: flutter analyze
- run: flutter test
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@

## 4.0.0

* **BREAKING:** Use the standalone [`material_ui`](https://pub.dev/packages/material_ui) package instead of `package:flutter/material.dart`. Requires Flutter `>=3.44.0` and Dart `^3.12.0`.
* Keep the animation duration in sync when `period` changes, and restart cleanly when `enabled` is toggled back on
* Start infinite loops with `AnimationController.repeat()` instead of a first `forward()` cycle
* Treat `direction` updates as paint work, not layout work
* Expand widget tests for construction, animation, looping, and highlight geometry
* Refresh README, example README, and add `docs/` for architecture and optimization notes
* Remove leftover `lib/main.dart` counter app from the package

## 3.0.0

* upgrade sdk constraint to support Dart 3
Expand Down
131 changes: 113 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,38 +1,133 @@
# Shimmer

[![pub package](https://img.shields.io/pub/v/shimmer.svg)](https://pub.dartlang.org/packages/shimmer) ![](https://github.com/hnvn/flutter_shimmer/workflows/unit%20test/badge.svg)
[![pub package](https://img.shields.io/pub/v/shimmer.svg)](https://pub.dev/packages/shimmer)
![unit test](https://github.com/hnvn/flutter_shimmer/workflows/unit%20test/badge.svg)

A package provides an easy way to add shimmer effect in Flutter project
A lightweight Flutter widget that paints a moving highlight over placeholder
UI. Typical uses are skeleton screens while data loads, and a sliding highlight
on a call to action.

<p>
<img src="https://github.com/hnvn/flutter_shimmer/blob/master/screenshots/loading_list.gif?raw=true"/>
<img src="https://github.com/hnvn/flutter_shimmer/blob/master/screenshots/slide_to_unlock.gif?raw=true"/>
</p>

## How to use
## Install

```yaml
dependencies:
shimmer: ^4.0.0
material_ui: ^1.0.1
```

`shimmer` 4.0 uses Flutter's standalone [`material_ui`](https://pub.dev/packages/material_ui) package (Flutter 3.44+). Apps that still import `package:flutter/material.dart` can wrap those subtrees in `MaterialUiCompatibilityBridge`.

```dart
import 'package:material_ui/material_ui.dart';
import 'package:shimmer/shimmer.dart';
```

## Usage

### Skeleton placeholder

`Shimmer.fromColors` is the usual constructor. Build the child from solid
shapes (`Container`, `Row`, `Column`). The gradient replaces those colors;
transparent pixels stay transparent.

```dart
Shimmer.fromColors(
baseColor: Colors.grey.shade300,
highlightColor: Colors.grey.shade100,
child: Column(
children: [
Container(height: 200, color: Colors.white),
const SizedBox(height: 16),
Container(height: 12, color: Colors.white),
const SizedBox(height: 8),
Container(height: 12, width: 200, color: Colors.white),
],
),
);
```

Dark theme:

```dart
Shimmer.fromColors(
baseColor: Colors.grey.shade800,
highlightColor: Colors.grey.shade600,
child: placeholder,
);
```

### Custom gradient

Use the default constructor when you need a `RadialGradient`, `SweepGradient`,
or a `LinearGradient` that follows `Theme`.

```dart
SizedBox(
width: 200.0,
height: 100.0,
child: Shimmer.fromColors(
baseColor: Colors.red,
highlightColor: Colors.yellow,
child: Text(
'Shimmer',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 40.0,
fontWeight:
FontWeight.bold,
),
),
Shimmer(
gradient: LinearGradient(
colors: [
Theme.of(context).colorScheme.surfaceContainerHighest,
Theme.of(context).colorScheme.surface,
Theme.of(context).colorScheme.surfaceContainerHighest,
],
stops: const [0.35, 0.5, 0.65],
),
child: placeholder,
);
```

### Direction, speed, and loops

```dart
Shimmer.fromColors(
direction: ShimmerDirection.rtl,
period: const Duration(milliseconds: 1200),
loop: 0, // 0 = forever
enabled: isLoading,
baseColor: Colors.grey.shade300,
highlightColor: Colors.grey.shade100,
child: placeholder,
);
```

| Parameter | Default | Role |
|-------------|----------------|------|
| `child` | required | Opaque area the highlight is blended onto |
| `gradient` | required\* | Highlight colors (`fromColors` builds this for you) |
| `direction` | `ltr` | `ltr`, `rtl`, `ttb`, `btt` |
| `period` | `1500ms` | Duration of one pass |
| `loop` | `0` | Passes before stopping; `0` repeats forever |
| `enabled` | `true` | `false` pauses the animation |

\*Required on `Shimmer(...)`. `Shimmer.fromColors` takes `baseColor` and
`highlightColor` instead.

## Performance

- Wrap a **list of placeholders in one `Shimmer`**, not one `Shimmer` per row.
- Keep `child` simple and static. Fancy widgets (images, text with decoration,
elevation) often look wrong because the shader replaces their colors.
- Toggle `enabled` to `false` when loading finishes so the ticker stops.

## Example

The `example/` app shows a loading list and a “slide to unlock” highlight.
From the repository root:

```bash
cd example && flutter run
```

## How it works

`Shimmer` drives an `AnimationController` and paints a `ShaderMaskLayer` over
the child (`BlendMode.srcIn`). The highlight rectangle is three times the
child size so the band can travel fully across the widget.

Project internals, tests, and further optimization notes live in
[`docs/overview.md`](docs/overview.md) and
[`docs/optimization.md`](docs/optimization.md).
12 changes: 7 additions & 5 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,15 @@ analyzer:
# Ignore analyzer hints for updating pubspecs when using Future or
# Stream and not importing dart:async
# Please see https://github.com/flutter/flutter/pull/24528 for details.
sdk_version_async_exported_from_core: ignore
exclude:
- bin/cache/**
- build/**
- android/**
- ios/**
- web/**
- windows/**
- macos/**
- linux/**

linter:
rules:
Expand All @@ -52,7 +58,6 @@ linter:
- avoid_field_initializers_in_const_classes
- avoid_function_literals_in_foreach_calls
- avoid_init_to_null
- avoid_null_checks_in_equality_operators
- avoid_private_typedef_functions
- avoid_relative_lib_imports
- avoid_renaming_method_parameters
Expand Down Expand Up @@ -87,12 +92,10 @@ linter:
- hash_and_equals
- implementation_imports
# - invariant_booleans # too many false positives: https://github.com/dart-lang/linter/issues/811
- iterable_contains_unrelated_type
# - join_return_with_assignment # not yet tested
- library_names
- library_prefixes
# - lines_longer_than_80_chars # not yet tested
- list_remove_unrelated_type
# - literal_only_boolean_expressions # too many false positives: https://github.com/dart-lang/sdk/issues/34181
- no_adjacent_strings_in_list
- no_duplicate_case_values
Expand All @@ -102,7 +105,6 @@ linter:
# - one_member_abstracts # too many false positives
# - only_throw_errors # https://github.com/flutter/flutter/issues/5792
- overridden_fields
- package_api_docs
- package_names
- package_prefixed_library_names
# - parameter_assignments # we do this commonly
Expand Down
18 changes: 18 additions & 0 deletions docs/lessons/material-ui-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Lessons: material_ui migration

## Add the package before trusting dart fix pubspec edits

`dart fix --apply --code=migrate_design_widgets` rewrites imports correctly, but
it may add `material_ui: any` to `example/pubspec.yaml`. Replace that with a
semver range (`^1.0.1`) after `flutter pub add material_ui`.

## Import order

The fix inserts `package:material_ui/material_ui.dart` where
`package:flutter/material.dart` was. Re-sort so `package:flutter/...` imports
stay together above `material_ui` (`directives_ordering`).

## Package major version

Treat the move off SDK Material as a breaking release. Consumers on Flutter
older than 3.44 cannot resolve `material_ui` 1.x.
16 changes: 16 additions & 0 deletions docs/lessons/widget-tests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Lessons

## Widget tests need Directionality for Text

Pumping `Text` (or any `RichText`) under `Shimmer` alone fails with
`No Directionality widget found`. Wrap the widget under test in
`Directionality` (or `MaterialApp`) inside the test helper, even when
the production widget does not require it.

## Do not assert animation completion at exactly `period`

`AnimationController` may still be running after `pump(period)` because
the ticker starts on a later frame. For finite `loop` tests, use
`pumpAndSettle()`. To prove `period` updates without restarting, lengthen
the duration near the end of a cycle and assert it finishes in the
remaining fraction of the **new** duration.
68 changes: 68 additions & 0 deletions docs/optimization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Optimization notes

Changes already applied in this pass are listed first. Items below that are
suggestions: they improve the package but change behavior, pub constraints, or
the public API enough to confirm before shipping.

## Applied

- Start infinite loops with `AnimationController.repeat()` instead of one
`forward()` cycle then `repeat()`.
- Update `controller.duration` when `period` changes.
- Restart cleanly when `enabled` goes from `false` to `true`, including after
a finite `loop` has finished.
- `direction` calls `markNeedsPaint()` rather than `markNeedsLayout()`.
- Extract `shimmerHighlightRect` so highlight travel can be unit-tested.
- Remove leftover `lib/main.dart` (default Flutter counter, not part of the
package API).

## Rendering

1. **One shimmer per screen, not per row.** The example already does this.
Document it in app code reviews: each `Shimmer` owns a ticker and a
compositing layer.
2. **Optional `RepaintBoundary` around `Shimmer`.** Isolates the shader
animation from ancestors. Apps can wrap it; adding it inside the package
can surprise layout that relies on parent layer merging.
3. **Reuse the `Shader` when only `percent` is unchanged.** Today
`createShader` runs every paint, which is required because the rect moves.
If `percent` and `size` are unchanged, skip shader creation (the existing
setters already avoid extra paints).
4. **Avoid `saveLayer` elsewhere in the child.** `BlendMode.srcIn` already
composites; extra opacity/save layers on the child increase GPU cost.

## Animation

5. **`loop == 1` vs `loop == 0`.** Finite loops still use `forward` plus a
status listener. That is correct; do not switch finite loops to `repeat`
with a counter unless you also handle `enabled` mid-cycle.
6. **Reset `_count` when `loop` shrinks** while the widget is still mounted
and enabled. Current code only restarts when the controller is idle.
7. **Honor `Duration.zero` / negative `loop`.** Guard with asserts so bad
values fail in debug instead of hanging a ticker.

## API (confirm before adding)

8. **Theme-aware defaults.** A `Shimmer.theme(context)` helper that reads
`ColorScheme` would reduce boilerplate for skeleton screens, but it is a
new constructor.
9. **`Semantics` / accessibility.** Announce “Loading” while `enabled` is
true. Must be opt-in so existing trees do not get duplicate semantics.
10. **`fromColors` diagonal gradient.** `begin: topLeft` and
`end: centerRight` is slightly diagonal. A true horizontal band would
use `Alignment.centerLeft` → `Alignment.centerRight`. Changing it would
visually break apps that depend on the current slant.

## Project hygiene

11. **CI** still used `actions/checkout@v1`, Java 12, and the Flutter beta
channel. Unit tests for this package do not need Android toolchains.
12. **`analysis_options.yaml`** is a dated Flutter-repo snapshot. Rules such
as `iterable_contains_unrelated_type` were removed from the linter.
Prefer `package:flutter_lints` once you are ready to fix new findings.
13. **`example` pubspec** is still named `new_example` with a default
description. Rename for pub.dev example scoring if you republish.
14. **LICENSE** text is the Dart project BSD header (Google Inc., 2013), not
a project-specific copyright. Confirm with the maintainer before editing.
15. **Minimum Flutter SDK** was raised to `>=3.44.0` (Dart `^3.12.0`) with
the `material_ui` 4.0.0 migration.
Loading
Loading