美文网首页
so easy! flutter从零创建一个最小APP单元

so easy! flutter从零创建一个最小APP单元

作者: 尼古拉斯小韭菜 | 来源:发表于2022-06-01 15:51 被阅读0次

先上图吧,就是so easy!当你能创建一个最小APP单元后,你就跨过了flutter门槛了,剩下的就逐渐完善APP功能了,相信任何其他复杂功能也只不过需要慢慢积累而已。


信图片_20220601154812.jpg

首先我们创建首页三个Tab切换的页面结构,这用到了flutter组件BottomNavigationBar,先上一个主页全部的代码,方便您进行代码的编译运行。

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import './tabs/Home.dart';
import './tabs/Movie.dart';
import './tabs/Personal.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key}) : super(key: key);

  @override
  State<StatefulWidget> createState() {
    return _MyHomePageState();
  }
}

class _MyHomePageState extends State<MyHomePage>{

  int _index = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("豆瓣电影"),
        ),
        body: IndexedStack(
          index: _index,
          children: <Widget>[
            HomePage(),
            MoviePage(),
            PersonalPage(),
          ],
        ),
        drawer: mDrawerLayout,
        bottomNavigationBar: BottomNavigationBar(
          currentIndex: _index,
          onTap: (index){
            setState(() {
              this._index = index;
            });
          },
          items: [
            BottomNavigationBarItem(
                icon: Icon(Icons.home), title: Text("首页")),
            BottomNavigationBarItem(
                icon: Icon(Icons.movie_filter), title: Text("正在热映")),
            BottomNavigationBarItem(
                icon: Icon(Icons.movie_creation), title: Text("Top250")),
          ],
        ));
  }
}

Drawer mDrawerLayout = Drawer(
  child: ListView(
    padding: EdgeInsets.all(0), // 干掉侧边栏灰色状态栏
    children: <Widget>[
      UserAccountsDrawerHeader(
        accountEmail: Text("123456789@qq.com"),
        accountName: Text("pickahu"),
        currentAccountPicture: CircleAvatar(
          backgroundImage: NetworkImage(
              "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fup.enterdesk.com%2Fedpic%2F52%2Fc9%2F24%2F52c924bd5db63b23aeb4ebb41de4192e.jpg&refer=http%3A%2F%2Fup.enterdesk.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1656040583&t=1d217c27eb386c97fd96d53224cff174"),
        ),
        decoration: BoxDecoration(
            image: DecorationImage(
                fit: BoxFit.cover,
                image: NetworkImage(
                  "https://img0.baidu.com/it/u=380810917,3888082992&fm=253&fmt=auto&app=120&f=JPEG?w=800&h=600",
                ))),
      ),
      ListTile(
        title: Text(
          "用户反馈",
          style: TextStyle(fontSize: 16, color: Colors.black54),
        ),
        trailing: Icon(Icons.feedback),
      ),
      Container(
        padding: EdgeInsets.only(left: 10, right: 10),
        child: Divider(
          color: Colors.grey,
        ),
      ),
      ListTile(
        title: Text("系统设置"),
        trailing: Icon(Icons.settings),
      ),
      Container(
        padding: EdgeInsets.only(left: 10, right: 10),
        child: Divider(
          color: Colors.grey,
        ),
      ),
      ListTile(
        title: Text("我要发布"),
        trailing: Icon(Icons.publish),
      ),
      Container(
        padding: EdgeInsets.only(left: 10, right: 10),
        child: Divider(
          color: Colors.grey,
        ),
      ),
      ListTile(
        title: Text("注销"),
        trailing: Icon(Icons.exit_to_app),
      ),
    ],
  ),
);

OK,看完了全部代码,我们拆分来看,首先需要一个BottomNavigationBar配合三个子页面,这一部分代码很简单

class _MyHomePageState extends State<MyHomePage>{

  int _index = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("豆瓣电影"),
        ),
        body: IndexedStack(
          index: _index,
          children: <Widget>[
            HomePage(),
            MoviePage(),
            PersonalPage(),
          ],
        ),
        bottomNavigationBar: BottomNavigationBar(
          currentIndex: _index,
          onTap: (index){
            setState(() {
              this._index = index;
            });
          },
          items: [
            BottomNavigationBarItem(
                icon: Icon(Icons.home), title: Text("首页")),
            BottomNavigationBarItem(
                icon: Icon(Icons.movie_filter), title: Text("正在热映")),
            BottomNavigationBarItem(
                icon: Icon(Icons.movie_creation), title: Text("Top250")),
          ],
        ));
  }
}

之所以用StatefulWidget 组件是因为我们要进行页面切换并保持,我们通过setState函数进行状态更新,这里用IndexedStack容纳三个页面的原因是flutter页面状态保持需要,后续会提到这一点。

而这个从左边弹出的状态栏则是scaffold的一个属性决定的,就是drawer,而真正的View就是我们的mDrawerLayout,一个控件加上基本的布局,没啥好说的。

然后我们首页页面,首页页面就是一个类似于新闻列表页,有刷新有加载更多,有网络获取数据,数据解析,是一个APP不可获取的组成部分。先上代码:

import 'package:douban_flutter_demo/Detail.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:dio/dio.dart';

Dio dio = new Dio();

class _HomePageState extends State<HomePage>
    with AutomaticKeepAliveClientMixin {
  int page = 1;
  int pagesize = 10;
  var mList = [];
  var total = 0;

  ScrollController _scrollController = new ScrollController();

  @override
  void initState() {
    // TODO: implement initState
    _scrollController.addListener(() {
      if (_scrollController.position.pixels ==
          _scrollController.position.maxScrollExtent) {
        getMovieList(false);
      }
    });
    super.initState();

    getMovieList(true);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: RefreshIndicator(
        onRefresh: _pullToRefresh,
        child: ListView.builder(
            controller: _scrollController,
            itemCount: mList.length,
            itemBuilder: (BuildContext context, int index) {
              var item = mList[index];
              return ItemHolder(item);
            }),
      ),
    );
  }

  Future _pullToRefresh() async {
    getMovieList(true);
    return null;
  }

  @override
  void dispose() {
    super.dispose();
    // 释放资源
    _scrollController.dispose();
  }

  getMovieList(bool refresh) async {
    if (refresh) {
      page = 1;
    } else {
      page++;
    }
    var response = await dio.get(
        "http://api.tianapi.com/film/index?key=你的key(可以去天行API申请)&num=10&page=$page");
    setState(() {
      if (refresh) {
        this.mList.clear();
        this.mList.addAll(response.data["newslist"]);
      } else {
        this.mList.addAll(response.data["newslist"]);
      }
    });
    print(mList);
  }

  @override
  // TODO: implement wantKeepAlive
  bool get wantKeepAlive => true;
}

class ItemHolder extends StatelessWidget {
  var itemData = null;

  ItemHolder(this.itemData);

  @override
  Widget build(BuildContext context) {
    return new GestureDetector(
      onTap: () {
        print("aaaaa");
        Navigator.push(context,
            MaterialPageRoute(builder: (BuildContext context) {
          return new MovieDetail(itemData["id"], itemData["title"]);
        }));
      },
      child: Container(
        decoration: BoxDecoration(
            border: Border(bottom: BorderSide(color: Colors.grey))),
        child: Row(
          children: <Widget>[
            Container(
              padding: EdgeInsets.all(10),
              child: Image.network(
                itemData["picUrl"],
                width: 80,
                height: 110,
                fit: BoxFit.cover,
              ),
            ),
            Expanded(
              //增加Expanded包裹,为了约束text太长溢出,测试溢出作用控件可以给控件增加宽高限制进行测试
              child: Container(
                height: 110,
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  mainAxisAlignment: MainAxisAlignment.spaceAround,
                  children: <Widget>[
                    Text(
                      itemData["title"],
                      overflow: TextOverflow.ellipsis,
                      softWrap: true,
                      style: TextStyle(fontSize: 16),
                    ),
                    Text(
                      itemData["description"],
                      overflow: TextOverflow.clip,
                      maxLines: 2,
                      softWrap: true,
                      style: TextStyle(
                        fontSize: 14,
                      ),
                    ),
                    Text(
                      itemData["source"],
                      softWrap: true,
                      overflow: TextOverflow.clip,
                    ),
                    Text(
                      itemData["ctime"],
                      maxLines: 1,
                      softWrap: true,
                      overflow: TextOverflow.clip,
                    )
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return new _HomePageState();
  }
}

最基础的组件是ListView,ListView的每一个子View通过ItemHolder构建,数据存储在mList中,我们写了一个getMovieList方法,可以获取刷新数据也可以获取加载更多数据,这是一个异步的方法,需要async 修饰,数据获取简单得通过Dio获取,这里有一个特别的地方就是页面 的状态保持,我们通过_HomePageState with AutomaticKeepAliveClientMixin来实现,然后我们复写wantKeepAlive 方法并且返回true,状态保持就是当我们在列表页滑到一个位置的时候切换去别的页面,再切换回来时候我们原来的列表页可以保持在原来的位置,其实就是状态不变,页面也不重绘。下拉刷新采用RefreshIndicator包裹listView实现,刷新回调方法需要返回Feature对象,然后我们给listview加上_scrollController对象来判断上拉加载更多的触发时机。。。

这里只得一说的是我们的列表每一个子View文字部分我们通过Expanded包裹,又嵌套一层Container,目的是为了解决文字超过边界预警的问题。

好了,最后我们通过一段路由

Navigator.push(context,
            MaterialPageRoute(builder: (BuildContext context) {
          return new MovieDetail(itemData["id"], itemData["title"]);

将id和标题传递到电影详情页面,没啥好说的,最后电影详情页展示出了id和title,说明传递成功,后边具体的数据可以通过id去获取,不加累述。一段详情页的代码,so easy!

import 'package:flutter/material.dart';

class MoviePage extends StatelessWidget{
  @override
  Widget build(BuildContext context) {

    return Scaffold(
      body: Center(
        child: Text("热映"),
      ),
    );
  }

最后两个页面就随便搞个View就搞定啦,本篇文档到此结束啦。

相关文章

网友评论

      本文标题:so easy! flutter从零创建一个最小APP单元

      本文链接:https://www.haomeiwen.com/subject/ckmxmrtx.html