Flutter【繪制】制作一個掘金Logo組件

掘金logo

掘金官方使用的logo從網(wǎng)頁上看到是個svg文件,官方掘金logo刹帕,點(diǎn)擊去可以看到logo和文字都是些path標(biāo)簽禁灼。

svg的原理也是通過路徑繪制出來的圖形誉己,和Flutter路徑繪制原理相似衣盾,同樣可以繪制出任何平面圖形拾酝,了解svg相關(guān)知識劲绪,可以看看張老師的svg解析:【Flutter 繪制番外】svg 文件與繪制 (上)男窟。

為了鞏固下Flutter繪制的相關(guān)知識盆赤,今天我們就用Flutter路徑從頭開始制作封裝一個掘金的logo組件, 掘金的logo看起來很簡單歉眷,但是其中還是涉及到了很多繪制以及三角函數(shù)的知識的弟劲。

繪制菱形

首先我們可以看到掘金最上面是一個菱形,通過量角器測得掘金logo的角度大約為100°姥芥,那么菱形的上下角度也就為100°兔乞。

為了封裝的通用性,我們設(shè)菱形的邊長為side凉唐,菱形上方一半的角度為angle= 50°庸追,根據(jù)三角函數(shù)就可以得到菱形的四個坐標(biāo)點(diǎn),通過path路徑進(jìn)行鏈接台囱。
代碼:

double angle = pi / 18* 5;
// 菱形邊長
double side = 50;
Paint paint = Paint()
  ..style = PaintingStyle.fill
  ..isAntiAlias = true
  ..strokeJoin= StrokeJoin.miter
   ..color = Color(0xff1E80FF).withOpacity(0.7);

// 頂部菱形
Path path = Path();
path.moveTo(-side * sin(angle), 0);
path.lineTo(0, -side * cos(angle));
path.lineTo(side * sin(angle), 0);
path.lineTo(0, side * cos(angle));
path.close();
canvas.drawPath(path, paint);

就可以得到以下效果淡溯,設(shè)置透明度為了下面計(jì)算效果可以看的更加直觀。

繪制折線

接下來繪制菱形下方的折線簿训,折線我們使用非填充畫筆來實(shí)現(xiàn)咱娶,首先掘金的logo整體關(guān)于y軸對稱,角度一致强品,關(guān)鍵要計(jì)算折線之間與菱形的距離膘侮,首先我們知道菱形四個點(diǎn)的坐標(biāo),那么最下面的坐標(biāo)就是(0, side * cos(angle));, 根據(jù)掘金logo的設(shè)計(jì)的榛,折線的寬度大約為菱形邊長的0.7倍琼了,所以這里我們暫設(shè)畫筆的寬度為double paintWidth = side * 0.7;,y軸折線中心點(diǎn)距離菱形底部的距離為下圖紅線部分,這個距離大約為菱形邊長的1.5倍夫晌。

左右連接

代碼:

Path path2 = Path();
// 原點(diǎn)距離下方折線中心y軸距離
double h1 = side * cos(angle) + side * 1.5;
path2.moveTo(-h1 * tan(angle), 0);
path2.lineTo(0, h1);
path2.lineTo(h1 * tan(angle), 0);
canvas.drawPath(path2, paint);

接下來繪制最下面的折線雕薪,這里為了讓兩條折線之間距離一致,我們需要計(jì)算出下圖c點(diǎn)坐標(biāo)晓淀,下圖中b是中點(diǎn)所袁,那么ab=bc,求出ab的長度也就知道c點(diǎn)的坐標(biāo)了凶掰,過a點(diǎn)做bd垂直線交點(diǎn)設(shè)為g燥爷,那么已知ag等于線寬的1/2,角abg= angle°;锄俄,就能得出ab的長度 ab = paintWidth / 2 / sin(angle);局劲,那么也就得到c點(diǎn)坐標(biāo)=(0, h1+ab);

那么折線之間的距離也就可以算出來了。
代碼:

Path path3 = Path();
double h2 = h1 +
    (paintWidth / 2 / sin(angle) + side * 1.5);
path3.moveTo(-h2 * tan(angle), 0);
path3.lineTo(0, h2);
path3.lineTo(h2 * tan(angle), 0);

效果:

裁剪

上方大致畫出來了效果奶赠,接下來需要進(jìn)行對畫布進(jìn)行裁剪成以下陰影效果鱼填,主要就是計(jì)算b點(diǎn)和d點(diǎn)的坐標(biāo),涉及到兩條直線的交點(diǎn)和三角函數(shù)毅戈。

首先a點(diǎn)的值可以通過兩條相交直線求交點(diǎn)公式可以得出苹丸,然后過b點(diǎn)做紅線的中垂線先計(jì)算出ab的值愤惰,已知bd = paintWidth / 2,角bad = 180°-100°=80°,那么就可以得出ab = paintWidth / 2 / sin(pi - angle * 2)赘理,然后再分別過a點(diǎn)和b點(diǎn)做垂直三角形宦言,就能得出b點(diǎn)坐標(biāo)為(a.x - paintWidth / 2 / sin(pi - angle * 2) * sin(angle), a.y + paintWidth / 2 / sin(pi - angle * 2) * cos(angle));

同理d點(diǎn)坐標(biāo)也可得出。
計(jì)算代碼:

Point left = toTwoPoint(Point(-side * sin(angle), 0),
    Point(0, -side * cos(angle)), Point(-h2 * tan(angle), 0), Point(0, h2));
Point right = toTwoPoint(Point(side * sin(angle), 0),
    Point(0, -side * cos(angle)), Point(h2 * tan(angle), 0), Point(0, h2));

Path pathBg = Path();
pathBg.moveTo(0, -side * cos(angle));
pathBg.lineTo(
    left.x.toDouble() - paintWidth / 2 / sin(pi - angle * 2) * sin(angle),
    left.y.toDouble() + paintWidth / 2 / sin(pi - angle * 2) * cos(angle));
pathBg.lineTo(left.x.toDouble(), h2 + (paintWidth / 2 / sin(pi - angle * 2) / sin(angle)));
pathBg.lineTo(right.x.toDouble(), h2 + (paintWidth / 2 / sin(pi - angle * 2)/ sin(angle)));
pathBg.lineTo(right.x.toDouble() + paintWidth / 2 / sin(pi - angle * 2) * sin(angle),

right.y.toDouble() + paintWidth / 2 * cos(angle));
pathBg.close();
// 通過裁剪畫布得到最終效果
 canvas.clipPath(pathBg);

效果:

原始 移到畫布中間

上面我們是通過菱形邊長去求的各個坐標(biāo)點(diǎn)商模,現(xiàn)在我們?yōu)榱耸褂梅奖阈枨笫且阎M件寬高奠旺,求菱形的邊長,這樣組件使用起來才會比較方便精準(zhǔn)的控制組件大小施流,這里就是一些的繁瑣的倒推計(jì)算响疚,假設(shè)我們的高度是我們設(shè)定的height,那么寬度其實(shí)是也就確定了瞪醋,因?yàn)榻嵌纫坏┐_立忿晕,寬度自然也就確定了,所以這里我們向外暴露兩個屬性银受,一個組件高度践盼,一個菱形角度即可。

完整源碼:

/// 掘金logo組件
class JueJinLogo extends StatelessWidget {
  final double height; // 組件高度
  final double angle; // 菱形上下角度1/2

  const JueJinLogo({Key? key, this.height = 140, this.angle = pi / 18 * 5})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    double m = 0.7;// 折線線寬相對菱形邊長倍數(shù)
    double n = 1.5;// 折線之間線寬相對菱形邊長倍數(shù)
    var a = (2 * cos(angle) + m * 0.5 / sin(angle) + 3);
    double side = height / (a + m * 0.5 / sin(pi - angle * 2) / sin(angle));
    double paintWidth = m * side;
    double h2 = side * cos(angle) +
        side * n +
        (paintWidth / 2 / sin(angle) + side * n);
    Point right = PointUtil.toTwoPoint(Point(side * sin(angle), 0),
        Point(0, -side * cos(angle)), Point(h2 * tan(angle), 0), Point(0, h2));
    double width = (right.x.toDouble() +
            paintWidth / 2 / sin(pi - angle * 2) * sin(angle)) *
        2;

    return CustomPaint(
      size: Size(width, height),
      painter: _JueJinLogoPaint(side, angle),
    );
  }
}

class _JueJinLogoPaint extends CustomPainter {
  double side;
  double angle;

  _JueJinLogoPaint(this.side, this.angle);

  @override
  void paint(Canvas canvas, Size size) {
    canvas.translate(size.width / 2, size.height / 2);
    double paintWidth = side * 0.7;
    Paint paint = Paint()
      ..strokeWidth = paintWidth
      ..style = PaintingStyle.fill
      ..isAntiAlias = true
      ..strokeJoin = StrokeJoin.miter
      ..color = Color(0xff1E80FF);

    canvas.save();
    Path path = Path();
    path.moveTo(-side * sin(angle), 0);
    path.lineTo(0, -side * cos(angle));
    path.lineTo(side * sin(angle), 0);
    path.lineTo(0, side * cos(angle));
    path.close();

    Path path2 = Path();
    double h1 = side * cos(angle) + side * 1.5;
    path2.moveTo(-h1 * tan(angle), 0);
    path2.lineTo(0, h1);
    path2.lineTo(h1 * tan(angle), 0);

    Path path3 = Path();
    double h2 = h1 + (paintWidth / 2 / sin(angle) + side * 1.5);
    path3.moveTo(-h2 * tan(angle), 0);
    path3.lineTo(0, h2);
    path3.lineTo(h2 * tan(angle), 0);

    // 平移組件到畫布中心
    canvas.translate(
        0,
        side * cos(angle) -
            (h2 + (paintWidth / 2 / sin(angle)) + side * cos(angle)) / 2);

    Point left = PointUtil.toTwoPoint(Point(-side * sin(angle), 0),
        Point(0, -side * cos(angle)), Point(-h2 * tan(angle), 0), Point(0, h2));
    Point right = PointUtil.toTwoPoint(Point(side * sin(angle), 0),
        Point(0, -side * cos(angle)), Point(h2 * tan(angle), 0), Point(0, h2));

    Path pathBg = Path();
    pathBg.moveTo(0, -side * cos(angle));
    pathBg.lineTo(
        left.x.toDouble() - paintWidth / 2 / sin(pi - angle * 2) * sin(angle),
        left.y.toDouble() + paintWidth / 2 / sin(pi - angle * 2) * cos(angle));
    pathBg.lineTo(left.x.toDouble(),
        h2 + (paintWidth / 2 / sin(pi - angle * 2) / sin(angle)));
    pathBg.lineTo(right.x.toDouble(),
        h2 + (paintWidth / 2 / sin(pi - angle * 2) / sin(angle)));
    pathBg.lineTo(right.x.toDouble() + paintWidth / 2 * sin(angle),
        right.y.toDouble() + paintWidth / 2 * cos(angle));
    pathBg.close();
    // 裁剪畫布
    canvas.clipPath(pathBg);
    // 繪制菱形以及折線
    canvas.drawPath(path, paint);
    canvas.drawPath(path2, paint..style = PaintingStyle.stroke);
    canvas.drawPath(path3, paint..style = PaintingStyle.stroke);
    canvas.restore();
  }

  @override
  bool shouldRepaint(covariant _JueJinLogoPaint oldDelegate) {
    return false;
  }
}

class PointUtil {
  /// 兩點(diǎn)求直線方程
  static double towPointKb(Point<double> p1, Point<double> p2,
      {bool isK = true}) {
    /// 求得兩點(diǎn)斜率
    double k = 0;
    double b = 0;
    // 防止除數(shù) = 0 出現(xiàn)的計(jì)算錯誤 a e x軸重合
    if (p1.x == p2.x) {
      k = (p1.y - p2.y) / (p1.x - p2.x - 1);
    } else {
      k = (p1.y - p2.y) / (p1.x - p2.x);
    }
    b = p1.y - k * p1.x;
    if (isK)
      return k;
    else
      return b;
  }

  static Point<double> toTwoPoint(
      Point<double> a, Point<double> b, Point<double> m, Point<double> n) {
    double k1 = towPointKb(a, b);
    double b1 = towPointKb(a, b, isK: false);

    double k2 = towPointKb(m, n);
    double b2 = towPointKb(m, n, isK: false);

    return Point((b2 - b1) / (k1 - k2), (b2 - b1) / (k1 - k2) * k1 + b1);
  }
}

使用
使用也是非常的方便宾巍,直接設(shè)置組件高度即可咕幻。

Widget build(BuildContext context) {
  return Container(
    color: Colors.white,
    child: JueJinLogo(
      height: 200,
    ),
  );
}

效果:

因?yàn)檫@里暴露了角度,所以也可以自定義角度.

當(dāng)然這里還可以暴露一些顏色蜀漆、漸變色等一些屬性谅河,就不一一展示了咱旱。掌握原理即可确丢。

總結(jié)

通過制作掘金logo這個組件,又鞏固了Flutter繪制的的相關(guān)知識和一些基礎(chǔ)的三角函數(shù)計(jì)算知識吐限,同時(shí)對封裝組件所需注意的事項(xiàng)也有了加深的理解鲜侥,那這篇文章就到這里,希望對你在Flutter繪制以及封裝組件方面有所幫助诸典,如有幫助描函,歡迎點(diǎn)贊,如有疑問狐粱,歡迎指正~

作者:老李code
鏈接:https://juejin.cn/post/7165139559875870750

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末舀寓,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子肌蜻,更是在濱河造成了極大的恐慌互墓,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蒋搜,死亡現(xiàn)場離奇詭異篡撵,居然都是意外死亡判莉,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進(jìn)店門育谬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來券盅,“玉大人,你說我怎么就攤上這事膛檀∶潭疲” “怎么了?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵咖刃,是天一觀的道長互站。 經(jīng)常有香客問我,道長僵缺,這世上最難降的妖魔是什么胡桃? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮磕潮,結(jié)果婚禮上翠胰,老公的妹妹穿的比我還像新娘。我一直安慰自己自脯,他們只是感情好之景,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著膏潮,像睡著了一般锻狗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上焕参,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天轻纪,我揣著相機(jī)與錄音,去河邊找鬼叠纷。 笑死刻帚,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的涩嚣。 我是一名探鬼主播崇众,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼航厚!你這毒婦竟也來了顷歌?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤幔睬,失蹤者是張志新(化名)和其女友劉穎眯漩,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體溪窒,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡坤塞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年冯勉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片摹芙。...
    茶點(diǎn)故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡灼狰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出浮禾,到底是詐尸還是另有隱情交胚,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布盈电,位于F島的核電站蝴簇,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏匆帚。R本人自食惡果不足惜熬词,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望吸重。 院中可真熱鬧互拾,春花似錦、人聲如沸嚎幸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嫉晶。三九已至骑疆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間替废,已是汗流浹背箍铭。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留舶担,地道東北人坡疼。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像衣陶,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子闸氮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容