Flutter:WahtApp_Clones使用Camera&TabBar&ListView

新建項目、新建包,再新建一個dart文件,不一樣於Java文件會自動填充類目,這是一個空白文件,其次dart中文件名不須要和類型相同。由於同一個文件中能夠存在多個一級類,而Java多以匿名內部類的形式存在。類的建立方式就不作贅述了,須要注意的是筆者用到的時候須要本身手動導包,固然若是你記得完整類名也能夠用快捷鍵導包,該類是此App的主界面,一般來講直接繼承自`StatelessWidget或者StatefulWidget,Flutter中全部都是Weight(組件),包括padding、color等。而在Flutter中Weight大體能夠分爲三類,也有認爲分兩類的均可以吧。

  • 第一類:StatelessWidget以及繼承自該類的其餘Widget如Text、ButtonBar等。此類爲無狀態組件,必須實現其構造方法,不須要維護其狀態,內容一般沒法動態更改。
  • 第二類:StatefulWidget以及繼承自該類的其餘Widget如Image、Scaffold等。此類爲有狀態的組件,必須實現createState()方法,以建立須要維護的State,能夠用來更改控件的內容或者狀態。
  • 第三類:RenderObjectWidget以及直接或者間接繼承自該類的Widget如Padding、Align(對齊)等。改類多位一些小部件,用來修飾其餘Widget。

還有就是上面涉及到的導包操做,格式以下html

//import '路徑'
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/widgets.dart';
複製代碼

這裏用到的Widget爲StatefulWidget,導入上述文件中任何一個均可以,根據不一樣平臺進行選擇。這三個文件包含內容的大小從上到下依次減小,前兩個文件包含了第三個文件。第一個主要爲Material Design風格UI等(Android平臺主流風格),第二個主要包含Cupertino風格UI等(IOS平臺主流風格),第三個即經常使用Widget等。git

import 'package:flutter/material.dart';
//import 'package:flutter/widgets.dart';
//import 'package:flutter/cupertino.dart';

//規範:同Java,駝峯命名不推薦下劃線等特殊符號
class WhatsappHome extends StatefulWidget {

	@override
	State createState() {
		return null;
	}
}
複製代碼

接下來爲createState()方法建立一個返回值,這種狀況能夠考慮直接new一個State,可是會報錯由於State是一個抽象類,因此這裏咱們自定義一個類,繼承State。代碼以下github

class _WhatsAppHomeState extends State{
  @override
  Widget build(BuildContext context) {
    return null;
  }
}
複製代碼

上面代碼依舊在同一個文件中,必須實現build方法,同時該方法要求返回一個Widget,前面提到的任何Widget均可以在這裏做爲返回值,但這裏咱們選擇使用Scaffold做爲返回值,Scaffold是一個組合Widget,由Flutter內部幫咱們將多個Widget組合到一塊兒,實現了基本的Material Design佈局結構(Android),經過閱讀源碼能夠查看該Widget由那些Widget組合而來,請自行查閱,Scaffold是及其經常使用的,其子Widget中又包含了其餘組合Widget好比AppBar等,瞭解各個Widget有助於之後自定義(組合)佈局結構。算法

接下來往Scaffold中填充元素,根據上面的效果圖能夠分析出來當前頁面須要哪些Widget,填充後完整代碼以下api

@override
  Widget build(BuildContext context) {
    // TODO: implement build
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("WhatApp"),
        elevation: 0.7,//陰影
        bottom: new TabBar(//注意這裏bottom是指Appbar的bottom
		  //硬性規定:TabBar必須配合TabController一塊兒使用(或者TabController的實現類),用於控制tab切換,不然程序報錯
          controller: new TabController(length: 4, vsync: this),
          indicatorColor: Colors.white,
          //選中時顏色
          tabs: <Widget>[
            new Tab(icon: new Icon(Icons.camera_alt)),
            new Tab(text: "CHATS"),
            new Tab(text: "STATUS"),
            new Tab(text: "CALLS")
          ],
        ),
		//右側按鈕
        actions: <Widget>[
          new Icon(Icons.search),
          new Padding(padding: const EdgeInsets.symmetric(horizontal: 5.0)),
          //常量建議加上const關鍵字(非強制),EdgeInsets表示邊緣插入,symmetric爲對稱插入方向的描述之一
          new Icon(Icons.more_vert)
        ],
      ),
    );
  }
}
複製代碼

可是若是使用hot load功能程序會出現以下錯誤後來發現圖片丟了......數組

看到關鍵點便可,能夠看到錯誤中提到了一個叫SingleTickerProviderStateMixin的類,並指明瞭_WhatsAppHomeState須要使用它,代碼中有註釋明確表示須要配合TickerProvider來使用Tabbar,而錯誤中提到的類也包含這個單詞,到這裏大體就能夠推斷出來SingleTickerProviderStateMixinTickerProvider的一個子類。那替換成它要求的類便可。網絡

在動手前先看下SingleTickerProviderStateMixin的具體實現,這裏算是難點了 點擊controller屬性進入源碼中查看,能夠看到源碼中要求使用TabController,而這裏直接new一個對象的話會出現以下錯誤app

The argument type 'SingleTickerProviderStateMixin<StatefulWidget>' can't be assigned to the parameter type 'TabController'
複製代碼

大意爲SingleTickerProviderStateMixin不能轉換爲TabController,點開SingleTickerProviderStateMixin源碼以下less

@optionalTypeArgs
mixin SingleTickerProviderStateMixin<T extends StatefulWidget> on State<T> implements TickerProvider {
  Ticker _ticker;
	...
複製代碼

多的就不看了,第一行就夠,很明顯該類實現了TickerProvider在Dart語法中可使用implements實現其餘類不必定是抽象類,以下異步

abstract class A{

}
abstract class B{

}
class D{

}
abstract class C extends A implements B,D{

}
複製代碼

由於這裏是實現關係,並不是繼承因此對象不能轉換。其餘該類的聲明過程當中還出現了幾個關鍵詞mixinon,一般和前兩個關鍵詞配合使用的還有一個with,受限於篇幅這裏就不詳述了,看下以下代碼應該就差很少明白了,說明在註釋中 class A { void a() { print("A"); } }

class B {
  void b() {
    print("B");
  }
}

class D {
  void b() {
    print("D");
  }
}

mixin E on D {}//該行表示類E容許被with多繼承,可是受限於類D,再直白點的說法就是:再直白點的說法就是:某X使用with多繼承E,則X必須是繼承D

//with用於實現多繼承,如有同名方法優先級從右往左依次從高到低
class C extends A with B, D, E {
  void text() {
    this.a();
    this.b(); //能夠調用到BD中方法,且輸出D,由於D中b()覆蓋了B中b().
  }
}
//如下下寫法將出錯
//class F with E{}由於F和D並沒有繼承關係
複製代碼

這裏須要着重理解一下從事移動開發的對多繼承的概念可能比較薄弱,其次Dart低(高)版本中該關鍵字使用方法可能有差別。

確保上面的三個關鍵字理解了,再回頭看SingleTickerProviderStateMixin得使用就比較明瞭了。完整代碼以下

import 'package:flutter/material.dart';
import 'package:flutter/src/foundation/diagnostics.dart';
import 'package:flutter_teaching/whatsapp/pages/call_screen.dart';
import 'package:flutter_teaching/whatsapp/pages/camera_screen.dart';
import 'package:flutter_teaching/whatsapp/pages/chat_screen.dart';
import 'package:flutter_teaching/whatsapp/pages/status_screen.dart';

//import 'package:flutter/widgets.dart';
//import 'package:flutter/cupertino.dart';

//規範:同Java,駝峯命名不推薦下劃線等特殊符號
class WhatsappHome extends StatefulWidget {
  @override
  State createState() {
    return new _WhatsAppHomeState();
  }
}

class _WhatsAppHomeState extends State with SingleTickerProviderStateMixin {
  TabController _tabController;

  @override
  void initState() {
    super.initState();
    _tabController = new TabController(length: 4, vsync: this, initialIndex: 1);
  }

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("WhatApp"),
        elevation: 0.7, //陰影
        bottom: new TabBar(
          controller: _tabController,
          //TabBar必須配合TabController一塊兒使用
          indicatorColor: Colors.white,
          //選中時顏色
          tabs: <Widget>[
            new Tab(icon: new Icon(Icons.camera_alt)),
            new Tab(text: "CHATS"),
            new Tab(text: "STATUS"),
            new Tab(text: "CALLS")
          ],
        ),
        actions: <Widget>[
          new Icon(Icons.search),
          new Padding(padding: const EdgeInsets.symmetric(horizontal: 5.0)),
          //常量建議加上const關鍵字(非強制),EdgeInsets表示邊緣插入,symmetric爲對稱插入方向的描述之一
          new Icon(Icons.more_vert)
        ],
      ),
      //TabBarView表示爲TabBar的頁面,該空間會自動按順序關聯TabBar,這裏簡單建立了4個類
      // 每一個類寫法徹底相同以下,記得導入文件
      //class CallsScreen extends StatelessWidget {
      //  @override
      //  Widget build(BuildContext context) {
      //    return new Center(
      //      child: new Text(
      //        "Calls",
      //        style: new TextStyle(fontSize: 20.0),
      //      ),
      //    );
      //  }
      //}
	  //注意這是Scaffold中屬性
      body: new TabBarView(
        controller: _tabController,
        children: <Widget>[
          new CameraScreen(),
          new ChatScreen(),
          new StatusScreen(),
          new CallsScreen(),
        ],
      ),
      floatingActionButton: new FloatingActionButton(
          backgroundColor: Theme.of(context).accentColor,
          child: new Icon(
            Icons.message,
            color: Colors.white,
          ),
          onPressed: () => print("open chat")),
    );
  }
}
複製代碼

這裏說明一下Dart的語法是嵌套+構造的寫法,若是遇到不清楚的Widget不知道有些啥屬性那麼點進去看一下它的構造方法就明白了。這裏代碼可能看起來不清晰,但在AS中會自動填充標記所嵌套的層次。 到目前爲止整個功能剩餘對攝像頭的支持了,目前效果圖以下

Gif圖片

然而並無圖

接下來對camera_screen.dart文件改造下。Flutter中攝像頭使用的官方連接,大體看一下知道大概哪些步驟。 前面爲了方便對四個View都是使用的StatelessWidget,也就是說類容固定,而要使用攝像頭必須使用StatefulWidget,還須要添加依賴。其次這裏還涉及到異步,文檔第二步中的使用到的await,async,Future就是Flutter中異步操做須要用到的關鍵詞,經常使用於網絡請求,以及耗時任務等阻塞線程的操做中。以後初始化CameraController這玩意就是Android中的CameraManager,不初始化沒法使用拍照等功能。最後建立一個Widget用於顯示攝像頭獲取到的畫面,這裏指定使用CameraPreview來展現畫面。剩餘的兩部分別是拍照和展現照片。
第一步:按照文檔中的第一步,添加依賴。添加後pubspec.yaml文件以下,添加以後點擊左上角的Packages get同步完成後進行下一步

# 注意這裏的依賴縮進須要注意 縮進錯誤會致使找不到包報錯
dependencies:
  flutter:
    sdk: flutter
  camera:
  path_provider:
  path:
複製代碼

第二步:獲取可用Camera列表,並從列表中獲取第一個可用的攝像頭代碼以下

// Obtain a list of the available cameras on the device.
final cameras = await availableCameras();

// Get a specific camera from the list of available cameras.
final firstCamera = cameras.first; 
複製代碼

重點來了,前面講到了mixinwithon三個關鍵字以及組合使用,這裏依舊是三個相互關聯的關鍵字分別是awaitFutureasync

await用在調用的異步方法時做爲前置修飾。若某方法體內使用await關鍵字,則該方法一定須要使用async修飾,若由await修飾的部分爲返回值,則返回值類型必須聲明爲Future類型

Future getCamera() async {
    // 該方法調用異步方法availableCameras(),須要使用await來調用,且該方法須要使用async修飾,返回值爲Future
    final cameras = await availableCameras();
    // Get a specific camera from the list of available cameras.
    final firstCamera = cameras.first;
  }
複製代碼

上面代碼中異步方法availableCameras()來自文件'package:camera/camera.dart';,

第三步:初始化CameraController

class TakePictureScreen extends StatefulWidget {
  final CameraDescription camera;

  const TakePictureScreen({
    Key key,
    @required this.camera,
  }) : super(key: key);

  @override
  TakePictureScreenState createState() => TakePictureScreenState();
}

class TakePictureScreenState extends State<TakePictureScreen> {
  // Add two variables to the state class to store the CameraController and
  // the Future.
  CameraController _controller;
  Future<void> _initializeControllerFuture;

  @override
  void initState() {
    super.initState();
    // To display the current output from the camera,
    // create a CameraController.
    _controller = CameraController(
      // Get a specific camera from the list of available cameras.
      widget.camera,
      // Define the resolution to use.
      ResolutionPreset.medium,
    );

    // Next, initialize the controller. This returns a Future.
    _initializeControllerFuture = _controller.initialize();
  }

  @override
  void dispose() {
    // Dispose of the controller when the widget is disposed.
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    // Fill this out in the next steps.第四步中完成該方法
  }
}
複製代碼

第四步:使用CameraPreview顯示攝像頭獲取到的畫面

//第三部中的build方法
  @override
  Widget build(BuildContext context) {
    if (!controller.value.isInitialized) {
      return new Container();
    }

    return new AspectRatio(
      aspectRatio: controller.value.aspectRatio,
      child: new CameraPreview(controller),
    );
  }
複製代碼

最後,在main函數中異步獲取可用camera,並將該對象以構造的方式傳入到camera_screen()中。繼續按照官方文檔中的第五六步能夠實現拍照且,將照片顯示出來。

注意:前面講過Flutter類名和文件名無關,且能夠有多個一級類好比前面講到的WhatsappHome中的_WhatsAppHomeState,那麼要在_WhatsAppHomeState中調用到WhatsappHome中的變量可使用以下寫法

class _WhatsAppHomeState extends State<WhatsappHome> with SingleTickerProviderStateMixin {
複製代碼

經過查看State類的源碼可得知該類支持泛型,而State一般是做爲StatefulWidget中createState的返回值出現,故State中泛型一般傳入與之關聯的StatefulWidget。

@optionalTypeArgs
abstract class State<T extends StatefulWidget> extends Diagnosticable {...}
複製代碼

最後是Flutter中ListView的使用 在Flutter中滑動控件也叫ListView,其相關的列表控件包括ScrollView以及直接或間接繼承自該類的子控件如CustomScrollView、GridView等 ListView官方文檔 經過閱讀文檔或者查看源碼能夠發現,ListView共四種用法,原文以下

///  1. The default constructor takes an explicit [List<Widget>] of children. This
///     constructor is appropriate for list views with a small number of
///     children because constructing the [List] requires doing work for every
///     child that could possibly be displayed in the list view instead of just
///     those children that are actually visible.默認構造方法,WIdget數組便可,適用數少許,固定的列表
///
///  2. The [ListView.builder] constructor takes an [IndexedWidgetBuilder], which
///     builds the children on demand. This constructor is appropriate for list views
///     with a large (or infinite) number of children because the builder is called
///     only for those children that are actually visible.根據須要構建子項。此構造函數適用於具備大量(或無限)子項數的列表視圖
///
///  3. The [ListView.separated] constructor takes two [IndexedWidgetBuilder]s:
///     `itemBuilder` builds child items on demand, and `separatorBuilder`
///     similarly builds separator children which appear in between the child items.
///     This constructor is appropriate for list views with a fixed number of children.
///     按需構建子項,而separatorBuilder相似地構建出如今子項之間的子項(分割線或者多層次的ListView)。此構造函數適用於具備固定數量子項的列表視圖。
///
///  4. The [ListView.custom] constructor takes a [SliverChildDelegate], which provides
///     the ability to customize additional aspects of the child model. For example,
///     a [SliverChildDelegate] can control the algorithm used to estimate the
///     size of children that are not actually visible.
///		採用SliverChildDelegate,它提供了自定義子模型的其餘方面的功能。例如,SliverChildDelegate能夠控制用於估計實際上不可見的子項大小的算法。
///		這個涉及到Sliver以及其衍生子類,動畫效果都很好,漸變漸隱之類的如SliverAppBar等。
複製代碼

下面是改造後的ChatScreen,其中方向由構造方法中的參數決定,默認豎向Axis scrollDirection = Axis.vertical,

class ChatScreen extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return new ChatScreenState();
  }
}

class ChatScreenState extends State<ChatScreen> {
  final List<String> entries = <String>["A", "B", "C", "A", "B", "C"];
  final List<int> background = <int>[50, 100, 200, 300, 400, 500];//顏色深度有一張表,在一個叫Gallery的項目中找到的

  @override
  Widget build(BuildContext context) {
      //不一樣構造方法
//    ListView.separated(itemBuilder: null, separatorBuilder: null, itemCount: null);
//    ListView.builder(itemBuilder: null);
//    ListView.custom(childrenDelegate: null);

    return new ListView.separated(
        padding: const EdgeInsets.all(5.0),
        itemCount: entries.length,
        separatorBuilder: (BuildContext context, int index) => const Divider(),//默認分隔線
        itemBuilder: (BuildContext context, int index) {
          return Container(
            height: 100,
            color: Colors.red[background[index]],
            child: new Center(
              child: new Text("Item_${entries[index]}"),//支持字符串中直接拼接
            ),
          );
        });
  }
}
複製代碼

Demo

相關文章
相關標籤/搜索