首页 > 解决方案 > ListView.builder 到达列表末尾并向后滚动后返回起始位置

问题描述

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

当我滚动列表时,它会正常运行,直到到达最后两项。然后当我开始向后滚动并且从末尾开始的第三个项目即将显示在屏幕上时,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 函数正在启动(当最后的第三个项目即将出现在屏幕上时)

我该如何解决?

标签: flutterlistviewdartsetstate

解决方案


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

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

推荐阅读