滚动结束在颤动中多次检测到

发布于 01-22 18:10 字数 536 浏览 1 评论 0原文

我使用ListView.builder。它多次检测滚动结束,这就是为什么API呼叫多次并在ListView中添加重复数据的原因。

代码: -

ListView.builder(
    controller: _scrollController
    ..addListener(() async {
  if (_scrollController
      .position.pixels -
      10 ==
      _scrollController.position
          .maxScrollExtent -
          10 &&
      !state.isPaginationLoading) {
    print("Scroll End TEst Screen");
    await ctx
        .read<ProfileCubit>()
        .getProfiles(
        context, true, null);
  }

I uses Listview.builder. it detect scroll end to many time that is why API call to many times and add duplicate Data in Listview.

Code:-

ListView.builder(
    controller: _scrollController
    ..addListener(() async {
  if (_scrollController
      .position.pixels -
      10 ==
      _scrollController.position
          .maxScrollExtent -
          10 &&
      !state.isPaginationLoading) {
    print("Scroll End TEst Screen");
    await ctx
        .read<ProfileCubit>()
        .getProfiles(
        context, true, null);
  }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

維他命╮2025-01-29 18:10:12

不要将逻辑代码放入构建中。在您的情况下,_scrollController每次都称为widget build,原因都会触发。

建议您的建议是创建并将句柄逻辑放在函数上,将addListener/removelistenerinitstate/dispose中添加,因为它们仅被称为一次。

有了您的问题,您可以创建一个variale来检查API并防止其他呼叫。


class AppState extends State<App> {
  var scroll = ScrollController();
  var preventCall = false;

  @override
  initState() {
    scroll.addListener(onScroll);
    super.initState();
  }

  @override
  void dispose() {
    scroll.removeListener(onScroll);
    super.dispose();
  }

  Future yourFuture() async {}

  void onScroll() {
    var position = scroll.position.pixels;
    if (position >= scroll.position.maxScrollExtent - 10) {
      if (!preventCall) {
        yourFuture().then((_) => preventCall = false);
        preventCall = true;
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return ...
  }
}

Dont put logic code inside build. In your case _scrollController will addListener every times widget build called, cause multiple handle will trigger.

Advice for you is create and put handle logic to a function, put addListener/removeListener in initState/dispose because they was called only once.

With your problem, you can create a variale to check api was called yet and prevent other call.


class AppState extends State<App> {
  var scroll = ScrollController();
  var preventCall = false;

  @override
  initState() {
    scroll.addListener(onScroll);
    super.initState();
  }

  @override
  void dispose() {
    scroll.removeListener(onScroll);
    super.dispose();
  }

  Future yourFuture() async {}

  void onScroll() {
    var position = scroll.position.pixels;
    if (position >= scroll.position.maxScrollExtent - 10) {
      if (!preventCall) {
        yourFuture().then((_) => preventCall = false);
        preventCall = true;
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return ...
  }
}

季末如歌2025-01-29 18:10:12

您可以添加一个条件以检查API调用是否发生,并且可以基于此条件,您可以调用API。如果加载了所有信息,您还需要处理分页逻辑。

You can add a condition to check if API call is happening or not and based on it you can you can call the API. You would also need to handle pagination logic if all info is loaded.

温暖的光2025-01-29 18:10:12

您可以随时检查您是否以滚动控制器的最大限制的极限到达,然后可以调用API

条件就像

  child: ListView.builder(
                    controller: _controller
                      ..addListener(() async {
                        if (_controller.position.pixels >
                            _controller.position.maxScrollExtent) {
                          _loadMore();
                        }
                      }),

您可能就像完整的示例

import 'dart:convert';
import 'dart:developer';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;

class Paggination2HomeScreen extends StatefulWidget {
  const Paggination2HomeScreen({Key? key}) : super(key: key);

  @override
  State<Paggination2HomeScreen> createState() => _Paggination2HomeScreenState();
}

class _Paggination2HomeScreenState extends State<Paggination2HomeScreen> {
  final String _baseUrl = "https://jsonplaceholder.typicode.com/photos";
  int _page = 1;
  final int _limit = 10;
  bool _hasNextPage = true;
  bool _isFirstLoadRunning = false;
  bool _isLoadMoreRunning = false;
  List _posts = [];

  void _firstLoad() async {
    setState(() {
      _isFirstLoadRunning = true;
    });

    try {
      final res = await http.get(
        Uri.parse("$_baseUrl?_page=$_page&_limit=$_limit"),
      );
      if (res.statusCode == 200) {
        setState(() {
          _posts = jsonDecode(res.body);
        });
        log('receivedData : ${jsonDecode(res.body)}');
      }
    } catch (e) {
      if (kDebugMode) {
        log('Something went wrong');
      }
    }

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

  void _loadMore() async {
    if (_hasNextPage == true &&
        _isFirstLoadRunning == false &&
        _isLoadMoreRunning == false) {
      setState(() {
        _isLoadMoreRunning = true;
      });
      _page++;

      try {
        final res = await http.get(
          Uri.parse('$_baseUrl?_page=$_page&_limit=$_limit'),
        );

        log('url : $_baseUrl?_page=$_page&_limit=$_limit');
        if (res.statusCode == 200) {
          final List fetchedPost = jsonDecode(res.body);
          if (fetchedPost.isNotEmpty) {
            setState(() {
              _posts.addAll(fetchedPost);
            });
          } else {
            setState(() {
              _hasNextPage = false;
            });
          }
        }
      } catch (e) {
        if (kDebugMode) {
          log('something went wrong');
        }
      }
      setState(() {
        _isLoadMoreRunning = false;
      });
    }
  }

  // the controller for listyView
  late ScrollController _controller;
  @override
  void initState() {
    super.initState();
    _firstLoad();
    _controller = ScrollController();
  }

  @override
  void dispose() {
    super.dispose();
    _controller.removeListener(_loadMore);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.orangeAccent,
        elevation: 0.0,
        centerTitle: true,
        title: Text(
          'Paggination',
          style: TextStyle(
            color: Colors.black.withOpacity(0.52),
            fontSize: 20,
          ),
        ),
        systemOverlayStyle: const SystemUiOverlayStyle(
          statusBarColor: Colors.transparent,
        ),
      ),
      body: _isFirstLoadRunning
          ? Center(
              child: CircularProgressIndicator(
                color: Colors.black.withOpacity(0.25),
              ),
            )
          : Column(
              children: [
                Expanded(
                  child: ListView.builder(
                    controller: _controller
                      ..addListener(() async {
                        if (_controller.position.pixels >
                            _controller.position.maxScrollExtent) {
                          _loadMore();
                        }
                      }),
                    physics: const BouncingScrollPhysics(),
                    itemCount: _posts.length,
                    itemBuilder: (context, index) => Container(
                      padding: const EdgeInsets.all(10),
                      margin: const EdgeInsets.symmetric(
                        horizontal: 10,
                        vertical: 5,
                      ),
                      decoration: BoxDecoration(
                        color: Colors.black.withOpacity(0.065),
                        borderRadius: BorderRadius.circular(10),
                        border: Border.all(
                          color: Colors.black.withOpacity(0.2),
                          width: 0.5,
                        ),
                      ),
                      child: Column(
                        mainAxisSize: MainAxisSize.min,
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Text(
                            'ID : ${_posts.elementAt(index)['id']}',
                          ),
                          Text(
                            'AlbumID : ${_posts.elementAt(index)['albumId']}',
                          ),
                          Text(
                            'Title : ${_posts.elementAt(index)['title']}',
                            textAlign: TextAlign.justify,
                          ),
                          Row(
                            crossAxisAlignment: CrossAxisAlignment.end,
                            mainAxisAlignment: MainAxisAlignment.spaceBetween,
                            children: [
                              SizedBox(
                                height: MediaQuery.of(context).size.width * 0.4,
                                width: MediaQuery.of(context).size.width * 0.4,
                                child: Image.network(
                                  _posts.elementAt(index)['url'],
                                ),
                              ),
                              SizedBox(
                                height: MediaQuery.of(context).size.width * 0.4,
                                width: MediaQuery.of(context).size.width * 0.4,
                                child: Image.network(
                                  _posts.elementAt(index)['thumbnailUrl'],
                                ),
                              ),
                            ],
                          ),
                        ],
                      ),
                    ),
                  ),
                ),

                // when the _loadMore running
                if (_isLoadMoreRunning == true)
                  Container(
                    color: Colors.transparent,
                    padding: const EdgeInsets.only(top: 10, bottom: 20),
                    child: const Center(
                      child: CircularProgressIndicator(
                        color: Colors.amberAccent,
                        strokeWidth: 3,
                        backgroundColor: Colors.transparent,
                        valueColor: AlwaysStoppedAnimation<Color>(Colors.blue),
                      ),
                    ),
                  ),

                if (_hasNextPage == false)
                  SafeArea(
                    child: Container(
                      width: double.maxFinite,
                      padding: const EdgeInsets.only(top: 20, bottom: 20),
                      color: Colors.orangeAccent,
                      child: const Text('you get all'),
                    ),
                  )
              ],
            ),
    );
  }
}

you can always check if you reached at the limit of max Limit of your scroll controller then you can call API

condition is like

  child: ListView.builder(
                    controller: _controller
                      ..addListener(() async {
                        if (_controller.position.pixels >
                            _controller.position.maxScrollExtent) {
                          _loadMore();
                        }
                      }),

you may be like full example

import 'dart:convert';
import 'dart:developer';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;

class Paggination2HomeScreen extends StatefulWidget {
  const Paggination2HomeScreen({Key? key}) : super(key: key);

  @override
  State<Paggination2HomeScreen> createState() => _Paggination2HomeScreenState();
}

class _Paggination2HomeScreenState extends State<Paggination2HomeScreen> {
  final String _baseUrl = "https://jsonplaceholder.typicode.com/photos";
  int _page = 1;
  final int _limit = 10;
  bool _hasNextPage = true;
  bool _isFirstLoadRunning = false;
  bool _isLoadMoreRunning = false;
  List _posts = [];

  void _firstLoad() async {
    setState(() {
      _isFirstLoadRunning = true;
    });

    try {
      final res = await http.get(
        Uri.parse("$_baseUrl?_page=$_page&_limit=$_limit"),
      );
      if (res.statusCode == 200) {
        setState(() {
          _posts = jsonDecode(res.body);
        });
        log('receivedData : ${jsonDecode(res.body)}');
      }
    } catch (e) {
      if (kDebugMode) {
        log('Something went wrong');
      }
    }

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

  void _loadMore() async {
    if (_hasNextPage == true &&
        _isFirstLoadRunning == false &&
        _isLoadMoreRunning == false) {
      setState(() {
        _isLoadMoreRunning = true;
      });
      _page++;

      try {
        final res = await http.get(
          Uri.parse('$_baseUrl?_page=$_page&_limit=$_limit'),
        );

        log('url : $_baseUrl?_page=$_page&_limit=$_limit');
        if (res.statusCode == 200) {
          final List fetchedPost = jsonDecode(res.body);
          if (fetchedPost.isNotEmpty) {
            setState(() {
              _posts.addAll(fetchedPost);
            });
          } else {
            setState(() {
              _hasNextPage = false;
            });
          }
        }
      } catch (e) {
        if (kDebugMode) {
          log('something went wrong');
        }
      }
      setState(() {
        _isLoadMoreRunning = false;
      });
    }
  }

  // the controller for listyView
  late ScrollController _controller;
  @override
  void initState() {
    super.initState();
    _firstLoad();
    _controller = ScrollController();
  }

  @override
  void dispose() {
    super.dispose();
    _controller.removeListener(_loadMore);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.orangeAccent,
        elevation: 0.0,
        centerTitle: true,
        title: Text(
          'Paggination',
          style: TextStyle(
            color: Colors.black.withOpacity(0.52),
            fontSize: 20,
          ),
        ),
        systemOverlayStyle: const SystemUiOverlayStyle(
          statusBarColor: Colors.transparent,
        ),
      ),
      body: _isFirstLoadRunning
          ? Center(
              child: CircularProgressIndicator(
                color: Colors.black.withOpacity(0.25),
              ),
            )
          : Column(
              children: [
                Expanded(
                  child: ListView.builder(
                    controller: _controller
                      ..addListener(() async {
                        if (_controller.position.pixels >
                            _controller.position.maxScrollExtent) {
                          _loadMore();
                        }
                      }),
                    physics: const BouncingScrollPhysics(),
                    itemCount: _posts.length,
                    itemBuilder: (context, index) => Container(
                      padding: const EdgeInsets.all(10),
                      margin: const EdgeInsets.symmetric(
                        horizontal: 10,
                        vertical: 5,
                      ),
                      decoration: BoxDecoration(
                        color: Colors.black.withOpacity(0.065),
                        borderRadius: BorderRadius.circular(10),
                        border: Border.all(
                          color: Colors.black.withOpacity(0.2),
                          width: 0.5,
                        ),
                      ),
                      child: Column(
                        mainAxisSize: MainAxisSize.min,
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Text(
                            'ID : ${_posts.elementAt(index)['id']}',
                          ),
                          Text(
                            'AlbumID : ${_posts.elementAt(index)['albumId']}',
                          ),
                          Text(
                            'Title : ${_posts.elementAt(index)['title']}',
                            textAlign: TextAlign.justify,
                          ),
                          Row(
                            crossAxisAlignment: CrossAxisAlignment.end,
                            mainAxisAlignment: MainAxisAlignment.spaceBetween,
                            children: [
                              SizedBox(
                                height: MediaQuery.of(context).size.width * 0.4,
                                width: MediaQuery.of(context).size.width * 0.4,
                                child: Image.network(
                                  _posts.elementAt(index)['url'],
                                ),
                              ),
                              SizedBox(
                                height: MediaQuery.of(context).size.width * 0.4,
                                width: MediaQuery.of(context).size.width * 0.4,
                                child: Image.network(
                                  _posts.elementAt(index)['thumbnailUrl'],
                                ),
                              ),
                            ],
                          ),
                        ],
                      ),
                    ),
                  ),
                ),

                // when the _loadMore running
                if (_isLoadMoreRunning == true)
                  Container(
                    color: Colors.transparent,
                    padding: const EdgeInsets.only(top: 10, bottom: 20),
                    child: const Center(
                      child: CircularProgressIndicator(
                        color: Colors.amberAccent,
                        strokeWidth: 3,
                        backgroundColor: Colors.transparent,
                        valueColor: AlwaysStoppedAnimation<Color>(Colors.blue),
                      ),
                    ),
                  ),

                if (_hasNextPage == false)
                  SafeArea(
                    child: Container(
                      width: double.maxFinite,
                      padding: const EdgeInsets.only(top: 20, bottom: 20),
                      color: Colors.orangeAccent,
                      child: const Text('you get all'),
                    ),
                  )
              ],
            ),
    );
  }
}

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文