ListView.builder 到达列表末尾并向后滚动后返回起始位置

Posted

技术标签:

【中文标题】ListView.builder 到达列表末尾并向后滚动后返回起始位置【英文标题】:ListView.builder returns to starting position after reaching the end of list and scrolling backwards 【发布时间】:2021-04-29 18:18:09 【问题描述】:

以下 ListView 返回 CourseTile 类型项目的水平列表。每个项目大约占据屏幕宽度的一半。因此,屏幕上一次可以看到 2 个 CourseTiles。

当我滚动列表时,它会正常运行,直到它到达最后 2 项。然后当我开始向后滚动并且从末尾开始的第三个项目即将显示在屏幕上时,ListView 刷新到它的初始位置,即。而不是向后滚动,它只是重置到第一个项目,跳过中间的项目。

这是列表视图

child: ListView.builder(
              scrollDirection: Axis.horizontal,
              shrinkWrap: false,
              itemCount: this.courses != null && this.courses.length > 0
                  ? this.courses.length
                  : 0,
              itemBuilder: (context, position) 
                final document = this.courses[position];
                return CourseTile(
                  course: document,
                  minWidth: this.minWidth,
                  minHeight: this.minHeight,
                  isCohort: widget.isCohort,
                );
              ,
            ),

这是 CourseTile 文件-

import 'dart:convert';
import 'package:intl/intl.dart';
import 'package:flutter/material.dart';
import 'package:masterlife/common_widgets/shimmer_Image.dart';
import 'package:masterlife/components/coming_soon.dart';
import 'package:masterlife/screens/course_detail/course_detail.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:masterlife/services/backend_api_service.dart';

class CourseTile extends StatefulWidget 
  var course;
  String toColor;
  String fromColor;
  double minWidth;
  double minHeight;
  bool isCohort;
  bool fromAssessment;

  CourseTile(
    this.course,
    this.minWidth,
    this.minHeight,
    this.fromColor,
    this.toColor,
    this.isCohort,
    this.fromAssessment = false,
  );

  @override
  _CourseTileState createState() => _CourseTileState();


class _CourseTileState extends State<CourseTile> 
  List<DocumentSnapshot> tags = [];
  List cohortDates = [];
  final courseService = new BackendAPIService();
  var fromDate, toDate, slots, course;
  Map cohortInstances = ;

  var category;
  @override
  void initState() 
    super.initState();

    if (this.widget.course.runtimeType.toString() != "DocumentSnapshot") 
      this.category = this.widget.course["category"];
     else 
      this.widget.course["category"].get().then((currentCategory) 
        if (!mounted) return;

        //This is firing unexpectedly when scrolling backwards 

        setState(() 
          this.category = currentCategory;
        );
      );
    
    if (widget.isCohort == true) 
      courseService.listUpcomingCohort("instructor", null).then((e) 
        if (!mounted) return;
        setState(() 
          print('statnig');
          this.cohortDates = json.decode(e);
        );
        loadData();
      );
    
  

  loadData() 
    for (var i = 0; i < cohortDates.length; i++) 
      var from = new DateFormat("d MMM").format(
          new DateTime.fromMillisecondsSinceEpoch(
              cohortDates[i]["from"]["_seconds"] * 1000));
      var to = new DateFormat("d MMM").format(
          new DateTime.fromMillisecondsSinceEpoch(
              cohortDates[i]["to"]["_seconds"] * 1000));
      var spotsRemaining = cohortDates[i]["remainingSeats"].toString();
      var courseId = cohortDates[i]["course"]["id"].toString();
      cohortInstances[courseId] = 
        "fromDate": from,
        "toDate": to,
        "remainingSlots": spotsRemaining
      ;
    
  

  @override
  Widget build(BuildContext context) 
    bool isDocSnap =
        this.widget.course.runtimeType.toString() == "DocumentSnapshot";
    if (isDocSnap) if (this.category == null) 
      return Container();
    
    Widget mainComponent = GestureDetector(
      onTap: () 
        Navigator.push(
          context,
          MaterialPageRoute(
              builder: (context) => CourseDetail(
                  course: isDocSnap ? this.widget.course : null,
                  courseId: isDocSnap ? null : this.widget.course["id"],
                  courseCategory: this.category["name"]),
              settings: RouteSettings(
                  name: "Course Details of $this.widget.course["name"]")),
        );
      ,
      child: Container(
        margin: EdgeInsets.only(right: 10),
        child: Stack(
          children: <Widget>[
            ClipRRect(
              borderRadius: new BorderRadius.circular(14),
              child: Container(
                  decoration: BoxDecoration(
                    gradient: LinearGradient(
                      colors: [
                        Color(int.parse(
                                '0x$this.widget.fromColor == null ? this.category["fromColor"] : this.widget.fromColor'))
                            .withOpacity(0.75),
                        Color(int.parse(
                                '0x$this.widget.fromColor == null ? this.category["toColor"] : this.widget.fromColor'))
                            .withOpacity(0.75)
                      ],
                    ),
                  ),
                  child: this.widget.course["coming_soon"] == true
                      ? Container(
                          width: MediaQuery.of(context).size.width * 0.6,
                          height: MediaQuery.of(context).size.width * 2,
                        )
                      : ShimmerImage(
                          imageUrl: this.widget.course["image"],
                          cornerRadius: 14,
                          fit: BoxFit.cover,
                          width: MediaQuery.of(context).size.width * 0.6,
                          height: MediaQuery.of(context).size.width * 2,
                        )),
            ),
            ClipRRect(
              borderRadius: new BorderRadius.circular(14),
              child: Container(
                padding: (widget.fromAssessment == false)
                    ? (cohortInstances
                            .containsKey(this.widget.course.documentID))
                        ? EdgeInsets.all(0)
                        : EdgeInsets.only(bottom: 15, left: 8)
                    : EdgeInsets.only(bottom: 15, left: 8),
                alignment: Alignment.bottomCenter,
                width: MediaQuery.of(context).size.width * 0.6,
                height: MediaQuery.of(context).size.width * 0.6,
                child: Container(
                  child: Stack(
                    children: <Widget>[
                      if (this.widget.course["is_free"] == true)
                        Container(
                          padding: EdgeInsets.only(right: 10, top: 10),
                          child: Row(
                            mainAxisAlignment: MainAxisAlignment.end,
                            children: <Widget>[
                              ClipRRect(
                                borderRadius: new BorderRadius.circular(4),
                                child: Container(
                                  color: Colors.grey.withOpacity(0.6),
                                  padding: EdgeInsets.only(
                                      top: 2, bottom: 2, left: 5, right: 5),
                                  child: Text(
                                    "Free",
                                    style: TextStyle(
                                      color: Colors.white,
                                      fontWeight: FontWeight.w600,
                                      letterSpacing: 0.36,
                                    ),
                                  ),
                                ),
                              ),
                            ],
                          ),
                        ),
                      Column(
                        mainAxisAlignment: MainAxisAlignment.end,
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: <Widget>[
                          Column(
                            mainAxisAlignment: MainAxisAlignment.end,
                            crossAxisAlignment: CrossAxisAlignment.stretch,
                            mainAxisSize: MainAxisSize.max,
                            children: <Widget>[
                              if (this.widget.course["coming_soon"] == true)
                                Padding(
                                  padding: const EdgeInsets.only(left: 10),
                                  child: Text(
                                    "Coming soon",
                                    style: Theme.of(context)
                                        .textTheme
                                        .button
                                        .copyWith(
                                          letterSpacing: 0.4,
                                        ),
                                  ),
                                ),
                              Padding(
                                padding: EdgeInsets.only(left: 8, right: 4),
                                child: Text(
                                  this.widget.course["name"],
                                  style: Theme.of(context)
                                      .textTheme
                                      .subtitle1
                                      .copyWith(
                                        letterSpacing: 0.4,
                                        fontWeight: FontWeight.w600,
                                      ),
                                ),
                              ),
                              if (widget.fromAssessment == false)
                                if (cohortInstances.containsKey(
                                        this.widget.course.documentID) &&
                                    widget.isCohort == true)
                                  Container(
                                    child: Column(
                                      mainAxisAlignment: MainAxisAlignment.end,
                                      children: [
                                        Container(
                                          height: MediaQuery.of(context)
                                                  .size
                                                  .height *
                                              0.06,
                                          width: MediaQuery.of(context)
                                                  .size
                                                  .width *
                                              0.60,
                                          child: ClipRRect(
                                            borderRadius: new BorderRadius.only(
                                              topLeft: Radius.circular(0),
                                              topRight: Radius.circular(0),
                                              bottomLeft: Radius.circular(14),
                                              bottomRight: Radius.circular(14),
                                            ),
                                            child: Container(
                                              padding: EdgeInsets.only(
                                                  left: 10, right: 8),
                                              color: Color(int.parse(
                                                      '0x$this.category['fromColor']'))
                                                  .withOpacity(0.9),
                                              width: MediaQuery.of(context)
                                                  .size
                                                  .width,
                                              height: MediaQuery.of(context)
                                                  .size
                                                  .height,
                                              child: Column(
                                                mainAxisAlignment:
                                                    MainAxisAlignment.center,
                                                crossAxisAlignment:
                                                    CrossAxisAlignment.start,
                                                children: [
                                                  Row(
                                                    mainAxisAlignment:
                                                        MainAxisAlignment
                                                            .spaceBetween,
                                                    children: [
                                                      Text(
                                                        "$cohortInstances[this.widget.course.documentID]["remainingSlots"] == "0" ? "Spots Filled" : cohortInstances[this.widget.course.documentID]["remainingSlots"] + " Spots Left"",
                                                        style: Theme.of(context)
                                                            .textTheme
                                                            .subtitle1
                                                            .copyWith(
                                                                color: Colors
                                                                    .black,
                                                                fontSize: 12,
                                                                fontWeight:
                                                                    FontWeight
                                                                        .bold),
                                                      ),
                                                      Text(
                                                        "$cohortInstances[this.widget.course.documentID]["fromDate"] - $cohortInstances[this.widget.course.documentID]["toDate"]",
                                                        style: Theme.of(context)
                                                            .textTheme
                                                            .subtitle1
                                                            .copyWith(
                                                              color:
                                                                  Colors.black,
                                                              fontSize: 12,
                                                              fontWeight:
                                                                  FontWeight
                                                                      .w600,
                                                            ),
                                                      )
                                                    ],
                                                  ),
                                                  Text(
                                                    "Join Now",
                                                    style: Theme.of(context)
                                                        .textTheme
                                                        .subtitle1
                                                        .copyWith(
                                                          color: Colors.black,
                                                          fontSize: 12,
                                                        ),
                                                  ),
                                                ],
                                              ),
                                            ),
                                          ),
                                        ),
                                      ],
                                    ),
                                  ),
                            ],
                          ),
                        ],
                      ),
                    ],
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );

    if (this.widget.course["coming_soon"] == null ||
        this.widget.course["coming_soon"] == false) 
      return mainComponent;
    
    return ComingSoon(
      child: mainComponent,
      type: "Course",
      featureName: this.widget.course["name"],
    );
  


当我开始向后滚动时(当最后第三个项目即将出现在屏幕上时),setState 函数正在启动

我该如何解决这个问题?

【问题讨论】:

【参考方案1】:

要保持状态,可以尝试使用AutomaticKeepAliveClientMixin

class _CourseTileState extends State<CourseTile> with AutomaticKeepAliveClientMixin
  Your code here
   @override
   bool get wantKeepAlive => true;
 

【讨论】:

这是一个非常糟糕的做法,因为所有元素都将“保持活跃”,这意味着它会占用内存。如果您有数千个元素,这将无法正常工作!

以上是关于ListView.builder 到达列表末尾并向后滚动后返回起始位置的主要内容,如果未能解决你的问题,请参考以下文章

在 Flutter 中构建列表项时如何更新 ListView.builder 的 itemCount?

ListView.builder itemCount 未更新

listview.builder 底部溢出和灵活和扩展小部件的错误

Flutter ListView.builder 创建一个无限滚动。如何将其设置为在内容结束时停止?

无法使用列表创建 ListView.builder

Flutter 中 ListView.builder 中的反向列表