美文网首页Flutter
Flutter 状态管理 Bloc 之永动列表示例

Flutter 状态管理 Bloc 之永动列表示例

作者: _凌浩雨 | 来源:发表于2019-12-13 11:23 被阅读0次
    1. 依赖
    dependencies:
      flutter_bloc: ^2.1.1
      equatable: ^1.0.1
      http: ^0.12.0
    
    2. 免费REST API
    3. 数据模型

    post.dart

    import 'package:equatable/equatable.dart';
    
    /// 文章模型
    class Post extends Equatable {
      /// 文章ID
      final int id;
      /// 文章标题
      final String title;
      /// 文章内容
      final String body;
    
      const Post({this.id, this.title, this.body});
    
      @override
      List<Object> get props => [id, title, body];
    
      @override
      String toString() => "Post { id: $id }";
    }
    

    注: 继承 Equatable 后, 当且仅当this和other是相同的实例时,相等运算符才返回true。

    4. 文章事件

    此处我们只有一个加载文章的事件

    infinite_event.dart

    import 'package:equatable/equatable.dart';
    import 'package:meta/meta.dart';
    
    /// 永动事件
    @immutable
    abstract class InfiniteEvent extends Equatable{
      @override
      List<Object> get props => [];
    }
    
    /// 加载事件
    class Fetch extends InfiniteEvent {}
    
    5. 文章状态
    • PostUninitialized: 第一批文章被加载时需要呈现一个加载指示器
    • PostLoaded: 有内容需要展示
      • posts - 将被显示的文章列表List<Post>
      • hasReachedMax - 是否达到最大文章数
    • PostError: 获取文章异常

    infinite_state.dart

    import 'package:equatable/equatable.dart';
    import 'package:meta/meta.dart';
    import 'package:state_manage/infinite_list/post.dart';
    
    /// 文章的状态
    @immutable
    abstract class InfiniteState extends Equatable {
      const InfiniteState();
      @override
      List<Object> get props => [];
    }
    
    /// 文章初始化状态
    class PostUninitialized extends InfiniteState {}
    
    /// 文章异常状态
    class PostError extends InfiniteState {}
    
    /// 有文章需要加载
    class PostLoaded extends InfiniteState {
      /// 文章列表
      final List<Post> posts;
    
      /// 是否还有更多文章
      final bool hasReachedMax;
    
      const PostLoaded({this.posts, this.hasReachedMax});
    
      /// 拷贝对象
      /// @param post 文章列表
      /// @param hasReachMax 是否还有更多文章
      PostLoaded copyWith({List<Post> posts, bool hasReachedMax}) {
        return PostLoaded(
            posts: posts ?? this.posts,
            hasReachedMax: hasReachedMax ?? this.hasReachedMax);
      }
    
      @override
      List<Object> get props => [posts, hasReachedMax];
    
      @override
      String toString() =>
          "PostLoaded { posts: ${posts.length}, hasReachedMax: $hasReachedMax }";
    }
    

    copyWith以便我们可以复制的实例PostLoaded并方便地更新零个或多个属性。

    6. 文章 Bloc 实现
    1. 设置初始化状态
    2. 实现mapEventToState方法
    3. 使用http进行网络请求
    4. 设置事件连续间隔

    infinite_bloc.dart

    import 'dart:async';
    import 'dart:convert';
    import 'package:bloc/bloc.dart';
    import 'package:flutter/material.dart';
    import 'package:state_manage/infinite_list/post.dart';
    import './bloc.dart';
    import 'package:http/http.dart' as Http;
    import 'package:rxdart/rxdart.dart';
    
    /// 永动列表 Bloc
    class InfiniteBloc extends Bloc<InfiniteEvent, InfiniteState> {
      /// 网络请求工具
      final Http.Client httpClient;
    
      InfiniteBloc({@required this.httpClient});
    
      @override
      InfiniteState get initialState => PostUninitialized();
    
      @override
      Stream<InfiniteState> mapEventToState(
        InfiniteEvent event,
      ) async* {
        // 当前状态
        final currentState = state;
    
        /// 判断状态
        if (event is Fetch && !_hasReachMax(currentState)) {
          try {
            if (currentState is PostUninitialized) {
              // 加载数据
              final posts = await _fetchPosts(0, 20);
              yield PostLoaded(posts: posts, hasReachedMax: false);
            } else if (currentState is PostLoaded) {
              // 加载数据
              final posts = await _fetchPosts(currentState.posts.length, 20);
              yield posts.isEmpty
                  ? currentState.copyWith(hasReachedMax: true)
                  : PostLoaded(
                      posts: currentState.posts + posts, hasReachedMax: false);
            }
          } catch (_) {
            yield PostError();
          }
        }
      }
    
      /// 是否还有更多数据
      bool _hasReachMax(InfiniteState currentState) {
        if (state is PostLoaded) {
          return (state as PostLoaded).hasReachedMax;
        } else {
          return false;
        }
      }
    
      /// 加载数据
      Future<List<Post>> _fetchPosts(int startIndex, int limit) async {
        // 获取数据
        final response = await httpClient.get('https://jsonplaceholder.typicode.com/posts?_start=$startIndex&_limit=$limit');
        // 检测状态码
        if (response.statusCode == 200) {
          final data = json.decode(response.body) as List;
          return data.map((rawPost) {
            return Post(
              id: rawPost['id'],
              title: rawPost['title'],
              body: rawPost['body']
            );
          }).toList();
        } else {
          throw Exception('error fetching posts');
        }
      }
    
      // 设置事件间隔事件
      @override
      Stream<InfiniteState> transformEvents(Stream<InfiniteEvent> events, Stream<InfiniteState> Function(InfiniteEvent event) next) {
        return super.transformEvents((events as Observable<InfiniteEvent>).debounceTime(Duration(milliseconds: 500)), next);
      }
    }
    
    7. UI
    import 'package:flutter/material.dart';
    import 'package:flutter_bloc/flutter_bloc.dart';
    import 'package:state_manage/infinite_list/bloc/bloc.dart';
    import 'package:http/http.dart' as http;
    import 'package:state_manage/infinite_list/post.dart';
    
    /// 永动列表
    class InfiniteListTest extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Infinite Scroll',
          home: Scaffold(
            appBar: AppBar(
              title: Text('Posts'),
            ),
            body: BlocProvider(
              create: (ctx) =>
                  InfiniteBloc(httpClient: http.Client())..add(Fetch()),
              child: HomePage(),
            ),
          ),
        );
      }
    }
    
    //// 主页
    class HomePage extends StatefulWidget {
      @override
      State<StatefulWidget> createState() => _HomePageState();
    }
    
    /// 页面状态
    class _HomePageState extends State<HomePage> {
      // 滚动控制器
      final _scrollController = ScrollController();
      // 滚动域
      final _scrollThreshold = 200.0;
      // 文章Bloc
      InfiniteBloc _bloc;
    
      /// 初始化
      @override
      void initState() {
        super.initState();
        // 添加监听
        _scrollController.addListener(_onScroll);
        // 获取Bloc
        _bloc = BlocProvider.of(context);
      }
    
      @override
      Widget build(BuildContext context) {
        return BlocBuilder<InfiniteBloc, InfiniteState>(
          builder: (ctx, state) {
            // 第一批数据加载
            if (state is PostUninitialized) {
              return Center(
                child: CircularProgressIndicator(),
              );
            } else if (state is PostError) {
              return Center(
                child: Text('failed to fetch posts.'),
              );
            } else if (state is PostLoaded) {
              // 文件为空
              if (state.posts.isEmpty) {
                return Center(
                  child: Text('no posts.'),
                );
              }
              return ListView.builder(
                itemBuilder: (ctx, index) {
                  return index >= state.posts.length
                      ? BottomLoader()
                      : InfiniteListItem(post: state.posts[index]);
                },
                itemCount: state.hasReachedMax
                    ? state.posts.length
                    : state.posts.length + 1,
                controller: _scrollController,
              );
            }
          },
        );
      }
    
      @override
      void dispose() {
        _scrollController.dispose();
        super.dispose();
      }
    
      /// 滚动监听
      void _onScroll() {
        // 最大滚动位置
        final maxScroll = _scrollController.position.maxScrollExtent;
        // 获取当前位置
        final currentScroll = _scrollController.position.pixels;
        // 判断是否加载数据
        if (maxScroll - currentScroll <= _scrollThreshold) {
          _bloc.add(Fetch());
        }
      }
    }
    
    /// 底部加载控件
    class BottomLoader extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Container(
          alignment: Alignment.center,
          child: Center(
            child: SizedBox(
              width: 33,
              height: 33,
              child: CircularProgressIndicator(
                strokeWidth: 1.5,
              ),
            ),
          ),
        );
      }
    }
    
    /// 列表组件
    class InfiniteListItem extends StatelessWidget{
      /// 文章
      final Post post;
    
      const InfiniteListItem({Key key, @required this.post}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {    
        return ListTile(
          leading: Text(
            '${post.id}',
            style: TextStyle(fontSize: 10.0),
          ),
          title: Text(post.title),
          isThreeLine: true,
          subtitle: Text(post.body),
          dense: true,
        );
      }
    }
    

    效果图:

    效果图.gif

    相关文章

      网友评论

        本文标题:Flutter 状态管理 Bloc 之永动列表示例

        本文链接:https://www.haomeiwen.com/subject/ibbtnctx.html