首页 > 解决方案 > 用 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

仅此而已,我们将不胜感激。

标签: androidiosmysqldartflutter

解决方案


我相信您遇到的主要问题是您使用的是 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返回作为结果的一部分,告诉客户有多少图像。您也可以选择接受要返回的项目数。

对于后续请求,您可以接受参数numoffset。偏移量应该是描述返回的最后一个结果的一种方式,以便查询知道从哪里开始 - 如果您按 ID 排序,它可能是那个,或者按日期它可能是日期。并且num将是要返回的最大结果数。

如果您不返回 totalItems 作为响应的一部分,您可以选择有一个不同的请求,它只会为您提供最大数量的项目,因为这可能会让您的生活更轻松一些。如果您不想执行其中任何一项,请查看某人在评论中发布的链接,该链接描述了如何编写无限加载列表。

请注意,这只会返回图像的 URL,而不是图像本身。

接下来,您需要制作某种系统来缓存您的颤振代码。这可能会有点复杂,因为您不仅需要获取图像,还需要在页面滚动时获取图像的描述。

我会怎么做如下:

  • 制作一个分页管理器来处理加载项目的描述
  • 分页管理器可以请求项目,即从 0 开始,编号 30
  • 分页管理器启动此请求并跟踪它
  • 每个单独的列表项都向分页管理器请求其数据
  • 每个列表项都有一个 FutureBuilder,如果数据正在加载,它会显示一些内容,如果数据已加载,则会显示其他内容。
  • 经理知道该项目是否已启动请求,如果没有,它会对该项目+及其周围的项目发出请求(这样您就不会一次发送一堆单独的请求)
  • 列表项返回一个仅返回其数据的未来,如果数据已经加载或等待数据从服务器返回,它会立即完成
  • 在某些时候,如果您有大量图像,您可能希望取消初始化缓存管理器中的数据 - 您可以通过跟踪当前正在请求哪些图像并删除任何低于 100 或高于 100 的东西来做到这一点像那样。

还有另一种方法可以做到这一点,但可能不是特别适合您的数据库(取决于它的设置方式)。无需在客户端进行缓存,您可以简单地为每个图像(即 GET user/images/1、user/images/2 等)向服务器发出请求。然后您的服务器将在内部确定从哪里加载该图像并直接返回它,标题等作为标题。

这样做的问题是,天真的方法是为每个传入的请求执行数据库请求。这会起作用,但会导致对您的数据库的大量请求。取决于您的数据库设置,这可能很好,但很可能最终会导致问题。相反,您可以在您的服务器上对请求进行批处理(即,如果请求为 0,则还获取并缓存 0-30),并简单地对缓存的数据进行处理。我将把它留给你来实施,因为它超出了这个问题的范围。


推荐阅读