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