Как добавить новые элементы в построитель вида сетки Flutter из API, когда вид прокрутки достигает середины

#flutter

#flutter

Вопрос:

Я пытаюсь обновить список моделей, когда вид прокрутки достигает конца, хотя я смог это сделать, я не могу сделать это плавно. Я хочу загружать новые изображения из API в список моделей всякий раз, когда пользователь достигает середины прокрутки, чтобы прокрутка была плавной, как в текущем сценарии, когда пользователь достигает конца, наступает секунда или две паузы, после чего будут добавлены новые элементы, чтобы пользователь мог видеть, как они отображаются.пользователь не будет знать, добавлены ли новые элементы. Кроме того, мой текущий подход слишком сильно нагружает основной поток и приводит к сбою приложения.

вот код ниже:

home.dart

 import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:wallpaper_app/data/data.dart';
import 'package:wallpaper_app/model/categories.dart';
import 'package:wallpaper_app/model/wallpaper_model.dart';
import 'package:wallpaper_app/network/network.dart';
import 'package:wallpaper_app/widgets/branding.dart';
import 'package:wallpaper_app/widgets/list_tile.dart';
import 'package:wallpaper_app/widgets/wallpaper_tile.dart';
var pageIndex=1;
Data data = Data();
List<CategoriesModel> categories;
List<WallpaperModel> wallpapers =[];

class Home extends StatefulWidget {
  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State<Home> {
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _controller = ScrollController();
    _controller.addListener(() {
      if (_controller.position.atEdge) {
        if (_controller.position.pixels == 0)
          print('top');
      // you are at top position
      else{
          print('end');
          incrementIndex();
          getWallpapers(pageIndex);
        }

      // you are at bottom position
    }
    });
    categories = data.getCategories();
    getWallpapers(pageIndex);
  }
  ScrollController _controller;

  void incrementIndex(){
    setState(() {
      pageIndex  ;
    });
  }

  void getWallpapers(var index) async {
    try {
      Network network = Network();
      Map<String, dynamic> jsonData = await network.getCuratedWallpapers(
          index, 80);
      jsonData["photos"].forEach((element) {
        WallpaperModel wallpaperModel = WallpaperModel.fromMap(element);
        setState(() {
          wallpapers.add(wallpaperModel);
        });
//      for (var wallpaper in wallpapers){
//        print(wallpaper.src.small);
//      }
      });
    }
    catch(e){
      print(e);
    }
  }

  @override
  Widget build(BuildContext context) {
    SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
      systemNavigationBarColor: Color(0xff999999), // navigation bar color
    ));
    return Scaffold(
      appBar: AppBar(
        elevation: 0,
        backgroundColor: Colors.white,
        title: Brand(),
      ),
      body: SingleChildScrollView(
        controller: _controller,
        child: Container(
          child: Column(
            children: [
              Container(
                height: 100,
                child: ListView.builder(
//                  controller: _controller,
                    padding: EdgeInsets.all(10),
                    itemCount: categories.length,
//                  shrinkWrap: true,
                    scrollDirection: Axis.horizontal,
                    itemBuilder: (context, index) {
                      return CategoriesTile(
                        imageUrl: categories[index].imageUrl,
                        title: categories[index].categoriesName,
                      );
                    }),
              ),
              SizedBox(
                height: 20,
              ),
              Container(
                padding: EdgeInsets.symmetric(horizontal: 10),
                child: GridView.builder(
                  physics: ScrollPhysics(),
                  shrinkWrap: true,
                    gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                        crossAxisCount: 2,
                        crossAxisSpacing: 6.0,
                        mainAxisSpacing: 6.0,
                        childAspectRatio: 0.6),
                    itemBuilder: (context,index){
//                      if(index==50){
////                        incrementIndex();
//                        getWallpapers(pageIndex 1);
//                      }
                      return WallpaperTile(small: wallpapers[index].src.portrait,);
                    },
                  itemCount: wallpapers.length,
                    ),
              )
            ],
          ),
        ),
      ),
    );
  }
}
  

wallpaper_model.dart

 class WallpaperModel {
  String photographer;
  String photographerUrl;
  int photographerId;
  SrcModel src;
  WallpaperModel(
      {this.photographer, this.photographerUrl, this.photographerId, this.src});
  factory WallpaperModel.fromMap(Map<String,dynamic> jsonData){
    return WallpaperModel(
      photographer: jsonData["photographer"],
      photographerId: jsonData["photographer_id"],
      photographerUrl: jsonData["photographer_url"],
      src: SrcModel.fromMap(jsonData["src"])
    );
  }
}

class SrcModel {
  String original;
  String small;
  String portrait;
  SrcModel({this.original, this.small, this.portrait});
  factory SrcModel.fromMap(Map<String, dynamic> jsonData) {
    return SrcModel(
        original: jsonData["original"],
        small: jsonData["small"],
        portrait: jsonData["portrait"]);
  }
}
  

wallpaper_tile.dart

 import 'package:flutter/material.dart';

class WallpaperTile extends StatelessWidget {
  final String small;
  WallpaperTile({this.small});
  @override
  Widget build(BuildContext context) {
    return Container(
      child: ClipRRect(
        borderRadius: BorderRadius.circular(20),
          child:
          Image.network(
        small,
        fit: BoxFit.cover,
      )),
    );
  }
}
  

network.dart

 import 'dart:convert';

import 'package:http/http.dart' as http;
const apiKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
class Network{
 Future<dynamic> getCuratedWallpapers(var pageNo, var maxWallpapers) async{

   var response = await http.get("https://api.pexels.com/v1/curated/?page=$pageNoamp;per_page=$maxWallpapers",
       headers: {
     "Authorization":apiKey
   });
   return jsonDecode(response.body);
 }
}
  

вид верхнего списка предназначен только для категорий и может быть проигнорирован.
именно grid view.builder обладает основной функциональностью

Комментарии:

1. то, что вы делаете, вообще не является хорошей практикой, вы очень часто перестраиваете свои виджеты, вам следует использовать Provider или InjeritedWidget, чтобы поднять список обоев, это означает, что у вас будет какой-то объект над всеми виджетами, и вы введете его в виджет, где вы этого хотите. после этого выобновит список обоев введенного объекта и перестроит GridView.builder. Особенность поставщиков и унаследованных виджетов заключается в том, что они не теряют данные и не изменяют их при обновлении своих дочерних виджетов, если только вы не обновляете данные поставщика с помощью какой-либо функции нажатия кнопок.

2. Спасибо, я внедрю Provider и изменю Notifier для списка обоев, надеюсь, это исправит проблему с потоком пользовательского интерфейса. Не могли бы вы также посмотреть, могу ли я добавить обратный вызов, когда прокрутка достигает середины?

Ответ №1:

Хотя я не мог решить основную проблему (загрузка каждый раз, когда прокрутка достигает середины экрана, чтобы пользователю не приходилось ждать и видеть индикатор выполнения), но я смог решить все другие проблемы, помимо этого.

  1. слишком много изображений (> 3000) приводили к сбою приложения без трассировки стека. Решение-> Вместо использования представления сетки внутри singleChildScrollView я добавил его в расширенный виджет, который решил проблему сбоев и вялого поведения прокрутки.
  2. после определенного количества страниц я начал очищать ImageCache, потому что из того, что я искал, я обнаружил, что после сжатия изображений они все еще могут оставаться в памяти (я до сих пор не знаю, насколько это верно, поскольку это внутренний процесс flutter).
  3. Я также использовал Provider вместе с ChangeNotifier вместо setState, чтобы избежать дополнительной перестройки виджетов. Результат: — Хотя пользовательский интерфейс не очень плавный, прокрутка происходит плавно, изображения автоматически очищаются, когда они не отображаются. Появляется индикатор выполнения на секунду, после чего в список загружается следующая страница из вызова API. В некотором смысле это моя реализация отложенной загрузки. Я не вызываю асинхронный вызов, когда выполняется другой, используя логическое значение.
  4. Я также добавил cacheheight и cachewidth к каждому изображению, чтобы просто загружать сжатые размеры изображений в виде сетки, а не загружать в него полноразмерные изображения.
  5. Кроме того, я использовал контроллер прокрутки для загрузки дополнительных элементов в список каждый раз, когда прокрутка достигает нижней части страницы. Вот основные изменения.

main.dart

 import 'package:flutter/material.dart';
import 'package:wallpaper_app/data/provider_data.dart';
import 'package:wallpaper_app/screens/home.dart';
import 'package:provider/provider.dart';
void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context)=>ProviderData(),
      child: MaterialApp(
        theme: ThemeData.dark().copyWith(
          scaffoldBackgroundColor: Colors.black,
        ),
        title: 'Wallpaper App',
        home: Home()
      ),
    );
  }
}
  

home.dart

 import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:wallpaper_app/data/data.dart';
import 'package:wallpaper_app/data/provider_data.dart';
import 'package:wallpaper_app/model/categories.dart';
import 'package:wallpaper_app/model/wallpaper_model.dart';
import 'package:wallpaper_app/network/network.dart';
import 'package:wallpaper_app/widgets/branding.dart';
import 'package:wallpaper_app/widgets/list_tile.dart';
import 'package:wallpaper_app/widgets/wallpaper_tile.dart';
import 'package:provider/provider.dart';
import 'package:flutter/services.dart';
import 'package:flutter_pagewise/flutter_pagewise.dart';
Data data = Data();
List<CategoriesModel> categories;
class Home extends StatefulWidget {
  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State<Home> {
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    categories = data.getCategories();
    WidgetsBinding.instance.addPostFrameCallback((_) async {
      _controller = ScrollController();
      _controller.addListener(() {
        if (_controller.position.atEdge) {
          if (_controller.position.pixels == 0)
            print('top');
          // you are at top position
          else{
            print('end');
//          incrementIndex();
            if(Provider.of<ProviderData>(context,listen: false).pageIndex>5){
              print('image cache: ${imageCache.currentSize}');
              imageCache.clear();
            }
            Provider.of<ProviderData>(context,listen: false).incrementPage();
            getWallpapers(Provider.of<ProviderData>(context,listen: false).pageIndex);
          }

          // you are at bottom position
        }
      });
      getWallpapers(Provider.of<ProviderData>(context,listen: false).pageIndex);
    });

  }
  ScrollController _controller;

  void getWallpapers(var index) async {
    if(!Provider.of<ProviderData>(context,listen: false).isPerformingRequest){
      Provider.of<ProviderData>(context,listen: false).togglePerformingRequest();

      try {
        Network network = Network();
        Map<String, dynamic> jsonData = await network.getCuratedWallpapers(
            index, 80);
        jsonData["photos"].forEach((element) {
          WallpaperModel wallpaperModel = WallpaperModel.fromMap(element);
          setState(() {
            Provider.of<ProviderData>(context,listen: false).addWallpapers(wallpaperModel);
          });
        });
      }
      catch(e){
        print(e);
      }
      Provider.of<ProviderData>(context,listen: false).togglePerformingRequest();
    }


  }

  @override
  Widget build(BuildContext context) {
  SystemChrome.setEnabledSystemUIOverlays([]);
    return Scaffold(
//      appBar: AppBar(
//        backgroundColor: Colors.black,
//        elevation: 0,
////        backgroundColor: Colors.white,
//        title: Brand(),
//      ),
      body:
//      SingleChildScrollView(
//        controller: _controller,
//        child:
        Container(
          child: Column(
            children: [
//              Container(
//                height: 100,
//                child: ListView.builder(
////                  controller: _controller,
//                    padding: EdgeInsets.all(10),
//                    itemCount: categories.length,
//                  shrinkWrap: true,
//                    scrollDirection: Axis.horizontal,
//                    itemBuilder: (context, index) {
//                      return CategoriesTile(
//                        imageUrl: categories[index].imageUrl,
//                        title: categories[index].categoriesName,
//                      );
//                    }),
//              ),
//              SizedBox(
//                height: 20,
//              ),
              Expanded(
//                padding: EdgeInsets.symmetric(horizontal: 10),
                child: GridView.builder(
                  controller: _controller,
                  physics: ScrollPhysics(),
                  shrinkWrap: true,
                  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                      crossAxisCount: 2,
                      crossAxisSpacing: 6.0,
                      mainAxisSpacing: 6.0,
                      childAspectRatio: 0.6),
                      addAutomaticKeepAlives: false,
                  itemBuilder: (context,index){
                    if(index==(Provider.of<ProviderData>(context,listen: false).wallpapers!=null?Provider.of<ProviderData>(context,listen: false).wallpapers.length 0:80)){
                      return _buildProgressIndicator();
                    }
                    else
                    return WallpaperTile(small: Provider.of<ProviderData>(context,listen: false).wallpapers[index].src.medium,);
                  },
                  itemCount: Provider.of<ProviderData>(context,listen: false).wallpapers!=null?Provider.of<ProviderData>(context,listen: false).wallpapers.length 1:81,
                ),
              )
            ],
          ),
        ),
//      ),
    );
  }
}

Widget _buildProgressIndicator() {
  return new Padding(
    padding: const EdgeInsets.all(8.0),
    child: new Center(
      child: new Opacity(
      opacity: 1,
        child: new CircularProgressIndicator(),
      ),
    ),
  );
}
  

wallpaper_model.dart

 class WallpaperModel {
  String photographer;
  String photographerUrl;
  int photographerId;
  SrcModel src;
  WallpaperModel(
      {this.photographer, this.photographerUrl, this.photographerId, this.src});
  factory WallpaperModel.fromMap(Map<String,dynamic> jsonData){
    return WallpaperModel(
      photographer: jsonData["photographer"],
      photographerId: jsonData["photographer_id"],
      photographerUrl: jsonData["photographer_url"],
      src: SrcModel.fromMap(jsonData["src"])
    );
  }
}

class SrcModel {
  String original;
  String small;
  String portrait;
  String medium;
  SrcModel({this.original, this.small, this.portrait,this.medium});
  factory SrcModel.fromMap(Map<String, dynamic> jsonData) {
    return SrcModel(
        original: jsonData["original"],
        small: jsonData["small"],
        medium: jsonData["medium"],
        portrait: jsonData["portrait"]);
  }
}
  

network.dart

 import 'dart:convert';

import 'package:http/http.dart' as http;
const apiKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
class Network{
 Future<dynamic> getCuratedWallpapers(var pageNo, var maxWallpapers) async{

   var response = await http.get("https://api.pexels.com/v1/curated/?page=$pageNoamp;per_page=$maxWallpapers",
       headers: {
     "Authorization":apiKey,
   });
   print(response.statusCode);
//   print(response.body);
   return jsonDecode(response.body);
 }
}
  

wallpaper_tile.dart

 import 'package:flutter/material.dart';

class WallpaperTile extends StatelessWidget {
  final String small;
  WallpaperTile({this.small});
  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.all(5),
      decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(25),
          color: Colors.grey
      ),
      child: ClipRRect(
        borderRadius: BorderRadius.circular(25),
          child:
          Image.network(
        small,
        fit: BoxFit.cover,
        cacheHeight: 400,
        cacheWidth: 225,
      )
      ),
    );
  }
}
  

provider_data.dart

 import 'package:flutter/foundation.dart';
import 'package:wallpaper_app/model/wallpaper_model.dart';

class ProviderData extends ChangeNotifier{
  List<WallpaperModel> wallpapers =[];
  bool isPerformingRequest = false;
  var pageIndex=1;

  void togglePerformingRequest(){
    isPerformingRequest = !isPerformingRequest;
    notifyListeners();
  }
  void incrementPage(){
    pageIndex  ;
    print(pageIndex);
    notifyListeners();
  }

  void addWallpapers(WallpaperModel wallpaperModel){
    wallpapers.add(wallpaperModel);
    notifyListeners();
  }
}
  

Комментарии:

1. «Я добавил его в расширенный виджет, который решил проблему сбоев и вялого поведения прокрутки». эта строка спасла мой день. Спасибо!

Ответ №2:

Вам нужно использовать NotificationListener with ScrollNotification . Измените это в соответствии с вашими требованиями.

 
import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  int present = 0;
  int perPage = 15;

  final originalItems = List<String>.generate(10000, (i) => "Item $i");
  var items = List<String>();


  @override
  void initState() {
    super.initState();
    setState(() {
      items.addAll(originalItems.getRange(present, present   perPage));
      present = present   perPage;
    });
  }

  void loadMore() {
    setState(() {
      if((present   perPage )> originalItems.length) {
        items.addAll(
            originalItems.getRange(present, originalItems.length));
      } else {
        items.addAll(
            originalItems.getRange(present, present   perPage));
      }
      present = present   perPage;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: NotificationListener<ScrollNotification>(
        onNotification: (ScrollNotification scrollInfo) {
          if (scrollInfo.metrics.pixels ==
              scrollInfo.metrics.maxScrollExtent / 2) {
            loadMore();
          }
        },
        child: ListView.builder(
          itemCount: (present <= originalItems.length) ? items.length   1 : items.length,
          itemBuilder: (context, index) {
            return (index == items.length ) ?
            Container(
              color: Colors.greenAccent,
              child: FlatButton(
                child: Text("Load More"),
                onPressed: () {
                  loadMore();
                },
              ),
            )
                :
            ListTile(
              title: Text('${items[index]}'),
            );
          },
        ),
      ),
    );
  }
}

  

Подробнее.

Комментарии:

1. Я попробовал, и это условие (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent / 2) никогда не достигается, деление на 2 дает другое значение, и даже взятие слова не позволяет достичь этого условия. Я использовал обработанный, чем eaqual, и созданная мной функция loadmore затем вызывается непрерывно после пересечения mid, что приводит к сбою приложения.

2. Можете ли вы попробовать что-то подобное? если (scrollInfo.metrics.pixels > MediaQuery.of(context).size.height / 2)

3. Я тоже пробовал это, но происходит то, что он продолжает вызывать бесконечно, когда я прокручиваю вниз, и продолжает непрерывно загружать больше изображений. Как и каждый раз, когда вызывается load more, я загружаю 80 изображений из API. Но высота, с которой она сравнивается, по какой-то причине остается неизменной и динамически не изменяется, что приводит к сбою приложения из-за одновременной загрузки слишком большого количества изображений,

Ответ №3:

попробуйте SliverGrid вместо GridView.builder в вашем home.dart .

потому что, когда вы будете использовать GridView.builder его, он загрузит все возможные результаты сразу, а не когда вы используете SliverGrid его, будут загружены только те данные, которые в данный момент присутствуют screen .

 SliverGrid(delegate: SliverChildBuilderDelegate((context, int index) {
          final _wallpaper = wallpaper.photos ? [index];
          return Container(decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0),), height: MediaQuery
              .of(context)
              .size
              .height / 2, child: GestureDetector(onTap: () {
            //Wallpaper Preview
          },
            child: Hero(tag: '${_wallpaper?.id}',
              child: ClipRRect(borderRadius: BorderRadius.circular(10.0),
                child: CachedNetworkImage(fit: BoxFit.cover,
                  imageUrl: _wallpaper!.src!.large!,
                  filterQuality: FilterQuality.high,
                  progressIndicatorBuilder: (context, url, progress) => Center(child: CircularProgressIndicator()),),),),),);
        }, childCount: wallpaper.photos?.length),
          gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, childAspectRatio: 0.5, crossAxisSpacing: 5.0 mainAxisSpacing: 5.0,),),
  

примечание: удалите лишнее, что вам не требуется.