在flutter项目中渲染列表

103 阅读3分钟

本文章是总结渲染列表的具体步骤,代码参考于正在着手开发的项目

首先,想要从后端服务器接收数据并将这个数据渲染到页面上,需要先接收并定义类模型,,也就是从服务器接收的数据

这个类模型会自动生成一个 .g.dart 文件,当你修改接收到的数据时,也要在对应 .g.dart 文件修改

进行演示的代码片段为点击目标位置进行弹窗,然后在弹窗中渲染一个输入框和列表,所演示流程仅为接收并渲染数据,和其他功能无关。

1.接收并定义类模型

目录 security.dart

import 'package:collection/collection.dart';
import 'package:decimal/decimal.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:rational/rational.dart' show Rational;
import '../enums.dart';
part 'security.g.dart';//对应的.g.dart文件

@JsonSerializable()
class Security {
  num? id;
  String? code;
  String? name;
  String? acronym;
  @JsonKey(name: 'exchange_type')
  ExchangeType exchangeType;
  @JsonKey(name: 'market_value')
  String? marketValue;
  String? pe;
  @JsonKey(name: 'yesterday_price')
  Decimal? yesterdayPrice;
  @JsonKey(name: 'yesterday_range')
  Decimal? yesterdayRange;
  @JsonKey(name: 'lowest_price_by_dividend')
  Decimal? lowestPriceByDividend;
  @JsonKey(name: 'max_draw_down_by_dividend')
  Decimal? maxDrawDownByDividend;
  @JsonKey(name: 'lowest_price_by_profit')
  Decimal? lowestPriceByProfit;
  @JsonKey(name: 'max_draw_down_by_profit')
  Decimal? maxDrawDownByProfit;

  @JsonKey(name: 'revenue_year_growth_rate')
  Decimal? revenueYearGrowthRate;
  @JsonKey(name: 'profit_year_growth_rate')
  Decimal? profitYearGrowthRate;
  @JsonKey(name: 'dividend_rate')
  String? dividendRate;

  @JsonKey(name: 'dividend')
  Decimal? dividend;

  @JsonKey(name: 'by_people')
  bool? byPeople;
  @JsonKey(name: 'is_dividend')
  bool? isDividend;
  @JsonKey(name: 'is_holdings')
  bool? isHoldings;
  @JsonKey(name: 'security_type')
  SecurityType? securityType;

  @JsonKey(name: 'current_price')
  Decimal? currentPrice;
  @JsonKey(name: 'percent_change')
  Decimal? percentChange;
  @JsonKey(name: 'updated_at')
  String? updatedAt;

  @JsonKey(name: 'industry')
  List<Industry>? industry;

  Security(
    this.id,
    this.code,
    this.name,
    this.acronym,
    this.exchangeType,
    this.marketValue,
    this.pe,
    this.yesterdayPrice,
    this.yesterdayRange,
    this.lowestPriceByDividend,
    this.maxDrawDownByDividend,
    this.lowestPriceByProfit,
    this.maxDrawDownByProfit,
    this.revenueYearGrowthRate,
    this.profitYearGrowthRate,
    this.dividendRate,
    this.dividend,
    this.byPeople,
    this.isDividend,
    this.isHoldings,
    this.securityType,
    this.currentPrice,
    this.percentChange,
    this.updatedAt,
    this.industry
  );

  String get yesterdayDisplay {
    if (yesterdayRange == null || yesterdayPrice == null) {
      return '--';
    }
    if (yesterdayRange! < Decimal.zero) {
      return "$yesterdayPrice ($yesterdayRange%)";
    }
    return "$yesterdayPrice (+$yesterdayRange%)";
  }

  Decimal? dividendRateTodayDisplay(Decimal? currentPrice, Decimal? dividendRatio) {
    if (dividend == null ||
        dividendRatio == null ||
        currentPrice == null ||
        currentPrice <= Decimal.zero) {
      return null;
    }

    Rational r = dividend! * dividendRatio! * Decimal.fromInt(100) / currentPrice;

    return r.toDecimal(scaleOnInfinitePrecision: 3);
  }

  bool isFundType() {
    if (securityType == null) {
      return false;
    }
    return securityType! == SecurityType.securityETF ||
        securityType! == SecurityType.securityLOF;
  }

  String get dividendRateDisplay {
    if (dividendRate == null) {
      return '--';
    }
    return "$dividendRate%";
  }

  @override
  String toString() {
    return 'Security(id: $id, code: $code, name: $name, acronym: $acronym, exchangeType: $exchangeType, marketValue: $marketValue)';
  }

  factory Security.fromJson(Map<String, dynamic> json) {
    return _$SecurityFromJson(json);
  }

  Map<String, dynamic> toJson() => _$SecurityToJson(this);

  @override
  bool operator ==(Object other) {
    if (identical(other, this)) return true;
    if (other is! Security) return false;
    final mapEquals = const DeepCollectionEquality().equals;
    return mapEquals(other.toJson(), toJson());
  }

  @override
  int get hashCode =>
      id.hashCode ^
      code.hashCode ^
      currentPrice.hashCode ^
      percentChange.hashCode ^
      securityType.hashCode;
}

@JsonSerializable()
  class Industry {

  @JsonKey(name: 'id')
  int? id;

  @JsonKey(name: 'ind_name')
  String? indName;

  Industry(this.id,this.indName,);

  factory Industry.fromJson(Map<String, dynamic> srcJson) => _$IndustryFromJson(srcJson);

}

目录 security.g.dart

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'security.dart';

// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************

Security _$SecurityFromJson(Map<String, dynamic> json) => Security(
      json['id'] as num?,
      json['code'] as String?,
      json['name'] as String?,
      json['acronym'] as String?,
      $enumDecode(_$ExchangeTypeEnumMap, json['exchange_type']),
      json['market_value'] as String?,
      json['pe'] as String?,
      json['yesterday_price'] == null
          ? null
          : Decimal.fromJson(json['yesterday_price'] as String),
      json['yesterday_range'] == null
          ? null
          : Decimal.fromJson(json['yesterday_range'] as String),
      json['lowest_price_by_dividend'] == null
          ? null
          : Decimal.fromJson(json['lowest_price_by_dividend'] as String),
      json['max_draw_down_by_dividend'] == null
          ? null
          : Decimal.fromJson(json['max_draw_down_by_dividend'] as String),
      json['lowest_price_by_profit'] == null
          ? null
          : Decimal.fromJson(json['lowest_price_by_profit'] as String),
      json['max_draw_down_by_profit'] == null
          ? null
          : Decimal.fromJson(json['max_draw_down_by_profit'] as String),
      json['revenue_year_growth_rate'] == null
          ? null
          : Decimal.fromJson(json['revenue_year_growth_rate'] as String),
      json['profit_year_growth_rate'] == null
          ? null
          : Decimal.fromJson(json['profit_year_growth_rate'] as String),
      json['dividend_rate'] as String?,
      json['dividend'] == null
          ? null
          : Decimal.fromJson(json['dividend'] as String),
      json['by_people'] as bool?,
      json['is_dividend'] as bool?,
      json['is_holdings'] as bool?,
      $enumDecodeNullable(_$SecurityTypeEnumMap, json['security_type']),
      json['current_price'] == null
          ? null
          : Decimal.fromJson(json['current_price'] as String),
      json['percent_change'] == null
          ? null
          : Decimal.fromJson(json['percent_change'] as String),
      json['updated_at'] as String?,
      (json['industry'] as List<dynamic>?)
          ?.map((e) => Industry.fromJson(e as Map<String, dynamic>))
          .toList(),
    );

Map<String, dynamic> _$SecurityToJson(Security instance) => <String, dynamic>{
      'id': instance.id,
      'code': instance.code,
      'name': instance.name,
      'acronym': instance.acronym,
      'exchange_type': _$ExchangeTypeEnumMap[instance.exchangeType]!,
      'market_value': instance.marketValue,
      'pe': instance.pe,
      'yesterday_price': instance.yesterdayPrice,
      'yesterday_range': instance.yesterdayRange,
      'lowest_price_by_dividend': instance.lowestPriceByDividend,
      'max_draw_down_by_dividend': instance.maxDrawDownByDividend,
      'lowest_price_by_profit': instance.lowestPriceByProfit,
      'max_draw_down_by_profit': instance.maxDrawDownByProfit,
      'revenue_year_growth_rate': instance.revenueYearGrowthRate,
      'profit_year_growth_rate': instance.profitYearGrowthRate,
      'dividend_rate': instance.dividendRate,
      'dividend': instance.dividend,
      'by_people': instance.byPeople,
      'is_dividend': instance.isDividend,
      'is_holdings': instance.isHoldings,
      'security_type': _$SecurityTypeEnumMap[instance.securityType],
      'current_price': instance.currentPrice,
      'percent_change': instance.percentChange,
      'updated_at': instance.updatedAt,
      'industry': instance.industry,
    };

const _$ExchangeTypeEnumMap = {
  ExchangeType.sz: 'SZ',
  ExchangeType.sh: 'SH',
  ExchangeType.nq: 'NQ',
  ExchangeType.bj: 'BJ',
};

const _$SecurityTypeEnumMap = {
  SecurityType.securityStockA: 0,
  SecurityType.securityStockB: 1,
  SecurityType.securityETF: 2,
  SecurityType.securityLOF: 3,
};

Industry _$IndustryFromJson(Map<String, dynamic> json) => Industry(
      json['id'] as int?,
      json['ind_name'] as String?,
    );

Map<String, dynamic> _$IndustryToJson(Industry instance) => <String, dynamic>{
      'id': instance.id,
      'ind_name': instance.indName,
    };

2. 定义一个接收APIRequester的类来获取列表数据

1.定义了一个名为 AssetsBuyRequest 的类,它继承自 APIRequester

  1. 这个类中包含了两个方法:

    • searchconvertible 方法用于模糊查询可转换证券,并返回 PaginationResponse<Security> 类型的数据。
    • fetchAssetsBuy 方法用于根据查询字符串获取证券列表,并返回 ListResponse<Security> 类型的数据。

3.这两个方法中,您都使用了 sendGetRequest 方法来发送 GET 请求到后端 API 接口,并根据响应结果构建相应的数据模型。

security_request.dart

image.png 这一段也属于第二步,

具体来说,这段代码是一个名为 AssetsBuyController 的类,它继承自 ListController<Security>。在这个类中,您定义了以下内容:

  1. AssetsBuyRequest 的实例 _request,用于从服务器获取数据。
  2. getSecurityRewards 方法,用于从服务器获取证券列表数据,并将数据存储在 _securityList 中,并输出一些证券信息。
  3. _securityList,这是一个 RxList<Security> 类型的响应式列表,用于存储证券数据。
  4. fetchDataFromNetwork 方法,这是一个重写的方法,用于从网络获取数据,这也是在渲染列表时常见的操作。

因此,这段代码属于第二步,即在 Flutter 中渲染列表的步骤,用于控制数据的获取和管理。

AssetsBuyController.dart

image.png 不过AssetsBuyController需要在binding中抛出,也就是注入依赖,让这个类可以在全局访问

binding.dart

image.png

3.将接收到的数据渲染到页面上(列表)

经过前面两个步骤,已经接收到服务器数据,接下来需要在需要的页面上进行渲染了

父级页面----这个页面是我需要渲染列表的父级页面,在这个页面中定义了我需要的弹窗。

assets_buy_input_widget.dart

image.png

image.png

image.png

子页面--在子页面中渲染数据

image.png

image.png