flutter_blurhash 0.8.2 copy "flutter_blurhash: ^0.8.2" to clipboard
flutter_blurhash: ^0.8.2 copied to clipboard

Compact representation of a placeholder for an image. Encode a blurry image under 30 caracters for instant display like used by Medium

example/lib/main.dart

import 'dart:developer' as dev;
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter_blurhash/flutter_blurhash.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:inview_notifier_list/inview_notifier_list.dart';

const entries = [
  [
    r'f8C6M$9tcY,FKOR*00%2RPNaaKjZUawdv#K4$Ps:HXELTJ,@XmS2=yxuNGn%IoR*',
    'https://www.auto-moto.com/wp-content/uploads/sites/9/2021/04/home-peugeot-3008-750x410.jpg',
    'LG6'
  ],
  [
    r'f86RZIxu4TITofx]jsaeayozofWB00RP?w%NayMxkDt8ofM_Rjt8_4tRD$IUWAxu',
    'https://www.auto-moto.com/wp-content/uploads/sites/9/2021/04/home-peugeot-3008-750x410.jpg',
    'ED8'
  ],
  [
    r'LZG6p1{I^6rX}G=0jGR$Z|t7NLW,',
    'https://www.auto-moto.com/wp-content/uploads/sites/9/2021/04/home-peugeot-3008-750x410.jpg',
    'MT2'
  ],
  [
    r'L371cr_3RKKFsqICIVNG00eR?d-r',
    'https://www.auto-moto.com/wp-content/uploads/sites/9/2021/04/home-peugeot-3008-750x410.jpg',
    'TK1'
  ],
  [
    r'L371cr_3RKKFsqICIVNG00eR?d-r',
    'https://www.auto-moto.com/wp-content/uploads/sites/9/2021/04/home-peugeot-3008-750x410.jpg',
    'TK2'
  ],
  [
    r'L371cr_3RKKFsqICIVNG00eR?d-r',
    'https://www.auto-moto.com/wp-content/uploads/sites/9/2021/04/home-peugeot-3008-750x410.jpg',
    'TK3'
  ],
  [
    r'L371cr_3RKKFsqICIVNG00eR?d-r',
    'https://www.auto-moto.com/wp-content/uploads/sites/9/2021/04/home-peugeot-3008-750x410.jpg',
    'TK4'
  ],
];

const duration = Duration(milliseconds: 500);

const radius = Radius.circular(16);

const topMark = .7;

void main() => runApp(
    const MaterialApp(debugShowCheckedModeBanner: false, home: BlurHashApp()));

class BlurHashApp extends StatefulWidget {
  const BlurHashApp({super.key});

  @override
  BlurHashAppState createState() => BlurHashAppState();
}

class BlurHashAppState extends State<BlurHashApp> {
  double progression = 0;

  void onStarted() {
    debugPrint("Ready");
  }

  double norm(double value, double min, double max) =>
      (value - min) / (max - min);

  @override
  Widget build(BuildContext context) =>
      NotificationListener<ScrollNotification>(
          onNotification: (ScrollNotification notif) {
            // NO need to setState
            setState(() {
              progression = norm(notif.metrics.pixels, 0, 1);
              // print("Progression $progression / px ${notif.metrics.pixels}");
            });
            return true;
          },
          child: Stack(children: [
            FractionallySizedBox(
              heightFactor: topMark,
              child: Container(
                decoration: const BoxDecoration(
                  gradient: LinearGradient(
                    colors: [Color(0xEEFFFFFF), Color(0xCCFFFFFF)],
                    begin: Alignment.bottomCenter,
                    end: Alignment.topCenter,
                  ),
                ),
              ),
            ),
            Align(
              alignment: const Alignment(-.8, -.5),
              child: Container(
                margin: const EdgeInsets.only(top: 100),
                child: Header(progression: progression),
              ),
            ),
            //BackdropFilter(child: , filter: ImageFilter.blur(sigmaY: 15, sigmaX: 15)),
            buildInViewNotifierList()
          ]));

  Widget buildList() => ListView.builder(
      itemCount: entries.length,
      itemBuilder: (ctx, idx) => buildEntry(true, idx));

  Widget buildInViewNotifierList() => InViewNotifierList(
      itemCount: entries.length + 2,
      builder: (ctx, idx) => InViewNotifierWidget(
          id: '$idx',
          builder: (BuildContext context, bool isInView, Widget? child) {
            if (idx == 0) return const SizedBox(height: 500);
            if (idx == entries.length + 1) return const SizedBox(height: 800);

            return buildEntry(isInView, idx - 1);
          }),
      isInViewPortCondition:
          (double deltaTop, double deltaBottom, double viewPortDimension) =>
              deltaTop < (topMark * viewPortDimension)
      //&& deltaBottom > (0.3 * viewPortDimension)
      );

  Container buildEntry(bool isInView, int idx) => Container(
      padding: const EdgeInsets.only(left: 0, right: 200),
      height: 510,
      margin: const EdgeInsets.only(bottom: 24),
      child: isInView || idx == 0
          ? SynchronizedDisplay(
              hash: entries[idx][0],
              uri: entries[idx][1],
              title: entries[idx][2])
          : BlurHash(hash: entries[idx][0]));
}

class Header extends StatelessWidget {
  Header({
    super.key,
    required this.progression,
  });

  final gradient =
      ColorTween(begin: const Color(0xFF222222), end: Colors.black87);

  final double progression;

  @override
  Widget build(BuildContext context) {
    final base = progression / 100;
    final color = gradient.lerp(base);

    return Column(
      children: <Widget>[
        Text(
          "Discover",
          style: GoogleFonts.josefinSans(
            textStyle: TextStyle(
                color: color,
                fontSize: 180,
                height: .84,
                fontWeight: FontWeight.bold,
                decoration: TextDecoration.none),
          ),
        ),
        Container(
          margin: const EdgeInsets.only(top: 16),
          child: Text(
            "Our\nCollection",
            style: GoogleFonts.josefinSans(
              textStyle: TextStyle(
                  color: color,
                  fontSize: 130,
                  height: .84,
                  fontWeight: FontWeight.bold,
                  decoration: TextDecoration.none),
            ),
          ),
        ),
      ],
    );
  }
}

class SynchronizedDisplay extends StatefulWidget {
  const SynchronizedDisplay(
      {super.key, required this.hash, required this.uri, required this.title});

  final String hash;
  final String uri;
  final String title;

  @override
  SynchronizedDisplayState createState() => SynchronizedDisplayState();
}

class SynchronizedDisplayState extends State<SynchronizedDisplay>
    with SingleTickerProviderStateMixin {
  late Animation<double> animatedWidth;
  late AnimationController controller;

  double end = 100;

  @override
  Widget build(BuildContext context) => Stack(
        alignment: const Alignment(1.225, 0.0),
        children: [
          Transform.translate(
            // Animated width
            offset: Offset(animatedWidth.value, 0),
            child: Container(
              width: 200,
              decoration: const BoxDecoration(
                  gradient: LinearGradient(
                    colors: [Color(0xFF888888), Color(0xFFAAAAAA)],
                    stops: [.1, 1],
                    begin: Alignment.centerLeft,
                    end: Alignment.centerRight,
                  ),
                  borderRadius:
                      BorderRadius.only(topRight: radius, bottomRight: radius)),
            ),
          ),
          BlurHash(
            hash: widget.hash,
            image: widget.uri,
            duration: duration,
            onStarted: onStarted,
            onDecoded: onDecoded,
            onDisplayed: onDisplayed,
          ),
          const Align(
            alignment: Alignment(1.4, 0),
            child: Icon(
              Icons.chevron_right,
              size: 60,
              color: Colors.white,
            ),
          ),
          Transform.rotate(
            angle: pi * -.5,
            child: Text(
              widget.title,
              style: GoogleFonts.josefinSans(
                  textStyle: const TextStyle(
                      color: Color(0xFFDDDDDD),
                      fontSize: 45,
                      fontWeight: FontWeight.bold,
                      decoration: TextDecoration.none)),
            ),
          )
        ],
      );

  @override
  void initState() {
    super.initState();
    controller = AnimationController(duration: duration, vsync: this);
    final curved =
        CurvedAnimation(parent: controller, curve: Curves.easeOutCirc);
    animatedWidth = Tween<double>(begin: -50, end: end).animate(curved);
    controller.addListener(() => setState(() {}));
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  void onStarted() => controller.forward();

  void onDecoded() => dev.log("Hash ${widget.hash} decoded");

  void onDisplayed() => dev.log("Hash ${widget.uri} displayed");
}
1.56k
likes
140
points
153k
downloads
screenshot

Publisher

verified publisherfluttercommunity.dev

Weekly Downloads

Compact representation of a placeholder for an image. Encode a blurry image under 30 caracters for instant display like used by Medium

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

flutter

More

Packages that depend on flutter_blurhash