← Назад к вопросам

Что такоe placeholder?

1.0 Junior🔥 151 комментариев
#Flutter виджеты

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Placeholder в Flutter: Концепция и применение

Определение

Placeholder — это временный виджет, который используется как заполнитель на месте элемента UI, который ещё не реализован, не загружен или находится в процессе загрузки. Это инструмент разработки для быстрого прототипирования и визуализации макета приложения.

Встроенный Placeholder виджет

В Flutter есть встроенный виджет Placeholder, который отображает квадратик с сеткой и помогает разработчикам видеть структуру приложения:

class PlaceholderExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Placeholder Demo')),
      body: Column(
        children: [
          // Реальный контент
          Text('Header'),
          
          // Заполнитель для контента, который ещё не готов
          Container(
            height: 200,
            child: Placeholder(
              fallbackHeight: 200,
              fallbackWidth: double.infinity,
              color: Colors.blue[100]!,
              strokeWidth: 2,
            ),
          ),
          
          Text('Footer'),
        ],
      ),
    );
  }
}

Практические применения

1. Loading State с Placeholder

class ImageWithPlaceholder extends StatefulWidget {
  final String imageUrl;
  final double width;
  final double height;

  const ImageWithPlaceholder({
    required this.imageUrl,
    required this.width,
    required this.height,
  });

  @override
  State<ImageWithPlaceholder> createState() => _ImageWithPlaceholderState();
}

class _ImageWithPlaceholderState extends State<ImageWithPlaceholder> {
  bool _isLoading = true;
  bool _hasError = false;

  @override
  Widget build(BuildContext context) {
    if (_isLoading) {
      return Container(
        width: widget.width,
        height: widget.height,
        color: Colors.grey[300],
        child: Placeholder(
          fallbackHeight: widget.height,
          fallbackWidth: widget.width,
        ),
      );
    }

    if (_hasError) {
      return Container(
        width: widget.width,
        height: widget.height,
        color: Colors.red[50],
        child: Icon(Icons.broken_image, color: Colors.red),
      );
    }

    return Image.network(
      widget.imageUrl,
      width: widget.width,
      height: widget.height,
      fit: BoxFit.cover,
      loadingBuilder: (context, child, loadingProgress) {
        if (loadingProgress == null) return child;
        
        return Container(
          width: widget.width,
          height: widget.height,
          color: Colors.grey[300],
          child: Center(
            child: CircularProgressIndicator(
              value: loadingProgress.expectedTotalBytes != null
                  ? loadingProgress.cumulativeBytesLoaded /
                      loadingProgress.expectedTotalBytes!
                  : null,
            ),
          ),
        );
      },
      errorBuilder: (context, error, stackTrace) {
        _hasError = true;
        return Container(
          width: widget.width,
          height: widget.height,
          color: Colors.red[50],
          child: Icon(Icons.image_not_supported),
        );
      },
    );
  }
}

2. Skeleton Loading (Shimmer эффект)

Это более изощрённая версия placeholder для создания скелета загружаемого контента:

import 'package:shimmer/shimmer.dart';

class SkeletonLoader extends StatelessWidget {
  final double width;
  final double height;
  final BorderRadius borderRadius;

  const SkeletonLoader({
    this.width = double.infinity,
    this.height = 100,
    this.borderRadius = const BorderRadius.all(Radius.circular(8)),
  });

  @override
  Widget build(BuildContext context) {
    return Shimmer.fromColors(
      baseColor: Colors.grey[300]!,
      highlightColor: Colors.grey[100]!,
      child: Container(
        width: width,
        height: height,
        decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: borderRadius,
        ),
      ),
    );
  }
}

// Использование
class UserListWithSkeleton extends StatefulWidget {
  @override
  State<UserListWithSkeleton> createState() => _UserListWithSkeletonState();
}

class _UserListWithSkeletonState extends State<UserListWithSkeleton> {
  late Future<List<User>> _usersFuture;

  @override
  void initState() {
    super.initState();
    _usersFuture = fetchUsers();
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<List<User>>(
      future: _usersFuture,
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          // Показываем skeleton loading
          return ListView.builder(
            itemCount: 5,
            itemBuilder: (context, index) => Padding(
              padding: EdgeInsets.all(8),
              child: SkeletonLoader(height: 80),
            ),
          );
        }

        if (snapshot.hasError) {
          return Center(child: Text('Error: ${snapshot.error}'));
        }

        final users = snapshot.data ?? [];
        return ListView.builder(
          itemCount: users.length,
          itemBuilder: (context, index) => UserTile(user: users[index]),
        );
      },
    );
  }

  Future<List<User>> fetchUsers() async {
    await Future.delayed(Duration(seconds: 2));
    return [
      User(name: 'John', email: 'john@example.com'),
      User(name: 'Jane', email: 'jane@example.com'),
    ];
  }
}

3. Placeholder для асинхронных данных

class AsyncDataPlaceholder<T> extends StatelessWidget {
  final Future<T> future;
  final Widget Function(BuildContext, T) builder;
  final Widget? placeholder;
  final Widget Function(BuildContext, Object)? errorBuilder;

  const AsyncDataPlaceholder({
    required this.future,
    required this.builder,
    this.placeholder,
    this.errorBuilder,
  });

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<T>(
      future: future,
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return placeholder ?? Placeholder();
        }

        if (snapshot.hasError) {
          return errorBuilder?.call(context, snapshot.error!) ??
              Center(child: Text('Error: ${snapshot.error}'));
        }

        return builder(context, snapshot.data as T);
      },
    );
  }
}

// Использование
AsyncDataPlaceholder<String>(
  future: fetchTitle(),
  placeholder: SkeletonLoader(height: 40),
  builder: (context, title) => Text(title, style: Theme.of(context).textTheme.headlineSmall),
  errorBuilder: (context, error) => Text('Failed to load', style: TextStyle(color: Colors.red)),
)

Лучшие практики

1. Соответствие размерам

// ✅ Placeholder имеет размер как настоящий контент
Container(
  width: 300,
  height: 200,
  child: Placeholder(),
)

// ❌ Mismatched size
Placeholder() // без явного размера

2. Использование в тестах

class ProductCard extends StatelessWidget {
  final Product product;

  const ProductCard({required this.product});

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Column(
        children: [
          // Временно заменить на Placeholder для быстрой разработки
          if (product.imageUrl.isNotEmpty)
            Image.network(product.imageUrl)
          else
            Placeholder(fallbackHeight: 200), // TODO: добавить реальное изображение
          Text(product.name),
        ],
      ),
    );
  }
}

3. Условный рендеринг с BlocBuilder

BlocBuilder<ProductBloc, ProductState>(
  builder: (context, state) {
    if (state is ProductLoading) {
      return SkeletonLoader();
    }
    if (state is ProductLoaded) {
      return ProductList(products: state.products);
    }
    return SizedBox.shrink();
  },
)

Вывод

Placeholder — это не просто визуальный инструмент, а важная часть разработки приложений Flutter. Он позволяет:

  • Быстро прототипировать макеты
  • Видеть структуру приложения до реализации всех компонентов
  • Улучшить UX, показывая скелет контента вместо пустого пространства
  • Разрабатывать UI параллельно с бизнес-логикой

Правильное использование placeholder'ов делает разработку более гибкой и пользовательский опыт более приятным.

Что такоe placeholder? | PrepBro