Flutter Skeleton Wireframe

Flutter Skeleton Wireframe

You can create different types of skeletons using WireFrame widget. Here is the code.

main.dart

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Skeleton Wireframe',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late final List<User> _users;
  late final List<Post> _posts;

  bool _loading = true;

  Future<void> _loadPosts() async {
    await Future.delayed(const Duration(seconds: 3));

    setState(() {
      _loading = false;
    });
  }

  @override
  void initState() {
    _users = const [
      User(
          name: 'Dan Brown',
          photo:
              'https://upload.wikimedia.org/wikipedia/commons/5/5f/Alberto_conversi_profile_pic.jpg'),
      User(
          name: 'Leanne Graham',
          photo:
              'https://i1.wp.com/www.alphr.com/wp-content/uploads/2020/12/Facebook-How-to-Change-Profile-Picture.jpg?fit=1200%2C666&ssl=1'),
      User(
          name: 'Ervin Howell',
          photo:
              'https://images.pexels.com/photos/220453/pexels-photo-220453.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500'),
    ];

    _posts = [
      Post(
          date: '22.03.2022',
          title: 'Lorem ipsum dolor sit amet',
          text:
              'It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters,',
          user: _users[0]),
      Post(
          date: '20.03.2022',
          title: 'many web sites still in their infancy',
          text:
              'and a search for lorem ipsum will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes',
          user: _users[1]),
      Post(
          date: '17.03.2022',
          title: 'Lorem Ipsum is not simply random text',
          text:
              'Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur,',
          user: _users[2]),
    ];

    _loadPosts();

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: SingleChildScrollView(
        child: Column(
            children: List.generate(_posts.length, (index) {
          return Padding(
            padding: const EdgeInsets.all(16.0),
            child: _loading
                ? const PostWireFrame()
                : PostWidget(post: _posts[index]),
          );
        })),
      ),
    );
  }
}

class PostWidget extends StatelessWidget {
  final Post post;

  const PostWidget({Key? key, required this.post}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                ClipOval(
                  clipBehavior: Clip.hardEdge,
                  child: SizedBox(
                    width: 50,
                    height: 50,
                    child: Image.network(
                      post.user.photo,
                      fit: BoxFit.fill,
                    ),
                  ),
                ),
                const SizedBox(width: 15),
                Text(post.title),
              ],
            ),
            const SizedBox(height: 10),
            Text(post.text),
            const SizedBox(height: 10),
            Align(
              alignment: Alignment.centerRight,
              child: Text(post.date),
            )
          ],
        ),
      ),
    );
  }
}

class PostWireFrame extends StatelessWidget {
  const PostWireFrame({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 200,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            children: const [
              WireFrame(
                width: 50,
                height: 50,
                shape: BoxShape.circle,
              ),
              SizedBox(width: 15),
              Expanded(
                child: WireFrame(
                  height: 25,
                ),
              )
            ],
          ),
          const WireFrame(
            height: 100,
          ),
          const Align(
            alignment: Alignment.centerRight,
            child: WireFrame(
              width: 50,
              height: 25,
            ),
          )
        ],
      ),
    );
  }
}

class WireFrame extends StatefulWidget {
  final double? width;
  final double? height;
  final BoxShape? shape;

  const WireFrame({Key? key, this.width, this.height, this.shape})
      : super(key: key);

  @override
  State<WireFrame> createState() => _WireFrameState();
}

class _WireFrameState extends State<WireFrame>
    with SingleTickerProviderStateMixin<WireFrame> {
  late final AnimationController _controller;
  late final Animation gradientFirstColorAnim;
  late final Animation gradientSecondColorAnim;

  final gradientFirstColorTween = TweenSequence([
    TweenSequenceItem(
        tween: ColorTween(begin: Colors.grey[200], end: Colors.grey[600]),
        weight: 1),
    TweenSequenceItem(
        tween: ColorTween(begin: Colors.grey[600], end: Colors.grey[200]),
        weight: 1),
  ]);

  final gradientSecondColorTween = TweenSequence([
    TweenSequenceItem(
        tween: ColorTween(begin: Colors.grey[600], end: Colors.grey[200]),
        weight: 1),
    TweenSequenceItem(
        tween: ColorTween(begin: Colors.grey[200], end: Colors.grey[600]),
        weight: 1),
  ]);

  @override
  void dispose() {
    _controller.dispose();

    super.dispose();
  }

  @override
  void initState() {
    _controller = AnimationController(
        duration: const Duration(milliseconds: 2000), vsync: this);

    gradientFirstColorAnim = gradientFirstColorTween.animate(_controller);
    gradientSecondColorAnim = gradientSecondColorTween.animate(_controller);

    _controller.repeat();

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
        animation: _controller,
        builder: (context, child) {
          return Container(
            width: widget.width,
            height: widget.height,
            decoration: BoxDecoration(
              borderRadius: widget.shape == BoxShape.circle
                  ? null
                  : BorderRadius.circular(5),
              shape: widget.shape ?? BoxShape.rectangle,
              gradient: LinearGradient(
                  begin: Alignment.topCenter,
                  end: Alignment.bottomCenter,
                  colors: [
                    gradientFirstColorAnim.value,
                    gradientSecondColorAnim.value,
                  ]),
            ),
          );
        });
  }
}

class User {
  final String name;
  final String photo;

  const User({required this.name, required this.photo});
}

class Post {
  final User user;
  final String title;
  final String text;
  final String date;

  const Post(
      {required this.user,
      required this.title,
      required this.text,
      required this.date});
}