1. 程式人生 > >Flutter第1天--初始分析+Dart方言+Canvas簡繪

Flutter第1天--初始分析+Dart方言+Canvas簡繪

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