Flutter 仿英雄聯(lián)盟客戶端詳解

Flutter 仿英雄聯(lián)盟客戶端


最近在用Flutter 模仿英雄聯(lián)盟客戶端,

制作頂部菜單的時候,發(fā)現(xiàn)了之前沒有注意的細節(jié)效果,寫個文章分享和記錄一下。


原本英雄聯(lián)盟的光影效果

分析:鼠標移動到菜單上,會產(chǎn)生一個光暈,從底部向上扇形擴散,光暈的中心點就是鼠標的X軸坐標。

現(xiàn)在嘗試來復現(xiàn)這個效果。

我們需要用到的第一個組件是:CustomPaint

它是一個自己處理繪制的組件。

一定要用RepaintBoundary包住,不然它會被其他組件的notifyListeners()影響導致CustomPaint重繪,如果沒有用RepaintBoundary包裹,它自身的notifyListeners()行為也會影響其他組件導致重繪。

RepaintBoundary(

//底層光圈繪制層

child:CustomPaint(painter:_painter,

// 上層元素child:widget.child,

),)



class TopMenuShadowPainter extends ChangeNotifier implements CustomPainter {

? TopMenuShadowPainter();

? @override

? void paint(Canvas canvas, Size size) {

? ? // 圓的尺寸

? ? double radius = 80;

? ? // 繪制一個 圓形

? ? canvas.drawOval(

? ? ? Rect.fromLTWH(0, 0, radius, radius),

? ? ? paint,

? ? );

? ? // 或者繪制一個 扇形? pi是系統(tǒng)定義的 3.1415926535897932 , pi + pi 就是從左側(cè)水平? 0°到 右側(cè) 180°

? ? canvas.drawArc(

? ? ? Rect.fromLTWH(0, 0, radius, radius),

? ? ? pi,

? ? ? pi,

? ? ? true,

? ? ? paint,

? ? );

? }


繪制出大小80直徑的圓


繪制出80直徑半圓


接下來就是讓這個圓在鼠標的位置中心點繪制,所以需要把鼠標傳進去,一開始我使用的是AnimatedBuilder+CustomPaint,這種方案每次繪制都會重新創(chuàng)建新的painter對象,不是特別的好。

我參考了Flutter 繪制探索 1 | CustomPainter 正確刷新姿勢 | 七日打卡這篇文章,里面有講到CustomPaint的幾種刷新機制,當前這個Menu 比較適合采用 ChangeNotifier的方式進行刷新,讓當前TopMenuShadowPainter繼承ChangeNotifier,并且實現(xiàn)CustomPainter,這樣它就帶有通知刷新和繪制能力。

我們需要使用MouseRegion捕獲鼠標的位置和移出移入事件。

/// 繪制器

late TopMenuShadowPainter _painter;

MouseRegion(

? ? ? /// 顯示為手指的效果

? ? ? cursor: SystemMouseCursors.click,

? ? ? onEnter: (event) {

? ? ? ? // 鼠標進入

? ? ? ? _painter.updateEnter(true);

? ? ? },

? ? ? onExit: (event) {

? ? ? ? // 鼠標離開

? ? ? ? _painter.updateEnter(false);

? ? ? },

? ? ? onHover: (event) {

? ? ? ? // 鼠標坐標移動

? ? ? ? _painter.updatePoint(event.localPosition.dx);

? ? ? },

? ? ? child: RepaintBoundary(

? ? ? ? //底層光圈繪制層

? ? ? ? child: CustomPaint(

? ? ? ? ? painter: _painter,

? ? ? ? ? child: widget.child,

? ? ? ? ),

? ? ? ),

);

內(nèi)部更新鼠標的坐標和進出狀態(tài)。

TopMenuShadowPainter extends ChangeNotifier implements CustomPainter

// 使用方法刷新鼠標的進入離開狀態(tài) 以及 鼠標的 坐標 =-=

// notifyListeners 觸發(fā)的時候,沒有用RepaintBoundary,會導致其他部分組件重繪。

/// 鼠標進入

? bool mouseEnter = false;

? // 鼠標的坐標點

? double mouseX = 0;

? /// 更新

? void updateEnter(bool newMouseEnter) {

? ? mouseEnter = newMouseEnter;

? ? //debugPrint("鼠標進入退出:$mouseEnter");

? ? notifyListeners();

? }

? /// 更新

? void updatePoint(double newMouseX) {

? ? mouseX = newMouseX;

? ? notifyListeners();

? }

重新計算繪制的圓位置。

// 計算鼠標的坐標 產(chǎn)生圓的位置

// 圓起點的X坐標 計算方式 = 鼠標的X軸坐標(鼠標是居中位置) 減去 圓的一半

double x =? mouseX - radius / 2,

// 圓起點的Y坐標 計算方式 = 繪圖Canvs的高度的一半

// 向下偏移 10px(使光暈更貼底,這個值也可以不加或者更大)

double y =? size.height / 2 + 10,

// 繪制圓

canvas.drawOval(

? ? Rect.fromLTWH(

? ? ? ? mouseX - radius? / 2,

? ? ? ? size.height / 2 + 10,

? ? ? ? radius,

? ? ? ? radius,

? ? ),

? ? paint,

);

這時候我們設(shè)置一下裁剪區(qū)域,

// 從0,0坐標 擴展到畫板尺寸size的Rect區(qū)域

canvas.clipRect(Offset.zero? & size);

調(diào)整一下裁剪區(qū)域

// 裁剪區(qū)域 向左移動 圓的大小 向右也移動圓的的大小,radius

Rect clipRect = Rect.fromLTRB(- radius, 0, size.width + radius, size.height);

canvas.clipRect(clipRect);


接下來,再給光暈增加一個高斯模糊的效果。

var paint = Paint()

? ? ? // 背景漸變刷子

? ? ? ..shader = gradient

? ? ? // 模糊效果

? ? ? ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 20);

// 繪制底部的金線

canvas.drawLine(

? ? ? Offset(mouseX - 50, size.height),

? ? ? Offset(mouseX + 50, size.height),

? ? ? Paint()

? ? ? ? ..strokeCap = StrokeCap.round

? ? ? ? // 從 中間向兩邊 漸變的背景刷

? ? ? ? ..shader = lineColor

? ? ? ? ..strokeWidth = 1.5,

? ? );



最終效果
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容