This article introduces the implementation of the Flutter_Weather module. The renderings are as follows:

Github.com/Zhengyi66/F…

Home page outermost layout implementation

The home page contains a top city name display bar and a PageView. Therefore, a vertical Column can be used for wrapping.

    returnContainer(child: Column(children: <Widget>[// header buildBar(context), // pageView Expanded(child: _buildPageView(), ) ], ), );Copy the code

Use Expanded to fill the remaining space, similar to the Android weight property.

PageView implementation

_buildPageView()

PageView attributes:

  • ScrollDirection: indicates the rolling direction. Horizontal horizontal vertical Vertical
  • Controller: PageController controls the scrolling of pageView
  • PageSnapping: Defaults to true. Loses the PageView feature when false is set

Top title bar implementation

/ / jump page and receive return value Future Future = Application. The router. NavigateTo (context, Routes. CityPage); Future. then((value){if(value ! = null){ } });Copy the code

The data load

1. Load the JSON data in assets

As the number of data calls is limited, only local data can be loaded during debugging

LoadWeatherAssets () async {Future<String> Future = DefaultassetBundle.of (context).loadString()"assets/json/weather.json");
    future.then((value){
      setState(() {
        weatherJson = value;
      });
    });
  }
Copy the code

Flutter recommends that we use DefaultAssetBundle for local data loading.

Loading Network Data
  loadWeatherData() async {
    final response = await http.get(Api.WEATHER_QUERY + city);
    setState(() {
      weatherJson = response.body;
    });
  }
Copy the code

You read that right, one line of code to load the data. Of course you should use await to complete the load, because the load method should be async in async because of the wait.

Json parsing

After loading the data, perform JSON parsing

Guide package import ‘dart: convert;

    if(weatherJson.isNotEmpty){
      WeatherBean weatherBean = WeatherBean.fromJson(json.decode(weatherJson));
      if(weatherBean.succeed()){
        loadState = 1;
        weatherResult = weatherBean.result;
      }else{ loadState = 0; }}Copy the code

Json.decode () returns a dynamic arbitrary type. So we need to manually parse.

Analytical object

The WeatherBean implementation is as follows:

WeatherBean.fromJson(Map<String,dynamic> json)

If the key being parsed is an object, such as the WeatherResult object above. Call the WeatherResult object’s fromJson.

To be on the safe side, parse the WeatherResult object with a non-null judgment.

Parsing array

Let’s look at what happens in WeatherResult again. (A little bit too much, added, just copy it)

class WeatherResult{ final String city; // Final String cityCode; // City code (int)... (omit some) final Aqi Aqi; final List<WeatherIndex> indexs; // Final List<WeatherDaily> dailys; // Final List<WeatherHourly> hours; // 24-hour weather WeatherResult({this.city,this.citycode,this.date,this.weather,this.temp,this.temphigh,this.templow,this.img,this.humidit y, this.pressure,this.windspeed,this.winddirect,this.windpower,this.updatetime,this.week,this.aqi,this.indexs,this.dailys,t his.hours}); Factory weatherResult. fromJson(Map<String,dynamic> json){var temIndexs = json['index'] as List; // Then convert each value in the array to a WeatherIndex object (call weatherindex.fromjson (I)) List<WeatherIndex> indexList = temIndexs.map((i)=>WeatherIndex.fromJson(i)).toList(); var temDailys = json['daily'] as List; // Convert each value in the array to a WeatherDaily object (call weatherdaily.fromjson (I)) List<WeatherDaily> dailyList = temDailys.map((i)=>WeatherDaily.fromJson(i)).toList(); var temHours = json['hourly'] as List; // Convert each value in the array to a WeatherHourly object (call WeatherHourly. FromJson (I)) List<WeatherHourly> hoursList = temHours.map((i)=>WeatherHourly.fromJson(i)).toList();return WeatherResult(
      city: json['city'],
      citycode: json['citycode'].toString(), ... Aqi: aqi.fromjson (json['aqi']), indexs: indexList, dailys: dailyList, hours: hoursList ); }}Copy the code

When parsing an array, it is first parsed into a List of unspecified types, then each item in the array is iterated over, and each item is converted to a corresponding object.

Var temIndexs = json['index'] as List; // Then convert each value in the array to a WeatherIndex object (call weatherindex.fromjson (I)) List<WeatherIndex> indexList = temIndexs.map((i)=>WeatherIndex.fromJson(i)).toList();Copy the code

WeatherIndex, WeatherDaily, hourly accounts are not posted here. It can be found at github.com/Zhengyi66/F…

Use PageController to temporarily resolve sliding conflicts

I actually used PageController in Pageview. Because our PageView is nested with scrollView, ListView and GridView, there will definitely be sliding conflicts. Use PageController to determine whether the first PageView has been slid to the second page.

  PageController _pageController = new PageController();
  
  @override
  void initState() { super.initState(); loadWeatherData(); _pageController. AddListener (() {/ / whether the first pageview complete slidingif(_pageController. Position. Pixels = = _pageController. Position. ExtentInside) {/ / slide, to the second page. Send messages to the second page eventbus.fire (PageEvent()); }}); }Copy the code

FirstPageView implementation

Pageview contains two child views, FirstPageView and SecondPageView. The first pageView looks like this:

Background to realize

Weather information realization

The weather layout as a whole can be divided into head, bottom and middle blanks. So use Column for the vertical layout. The white space in between is filled with Expanded.

1, head weather realization

Container(width: 200,height: 90, child: Stack(alignment: alignment. Center, fit: StackFit. Expand, children: <Widget>[ Positioned( child: Text(result.temp,style: TextStyle(color:Colors.white,fontSize: 90,fontWeight: FontWeight.w200),), left: 10, ), Positioned( child: Text("℃",style: TextStyle(color: Colors.white,fontSize: 20,fontWeight: FontWeight.w300),),
                  left: 110,
                  top: 5,
                ),
                Positioned(child: Text(result.weather,
                  style: TextStyle(color: Colors.white,fontSize: 18),maxLines: 1,overflow: TextOverflow.ellipsis,),
                  bottom: 5,
                  left: 110,
                )
              ],
            ),
          ),
Copy the code

Stack properties:

  • Alignment: alignment. Center Indicates the alignment in the center
  • A) fit B) fit C) expand D) fit

Positioned in the Stack using the position of the sub-widget: Positioned by the distance between left, top, right and bottom

2. Realization of bottom information.

SecondPageView implementation

Layout analysis

1. _buildTitle implementation

// widget widget _buildTitle(String title) {returnContainer( padding: EdgeInsets.all(10), child: Text( title, style: TextStyle(color: Colors.white70, fontSize: 16),),); }Copy the code

It’s just a simple Text. So I’m going to write methods for reuse

2. _buildLine implementation

// line widget widget _buildLine({double height, Color Color}) {returnContainer( height: height == null ? 5: Height, color?? Colors.white, ); }Copy the code

It’s just a line, you can choose height and color

3, 24-hour weather realization

// widget _buildHour(List<WeatherHourly> hours) {List< widget > widgets = [];for(int i=0; i<hours.length; i++){
    widgets.add(_getHourItem(hours[i]));
  }
  return Container(
    chil(
      scrollDirection: Axis.horizontal,
      child: Row(
        children: widgets,
      ),
    ),
  );
}
Copy the code

It’s just a simple horizontal ScrollView.

4. A week’s weather

//多天天气
Widget _buildDaily(List<WeatherDaily> dailys,List<ui.Image> dayImages,List<ui.Image> nightImages){
  return Container(
    height: 310,
    child: SingleChildScrollView(
      scrollDirection: Axis.horizontal,
      child: WeatherLineWidget(dailys, dayImages,nightImages),
    ),
  );
}
Copy the code

As you can see, this is also a simple Scrollview with our custom WeatherLineWidget wrapped inside

Custom weather line chart

class WeatherLineWidget extends StatelessWidget {
  WeatherLineWidget(this.dailys,this.dayIcons,this.nightIcons);

  final List<WeatherDaily> dailys;
  final List<ui.Image> dayIcons;
  final List<ui.Image> nightIcons;

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    returnCustomPaint (painter: _customPainter (dailys dayIcons, nightIcons), size: the size (420, 310), / / a custom Widget wide, high); } } class _customPainter extends CustomPainter { _customPainter(this.dailys,this.dayImages,this.nightIcons); List<WeatherDaily> dailys; // data source List<ui.Image> dayImages; Image List< UI.Image> nightIcons; Image final double itemWidth = 60; // Width of each item final double textHeight = 120; Final double temHeight = 80; Int maxTem, minTem; @override void paint(Canvas Canvas, Size Size) async{}}Copy the code

Then draw in the paint() method.

1, get the highest and lowest temperature

// Set maximum temperature and minimum temperaturesetMinMax(){
    minTem = maxTem = int.parse(dailys[0].day.temphigh);
    for(WeatherDaily daily in dailys){
      if(int.parse(daily.day.temphigh) > maxTem){
        maxTem = int.parse(daily.day.temphigh);
      }
      if(int.parse(daily.night.templow) < minTem){ minTem = int.parse(daily.night.templow); }}}Copy the code

2. Method of drawing text

DrawText (Canvas Canvas, int i,String text,double height,{double frontSize}) { var pb = ui.ParagraphBuilder(ui.ParagraphStyle( textAlign: Textalign. center,// center fontSize: frontSize == null? 14: frontSize, / / size)); // addText pb.addtext (text); PushStyle (ui.textStyle (color: color.white)); Var paragraph = pb.build().. layout(ui.ParagraphConstraints(width: itemWidth)); DrawParagraph (paragraph, Offset(itemWidth* I, height)); }Copy the code

Unlike Android, Flutter draws text using the drawParagraph() method

Paint () method

  @override
  void paint(Canvas canvas, Size size) async{
    setMinMax();

    List<Offset> maxPoints = [];
    List<Offset> minPoints = [];
    
    double oneTemHeight = temHeight / (maxTem - minTem); // The height of each temperature
    for(int i=0; i<dailys.length; i++){
      var daily = dailys[i];
      var dx = itemWidth/2 + itemWidth * i;
      var maxDy = textHeight + (maxTem - int.parse(daily.day.temphigh)) * oneTemHeight;
      var minDy = textHeight + (maxTem - int.parse(daily.night.templow)) * oneTemHeight;
      var maxOffset = new Offset(dx, maxDy);
      var minOffset = new Offset(dx, minDy);

      if(i == 0){
        maxPath.moveTo(dx, maxDy);
        minPath.moveTo(dx, minDy);
      }else {
        maxPath.lineTo(dx, maxDy);
        minPath.lineTo(dx, minDy);
      }
      maxPoints.add(maxOffset);
      minPoints.add(minOffset);

      if(i ! =0) {/ / draw a vertical bar
        canvas.drawLine(Offset(itemWidth * i ,0), Offset(itemWidth * i,  textHeight*2 + textHeight), linePaint);
      }

      var date;
      if(i == 0){
        date = daily.week + "\n" +  "Today";
      }else if(i == 1){
        date =  daily.week + "\n" + "Tomorrow";
      }else{
        date = daily.week + "\n" + TimeUtil.getWeatherDate(daily.date);
      }
      // Draw the date
      drawText(canvas, i, date ,10);
      // Draw daytime weather picture SRC original matrix DST output matrix
      canvas.drawImageRect(dayImages[i],Rect.fromLTWH(0.0, dayImages[i].width.toDouble(),  dayImages[i].height.toDouble()),
          Rect.fromLTWH(itemWidth/4 + itemWidth*i, 50.30.30),linePaint);
      // Draw daytime weather
      drawText(canvas, i, daily.day.weather, 90);
      // Draw nighttime weather pictures
      canvas.drawImageRect(nightIcons[i],Rect.fromLTWH(0.0, nightIcons[i].width.toDouble(),  nightIcons[i].height.toDouble()),
          Rect.fromLTWH(itemWidth/4 + itemWidth*i, textHeight + temHeight + 10.30.30),new Paint());
      // Draw nighttime weather information
      drawText(canvas, i, daily.night.weather, textHeight+temHeight + 45);
      // Draw wind direction and force
      drawText(canvas, i, daily.night.winddirect + "\n" + daily.night.windpower, textHeight+temHeight + 70,frontSize: 10);
    }
    // Maximum temperature line
    canvas.drawPath(maxPath, maxPaint);
    // Minimum temperature line
    canvas.drawPath(minPath, minPaint);
    // Maximum temperature point
    canvas.drawPoints(ui.PointMode.points, maxPoints, pointPaint);
    // The lowest temperature point
    canvas.drawPoints(ui.PointMode.points, minPoints, pointPaint);
Copy the code

Drawing is actually pretty easy. drawImageRect drawImageRect(Image Image, Rect SRC, Rect DST, Paint Paint)

  • The image is the package'dart:ui'Image in, not widget.
  • SRC The rect of the original image
  • DST Outputs the recT of the image. You can change the size of this widget to change the image size

Load the image in drawImageRect()

import 'dart:async';
import 'dart:ui' as ui;
import 'dart:typed_data'; initNightIcon(String path) async { final ByteData data = await rootBundle.load(path); ui.Image image = await loadNightImage(new Uint8List.view(data.buffer)); } // Load image Future<ui. image > loadNightImage(List<int> img) async {final Completer<ui. image > Completer = new Completer(); ui.decodeImageFromList(img, (ui.Image img){return completer.complete(img);
    });
    return completer.future;
  }
Copy the code

Pageview sliding conflict

This is a way I found a way to cheat, do not know what the official way is?

This uses a key scroll property called ScrollPhysics. Look at its implementation class:

getScrollPhysics()

ScrollPhysics getScrollPhysics(bool top){if(top){
    return NeverScrollableScrollPhysics();
  }else{
    returnBouncingScrollPhysics(); }}Copy the code

Top: indicates whether the scrollView slides to the top.

When the scrollview slip to the top, physics for NeverScrollableScrollPhysics (), scroll the scroll is prohibited.

When the scrollView is not at the top, physics BouncingScrollPhysics(), elastic scrolling.

Here is the scrollView to check whether the state has reached the top.

class _PageState extends State<SecondPageView> {

  ScrollController _scrollController = new ScrollController();
  bool top = false;
  StreamSubscription streamSubscription;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    top = false; / / the ListView control _scrollController sliding properties. The addListener (() {if (_scrollController.position.pixels ==
          _scrollController.position.maxScrollExtent) {
//        print("Slide to the bottom");
      } else if (_scrollController.position.pixels ==
          _scrollController.position.minScrollExtent) {
//        print("Slide to the top");
        setState(() {
          top = true;
        });
      } else {
        top = false; }}); StreamSubscription = eventBus.on<PageEvent>().listen((event) {streamSubscription = eventBus.on<PageEvent>().listen((event) {streamSubscription = eventBus.on<PageEvent>().setState(() {
        top = false
        ;
      });
    });
  }

  @override
  void dispose() {
    top = false;
    if (streamSubscription != null) {
      streamSubscription.cancel();
    }
    super.dispose();
  }
}
Copy the code

The _scrollController and the registered PageView’s scroll event are used to determine whether the ScrollView can be rolled.

The end of the

This is the content of the weather module, and the complete code has been uploaded to GitHub. Github.com/Zhengyi66/F…