欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

用Flutter做桌上彈球(繪圖(Canvas&CustomPaint)API)

 更新時間:2020年07月30日 17:09:13   作者:文翰代碼實驗室  
這篇文章主要介紹了用Flutter做桌上彈球 聊聊繪圖(Canvas&CustomPaint)API,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下

本文是Flutter中Canvas和CustomPaint API的使用實例。
首先看一下我們要實現(xiàn)的效果:

結(jié)合動圖演示,列出最終目標如下:

  • 在程序運行后,顯示一個小球;
  • 每次程序啟動后,小球的樣式均發(fā)生隨機性變化,體現(xiàn)在大小、顏色和位置三點;
  • 小球運行的規(guī)律參考桌球或三維彈球游戲;
  • 單擊屏幕,小球變色;
  • 雙擊屏幕,小球暫停/恢復運動;
  • 長按屏幕,小球開始/停止自動變色。

運用的主要技術(shù)點:Canvas和CustomPaint API。

運行平臺:Android、iOS

源碼地址:
Github Gitee

功能拆解

首先拆解前文中所列出的6個實現(xiàn)目標,顯而易見,要實現(xiàn)它們,我們需要:

  1. 隨機顏色生成器;
  2. 隨機位置生成器;
  3. 隨機尺寸生成器;
  4. 小球繪制邏輯;
  5. 小球運動邏輯:

邊界判定;
初始運動方向生成器;
定向移動位置更新器。

  1. 用戶手勢監(jiān)聽器。

功能實現(xiàn)

接下來,我們逐步實現(xiàn)功能拆解中所列舉的6個具體功能。

隨機顏色生成器

隨機顏色生成器在程序啟動、單擊屏幕和自動變色中使用。在Flutter中,我們可以通過Color類對紅、綠、藍和透明度分別定義,來定義某個唯一的顏色,數(shù)值范圍是0-255。對于透明度,0表示完全透明,255表示完全不透明。
對于隨機數(shù)值,我們使用Random類生成0-255之間的隨機整數(shù)。
隨機顏色生成器則主要使用上述兩個類來實現(xiàn),具體代碼片段如下:

Color _color = Color.fromARGB(0, 0, 0, 0);

// 改變小球顏色
void changeColor() {
	_color = Color.fromARGB(255, Random().nextInt(255), Random().nextInt(255),Random().nextInt(255));
}

隨機位置生成器

隨機位置生成器在程序啟動時使用。要生成隨機位置,方法依然是使用Random類,但要注意隨機值范圍。通常我們需要小球出現(xiàn)的位置在屏幕內(nèi),因此,我們需要生成兩次隨機數(shù),分別表示小球初始位置的x和y軸坐標。坐標值分別小于屏幕橫向尺寸和縱向尺寸。當然,它們都要大于0。
另外,我們還需要分別獲取屏幕的寬高。
因此,具體代碼實現(xiàn)如下:

[獲取屏幕寬高]

double screenX, screenY;
@override
Widget build(BuildContext context) {
	screenX = MediaQuery.of(context).size.width;
	screenY = MediaQuery.of(context).size.height;
	...
}

[生成隨機位置]

double _x = 0, _y = 0;

// 生成小球初始位置和大小
void generateBall() {
	_x = Random().nextDouble() * screenX;
	_y = Random().nextDouble() * screenY;
}

隨機尺寸生成器

隨機尺寸生成器在程序啟動時使用。完成了之前兩種隨機值的生成,到了尺寸這里,就很輕車熟路了。由于隨機尺寸和隨機位置都在程序啟動時調(diào)用,且操作對象都是小球,我們將其實現(xiàn)都放在generateBall()方法中。最終代碼如下:

double _x = 0, _y = 0, _size = 0;

// 生成小球初始位置和大小
void generateBall() {
 _size = Random().nextDouble() * (screenY - screenX).abs();
 _x = Random().nextDouble() * screenX;
 _y = Random().nextDouble() * screenY;
}

小球繪制邏輯

要在界面上繪制小球,我們需要使用CustomPaint組件。而CustomPaint組件需要一個CustomPainter實例。小球的繪制工作主要在繼承了CustomPainter的類中。我們直接看代碼:

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

class Ball extends CustomPainter {
 Paint _paint;

 double _x, _y, _size;

 Ball(double x, double y, double size, Color color) {
 _paint = new Paint();
 _paint.isAntiAlias = true;
 _paint.color = color;
 this._x = x;
 this._y = y;
 this._size = size;
 }

 @override
 void paint(Canvas canvas, Size size) {
 canvas.drawOval(Rect.fromCenter(center: Offset(_x, _y), width: _size, height: _size), _paint);
 }

 @override
 bool shouldRepaint(CustomPainter oldDelegate) {
 return oldDelegate != this;
 }
}

通過閱讀上面的代碼,可以發(fā)現(xiàn),整個Ball類除了構(gòu)造方法外,只有兩個override的方法,可以說是很簡單了。
在構(gòu)造方法中,我們初始化了_paint對象,它是可以看做是“畫筆”;
在paint()方法中,我們調(diào)用canvas對象的drawOval方法畫圓,表示小球。canvas可以看做是“畫板”;
shouldRepaint()方法表示在刷新布局的時是否需要重繪,只有在返回true時會發(fā)生重繪,這里我們讓程序自行判斷就可以了。
我們將上述代碼保存為ball.dart備用。
注意,這里面無論是位置、顏色還有尺寸,都沒有寫固定的值。是因為該類只負責“畫圓”,而具體畫什么樣的圓,則交給該類的使用者來定義,也就是main.dart。
在main.dart中,我們將App設置為全屏,并添加全屏尺寸的CustomPaint組件,組件內(nèi)放置Ball對象。

@override
Widget build(BuildContext context) {
 screenX = MediaQuery.of(context).size.width;
 screenY = MediaQuery.of(context).size.height;
 return Scaffold(
  body: GestureDetector(
  child: Container(
   width: double.infinity,
   height: double.infinity,
   child: CustomPaint(painter: Ball(_x, _y, _size, _color))),
  onTap: () {
  	// 改變小球顏色
  	changeColor();
  },
  onDoubleTap: () {
  	// 暫停/恢復移動
  	_keep_move = !_keep_move;
  },
  onLongPress: () {
  	// 自動改變小球顏色
  	_auto_change_color = !_auto_change_color;
  },
 ));
}

上述代碼中,GestureDetector組件負責接收用戶點擊事件,其中的_keep_move、_auto_change_color都是布爾類型變量,是小球移動和自動變色功能的開關(guān)。
接下來,我們在initState()方法中調(diào)用之前的隨機位置生成器、隨機尺寸生成器和隨機顏色生成器,賦值_x、_y、_size和_color。

@override
void initState() {
 super.initState();
 WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
  generateBall();
  changeColor();
  calculateMoveAngle();
  startMove();
 });
}

這里面,calculateMoveAngle()和startMove()方法分別對應初始運動方向生成器以及開始運動并定期更新UI的方法。除了這兩個方法外,如果現(xiàn)在運行程序的話,應該可以看到一個靜態(tài)的小球出現(xiàn)在屏幕上了,并且隨著每次重新運行程序,小球的樣式和位置都將發(fā)生變化。
接下來,我們就來讓小球動起來吧!

小球運動邏輯

要讓小球準確無誤地運動,我們需要遵循以下步驟:首先生成一個隨機的運動方向;然后以60FPS的頻率,每次在運動方向上前進5個像素的步長(當然,你可以自定義);最后還要注意邊界判定,在小球到達屏幕邊緣時正確轉(zhuǎn)向。
下面我們逐個實現(xiàn)。

初始運動方向生成器

既然是隨機方向,那么平面上360度范圍內(nèi)任何一個角度都有可能。因此,我們這里需要先生成0-360范圍內(nèi)的值。然后根據(jù)三角函數(shù)和運動方向的速度,計算出橫、縱坐標的速度。其實很簡單,就是勾股定理。

double _step_x, _step_y, _angle;

// 計算小球初始移動角度(方向)
void calculateMoveAngle() {
 _angle = Random().nextDouble() * 360;
 _step_x = sin(_angle) * _speed;
 _step_y = cos(_angle) * _speed;
}

我們這里把運動速度(_speed)看做是三角形的斜邊,橫、縱坐標的移動速度(_step_x、_step_y)看做是三角形的直角邊即可。沒記錯的話,都是初中幾何知識,不會很難理解。

定向移動位置更新器

前文說到,我們將以60FPS的刷新率更新界面,這也就意味著,每隔大約16ms刷新一次小球位置。因為只有小球的運動,才能讓人感到界面在“更新”。這一步驟,我們用到Timer類。并將更新器在initState()方法中調(diào)用,以便程序啟動后,小球即刻運動,也就是前文代碼中見到的startMove()方法。

// 開始移動
void startMove() {
 Timer.periodic(Duration(milliseconds: 16), (timer) {
  moveBall();
  setState(() {});
 });
}

// 小球移動
void moveBall() {
 _x += _step_x;
 _y += _step_y;
}

到此為止,小球已經(jīng)可以開始沿著某個隨機方向移動了。但很快,它將移出屏幕。

邊界判定

顯然,小球每前進一步,都要做屏幕邊界判定,以防小球移出屏幕范圍。而邊界判定在moveBall()方法中實現(xiàn)似乎是最恰當?shù)摹?br /> 我們可以輕松地總結(jié)出小球移動的規(guī)律,當小球移動到屏幕邊緣時,我們只需讓其反向運動即可。比如,小球以3的速度移動并接觸屏幕的右邊緣,接下來,仍以3的速度移動并朝向屏幕的左邊緣。
水平方向如此,垂直方向亦如此。
因此,我們的邊界判定邏輯如下:

// 帶有便捷判定的小球移動
void moveBall() {
 if (_x >= screenX || _x <= 0) {
  _step_x = 0 - _step_x;
 }
 _x += _step_x;
 if (_y >= screenY || _y <= 0) {
  _step_y = 0 - _step_y;
 }
 _y += _step_y;
}

用戶手勢監(jiān)聽器

最后,配合用戶手勢及相關(guān)的布爾變量,在每次刷新小球位置時實現(xiàn)變色和暫停移動。
繼續(xù)修改moveBall()方法:

// 帶有便捷判定的小球移動
void moveBall() {
 if (_keep_move) {
  if (_x >= screenX || _x <= 0) {
   _step_x = 0 - _step_x;
  }
  _x += _step_x;
  if (_y >= screenY || _y <= 0) {
   _step_y = 0 - _step_y;
  }
  _y += _step_y;
  if (_auto_change_color) {
   changeColor();
  }
 }
}

到此,程序全部實現(xiàn)完成。下面放上完整的main.dart代碼:

import 'dart:async';
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

import 'ball.dart';

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

class MyApp extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
 SystemChrome.setEnabledSystemUIOverlays([]);
 return MaterialApp(
  title: 'Flutter Demo',
  theme: ThemeData(
  primarySwatch: Colors.blue,
  visualDensity: VisualDensity.adaptivePlatformDensity,
  ),
  home: BounceBall(),
 );
 }
}

class BounceBall extends StatefulWidget {
 @override
 _BounceBallState createState() => _BounceBallState();
}

class _BounceBallState extends State<BounceBall> {
 final double _speed = 5;

 double _x = 0, _y = 0, _size = 0;

 double _step_x, _step_y, _angle;

 Color _color = Color.fromARGB(0, 0, 0, 0);

 bool _auto_change_color = false;

 bool _keep_move = true;

 double screenX, screenY;

 @override
 void initState() {
 super.initState();
 WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
  generateBall();
  changeColor();
  calculateMoveAngle();
  startMove();
 });
 }

 @override
 Widget build(BuildContext context) {
 screenX = MediaQuery.of(context).size.width;
 screenY = MediaQuery.of(context).size.height;
 return Scaffold(
  body: GestureDetector(
  child: Container(
   width: double.infinity,
   height: double.infinity,
   child: CustomPaint(painter: Ball(_x, _y, _size, _color))),
  onTap: () {
  // 改變小球顏色
  changeColor();
  },
  onDoubleTap: () {
  // 暫停/恢復移動
  _keep_move = !_keep_move;
  },
  onLongPress: () {
  // 自動改變小球顏色
  _auto_change_color = !_auto_change_color;
  },
 ));
 }

 // 開始移動
 void startMove() {
 Timer.periodic(Duration(milliseconds: 16), (timer) {
  moveBall();
  setState(() {});
 });
 }

 // 改變小球顏色
 void changeColor() {
 _color = Color.fromARGB(255, Random().nextInt(255), Random().nextInt(255),
  Random().nextInt(255));
 }

 // 生成小球初始位置和大小
 void generateBall() {
 _size = Random().nextDouble() * (screenY - screenX).abs();
 _x = Random().nextDouble() * screenX;
 _y = Random().nextDouble() * screenY;
 }

 // 計算小球初始移動角度(方向)
 void calculateMoveAngle() {
 _angle = Random().nextDouble() * 360;
 _step_x = sin(_angle) * _speed;
 _step_y = cos(_angle) * _speed;
 }

 // 帶有便捷判定的小球移動
 void moveBall() {
 if (_keep_move) {
  if (_x >= screenX || _x <= 0) {
  _step_x = 0 - _step_x;
  }
  _x += _step_x;
  if (_y >= screenY || _y <= 0) {
  _step_y = 0 - _step_y;
  }
  _y += _step_y;
  if (_auto_change_color) {
  changeColor();
  }
 }
 }
}

讓我們一起讓這個程序跑起來吧!

到此這篇關(guān)于用Flutter做桌上彈球 聊聊繪圖(Canvas&CustomPaint)API的文章就介紹到這了,更多相關(guān)Flutter桌上彈球內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • ProtoBuf動態(tài)拆分Gradle?Module解析

    ProtoBuf動態(tài)拆分Gradle?Module解析

    這篇文章主要為大家介紹了ProtoBuf動態(tài)拆分Gradle?Module解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-02-02
  • 利用Android實現(xiàn)一種點贊動畫效果的全過程

    利用Android實現(xiàn)一種點贊動畫效果的全過程

    最近做項目需要實現(xiàn)點贊動畫,下面這篇文章主要給大家介紹了關(guān)于利用Android實現(xiàn)一種點贊動畫效果的相關(guān)資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下
    2022-12-12
  • Android BroadcastReceiver常見監(jiān)聽整理

    Android BroadcastReceiver常見監(jiān)聽整理

    這篇文章主要介紹了Android BroadcastReceiver常見監(jiān)聽整理的相關(guān)資料,需要的朋友可以參考下
    2016-10-10
  • Android?獲取手機已安裝的應用列表實現(xiàn)詳解

    Android?獲取手機已安裝的應用列表實現(xiàn)詳解

    這篇文章主要介紹了Android?獲取手機已安裝的應用列表的實現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-08-08
  • Android APP之WebView校驗SSL證書的方法

    Android APP之WebView校驗SSL證書的方法

    這篇文章主要介紹了Android APP之WebView校驗SSL證書的方法,需要的朋友可以參考下
    2017-09-09
  • Gradle配置教程之自定義APK名稱與輸出路徑

    Gradle配置教程之自定義APK名稱與輸出路徑

    Gradle是一個基于JVM的富有突破性構(gòu)建工具,下面這篇文章主要給大家介紹了關(guān)于Gradle配置教程之自定義APK名稱與輸出路徑的相關(guān)資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考借鑒,下面隨著小編來一起學習學習吧。
    2018-03-03
  • 探尋Android的線程問題

    探尋Android的線程問題

    這篇文章主要介紹了探尋Android的線程問題的相關(guān)資料,需要的朋友可以參考下
    2016-03-03
  • Android Drawable必備知識小結(jié)

    Android Drawable必備知識小結(jié)

    這篇文章主要為大家詳細了Android Drawable必備基礎知識 ,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-10-10
  • Android調(diào)試神器stetho使用詳解和改造

    Android調(diào)試神器stetho使用詳解和改造

    今天小編就為大家分享一篇關(guān)于Android調(diào)試神器stetho使用詳解和改造,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-02-02
  • Android控件RadioButton的使用方法

    Android控件RadioButton的使用方法

    這篇文章主要為大家詳細介紹了Android控件RadioButton的使用方法,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-05-05

最新評論