不知你注意或是沒注意組件的
shape
屬性,
可能你以爲沒啥用,或說一帶而過,今天就來掰扯一下這個ShapeBorder
對象
它的強大遠遠超出你的想象,不過記住:Path 在手,天下我有
,先看下效果編程
打洞 | - |
---|---|
header 1 | header 2 |
---|---|
Material
Card
FloatingActionButton
RawMaterialButton
MaterialButton
|----FlatButton
|----RaisedButton
|----OutlineButton
...
ClipPath
複製代碼
shape
屬性 對應的幾類對象shape 對應 ShapeBorder 對象 , 它的子類以下:
ShapeBorder [abstract]
|---BoxBorder [abstract]
|---BorderDirectional
|---Border
|---RoundedRectangleBorder
|---ContinuousRectangleBorder
|---CircleBorder
|---InputBorder [abstract]
|---OutlineInputBorder
|---UnderlineInputBorder
複製代碼
估計這個組件用的人很少,可是翻看一下源碼
Card
,RawMaterialButton
及子族
它們的shape屬性都來自於Material
組件,能夠說是它是shape的本宗,因此擒賊先擒王
下面是一個Material組件基本使用的demo:canvas
Widget _buildNoShape() {
return Material(
color: Colors.orangeAccent,
elevation: 10,
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.all(10),
height: 80,
child: Text(
"No Shape",
style: TextStyle(color: Colors.white, fontSize: 20),
),
),
);
}
複製代碼
BoxBorder$BorderDirectional 與 BoxBorder$Border
BoxBorder主要掌管邊線方面的事,自身是abstract,不能直接用
BorderDirectional 經過【top】【bottom】【start】【end】
分別控制上下左右的邊線
邊線對象BorderSide
bash
Widget _buildBorderDirectional() {
return Material(
color: Colors.orangeAccent,
shape: BorderDirectional(
top: BorderSide(
color: Colors.white,
),
start: BorderSide(
color: Colors.black,
width: 15
),
bottom: BorderSide(
color: Colors.white,
)
),
elevation: 2,
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.all(10),
height: 80,
child: Text(
"BorderDirectional",
style: TextStyle(color: Colors.white, fontSize: 20),
),
),
);
}
複製代碼
Border 經過
【top】【bottom】【left】【right】
分別控制上下左右的邊線
本質上和BorderDirectional並無什麼區別微信
Widget _buildBorder() {
return Material(
color: Colors.orangeAccent,
shape: Border(
top: BorderSide(width: 5.0, color: Color(0xFFFFDFDFDF)),
left: BorderSide(width: 5.0, color: Color(0xFFFFDFDFDF)),
right: BorderSide(width: 5.0, color: Color(0xFFFF7F7F7F)),
bottom: BorderSide(width: 5.0, color: Color(0xFFFF7F7F7F)),
),
elevation: 10,
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.all(10),
height: 80,
child: Text(
"Border",
style: TextStyle(color: Colors.white, fontSize: 20),
),
),
);
}
複製代碼
CircleBorder
CircleBorder 會以min(with,height) 爲直徑,裁處一個圓形框架
Widget _buildCircleBorder() {
return Material(
color: Colors.orangeAccent,
shape: CircleBorder(
side: BorderSide(width: 2.0, color: Color(0xFFFFDFDFDF)),
),
elevation: 2,
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.all(10),
height: 80,
child: Text(
"Circle",
style: TextStyle(color: Colors.white, fontSize: 20),
),
),
);
}
複製代碼
RoundedRectangleBorder
和ContinuousRectangleBorder
圓角類矩形ide
Widget _buildRoundedRectangleBorder() {
return Material(
color: Colors.orangeAccent,
shape: RoundedRectangleBorder(
side: BorderSide(width: 1.0, color: Colors.black),
borderRadius: BorderRadius.all(Radius.circular(15))),
elevation: 2,
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.all(10),
height: 80,
child: Text(
"RoundedRectangleBorder",
style: TextStyle(color: Colors.white, fontSize: 20),
),
),
);
}
複製代碼
Material _buildContinuousRectangleBorder() {
return Material(
color: Colors.orangeAccent,
elevation: 2,
shape: ContinuousRectangleBorder(
side: BorderSide.none,
borderRadius: BorderRadius.circular(40.0),
),
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.all(10),
height: 80,
child: Text(
"ContinuousRectangleBorder",
style: TextStyle(color: Colors.white, fontSize: 20),
),
),
);
}
複製代碼
OutlineInputBorder
和UnderlineInputBorder
經常使用與輸入框的邊線post
Material _buildOutlineInputBorder() {
return Material(
color: Colors.orangeAccent,
elevation: 2,
shape: OutlineInputBorder(
borderSide: BorderSide(width: 2.0, color: Colors.purple),
borderRadius: BorderRadius.circular(20.0),
),
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.all(10),
height: 80,
child: Text(
"OutlineInputBorder",
style: TextStyle(color: Colors.white, fontSize: 20),
),
),
);
}
Material _buildUnderlineInputBorder() {
return Material(
color: Colors.orangeAccent,
elevation: 2,
shape: UnderlineInputBorder(
borderSide: BorderSide(width: 5.0, color: Colors.blue),
borderRadius: BorderRadius.circular(20),
),
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.all(10),
height: 80,
child: Text(
"UnderlineInputBorder",
style: TextStyle(color: Colors.white, fontSize: 20),
),
),
);
}
複製代碼
這樣Flutter內置的形狀就over了,好了,引言結束,下面開始正題。ui
一共有五個抽象方法this
class SimpleShapeBoder extends ShapeBorder{
@override
EdgeInsetsGeometry get dimensions => null;
@override
Path getInnerPath(Rect rect, {TextDirection textDirection}) {
return null;
}
@override
Path getOuterPath(Rect rect, {TextDirection textDirection}) {
return null;
}
@override
void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) {
}
@override
ShapeBorder scale(double t) {
return null;
}
}
複製代碼
看到paint中的Canvas對象,心想:
又到裝13的機會了
先瞄一眼這個rect對象的信息:spa
@override
void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) {
print(rect.toString());
}
複製代碼
I/flutter ( 8697): Rect.fromLTRB(0.0, 0.0, 395.4, 80.0)
代表能夠直接拿到組件的區域,而後....隨心所欲吧
先畫個小圓以表敬意: 這表示你能夠經過shape屬性來在一個組件上畫任意的東西
若是有耐心畫幅清明上河圖也不成問題。paint是否是很是強大?
@override
void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) {
var paint = Paint()
..color = Colors.white
..strokeWidth = 2.0
..style = PaintingStyle.stroke
..strokeJoin = StrokeJoin.round;
var w = rect.width;
var h = rect.height;
canvas.drawCircle(Offset(0.3*h,0.23*h), 0.12*h, paint);
canvas.drawCircle(Offset(0.3*h,0.23*h), 0.06*h, paint..style=PaintingStyle.fill..color=Colors.black);
}
複製代碼
getOuterPath 返回一個Path對象,也就是形狀的裁剪,這個更厲害
先來看圓角怎麼切: 用path.addRRect
來添加一個圓角矩形,而後就出現效果了
@override
Path getOuterPath(Rect rect, {TextDirection textDirection}) {
var path = Path();
path.addRRect(RRect.fromRectAndRadius(rect, Radius.circular(10)));
return path;
}
複製代碼
getOuterPath方法
來打個洞吧。 下面根據位置計算出一個圓形路徑
將圓角矩形和圓形兩個路徑疊加,最後使用奇偶環繞
來處理路徑
關於路徑Path的環繞規則已經其餘的東西,能夠看之前寫的Android的路徑文章
Android關於Path你所知道的和不知道的一切-填充的環繞原則
@override
Path getOuterPath(Rect rect, {TextDirection textDirection}) {
var path = Path();
path.addRRect(RRect.fromRectAndRadius(rect, Radius.circular(10)));
var w = rect.width;
var h = rect.height;
var radius = 0.2*h;
var pl= 0.1*h;
var pt= 0.1*h;
var left = w - radius - pl;
var top = pt;
var right = left + radius;
var bottom = top + radius;
path.addOval(Rect.fromLTRB(left, top, right, bottom));
path.fillType = PathFillType.evenOdd;
return path;
}
複製代碼
就此來封裝一個打洞的形狀
HoleShapeBorder
,可指定洞的大小和偏移分率
這樣洞在組件之間就能夠隨意移動
打洞 | - |
---|---|
import 'package:flutter/material.dart';
/// create by 張風捷特烈 on 2020-03-06
/// contact me by email 1981462002@qq.com
/// 說明: 打個洞
/// offset 洞的偏移量分率 x,y 在 0~1 之間
/// size 洞的大小
class HoleShapeBorder extends ShapeBorder {
final Offset offset;
final double size;
HoleShapeBorder({this.offset=const Offset(0.1, 0.1), this.size=20});
@override
EdgeInsetsGeometry get dimensions => null;
@override
Path getInnerPath(Rect rect, {TextDirection textDirection}) {
var path = Path();
path.addRRect(RRect.fromRectAndRadius(rect, Radius.circular(5)));
return path;
}
@override
Path getOuterPath(Rect rect, {TextDirection textDirection}) {
var path = Path();
path.addRRect(RRect.fromRectAndRadius(rect, Radius.circular(10)));
var w = rect.width;
var h = rect.height;
var offsetXY = Offset( offset.dx*w,offset.dy*h);
var d = size;
_getHold(path, 1, d, offsetXY);
path.fillType = PathFillType.evenOdd;
return path;
}
_getHold(Path path, int count, double d, Offset offset) {
var left = offset.dx;
var top = offset.dy;
var right = left + d;
var bottom = top + d;
path.addOval(Rect.fromLTRB(left, top, right, bottom));
}
@override
void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) {
}
@override
ShapeBorder scale(double t) {
// TODO: implement scale
return null;
}
}
複製代碼
Material _buildHoleShapeBorder() {
return Material(
color: Colors.orangeAccent,
shape: HoleShapeBorder(
size: 20,
offset: Offset(0.05,0.1)
),
//英雄所見...
}
複製代碼
既然能打一個洞,那也能夠多打幾個洞
把相關的屬性抽離一下,作的打洞ShapeBorder
豈不更香
@override
Path getOuterPath(Rect rect, {TextDirection textDirection}) {
var path = Path();
path.addRRect(RRect.fromRectAndRadius(rect, Radius.circular(10)));
var w = rect.width;
var h = rect.height;
var holeCount = 12;
var d = w / (1 + 2 * holeCount);
_getHold(path, holeCount, d, 8);
path.fillType = PathFillType.evenOdd;
return path;
}
_getHold(Path path, int count, double d, double pt) {
for (int i = 0; i < count; i++) {
var left = d + 2 * d * i;
var top = pt;
var right = left + d;
var bottom = top + d;
path.addOval(Rect.fromLTRB(left, top, right, bottom));
}
}
複製代碼
羣裏有個哥們提了一句,看能不能作一個優惠券:
Path在手,就是能夠隨心所欲,廢話很少說,開搞
核心方法和上面相似,但涉及到路徑操做還有些注意點
經過洞的個數和寬度來肯定洞的直徑,這樣會避免最邊上的尷尬,適配性更加
lineRate
來肯定白線的分率位置(0,1) 下面兩幅分別是0.718和0.618
dash
是不是虛線,color 爲線的顏色
適應寬高
: 分率線和小圓半徑都會根據寬高自動進行更改
class CouponShapeBorder extends ShapeBorder {
final int holeCount;
final double lineRate;
final bool dash;
final Color color;
CouponShapeBorder(
{this.holeCount = 6,
this.lineRate = 0.718,
this.dash = true,
this.color = Colors.white});
@override
EdgeInsetsGeometry get dimensions => null;
@override
Path getInnerPath(Rect rect, {TextDirection textDirection}) {
return null;
}
@override
Path getOuterPath(Rect rect, {TextDirection textDirection}) {
var w = rect.width;
var h = rect.height;
var d = h / (1 + 2 * holeCount);
var path = Path();
path.addRect(rect);
_formHoldLeft(path, d);
_formHoldRight(path, w, d);
_formHoleTop(path, rect);
_formHoleBottom(path, rect);
path.fillType = PathFillType.evenOdd;
return path;
}
void _formHoleBottom(Path path, Rect rect) {
path.addArc(
Rect.fromCenter(
center: Offset(lineRate * rect.width, rect.height),
width: 13.0,
height: 13.0),
pi,
pi);
}
void _formHoleTop(Path path, Rect rect) {
path.addArc(
Rect.fromCenter(
center: Offset(lineRate * rect.width, 0),
width: 13.0,
height: 13.0),
0,
pi);
}
_formHoldLeft(Path path, double d) {
for (int i = 0; i < holeCount; i++) {
var left = -d / 2;
var top = 0.0 + d + 2 * d * (i);
var right = left + d;
var bottom = top + d;
path.addArc(Rect.fromLTRB(left, top, right, bottom), -pi / 2, pi);
}
}
_formHoldRight(Path path, double w, double d) {
for (int i = 0; i < holeCount; i++) {
var left = -d / 2 + w;
var top = 0.0 + d + 2 * d * (i);
var right = left + d;
var bottom = top + d;
path.addArc(Rect.fromLTRB(left, top, right, bottom), pi / 2, pi);
}
}
@override
void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) {
var paint = Paint()
..color = color
..strokeWidth = 1.5
..style = PaintingStyle.stroke
..strokeJoin = StrokeJoin.round;
var d = rect.height / (1 + 2 * holeCount);
if (dash) {
_drawDashLine(canvas, Offset(lineRate * rect.width, d / 2),
rect.height / 16, rect.height - 13, paint);
} else {
canvas.drawLine(Offset(lineRate * rect.width, d / 2),
Offset(lineRate * rect.width, rect.height - d / 2), paint);
}
}
_drawDashLine(
Canvas canvas, Offset start, double count, double length, Paint paint) {
var step = length / count / 2;
for (int i = 0; i < count; i++) {
var offset = start + Offset(0, 2 * step * i);
canvas.drawLine(offset, offset + Offset(0, step), paint);
}
}
@override
ShapeBorder scale(double t) {
// TODO: implement scale
return null;
}
}
複製代碼
上面主要在Material中使用,ClipPath中也有ShapeBorder的用武之地
如今我想用優惠券的裁切路徑來裁個圖片,so easy
Widget _buildClipPath() {
return Container(
width: 300,
height: 200,
child: ClipPath(
clipper: ShapeBorderClipper(
shape: CouponShapeBorder()
),
child: Image.asset('assets/images/bg.jpeg',fit: BoxFit.cover,),
),
);
}
複製代碼
Card是基於Material實現的,能夠直接使用shape屬性
好比下面的列表題目,能夠經過邊線來潤色一下
沒形狀 | 有形狀 |
---|---|
這篇就到這裏吧,只是爲你打開了一扇大門,究其核心仍是path的操做。
不要讓框架限制住你,它僅是最底的基層;在其之上的,應是用創造來築建的大廈和城樓。
另外本人有一個Flutter微信交流羣,歡迎小夥伴加入,共同探討Flutter的問題,期待與你的交流與切磋。
@張風捷特烈 2019.03.05 未允禁轉
個人公衆號:編程之王
聯繫我--郵箱:1981462002@qq.com --微信:zdl1994328
~ END ~