DEV Community

Cover image for Flutter Skeleton Wireframe
Caner Demirci
Caner Demirci

Posted on

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});
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)