android - 用 MySQL 扑动无限滚动视图
问题描述
最近我在一个类似画廊的项目上工作,它显示了来自数据库的一堆图像。我使用 MySQL 是因为我有一个专用服务器,并且由于定价,使用 firestore 可能不是一个好主意。我希望它有一个无限滚动视图,每次加载 10 张图像,但目前它同时加载所有图像,这让应用程序感觉非常慢。
这是我的 lib/main.dart
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:http/http.dart' as http;
//local import, you can ignore it
import './nav.dart';
import './detail.dart';
import './placeholder.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
@override
_MyApp createState() => new _MyApp();
}
class _MyApp extends State<MyApp> {
Future<List> getData() async {
final resp = await http.get("http://192.168.43.68/API/readImage.php");
return json.decode(resp.body);
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return new MaterialApp(
theme: new ThemeData.dark(),
home: Scaffold(
appBar: new AppBar(
title: new Text("Title"),
),
drawer: Nav(),
body: new FutureBuilder(
future: getData(),
builder: (context, snapshot) {
if (snapshot.hasError) {
print(snapshot.error);
}
return snapshot.hasData
? new ItemList(
list: snapshot.data,
)
: new Center(child: new CircularProgressIndicator());
},),),);}}
//Item builder to display each image
class ItemList extends StatelessWidget {
final List list;
ItemList({this.list});
@override
Widget build(BuildContext context) {
// TODO: implement build
return new Padding(
padding: const EdgeInsets.all(0.0),
child: new StaggeredGridView.countBuilder(
crossAxisCount: 4,
itemCount: list.length,
itemBuilder: (BuildContext context, int index) => new Container(
child: new GestureDetector(
onTap: () => Navigator.of(context).push(new MaterialPageRoute(
builder: (BuildContext context) => new Detail(
list: list,
index: index,
))),
child: Card(
child: new Column(
children: <Widget>[
new Stack(
children: <Widget>[
FadeInImage.memoryNetwork(
placeholder: kTransparentImage,
image: list[index]['url'],
),],),
new Padding(
padding: EdgeInsets.all(4.0),
child: new Column(
children: <Widget>[
new Text(list[index]['name'],
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white70,
))],),)],),))),
staggeredTileBuilder: (index) => new StaggeredTile.fit(2),
),);}}
这是我的 readImage.php (我仍然不知道该怎么做)
<?php
include 'dbcon.php';
$sth = "SELECT * FROM pict INNER JOIN user ON pict.uid=user.uid LIMIT 10";
$result = $conn->query($sth);
$outp = array();
$outp = $result->fetch_all(MYSQLI_ASSOC);
echo json_encode($outp);
enter code here
仅此而已,我们将不胜感激。
解决方案
我相信您遇到的主要问题是您使用的是 StaggeredTile.fit()。
一些背景信息:在颤振中,图像大小不是立即确定的,而是在图像实际加载后确定的。这对于少量图像来说很好,但是当您有大量(可能是无限的)图像时,它会导致问题。
这样做的原因是,当第一次构造列表时,它会布置足够的孩子以适应屏幕(+可能不适用于 StaggeredGridView 但适用于大多数颤动列表的 cacheExtent )。想一想列表中的每个项目将如何布置自己 - GestureDetector、Card、Column 和 Stack 所有代表其子项的大小。所以尺寸取决于图像。占位符并不能真正确定高度,因此您可以想象高度为零或一的图像。
这意味着在您的页面中,随着越来越多的项目添加到屏幕上,它会不断尝试加载越来越多的图像 - 垂直像素数或基本上无限的像素数,具体取决于处理占位符高度的方式。
对此有一个简单的解决方案,但它可能对您没有吸引力 - 不要使用 StaggeredTile.fit(),而是使用固定高度,然后让您的图像适合该高度。
不幸的是,如果您希望每个图像具有不同的高度,它会变得更加复杂。处理该问题的一种方法(因为您可以控制数据库)是将纵横比与每个图像的路径一起存储在数据库中。这样你就可以用 AspectRatio 小部件包装你的 FaceInImage,因此高度会立即确定。
当然,这只有在您的项目列表不是真正无限的情况下才有效,但由于它在数据库中,我认为假设 =D 是相当安全的。您可以使用PHP 中的 getimagesize 函数来计算每个图像的纵横比,然后在整个数据库中运行一次,然后从现在开始,每当您插入新图像时,您都必须计算纵横比。
另一种解决方案是从您自己指定的固定高度开始,然后在加载图像后更改高度。但是,我不知道 StaggeredGridView 会如何处理这个问题——我见过的大多数列表/网格类都希望他们的孩子保持相同的大小。在这种情况下,您可能必须查看 CustomScrollView 并编写自己的条子,这超出了这个问题的范围。
编辑:OP澄清说,显然MySQL部分是他们要问的,而不是为什么它很慢。我仍然认为图像是为什么它很慢而不是请求 URL 的原因,但我可能是错的。尚未指定是否正在加载 10、50、100 或 1000 张图像。
如果没有更多关于如何设置数据库的知识,很难就这是否是问题给出明确的答案。但我将简要介绍一种设置服务器的可能方法。
我不会为您的服务器编写实际代码,因为这超出了关于颤振的 SO 问题的范围。如果您无法弄清楚,我建议您先找到有关设置服务器的课程或教程,然后再问一个没有用颤振标记而是用 PHP/MySQL 等标记的问题,因为那实际上是什么你问的是你遇到的具体问题,而不是一般的“我不能帮助我”,因为这不会得到答案。
假设如下:
- 您已经设置了一个数据库,您可以在其中查询 URL、描述等列表
- 这个数据库预计会返回大量的值(例如超过 200 或什么)
在服务器端,您将不得不设置一个端点来响应请求并返回部分数据。如果您想使用 JSON,我希望没有参数的端点返回如下内容:
{
num: 30,
images: [
{ title: ..., url: ..., .... },
...
],
more: true
}
并且该列表必须按某些参数进行排序 - 标题、日期、id 等。您也可以选择totalItems
返回作为结果的一部分,告诉客户有多少图像。您也可以选择接受要返回的项目数。
对于后续请求,您可以接受参数num
和offset
。偏移量应该是描述返回的最后一个结果的一种方式,以便查询知道从哪里开始 - 如果您按 ID 排序,它可能是那个,或者按日期它可能是日期。并且num
将是要返回的最大结果数。
如果您不返回 totalItems 作为响应的一部分,您可以选择有一个不同的请求,它只会为您提供最大数量的项目,因为这可能会让您的生活更轻松一些。如果您不想执行其中任何一项,请查看某人在评论中发布的链接,该链接描述了如何编写无限加载列表。
请注意,这只会返回图像的 URL,而不是图像本身。
接下来,您需要制作某种系统来缓存您的颤振代码。这可能会有点复杂,因为您不仅需要获取图像,还需要在页面滚动时获取图像的描述。
我会怎么做如下:
- 制作一个分页管理器来处理加载项目的描述
- 分页管理器可以请求项目,即从 0 开始,编号 30
- 分页管理器启动此请求并跟踪它
- 每个单独的列表项都向分页管理器请求其数据
- 每个列表项都有一个 FutureBuilder,如果数据正在加载,它会显示一些内容,如果数据已加载,则会显示其他内容。
- 经理知道该项目是否已启动请求,如果没有,它会对该项目+及其周围的项目发出请求(这样您就不会一次发送一堆单独的请求)
- 列表项返回一个仅返回其数据的未来,如果数据已经加载或等待数据从服务器返回,它会立即完成
- 在某些时候,如果您有大量图像,您可能希望取消初始化缓存管理器中的数据 - 您可以通过跟踪当前正在请求哪些图像并删除任何低于 100 或高于 100 的东西来做到这一点像那样。
还有另一种方法可以做到这一点,但可能不是特别适合您的数据库(取决于它的设置方式)。无需在客户端进行缓存,您可以简单地为每个图像(即 GET user/images/1、user/images/2 等)向服务器发出请求。然后您的服务器将在内部确定从哪里加载该图像并直接返回它,标题等作为标题。
这样做的问题是,天真的方法是为每个传入的请求执行数据库请求。这会起作用,但会导致对您的数据库的大量请求。取决于您的数据库设置,这可能很好,但很可能最终会导致问题。相反,您可以在您的服务器上对请求进行批处理(即,如果请求为 0,则还获取并缓存 0-30),并简单地对缓存的数据进行处理。我将把它留给你来实施,因为它超出了这个问题的范围。
推荐阅读
- firebase - 没有电子邮件的 Firebase 身份验证
- mongodb - MongoDB errmsg:“无法从缺少的 BSON 类型转换为日期”
- android - 当收到 FCM 通知而不点击 Android 上的通知时,颤振打开应用程序,如 whatsapp
- sql - 具有数据库连接的 Blazor 无法删除条目
- python - AttributeError:“PositionalEncoding”对象没有属性“位置”
- javascript - Uncaught (in promise) TypeError: Cannot read property 'status' of undefined at VueComponent.sessionChecker
- python - 如何使用对象在 sqlalchemy 中更新?
- html - 为什么 npm sass 编译没有运行?
- android - 我使用带有改造功能的 firebase messeging 来发送通知,当应用程序处于 bakground 或从最近的应用程序中删除时,不会出现通知
- javascript - 如何在按钮单击上使用useEffect?