美文网首页Android开发Android技术栈Android技术知识
Flutter第1天--初始分析+Dart方言+Canvas简绘

Flutter第1天--初始分析+Dart方言+Canvas简绘

作者: e4e52c116681 | 来源:发表于2018-12-16 19:04 被阅读22次

    2018-12-16

    零前言:

    作为一名资深安卓业余爱好者(自诩),感觉应该入一下Flutter的坑了,
    不管怎么说,新技术多少要了解一点,本系列就作为我的学习笔记吧
    先把今天入坑的感觉写一写:

    1.环境的搭建前人把雷踩得差不多了,也不是很麻烦
    2.什么都没干呢,TM安装包28M...真把我吓一跳-----于是Flutter的"胖子"形象深入我心  
    3.Flutter热加载爽到爆,对于喜欢用真机的我,以前每次修改后-->确定安装-->打开...
    4.单引号亮了,总算能像写其他语言那样少按个Shift了,字符串插值也很良心
    5.flutter支持canvas,so我的四大战将(canvas,path,paint,贝塞尔)又能大显身手了,不过Api略有不同,也略显单薄
    6.程序员有三件法宝:Ctrl+ Z(大胆改) , debug(细心查) , 类比(善分析)
    

    一、Flutter初体验

    1、下载Flutter的SDK

    Android 的SDK要在环境变量配置一下:ANDROID_HOME
    有什么问题可以在cmd用flutter doctor命令检查一下,对症下药

    git clone -b beta https://github.com/flutter/flutter.git
    

    2、配置环境变量
    Flutter环境变量.png
    PUB_HOSTED_URL=https://pub.flutter-io.cn
    FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn
    

    3、AndroidStudio安装Dart和Flutter插件
    setting-->plugins-->下方第二个-->搜索-->安装-->重启
    

    4、新建项目

    打开AS后就能看到新建一个Flutter项目,然后就写名字
    initializing gradle 如果一直不动,android/gradle/wrapper/gradle-wrapper.properties
    对应的gradle版本在http://services.gradle.org/distributions/自己下载,放在本地

    第一个Flutter项目.png

    二、第一次看初始项目的内心戏

    android:我最熟悉的android
        |---app
            |---src
    ios:暂时不鸟它
    lib:
        |---main.dart
    test:顾名思义,测试包
    .gitignore .metadata .packages pubspec.lock pubspec.yaml README.md  连包都没有,暂时不睬
    

    1.看一下:android/app/src/main/AndroidManifest.xml
        <application
            android:name="io.flutter.app.FlutterApplication"
            android:label="my_flutter"
            android:icon="@mipmap/ic_launcher">
            <activity
                android:name=".MainActivity"
              //略...
                <intent-filter>
                    <action android:name="android.intent.action.MAIN"/>
                    <category android:name="android.intent.category.LAUNCHER"/>
                </intent-filter>
            </activity>
        </application>
    

    2.可见程序入口是:MainActivity.java

    让我有一种libgdx的即视感

    public class MainActivity extends FlutterActivity {
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        GeneratedPluginRegistrant.registerWith(this);
      }
    }
    

    3.GeneratedPluginRegistrant.java
    /**
     * Generated file. Do not edit.
        自动生成的文件,不要修改
     */
    public final class GeneratedPluginRegistrant {
      public static void registerWith(PluginRegistry registry) {
        if (alreadyRegisteredWith(registry)) {//如已结婚,直接走人
          return;
        }
      }
        //貌似是判断是否已经和registry结成连理
      private static boolean alreadyRegisteredWith(PluginRegistry registry) {
        final String key = GeneratedPluginRegistrant.class.getCanonicalName();
        if (registry.hasPlugin(key)) {//有结婚戒指,直接走人
          return true;
        }
        registry.registrarFor(key);//带上戒指
        return false;
      }
    }
    

    4、是谁弄脏了我雪白的界面(显示)
    4.1.MainActivity显然不是,怎么查在哪呢?

    好吧在:main.dart里

    搜索已有字符.png
    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    ------------//内心戏--------------
    开面相对象的天眼一看:`void main() => runApp(MyApp());`  
    什么鬼,不像Python,不像JavaScript,更不像Java,但我仿佛知道它想对我什么:  
    我是入口函数,执行runApp函数,里面传入了个MyApp(),so,我是清白的,熊孩子是MyApp()  
    

    4.2.对MyApp的认知
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: MyHomePage(title: 'Flutter Demo Home Page'),
        );
      }
    }
    
    ------------//内心戏--------------
    看到class有种他乡遇故知的感觉,继承了StatelessWidget类并重写了其build方法
    然后返回了一个Widget对象,并可以推理出MaterialApp()是一个Widget类对象
    其中括号里的感觉非常像Python的字典或JavaScript的对象,不过用()包起来真怪怪的
    
    按照一般的套路,左边是属性,右边是属性值,既然如此,玩玩呗.下面改了一下theme颜色
    home里传入了一个MyHomePage,估计就是我们要找的人了,title改一下
    
    flutter1.png
    4.3.对MyHomePage的认知
    class MyHomePage extends StatefulWidget {
      MyHomePage({Key key, this.title}) : super(key: key);
    
      final String title;
    
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    
    ------------//内心戏--------------
    MyHomePage也是StatefulWidget家的,第一句话感觉挺诡异,先mark一下
    super(key: key)应该是说,key用它爸(即StatefulWidget)的,从上一步的入参title来看
    this.title应该是入参的关键,so,这句话好像在说,我要两个参数,key从我爸那里拿
    
    @override可以看出createState()是一个父类方法,_MyHomePageState是一个类
    也就说明 _MyHomePageState()是一个对象,(ps:看到State直接想到React)
    

    4.4.对_MyHomePageState的认知
    class _MyHomePageState extends State<MyHomePage> {
      int _counter = 0;//定义变量
    
      void _incrementCounter() {
        setState(() {
          _counter++;//定义变量++
        });
      }
    
    ------------//内心戏--------------
    //结合JS和Python的经验,从这里可以看出,貌似加_的,是不想暴露在外的内部成员  
    _incrementCounter()显然是一个累加的方法,setState()里的东西让_counter++  
    setState个React这是一模一样,mark一下,估计会刷新界面
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(
                  'You have pushed the button this many times:',
                ),
                Text(
                  '$_counter',
                  style: Theme.of(context).textTheme.display1,
                ),
              ],
            ),
          ),
          
    ------------//内心戏--------------
    abstract class State<T extends StatefulWidget> extends Diagnosticable
    State有一个StatefulWidget的泛型,也重写了build方法
    [class Scaffold extends StatefulWidget] Scaffold也是StatefulWidget
    现在焦点应该汇聚在StatefulWidget身上,很多地方都出现了,mark一下
    
    Text(widget.title)----这里应该就是标题了,AppBar,顾名思义
    body应该是身体,Center,中间,child,孩子,Column列,mainAxisAlignment,主轴对齐,
    center中间,children孩子,Text文字:You have pushed the button this many times:
    感觉蛮好玩的,拼在一起大概是,列作为孩子居中,并将文本作为孩子主轴对齐方式居中
    
    4.5.floatingActionButton,这个安卓元素有
          floatingActionButton: FloatingActionButton(
            onPressed: _incrementCounter,
            tooltip: 'Increment',
            child: Icon(Icons.add),
          ),
        );
      }
    }
    
    //onPressed:点击响应的函数  tooltip--长按显示文字  child--Icon加号图片
    

    ok,这就是我第一次看Flutter代码时的感觉,mark了三处,
    下面带着问题正式学一下Dart方言。


    三、Dart语法一探

    1、圆的周长
    const PI = 3.141592654; //const:编译时就是常量
    //const double PI = 3.141592654;
    
    final x = 50; //final修饰的变量只能被赋值一次(运行时)
    //final int x = 100;
    
    main() {
    //  int radius = 10;
      var radius = 10;
       //radius = 10.0;//Error--A value of type 'double' can't be assigned to a variable of type 'int'.
      double c = getC(radius);
      //支持三目运算符
      bool isBig = c > x;
      print(isBig ? "圆的周长大于${x}" : r"圆的周长\n小于${x}"*2);
      //x=100 圆的周长\n小于${x}圆的周长\n小于${x}
      //x=50 圆的周长大于50
    }
    
    // 获取圆的周长 radius : 半径
    double getC(int radius) {
      var c = 2 * PI * radius;
      return c;
    }
    

    1.感觉const就像英雄天生天赋,final就像等级到了,选择英雄职业(不能转职)
    2.r会将里面字符串原样打出,无视各空白符
    3.字符串*2就打印两次,有点意思,差值表达式:${}和JS,kotlin相似
    4.可以省略类型,但是若初始时赋值就不能再赋值其他类型,所以Dart并非弱类型语言!!!
    但说它强又不怎么严谨,看下图,无力吐槽...(PS:原因:见后面dynamic类型)

    mark2018-12-15 22-52-33.png
    2.List的使用

    支持多类型,API比java多一些
    可以看成Java的ArrayList和数组的结合体,any,join等操作更像Python或js中的list

    void baseUse() {
      var list = [1, "a", "b", "c", true]; //支持多种类型
      //  var list=const[1,"a","b","c",true];
      //  var list =new List();
    
      list[0] = "10"; //数组元素可修改成不同类型
      var el = list[list.length - 1]; //获取--true
      list.add("toly"); //尾增--[10, a, b, c, true, toly]
      list.insert(1, true); //定点增--[10, true, a, b, c, true, toly]
      list.remove("10"); //删除元素--[true, a, b, c, true, toly]
      list.indexOf(true); //首出索引--1
      list.lastIndexOf(true); //尾出索引--4
      list.removeLast(); //移除尾--[true, a, b, c, true]
      print(list.sublist(2)); //截取--[b, c, true]
      print(list.sublist(2, 4)); //截取--[b, c]
      print(list);
      print(list.join("!")); //true!a!b!c!true
    }
    

    forEach、any、every、map
    void op() {
      var numList = [3, 2, 1, 4, 5];
      numList.sort();
      print(numList); //排序--[1, 2, 3, 4, 5]
      
      for (var value in numList) {
        print(value); //1,2,3,4,5
      }
      
      numList.forEach(addOne); //2,3,4,5,6
      numList.forEach((num) => print(num + 1)); //同上
      
      var any = numList.any((num) => num > 3);
      print(any); //只要有>3的任何元素,返回true
      
      var every = numList.every((num) => num < 6);
      print(every); //全部元素<6,返回true
      
      var listX5 = numList.map((e) => e*=5);
      print(listX5);//(5, 10, 15, 20, 25)
    }
    
    int addOne(int num) {
      print(num + 1);
    }
    

    3.Map

    这个不多说了,基本上与主流语言一致

    void baseUse() {
      //创建映射表
      var dict = {"a": "page1", "b": "page30", "c": "page70", "price": 40};
    //  var dict = new Map();
      print(dict); //{a: page1, b: page30, c: page70, price: 40}
      print(dict["price"]); //40
      dict["a"] = "page2";
      print(dict); //{a: page2, b: page30, c: page70, price: 40}
      print(dict.containsKey("price")); //true
      print(dict.containsValue("price")); //false
      print(dict.isEmpty); //false
      print(dict.isNotEmpty); //true
      print(dict.length); //4
      dict.remove("c");
      print(dict);//{a: page2, b: page30, price: 40}
    }
    

    void op() {
      var dict = {"a": "page1", "b": "page30", "c": "page70", "price": 40};
      dict.keys.forEach(print); //a,b,c,price
      dict.values.forEach(print); //a,b,c,price
      dict.forEach((k, v) => (print("$k=$v"))); //这里用括号包着,好想吐槽...
    }
    

    4.dynamic(动态的)

    原来是dynamic锅,让类型变成动态了

    dynamic.png int.png
      dynamic d = 20;
      d = "toly";
    
      var list = new List<dynamic>();
      list.add("1");
      list.add(3);
    
      var list2 = new List<int>();
      //list2.add("toly");//ERROR:The argument type 'String' can't be assigned to the parameter type 'int'.
    

    5.不同的东西
    //--------------------奇葩的~/----------
    int a=10;
    print(a/3);//3.3333333333333335
    print(a~/3);//3
    
    //--------------------奇葩的??=----------
    int b = 9;
    b = 5;
    b ??= a; //----如果b空的则赋值
    print(b); //5
    
    //--------------------奇葩的??----------
    int c = 10;
    int d = 8;
    var add10 = c = null ?? d + 10;//取第一个不为空的表达式
    print(add10); //18
    
    //--------------------简洁的=>----------
    => expr   等价于  {return expr;}
    
    //--------------------好玩的{参数}----------
    main() {
      fun("toly");//toly,24,null
      fun("toly", age: 24, sex: "男"); //toly,24,男
    }
    
    fun(String name, {int age=24, String sex}) {
      print("$name,$age,$sex");
    }
    
    //--------------------好玩的[参数]----------
    main() {
      fun("toly"); //toly,null,null
      fun2("toly", 24); //toly,24, 男
    }
    
    fun2(String name, [int age, String sex= "男"]) {
      print("$name,$age,$sex");
    }
    
    //--------------------有趣的匿名方法----------
      var power = (i) {
        return i * i;
      };
      print(power(6)); //36
      
    //--------------------这个理清楚,基本上匿名函数就OK了----------
    var li = [1, 2, 3, 4, 5];
    li.forEach((i) => print((i) {
          return i * i;
        }(i))); //1,4,9,16,25
    

    6.类那点事
    6.1:定义一个简单的类

    PerSon(this.name, this.age)简化了Java中的那一坨,其他差不多

    class PerSon {
      String name;
      int age;
    
      PerSon(this.name, this.age);
    
      say(String name) {
        print("are you ok $name");
      }
    }
    
    main(){
      var toly = new PerSon("toly", 24);
      toly.say("ls");//are you ok ls
    }
    

    6.2:继承

    注意语法形式

    class Student extends PerSon {
      String school;
      Student(String name, int age, this.school) : super(name, age);
    }
    
    main() {
      new Student("ls", 23, "星龙学院").say("toly");//are you ok toly
    }
    

    就先认知这么多吧,应该够玩一玩的了。


    四、Canvas走起

    新学一样东西,最好选择最熟悉的点切入,对我而言是绘制

    1.找到画板在哪

    有个CustomPainter类里有canvas,二话不说,继承之,为了避免看着乱,我新建了view包 view/star_view.dart

    import 'dart:ui';
    
    import 'package:flutter/cupertino.dart';
    import 'package:flutter/material.dart';
    
    class StarView extends CustomPainter {
    
      @override
      void paint(Canvas canvas, Size size) {
        
      }
    
      @override
      bool shouldRepaint(CustomPainter oldDelegate) {
        return null;
      }
    }
    

    2.StarView的用法

    前面分析过,视图的呈现在MyHomePage中-->createState方法-->build返回的对象里
    把文字的那块body改为CustomPaint就行了,FloatingActionButton就放着吧。

      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: CustomPaint(
            painter: StarView(),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: _incrementCounter,
            tooltip: 'Increment',
            child: Icon(Icons.add),
          ),
        );
      }
    

    3.屏幕尺寸的获取

    flutter中用的单位目测都是dp所以我用第三行那个,需要传入一个context
    就在构造方法里传一下,刚好build里有个context,你用前两个除一下也行

    window.physicalSize     //获取屏幕尺寸px----1080.0, 2196.0
    window.devicePixelRatio //设备像素比----3
    MediaQuery.of(context).size  //获得的是dp单位:360.0, 732.0
    
    //使用是传入context
     body: CustomPaint(
       painter: StarView(context),
     ),
    

    4.网格走起:
    4.1:StarView接收context,并初始化画笔
      Paint mHelpPaint;
      BuildContext context;
    
      StarView(this.context) {
        mHelpPaint = new Paint();
        mHelpPaint.style=PaintingStyle.stroke;
        mHelpPaint.color=Color(0xffBBC3C5);
        mHelpPaint.isAntiAlias=true;
      }
    

    4.2:绘制网格路径

    以前Android里面用的函数,修改了一些语法,给flutter用

    /**
     * 绘制网格路径
     *
     * @param step    小正方形边长
     * @param winSize 屏幕尺寸
     */
    Path gridPath(int step, Size winSize) {
      Path path = new Path();
    
      for (int i = 0; i < winSize.height / step + 1; i++) {
        path.moveTo(0, step * i.toDouble());
        path.lineTo(winSize.width, step * i.toDouble());
      }
    
      for (int i = 0; i < winSize.width / step + 1; i++) {
        path.moveTo(step * i.toDouble(), 0);
        path.lineTo(step * i.toDouble(), winSize.height);
      }
      return path;
    }
    

    4.3:绘制网格
      @override
      void paint(Canvas canvas, Size size) {
        var winSize = MediaQuery.of(context).size;
        canvas.drawPath(gridPath(20, winSize), mHelpPaint);
    
    网格
    4.4:坐标系绘制
    坐标系
    //绘制坐标系
    drawCoo(Canvas canvas, Size coo, Size winSize) {
      //初始化网格画笔
      Paint paint = new Paint();
      paint.strokeWidth = 2;
      paint.style = PaintingStyle.stroke;
    
      //绘制直线
      canvas.drawPath(cooPath(coo, winSize), paint);
      //左箭头
      canvas.drawLine(new Offset(winSize.width, coo.height),
          new Offset(winSize.width - 10, coo.height - 6), paint);
      canvas.drawLine(new Offset(winSize.width, coo.height),
          new Offset(winSize.width - 10, coo.height + 6), paint);
      //下箭头
      canvas.drawLine(new Offset(coo.width, winSize.height-90),
          new Offset(coo.width - 6, winSize.height - 10-90), paint);
      canvas.drawLine(new Offset(coo.width, winSize.height-90),
          new Offset(coo.width + 6, winSize.height - 10-90), paint);
    }
    

    5.小结一下

    感觉flutter里的Canvas很贫弱...好多api都没有,不知道是我没找到还是什么
    canvas竟然没办法画文字,这不科学,mark一下。坐标系也就只能这样凑合一下了
    还有Color用着挺别扭的,画线传参为什么非要Offset,连个重载都没有


    6.绘制n角星

    好吧,我又要拿星星来丢人现眼了
    我已经n角星的java代码翻译成dart方言了

    五角星分析
    /**
     * n角星路径
     *
     * @param num 几角星
     * @param R   外接圆半径
     * @param r   内接圆半径
     * @return n角星路径
     */
    Path nStarPath(int num, double R, double r) {
      Path path = new Path();
      double perDeg = 360 / num; //尖角的度数
      double degA = perDeg / 2 / 2;
      double degB = 360 / (num - 1) / 2 - degA / 2 + degA;
    
      path.moveTo(cos(_rad(degA)) * R, (-sin(_rad(degA)) * R));
      for (int i = 0; i < num; i++) {
        path.lineTo(
            cos(_rad(degA + perDeg * i)) * R, -sin(_rad(degA + perDeg * i)) * R);
        path.lineTo(
            cos(_rad(degB + perDeg * i)) * r, -sin(_rad(degB + perDeg * i)) * r);
      }
      path.close();
      return path;
    }
    
    double _rad(double deg) {
      return deg * pi / 180;
    }
    
    canvas.translate(160, 320);//移动到坐标系原点
    canvas.drawPath(nStarPath(5,80,40), mPaint);
    
    五角星.png
    7.正n角星和正多边形
    7.1:方法封装
    /**
     * 画正n角星的路径:
     *
     * @param num 角数
     * @param R   外接圆半径
     * @return 画正n角星的路径
     */
    Path regularStarPath(int num, double R) {
      double degA, degB;
      if (num % 2 == 1) {
        //奇数和偶数角区别对待
        degA = 360 / num / 2 / 2;
        degB = 180 - degA - 360 / num / 2;
      } else {
        degA = 360 / num / 2;
        degB = 180 - degA - 360 / num / 2;
      }
      double r = R * sin(_rad(degA)) / sin(_rad(degB));
      return nStarPath(num, R, r);
    }
    
    /**
     * 画正n边形的路径
     *
     * @param num 边数
     * @param R   外接圆半径
     * @return 画正n边形的路径
     */
    Path regularPolygonPath(int num, double R) {
      double r = R * cos(_rad(360 / num / 2)); //!!一点解决
      return nStarPath(num, R, r);
    }
    

    7.2.批量绘制:
        canvas.translate(0, 320);
    
        canvas.save();//绘制n角星
        for (int i = 5; i < 10; i++) {
          canvas.translate(64, 0);
          canvas.drawPath(nStarPath(i, 30, 15), mPaint);
        }
        canvas.restore();
    
        canvas.translate(0, 70);
        canvas.save();//绘制正n角星
        for (int i = 5; i < 10; i++) {
          canvas.translate(64, 0);
          canvas.drawPath(regularStarPath(i, 30), mPaint);
        }
        canvas.restore();
    
        canvas.translate(0, 70);
        canvas.save();//绘制正n边形
        for (int i = 5; i < 10; i++) {
          canvas.translate(64, 0);
          canvas.drawPath(regularPolygonPath(i, 30), mPaint);
        }
        canvas.restore();
    
    n角星与n边形.png
    8.状态控制,点击随机色

    第一个按钮的fab点击更改数字,这里换成颜色试一下:

    //-----------main.dart-------------------
      Color _color = Colors.black;
    
      void _changeColor() {
        setState(() {
          _color=randomRGB();
        });
      }
      
      body: CustomPaint(
            painter: StarView(context,_color),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: _changeColor,
            tooltip: 'Increment',
            child: Icon(Icons.add),
          ),
    //-----------随机颜色-------------------
    Color randomRGB(){
      Random random = new Random();
      int r = 30 + random.nextInt(200);
      int g = 30 + random.nextInt(200);
      int b = 30 + random.nextInt(200);
      return Color.fromARGB(255, r, g, b);
    }
    
    //-----------star_view.dart-------------------   
     StarView(this.context,Color color) {
        print(color);
        mPaint = new Paint();
        mPaint.color = color;
      }
    
    点击改变状态.gif

    五、汇集一下今天的mark

    经过初始项目的分析以及Dart方言的简单入门,再加上Canvas的绘制
    基本上熟悉了Dart的语法与Flutter的套路(和React很像),第一天就这样吧

    1.setState和React这是一模一样,mark一下,估计会刷新界面
    ----经过测试,是的,调用setState会重新绘制界面,和React一样
    
    2.MyHomePage也是StatefulWidget家的,第一句话感觉挺诡异,先mark一下
            class MyHomePage extends StatefulWidget {
             MyHomePage({Key key, this.title}) : super(key: key);
             
    ----根据继承的语法以及{}的可选参数,不难理解,key是从老爸那拿的
    
    3.现在焦点应该汇聚在StatefulWidget身上,很多地方都出现了,mark一下
    ---保持mark
    
    4.canvas竟然没办法画文字,这不科学,mark一下
    ---保持mark
    

    后记:捷文规范

    1.本文成长记录及勘误表
    项目源码 日期 备注
    V0.1-github 2018-12-16 Flutter第1天--初始分析+Dart方言+Canvas简绘
    2.更多关于我
    笔名 QQ 微信 爱好
    张风捷特烈 1981462002 zdl1994328 语言
    我的github 我的简书 我的掘金 个人网站
    3.声明

    1----本文由张风捷特烈原创,转载请注明
    2----欢迎广大编程爱好者共同交流
    3----个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正
    4----看到这里,我在此感谢你的喜欢与支持


    icon_wx_200.png

    相关文章

      网友评论

      本文标题:Flutter第1天--初始分析+Dart方言+Canvas简绘

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