用MySQL扑动无限滚动视图

时间:2018-08-23 15:03:10

标签: android ios mysql dart flutter

最近我在像项目这样的画廊工作,该画廊显示了数据库中的一堆图像。我使用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

仅此而已,任何回应将不胜感激。

1 个答案:

答案 0 :(得分:1)

我相信您遇到的主要问题是您正在使用StaggeredTile.fit()。

一些背景信息:在抖动中,图像大小不是立即确定的,而是在图像实际加载后确定的。这对于少量的图像很好,但是当数量较大(可能无限)时,会引起问题。

这样做的原因是,在第一次构建列表时,它会布局足以适合屏幕的子级(+ cacheExtent,这可能不适用于StaggeredGridView,但适用于大多数抖动列表)。考虑一下列表中的每个项目将如何布局-GestureDetector,Card,Column和Stack为其子代分配的所有委托大小。因此,大小取决于图像。占位符并不能真正确定高度,因此您可以想到高度为零或一的图像。

这意味着在您的页面中,随着越来越多的项目添加到屏幕上,它不断尝试加载越来越多的图像-垂直像素数或实际上是无限的,这取决于占位符高度的精确度

对此有一个简单的解决方案,但它可能对您没有吸引力-而不是使用StaggeredTile.fit(),而是使用固定的高度,然后使图像适合该高度。

不幸的是,如果您希望每个图像都具有不同的高度,它将变得更加复杂。解决该问题的一种方法(因为您可以控制数据库)是将纵横比以及每个图像的路径存储在数据库中。这样,您可以使用AspectRatio小部件包装FaceInImage,因此可以立即确定高度。

这当然只有在您的项目列表不是真正无限的情况下才有效,但是由于它在数据库中,所以我认为假定= D是相当安全的。您可以在PHP中使用the getimagesize function来计算每个图像的长宽比,然后一次在整个数据库中运行,然后从现在开始,每当插入新图像时,都必须计算长宽比。

另一种解决方案是从您指定的固定高度开始,然后在加载图像后更改高度。但是,我不知道StaggeredGridView会如何处理-我见过的大多数列表/网格类都希望他们的孩子保持相同的大小。在这种情况下,您可能必须查看CustomScrollView并编写自己的条子,这超出了此问题的范围。


编辑:OP阐明了MySQL部分显然是他们要问的,而不是为什么它运行缓慢。我仍然认为图像是为什么它慢而不是对URL的请求的原因,但是我可能是错的。尚未指定是要加载10、50、100还是1000张图像。

在没有更多有关如何设置数据库的知识的情况下,很难就是否存在问题给出明确的答案。但是,我将快速概述一下设置服务器的可能方法。

我不会为您的服务器编写实际的代码,因为这超出了关于抖动的SO问题的范围。如果您不知道该如何解决,我建议您先找到一门有关设置服务器的课程或教程,然后再问一个不是用flutter而是用PHP / MySQL等标记的问题确实是您要问的问题,而不是一般的“我无法帮助我”,是关于您遇到的具体问题,因为那样就无法得到答案。


假设以下内容:

  • 您已经建立了一个数据库,可以查询URL,描述等列表
  • 预计该数据库将返回大量值(例如,超过200个或类似值)

在服务器端,您将必须设置一个端点来响应请求并返回数据部分。如果您想使用JSON,我希望没有参数的端点返回类似以下的内容:

{
   num: 30,
   images: [
    { title: ..., url: ..., .... },
    ... 
   ],
   more: true
}

该列表必须按某些参数进行排序-标题,日期,ID等。您也可以使用totalItems作为返回结果的一部分来告诉客户,其中有多少图像。您也可以选择接受要退回的商品数量。

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

如果您不将totalItems作为响应的一部分返回,则可以选择有一个不同的请求,该请求仅向您提供最大数量的项目,因为这可能会使您的生活变得更轻松。如果您不想执行任何一项操作,请查看the link someone posted in a comment,其中介绍了如何编写无限加载列表。

请注意,这只会返回图片的网址,而不是图片本身。

接下来,您将需要建立某种系统来缓存Flutter代码。这可能会变得有些复杂,因为在页面滚动时,您不仅必须获取图像,而且还需要获取图像的描述。

我将如何操作如下:

  • 制作一个分页管理器来处理项目的描述
  • 分页管理器可以请求商品,即从0开始,数量为30
  • 分页管理器启动此请求并对其进行跟踪
  • 每个单独的列表项都向分页管理器请求其数据
  • 每个列表项都有一个FutureBuilder,它在加载数据时显示一些内容,在加载数据时显示其他内容。
  • 经理知道是否已针对该项目启动了一个请求,如果不是,它将针对该项目+及其周围的项目发出请求(这样一来,您就不会立即发送一堆单独的请求)
  • 列表项返回一个将来将仅返回其数据的状态,如果已加载数据或等待直到从服务器返回数据,该操作将立即完成
  • 在某些时候,如果您有很多图像,则可能需要在缓存管理器中取消初始化数据-您可以通过跟踪当前正在请求哪些图像并删除所有<100以下或> 100的图像来实现此目的更高或类似的东西。

还有另一种方法可以执行此操作,但是对于您的数据库可能并不是特别理想(取决于它的设置方式)。您无需在客户端进行缓存,只需向服务器发送每个图像的请求(即GET user / images / 1,user / images / 2等)。然后,您的服务器将在内部确定从何处加载该图像,并以标题等作为标题直接返回它。

这样做的问题是,幼稚的方法是对每个传入的请求都进行数据库请求。这会起作用,但会导致对数据库的大量请求。根据您的数据库设置,可能还可以,但是最终很可能会导致问题。相反,您可以在服务器上批量处理请求(即,如果请求为0,也获取并缓存0-30),然后只需对缓存的数据进行处理即可。我将把它留给您实施,因为它超出了此问题的范围。