前端悬浮窗效果_Flutter自绘组件:微信悬浮窗(一)
? 看微信公眾號(hào)的時(shí)候時(shí)常會(huì)想退出去回復(fù)消息,但又不想放棄已經(jīng)閱讀一半的文章,因?yàn)榛貜?fù)信息后再?gòu)墓娞?hào)找到該篇文章之間有不必要的時(shí)間花費(fèi),微信懸浮窗的出現(xiàn)解決了這個(gè)煩惱,回復(fù)完消息之后只需要點(diǎn)擊懸浮窗就可以回到之前在閱讀的文章中。在對(duì)比多篇公眾號(hào)文章的時(shí)候懸浮窗也使得在不同文章之間的切換更方便。懸浮窗的出現(xiàn)主要是為了省去用戶在不同頁(yè)面之間頻繁切換時(shí)不必要的時(shí)間和精力的開銷,這個(gè)組件小細(xì)節(jié)比較多,完整復(fù)刻估計(jì)需要分成三或四部分來講,這篇文章主要講述的是懸浮窗在點(diǎn)擊前的按鈕形態(tài)的實(shí)現(xiàn),下一篇文章講解按鈕在不同形態(tài)之間切換,動(dòng)起來。
先上最終效果對(duì)比圖:
實(shí)現(xiàn)思路
通過觀察可得,懸浮按鈕處于邊緣的時(shí)候是屬于不規(guī)則形狀。屬于官方的UI庫(kù)中是不存在的不規(guī)則圖形,也無法通過組合已有組件實(shí)現(xiàn)。這時(shí)候就需要用到Flutter提供的CustomPaint和CustomPainter來實(shí)現(xiàn)自繪組件,但具體的繪制工作是由Canvas類和Paint類進(jìn)行的。
按鈕圖解
對(duì)微信的懸浮按鈕進(jìn)行分析后,發(fā)現(xiàn)微信懸浮按鈕主要有三種形態(tài):左邊緣按鈕形態(tài),中心按鈕形態(tài),和右邊緣按鈕形態(tài),而每一個(gè)形態(tài)在按下的時(shí)候會(huì)有一個(gè)陰影,表示已選中,松開的時(shí)候陰影消失,表示未選中。具體形態(tài)圖解如下(按畫布大小為50x50設(shè)計(jì)):
圖解非一步到位,只是對(duì)原組件進(jìn)行分析后大概畫一下,在繪圖的過程中再進(jìn)行一些細(xì)微的調(diào)整達(dá)到較好的視覺效果(并非專業(yè)UI設(shè)計(jì)師,隨便畫畫)。進(jìn)行分析圖解后,便可以著手對(duì)每一個(gè)形態(tài)進(jìn)行繪制了。
使用到的類
在著手開始繪制前,我們需要了解我們繪制使用到的幾個(gè)類:
CustomPaint
構(gòu)造函數(shù)如下:
CustomPaint({Key key,
this.painter, //背景畫筆
this.foregroundPainter, //前景畫筆
this.size = Size.zero, //畫布即繪制區(qū)域的大小
this.isComplex = false, //是否為復(fù)雜繪制,若是Flutter則會(huì)啟用一些緩存策略減少繪制的開銷
this.willChange = false, //和isComplex配合使用,當(dāng)啟用緩存,表示下一幀中繪制是否會(huì)改變
Widget child, //子節(jié)點(diǎn),可以為空
})
CustomPainter
CustomPainter中定義的虛函數(shù)paint,主要的繪制工作都是在這個(gè)函數(shù)中完成,主要定義如下:
void paint(Canvas canvas, Size size);
size: 表示繪制區(qū)域的大小,傳遞自CustomPaint中的size
canvas: 畫布,其內(nèi)封裝了大量的繪制方法,此處列舉本文中用到方法:?
| drawCircle | 繪制圓形 |
| drawPath | 繪制路徑 |
| drawImageRect | 根據(jù)給出的圖片及原矩形(src)和目標(biāo)矩形(dst)繪制圖片 |
| clipRRect | 根據(jù)給出的圓角矩形對(duì)畫布進(jìn)行剪裁(超出區(qū)域不繪制) |
| drawShadow | 繪制陰影 |
| drawColor | 根據(jù)模式繪制顏色(本文用于繪制圖片背景填充顏色) |
Paint
如果說Canvas是畫布,那么Paint就是畫筆。Canvas中封裝的很多繪制方法都需要一個(gè)畫筆參數(shù)去進(jìn)行繪制。畫筆Paint中定義了一些畫筆的基本屬性,如畫筆寬度,畫筆顏色,筆觸類型等,例子如下:
Paint _paint = Paint()..color = Colors.blue //畫筆顏色,此處為藍(lán)色
..strokeCap = StrokeCap.round //畫筆筆觸類型
..isAntiAlias = true //是否啟動(dòng)抗鋸齒
..blendMode = BlendMode.exclusion //顏色混合模式
..style = PaintingStyle.fill //繪畫風(fēng)格,默認(rèn)為填充
..colorFilter = ColorFilter.mode(Colors.blueAccent,
BlendMode.exclusion) //顏色渲染模式
..maskFilter = MaskFilter.blur(BlurStyle.inner, 3.0) //模糊遮罩效果
..filterQuality = FilterQuality.high //顏色渲染模式的質(zhì)量
..strokeWidth = 15.0; //畫筆的寬度
我們?cè)趯?shí)際使用中根據(jù)需要去選擇相應(yīng)的屬性,并不需要全部初始化。
Path
路徑,使用drawPath中繪制不規(guī)則圖形可以通過函數(shù)或者點(diǎn)間連線、曲線等表示。本文用到的方法如下:
| moveTo | 將路徑起點(diǎn)移動(dòng)到指定位置 |
| lineTo | 從當(dāng)前位置連線到指定位置 |
| arcTo | 曲線 |
開始繪制
對(duì)于工程其他的文件和布局不做討論,主要討論如何實(shí)現(xiàn)繼承CustomPainter實(shí)現(xiàn)paint方法的繪制,對(duì)于如何使用這個(gè)Painter,可以參考《Flutter實(shí)戰(zhàn)》(https://book.flutterchina.club/chapter10/custom_paint.html)。
邊緣按鈕的繪制
由于左右邊緣按鈕的圖形繪制方法類似,因此我們主要討論 左邊緣按鈕 的實(shí)現(xiàn)。由圖解可知左邊緣按鈕是不規(guī)則的形狀。我們的畫布的尺寸為50x50,我們可以把這個(gè)形狀看作一個(gè)圓形和一個(gè)正方形的重合,這是一種思路。
但我寫的是另一種思路:看作是三條直線和一段圓弧所組成路徑,以邊緣按鈕內(nèi)層的具體代碼實(shí)現(xiàn)為例:
//edgePath: 按鈕外邊緣路徑,黑色線條var edgePath = new Path() ..moveTo(size.width / 2, size.height); //移動(dòng)去x軸中點(diǎn)(25,0)
edgePath.lineTo(0.0, size.height); //第一條直線
edgePath.lineTo(0.0, 0.0);//第二條直線
edgePath.lineTo(size.width / 2,0.0);//第三條直線
//圓弧在圓心在(25,25),半徑為25的圓上
Rect rect1 = Rect.fromCircle(center:Offset(size.width / 2,size.height / 2),radius: 25);
edgePath.arcTo(rect1,pi * 1.5,pi,true); //右半圓,從 3/2 Π處起步 經(jīng)過Π 個(gè)角度。
var paint = new Paint()
..isAntiAlias = true //畫曲線時(shí)抗鋸齒看起來更圓潤(rùn)
..strokeWidth = 0.75 //
..strokeCap = StrokeCap.round
..maskFilter = MaskFilter.blur(BlurStyle.normal,0.25) //線條模糊
..style = PaintingStyle.stroke //線條,不填充
..color = Color.fromRGBO(0xCF, 0xCF, 0xCF, 1);
canvas.drawPath(edgePath, paint);//繪制路徑
效果如下:
同理繪制內(nèi)層陰影部分為:
//繪制按鈕內(nèi)層var paint = Paint()
..isAntiAlias = false
..style = PaintingStyle.fill
..color = Color.fromRGBO(0xF3, 0xF3, 0xF3, 0.9);
//..color = Color.fromRGBO(0xDA,0xDA,0xDA,0.9);
//path : 按鈕內(nèi)邊緣路徑
var path = new Path() ..moveTo(size.width / 2 , size.height - 1.5);
path.lineTo(0.0, size.height - 1.5);
path.lineTo(0.0, 1.5);
path.lineTo(size.width / 2 ,1.5);
Rect rect = Rect.fromCircle(center:Offset(size.width / 2,size.height / 2),radius: 23.5);
path.arcTo(rect,pi * 1.5,pi,true);
canvas.drawPath(path, paint);
合起來效果為:
最重點(diǎn)是中間圖標(biāo)logo的繪制。?
此處有踩坑點(diǎn),canvas中繪制圖片的方法
void drawImageRect(Image image, Rect src, Rect dst, Paint paint)?
參數(shù)image的類型Image并不是我們?nèi)粘J褂玫降腎mage類型,而是封裝在ui庫(kù)中的一個(gè)官方私有類,只能通過監(jiān)聽圖片流返回一個(gè)Future,再通過FutureBuilder把image傳遞給CustomPainter的子類。詳情看 ui.Image加載探索(https://cloud.tencent.com/developer/article/1622733)這篇博客。具體內(nèi)容在下一篇文章中再解釋,本文暫時(shí)不涉及,不需要理解。
? 先講繪制。對(duì)于中間logo的繪制,我們將原圖片的大小輸出為目標(biāo)區(qū)域的大小。其次,區(qū)域?yàn)榫匦?#xff0c;而我們需要輸出的logo是圓形區(qū)域,因此需要對(duì)畫布進(jìn)行剪裁,示意圖如下:
具體實(shí)現(xiàn)代碼如下:
//繪制中間圖標(biāo)paint = new Paint();
canvas.save(); //剪裁前保存圖層
RRect imageRRect = RRect.fromRectAndRadius(Rect.fromLTWH(size.width / 2 - 17.5,size.width / 2 - 17.5, 35, 35),Radius.circular(17.5));
canvas.clipRRect(imageRRect);//圖片為圓形,圓形剪裁
canvas.drawColor(Colors.white, BlendMode.srcOver); //設(shè)置填充顏色為白色
Rect srcRect = Rect.fromLTWH(0.0, 0.0, buttonImage.width.toDouble(), buttonImage.height.toDouble());
Rect dstRect = Rect.fromLTWH(size.width / 2 - 17.5, size.height / 2 - 17.5, 35, 35);
canvas.drawImageRect(buttonImage, srcRect, dstRect, paint);
canvas.restore();//圖片繪制完畢恢復(fù)圖層
完整的左邊緣按鈕繪制代碼:
//繪制左邊界懸浮按鈕void paintLeftEdgeButton(Canvas canvas,Size size,bool isPress)
{
//繪制按鈕內(nèi)層
var paint = Paint()
..isAntiAlias = false
..style = PaintingStyle.fill
..color = Color.fromRGBO(0xF3, 0xF3, 0xF3, 0.9);
//..color = Color.fromRGBO(0xDA,0xDA,0xDA,0.9);
//path : 按鈕內(nèi)邊緣路徑
var path = new Path() ..moveTo(size.width / 2 , size.height - 1.5);
path.lineTo(0.0, size.height - 1.5);
path.lineTo(0.0, 1.5);
path.lineTo(size.width / 2 ,1.5);
Rect rect = Rect.fromCircle(center:Offset(size.width / 2,size.height / 2),radius: 23.5);
path.arcTo(rect,pi * 1.5,pi,true);
canvas.drawPath(path, paint);
//edgePath: 按鈕外邊緣路徑,黑色線條
var edgePath = new Path() ..moveTo(size.width / 2, size.height);
edgePath.lineTo(0.0, size.height);
edgePath.lineTo(0.0, 0.0);
edgePath.lineTo(size.width / 2,0.0);
Rect rect1 = Rect.fromCircle(center:Offset(size.width / 2,size.height / 2),radius: 25);
edgePath.arcTo(rect1,pi * 1.5,pi,true);
paint
..isAntiAlias = true
..strokeWidth = 0.75
..strokeCap = StrokeCap.round
..maskFilter = MaskFilter.blur(BlurStyle.normal,0.25) //線條模糊
..style = PaintingStyle.stroke
..color = Color.fromRGBO(0xCF, 0xCF, 0xCF, 1);
canvas.drawPath(edgePath, paint);
//按下則畫陰影,表示選中
if(isPress) canvas.drawShadow(edgePath, Color.fromRGBO(0xDA, 0xDA, 0xDA, 0.3), 0, false);
//繪制中間圖標(biāo)
paint = new Paint();
canvas.save(); //剪裁前保存圖層
RRect imageRRect = RRect.fromRectAndRadius(Rect.fromLTWH(size.width / 2 - 17.5,size.width / 2 - 17.5, 35, 35),Radius.circular(17.5));
canvas.clipRRect(imageRRect);//圖片為圓形,圓形剪裁
canvas.drawColor(Colors.white, BlendMode.srcOver); //設(shè)置填充顏色為白色
Rect srcRect = Rect.fromLTWH(0.0, 0.0, buttonImage.width.toDouble(), buttonImage.height.toDouble());
Rect dstRect = Rect.fromLTWH(size.width / 2 - 17.5, size.height / 2 - 17.5, 35, 35);
canvas.drawImageRect(buttonImage, srcRect, dstRect, paint);
canvas.restore();//圖片繪制完畢恢復(fù)圖層
}
效果圖:
同理,右邊緣按鈕的繪制為:
//繪制右邊界按鈕void paintRightEdgeButton(Canvas canvas,Size size){
var paint = Paint()
..isAntiAlias = false
..style = PaintingStyle.fill
..color = Color.fromRGBO(0xF3, 0xF3, 0xF3, 0.9);
var path = Path() ..moveTo(size.width / 2, 1.5);
path.lineTo(size.width,1.5);
path.lineTo(size.width, size.height - 1.5);
path.lineTo(size.width / 2, size.height - 1.5);
Rect rect = Rect.fromCircle(center: Offset(size.width / 2,size.height / 2),radius: 23.5);
path.arcTo(rect, pi * 0.5, pi, true);
canvas.drawPath(path, paint);//繪制
//edgePath: 按鈕外邊緣路徑
var edgePath = Path() ..moveTo(size.width / 2,0.0);
edgePath.lineTo(size.width,0.0);
edgePath.lineTo(size.width, size.height);
edgePath.lineTo(size.width / 2, size.height);
Rect edgeRect = Rect.fromCircle(center: Offset(size.width / 2,size.height / 2),radius: 25);
edgePath.arcTo(edgeRect, pi * 0.5, pi, true);
paint
..isAntiAlias = true
..strokeWidth = 0.75
..strokeCap = StrokeCap.round
..maskFilter = MaskFilter.blur(BlurStyle.normal,0.25)
..style = PaintingStyle.stroke
..color = Color.fromRGBO(0xCF, 0xCF, 0xCF, 1);
canvas.drawPath(edgePath, paint);
//如果按下則繪制陰影
if(isPress)
canvas.drawShadow(path, Color.fromRGBO(0xDA, 0xDA, 0xDA, 0.3), 0, false);
//繪制中間圖標(biāo)
paint = new Paint();
canvas.save(); //剪裁前保存圖層
RRect imageRRect = RRect.fromRectAndRadius(Rect.fromLTWH(size.width / 2 - 17.5,size.width / 2 - 17.5, 35, 35),Radius.circular(17.5));
canvas.clipRRect(imageRRect);//圖片為圓形,圓形剪裁
canvas.drawColor(Colors.white, BlendMode.srcOver); //設(shè)置填充顏色為白色
Rect srcRect = Rect.fromLTWH(0.0, 0.0, buttonImage.width.toDouble(), buttonImage.height.toDouble());
Rect dstRect = Rect.fromLTWH(size.width / 2 - 17.5, size.height / 2 - 17.5, 35, 35);
canvas.drawImageRect(buttonImage, srcRect, dstRect, paint);
canvas.restore();//圖片繪制完畢恢復(fù)圖層
}
中心按鈕的繪制
中心按鈕為規(guī)則的兩個(gè)圓型+中間的logo,因此繪制很簡(jiǎn)單,具體代碼如下:
//繪制中心按鈕void paintCenterButton(Canvas canvas,Size size)
{
//繪制按鈕內(nèi)層
var paint = new Paint()
..isAntiAlias = false
..style = PaintingStyle.fill
..color = Color.fromRGBO(0xF3, 0xF3, 0xF3, 0.9);
canvas.drawCircle(Offset(size.width / 2,size.height / 2), 23.5, paint);
//繪制按鈕外層邊線
paint
..isAntiAlias = true
..style = PaintingStyle.stroke
..strokeWidth = 0.75
..maskFilter = MaskFilter.blur(BlurStyle.normal,0.25)
..color = Color.fromRGBO(0xCF, 0xCF, 0xCF, 1);
canvas.drawCircle(Offset(size.width / 2,size.height / 2), 25, paint);
//如果按下則繪制陰影
if(isPress){
var circleRect = Rect.fromCircle(center: Offset(size.width / 2,size.height / 2),radius: 25);
var circlePath = new Path() ..moveTo(size.width / 2, size.height / 2);
circlePath.arcTo(circleRect, 0, 2 * 3.14, true);
canvas.drawShadow(circlePath, Color.fromRGBO(0xCF, 0xCF, 0xCF, 0.3), 0.5, false);
}
//繪制中間圖標(biāo)
paint = new Paint();
canvas.save(); //圖片剪裁前保存圖層
RRect imageRRect = RRect.fromRectAndRadius(Rect.fromLTWH(size.width / 2 - 17.5,size.width / 2 - 17.5, 35, 35),Radius.circular(35));
canvas.clipRRect(imageRRect);//圖片為圓形,圓形剪裁
canvas.drawColor(Colors.white, BlendMode.srcOver); //設(shè)置填充顏色為白色
Rect srcRect = Rect.fromLTWH(0.0, 0.0, buttonImage.width.toDouble(), buttonImage.height.toDouble());
Rect dstRect = Rect.fromLTWH(size.width / 2 - 17.5, size.height / 2 - 17.5, 35, 35);
canvas.drawImageRect(buttonImage, srcRect, dstRect, paint);
canvas.restore();//恢復(fù)剪裁前的圖層
}
如何選擇繪制
細(xì)心的你可能發(fā)現(xiàn)了一個(gè)變量isPress,他代表著什么呢,代表著的是否手指按下懸浮按鈕,若按下則繪制表示選中的陰影部分。當(dāng)你有了邊緣按鈕和中心按鈕,組件的繪制是一幀一幀的畫面,paint(Canvas canvas,Size size)繪制的是一幀的畫面,而如何判斷這一幀時(shí)選擇繪制左邊緣按鈕還是右邊緣按鈕,中心按鈕?因此,在FloatingButtonPainter中還應(yīng)該定義幾個(gè)判斷的變量。
FloatingButtonPainter({Key key,
@required this.isLeft,
@required this.isEdge,
@required this.isPress,
@required this.buttonImage
});
//按鈕是否在屏幕左側(cè),屏幕寬度中線為準(zhǔn)
final bool isLeft;
//按鈕是否在屏幕邊界,左/右邊界
final bool isEdge;
//按鈕是否被按下
final bool isPress;
//內(nèi)按鈕logo ui.image
final ui.Image buttonImage;
在paint方法中進(jìn)行判斷
@overridevoid paint(Canvas canvas, Size size) {// TODO: implement paint //按鈕是否在邊緣 if(isEdge){//按鈕在屏幕左邊或右邊 if(isLeft)paintLeftEdgeButton(canvas, size);//繪制左邊緣按鈕 else paintRightEdgeButton(canvas, size);//繪制右邊緣按鈕 }else{
paintCenterButton(canvas, size);//繪制中心按鈕 }
}
完整代碼
FloatingButtonPainter完整代碼:
import 'package:flutter/material.dart';import 'dart:math';
import 'dart:ui' as ui;
class FloatingButtonPainter extends CustomPainter
{
FloatingButtonPainter({
Key key,
@required this.isLeft,
@required this.isEdge,
@required this.isPress,
@required this.buttonImage
});
//按鈕是否在屏幕左側(cè),屏幕寬度 / 2
final bool isLeft;
//按鈕是否在屏幕邊界,左/右邊界
final bool isEdge;
//按鈕是否被按下
final bool isPress;
//內(nèi)按鈕圖片 ui.image
final ui.Image buttonImage;
@override
void paint(Canvas canvas, Size size) {
// TODO: implement paint
if(isEdge){
if(isLeft)
paintLeftEdgeButton(canvas, size);//繪制左邊緣按鈕
else
paintRightEdgeButton(canvas, size);//繪制右邊緣按鈕
}
else{
paintCenterButton(canvas, size); //繪制中心按鈕
}
}
//繪制左邊界懸浮按鈕
void paintLeftEdgeButton(Canvas canvas,Size size)
{
//繪制按鈕內(nèi)層
var paint = Paint()
..isAntiAlias = false
..style = PaintingStyle.fill
..color = Color.fromRGBO(0xF3, 0xF3, 0xF3, 0.9);
//..color = Color.fromRGBO(0xDA,0xDA,0xDA,0.9);
//path : 按鈕內(nèi)邊緣路徑
var path = new Path() ..moveTo(size.width / 2 , size.height - 1.5);
path.lineTo(0.0, size.height - 1.5);
path.lineTo(0.0, 1.5);
path.lineTo(size.width / 2 ,1.5);
Rect rect = Rect.fromCircle(center:Offset(size.width / 2,size.height / 2),radius: 23.5);
path.arcTo(rect,pi * 1.5,pi,true);
canvas.drawPath(path, paint);
//edgePath: 按鈕外邊緣路徑,黑色線條
var edgePath = new Path() ..moveTo(size.width / 2, size.height);
edgePath.lineTo(0.0, size.height);
edgePath.lineTo(0.0, 0.0);
edgePath.lineTo(size.width / 2,0.0);
Rect rect1 = Rect.fromCircle(center:Offset(size.width / 2,size.height / 2),radius: 25);
edgePath.arcTo(rect1,pi * 1.5,pi,true);
paint
..isAntiAlias = true
..strokeWidth = 0.75
..strokeCap = StrokeCap.round
..maskFilter = MaskFilter.blur(BlurStyle.normal,0.25) //線條模糊
..style = PaintingStyle.stroke
..color = Color.fromRGBO(0xCF, 0xCF, 0xCF, 1);
canvas.drawPath(edgePath, paint);
//按下則畫陰影,表示選中
if(isPress) canvas.drawShadow(edgePath, Color.fromRGBO(0xDA, 0xDA, 0xDA, 0.3), 0, false);
//繪制中間圖標(biāo)
paint = new Paint();
canvas.save(); //剪裁前保存圖層
RRect imageRRect = RRect.fromRectAndRadius(Rect.fromLTWH(size.width / 2 - 17.5,size.width / 2 - 17.5, 35, 35),Radius.circular(17.5));
canvas.clipRRect(imageRRect);//圖片為圓形,圓形剪裁
canvas.drawColor(Colors.white, BlendMode.srcOver); //設(shè)置填充顏色為白色
Rect srcRect = Rect.fromLTWH(0.0, 0.0, buttonImage.width.toDouble(), buttonImage.height.toDouble());
Rect dstRect = Rect.fromLTWH(size.width / 2 - 17.5, size.height / 2 - 17.5, 35, 35);
canvas.drawImageRect(buttonImage, srcRect, dstRect, paint);
canvas.restore();//圖片繪制完畢恢復(fù)圖層
}
//繪制右邊界按鈕
void paintRightEdgeButton(Canvas canvas,Size size){
var paint = Paint()
..isAntiAlias = false
..style = PaintingStyle.fill
..color = Color.fromRGBO(0xF3, 0xF3, 0xF3, 0.9);
var path = Path() ..moveTo(size.width / 2, 1.5);
path.lineTo(size.width,1.5);
path.lineTo(size.width, size.height - 1.5);
path.lineTo(size.width / 2, size.height - 1.5);
Rect rect = Rect.fromCircle(center: Offset(size.width / 2,size.height / 2),radius: 23.5);
path.arcTo(rect, pi * 0.5, pi, true);
canvas.drawPath(path, paint);//繪制
//edgePath: 按鈕外邊緣路徑
var edgePath = Path() ..moveTo(size.width / 2,0.0);
edgePath.lineTo(size.width,0.0);
edgePath.lineTo(size.width, size.height);
edgePath.lineTo(size.width / 2, size.height);
Rect edgeRect = Rect.fromCircle(center: Offset(size.width / 2,size.height / 2),radius: 25);
edgePath.arcTo(edgeRect, pi * 0.5, pi, true);
paint
..isAntiAlias = true
..strokeWidth = 0.75
..strokeCap = StrokeCap.round
..maskFilter = MaskFilter.blur(BlurStyle.normal,0.25)
..style = PaintingStyle.stroke
..color = Color.fromRGBO(0xCF, 0xCF, 0xCF, 1);
canvas.drawPath(edgePath, paint);
//如果按下則繪制陰影
if(isPress)
canvas.drawShadow(path, Color.fromRGBO(0xDA, 0xDA, 0xDA, 0.3), 0, false);
//繪制中間圖標(biāo)
paint = new Paint();
canvas.save(); //剪裁前保存圖層
RRect imageRRect = RRect.fromRectAndRadius(Rect.fromLTWH(size.width / 2 - 17.5,size.width / 2 - 17.5, 35, 35),Radius.circular(17.5));
canvas.clipRRect(imageRRect);//圖片為圓形,圓形剪裁
canvas.drawColor(Colors.white, BlendMode.srcOver); //設(shè)置填充顏色為白色
Rect srcRect = Rect.fromLTWH(0.0, 0.0, buttonImage.width.toDouble(), buttonImage.height.toDouble());
Rect dstRect = Rect.fromLTWH(size.width / 2 - 17.5, size.height / 2 - 17.5, 35, 35);
canvas.drawImageRect(buttonImage, srcRect, dstRect, paint);
canvas.restore();//圖片繪制完畢恢復(fù)圖層
}
//繪制中心按鈕
void paintCenterButton(Canvas canvas,Size size)
{
//繪制按鈕內(nèi)層
var paint = new Paint()
..isAntiAlias = false
..style = PaintingStyle.fill
..color = Color.fromRGBO(0xF3, 0xF3, 0xF3, 0.9);
canvas.drawCircle(Offset(size.width / 2,size.height / 2), 23.5, paint);
//繪制按鈕外層邊線
paint
..isAntiAlias = true
..style = PaintingStyle.stroke
..strokeWidth = 0.75
..maskFilter = MaskFilter.blur(BlurStyle.normal,0.25)
..color = Color.fromRGBO(0xCF, 0xCF, 0xCF, 1);
canvas.drawCircle(Offset(size.width / 2,size.height / 2), 25, paint);
//如果按下則繪制陰影
if(isPress){
var circleRect = Rect.fromCircle(center: Offset(size.width / 2,size.height / 2),radius: 25);
var circlePath = new Path() ..moveTo(size.width / 2, size.height / 2);
circlePath.arcTo(circleRect, 0, 2 * 3.14, true); //使用pi會(huì)出錯(cuò)。
canvas.drawShadow(circlePath, Color.fromRGBO(0xCF, 0xCF, 0xCF, 0.3), 0.5, false);
}
//繪制中間圖標(biāo)
paint = new Paint();
canvas.save(); //圖片剪裁前保存圖層
RRect imageRRect = RRect.fromRectAndRadius(Rect.fromLTWH(size.width / 2 - 17.5,size.width / 2 - 17.5, 35, 35),Radius.circular(35));
canvas.clipRRect(imageRRect);//圖片為圓形,圓形剪裁
canvas.drawColor(Colors.white, BlendMode.srcOver); //設(shè)置填充顏色為白色
Rect srcRect = Rect.fromLTWH(0.0, 0.0, buttonImage.width.toDouble(), buttonImage.height.toDouble());
Rect dstRect = Rect.fromLTWH(size.width / 2 - 17.5, size.height / 2 - 17.5, 35, 35);
canvas.drawImageRect(buttonImage, srcRect, dstRect, paint);
canvas.restore();//恢復(fù)剪裁前的圖層
}
//測(cè)試?yán)L制
void paintTest(Canvas canvas,Size size){
var paint = Paint()
..isAntiAlias = false
..style = PaintingStyle.fill
..color = Color.fromRGBO(0xF3, 0xF3, 0xF3, 0.9);
var path = Path() ..moveTo(size.width / 2, 1.5);
path.lineTo(size.width,1.5);
path.lineTo(size.width, size.height - 1.5);
path.lineTo(size.width / 2, size.height - 1.5);
Rect rect = Rect.fromCircle(center: Offset(size.width / 2,size.height / 2),radius: 23.5);
path.arcTo(rect, pi * 0.5, pi, true);
canvas.drawPath(path, paint);//繪制
//edgePath: 按鈕外邊緣路徑
var edgePath = Path() ..moveTo(size.width / 2,0.0);
edgePath.lineTo(size.width,0.0);
edgePath.lineTo(size.width, size.height);
edgePath.lineTo(size.width / 2, size.height);
Rect edgeRect = Rect.fromCircle(center: Offset(size.width / 2,size.height / 2),radius: 23.5);
edgePath.arcTo(edgeRect, pi * 0.5, pi, true);
paint
..isAntiAlias = true
..strokeWidth = 0.75
..strokeCap = StrokeCap.round
..maskFilter = MaskFilter.blur(BlurStyle.normal,0.25)
..style = PaintingStyle.stroke
..color = Color.fromRGBO(0xCF, 0xCF, 0xCF, 1);
canvas.drawPath(edgePath, paint);
//繪制中間圖標(biāo)
paint = new Paint();
RRect imageRRect = RRect.fromRectAndRadius(Rect.fromLTWH(size.width / 2 - 20,size.width / 2 - 20, 40, 40),Radius.circular(20));
canvas.clipRRect(imageRRect);//圖片為圓形,圓形剪裁
canvas.drawColor(Colors.white, BlendMode.srcOver); //設(shè)置填充顏色為白色
Rect srcRect = Rect.fromLTWH(0.0, 0.0, buttonImage.width.toDouble(), buttonImage.height.toDouble());
Rect dstRect = Rect.fromLTWH(size.width / 2 - 17.5, size.height / 2 - 17.5, 35, 35);
canvas.drawImageRect(buttonImage, srcRect, dstRect, paint);
//如果按下則繪制陰影
if(isPress)
canvas.drawShadow(path, Color.fromRGBO(0xDA, 0xDA, 0xDA, 0.3), 0, false);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
return true;
}
}
總結(jié)
寫這個(gè)的篇幅比我料想中需要的篇幅還多,目前只實(shí)現(xiàn)各種形態(tài)繪制,實(shí)現(xiàn)FloatingButtonPainter類。但如何使用這個(gè)類,讓它在各個(gè)形態(tài)之間切換,讓它動(dòng)起來,實(shí)現(xiàn)最終效果,在下一篇文章中繼續(xù),有興趣的可以關(guān)注,點(diǎn)贊,點(diǎn)個(gè)在看。
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的前端悬浮窗效果_Flutter自绘组件:微信悬浮窗(一)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【学习笔记】MOOC 数学文化赏析 笔记
- 下一篇: 【LeetCode笔记】104. 二叉树