1. <nobr id="easjo"><address id="easjo"></address></nobr>

      <track id="easjo"><source id="easjo"></source></track>
      1. 
        

      2. <bdo id="easjo"><optgroup id="easjo"></optgroup></bdo>
      3. <track id="easjo"><source id="easjo"><em id="easjo"></em></source></track><option id="easjo"><span id="easjo"><em id="easjo"></em></span></option>
          貴州做網站公司
          貴州做網站公司~專業!靠譜!
          10年網站模板開發經驗,熟悉國內外開源網站程序,包括DEDECMS,WordPress,ZBlog,Discuz! 等網站程序,可為您提供網站建設,網站克隆,仿站,網頁設計,網站制作,網站推廣優化等服務。我們專注高端營銷型網站,企業官網,集團官網,自適應網站,手機網站,網絡營銷,網站優化,網站服務器環境搭建以及托管運維等。為客戶提供一站式網站解決方案?。?!

          Flutter使用Canvas實現小白兔的繪制

          來源:互聯網轉載 時間:2024-01-29 08:16:55

          前言

          前面兩篇文章講解了在 Flutter 中使用 Canvas 分別實現了精美表盤和微信紅包效果,本篇將繼續帶領你使用 Canvas 實現簡筆的小白兔效果,使用的核心技術為二次貝塞爾曲線和三次貝塞爾曲線的運用。

          按照慣例,先看一下最終實現的效果:

          實現

          仔細觀察上面的效果圖,可以發現簡筆的小白兔實際上是通過多個不同形狀、不同位置的 ”3“ 的圖形組成的,所以核心就是如何繪制 ”3“ 的形狀,這里采用兩個三次貝塞爾曲線來繪制。

          整體的繪制是在 CustomPainterpaint 方法中進行,所以首先創建一個 RabbitPainter 繼承自 CustomPainter ,然后在 Widget 中通過 CustomPaint 進行使用,如下:

          class RabbitPainter extends CustomPainter{  @override  void paint(Canvas canvas, Size size) async{  }}Container(  color: Colors.white,  child: Center(    child: CustomPaint(      painter: RabbitPainter(),      size: Size(0.8.sw, 1.sw),    ),  ),);

          繪制 ”3“

          前面說到了使用三次貝塞爾曲線繪制 ”3“ 的形狀,而三次貝塞爾曲線需要 4 個點,兩個端點和兩個曲線控制曲線的點,如下圖所示:

          使用代碼中使用 Path 創建三次貝塞爾曲線路徑的代碼如下:

          Path path = Path();path.moveTo(x, y);path.cubicTo(x1, y1, x2, y2, x3, y3);

          這就創建了半個 ”3“ ,在此基礎上再添加一個三次貝塞爾曲線就實現了一個 ”3“ 的圖形路徑,封裝方法如下:

          /// 創建3形狀的pathPath createThreePath(List<Offset> points) {  Path path = Path();  path.moveToPoint(points[0]);  path.cubicToPoints(points.sublist(1, 4));  path.cubicToPoints(points.sublist(4, 7));  return path;}

          傳入的參數是一個點的集合,共七個點,創建一個 Path 首先 moveTo 到第一個點,然后將其余 6 個點分兩次每次 3 個點添加兩個三次貝塞爾曲線到 Path 中,最終組成了一個 ”3“ 的圖形。

          其中 moveToPointcubicToPoints 是自定義擴展 Path 的方法,方便使用,其實現如下:

          extension PathExt on Path{  void moveToPoint(Offset point){    moveTo(point.dx, point.dy);  }  void cubicToPoints(List<Offset> points){    if(points.length != 3){      throw "params points length must 3";    }    cubicTo(points[0].dx, points[0].dy, points[1].dx, points[1].dy, points[2].dx,  points[2].dy);  }}

          至此就實現了對一個 ”3“ 的形狀的封裝。

          身體

          首先繪制小白兔的主體,也就是左右兩邊的身體輪廓,而左右兩邊的身體輪廓則是由一個反向的 ”3“ 和一個正向的 ”3“ 組成的,所以首先我們使用上面封裝好的方法繪制一個反向 ”3“ 的形狀。

          List<Offset> createLeftBodyPoints(){  var position1 = Offset(110.w, 100.w);  var position2 = Offset(30.w, position1.dy + 20.w);  var position3 = Offset(40.w, position2.dy + 100.w);  var position4 = Offset(110.w, position3.dy + 10.w);  var position5 = Offset(50.w, position4.dy + 20.w);  var position6 = Offset(60.w, position5.dy + 80.w);  var position7 = Offset(125.w, position6.dy + 10.w);  return [    position1,    position2,    position3,    position4,    position5,    position6,    position7  ];}var leftBodyPoints = createLeftBodyPoints();var leftBodyPath = createThreePath(leftBodyPoints);canvas.drawPath(leftBodyPath, _paint);

          首先創建 7 個點,也就是用于繪制 ”3“ 形狀的 7 個點,然后調用封裝好的方法創建一個 Path,再使用 Canvas.drawPath 將圖形繪制出來。

          這里使用數值如 110.w 為適配單位,關于 Flutter 中的屏幕適配請參考 :Flutter屏幕適配

          實現效果如下:

          這樣就繪制出了兔子左邊身體輪廓了,使用同樣的方法是不是就可以繪制出右邊的輪廓了呢,當然可以,但是這里又更簡單的辦法,那就是將左邊的 Path 進行一個翻轉就行了,如下:

          var matrix4 = Matrix4.translationValues(0.8.sw, 0, 0);matrix4.rotateY(2*pi/2);var rightBodyPath = leftBodyPath.transform(matrix4.storage);canvas.drawPath(rightBodyPath, _paint);

          使用 Matrix4 對 Path 進行平移和旋轉,這里為什么平移 0.8.sw 是因為畫布的寬度設置的 0.8.sw,即將 x 平移到畫布最右邊,然后對 Y 軸旋轉 180 度,即將圖形翻轉過來,最終實現效果如下:

          這樣身體的左右輪廓就實現了。

          耳朵

          耳朵實際上也是一個 ”3“ 的形狀,只是是倒著放的,并且往上拉伸將 ”3“ 的兩個凸形顯得更凸出

          var leftFirstPosition = leftBodyPath.getPositionFromPercent(0);var rightFirstPosition = rightBodyPath.getPositionFromPercent(0);var centerWidth = rightFirstPosition.dx - leftFirstPosition.dx;var position1 = Offset(leftFirstPosition.dx, leftFirstPosition.dy);var position2 = Offset(leftFirstPosition.dx  -50.w, -20.w);var position3 = Offset(leftFirstPosition.dx  + centerWidth/2, -20.w);var position4 = Offset(leftFirstPosition.dx  + centerWidth/2, leftFirstPosition.dy);var position5 = Offset(leftFirstPosition.dx  + centerWidth/2 + 5.w, -12.w);var position6 = Offset(rightFirstPosition.dx  + 55.w, -12.w);var position7 = Offset(rightFirstPosition.dx, rightFirstPosition.dy);var points = [position1, position2, position3, position4, position5, position6, position7];var earPath = createThreePath(points);canvas.drawPath(earPath, _paint);

          耳朵的兩個端點分別為左邊身體的起點和右邊身體的起點,所以先計算出左右兩邊圖形的第一個點,并計算出兩個點的間距。其中 getPositionFromPercent 也是自定義擴展 Path 的方法,用于通過百分比得到在 Path 路徑上的對應的點,實現如下:

          extension PathExt on Path{    Offset getPositionFromPercent(double percent){    var pms = computeMetrics();    var pm = pms.first;    var position = pm.getTangentForOffset(pm.length * percent)?.position ?? Offset.zero;    return position;  }}

          然后創建用于構建 ”3“ 圖形的七個點,最終實現耳朵的效果:

          手腳

          兔子的手腳也是由兩個 “3” 的圖形組成的,一邊的一只手一只腳為一個 “3”,先繪制左邊的手腳,代碼如下:

          var handsFeetPosition1 = Offset(leftBodyPoints[3].dx + 10.w, leftBodyPoints[3].dy + 10.w);var handsFeetPosition2 = Offset(handsFeetPosition1.dx + 20.w, handsFeetPosition1.dy + 5.w);var handsFeetPosition3 = Offset(handsFeetPosition2.dx + 20.w, handsFeetPosition2.dy + 40.w);var handsFeetPosition4 = Offset(handsFeetPosition1.dx, handsFeetPosition3.dy + 15.w);var handsFeetPosition5 = Offset(handsFeetPosition4.dx + 20.w, handsFeetPosition4.dy + 10.w);var handsFeetPosition6 = Offset(handsFeetPosition5.dx + 10.w, handsFeetPosition5.dy + 20.w);var handsFeetPosition7 = Offset(leftBodyPoints.last.dx, leftBodyPoints.last.dy);var leftHandsFeetPoints = [  handsFeetPosition1,  handsFeetPosition2,  handsFeetPosition3,  handsFeetPosition4,  handsFeetPosition5,  handsFeetPosition6,  handsFeetPosition7,];var leftHandsFeetPath = createThreePath(leftHandsFeetPoints);canvas.drawPath(leftHandsFeetPath, _paint);

          同樣是創建繪制 “3” 的 7 個點,這里起始點是以兔子身體左邊的中心端點作為起始點進行偏移的,繪制效果如下:

          用同樣的方法繪制右邊的手腳:

          var rightHandsFeetPosition1 = Offset(leftBodyPoints[3].dx + 80.w, leftBodyPoints[3].dy + 15.w);var rightHandsFeetPosition2 = Offset(rightHandsFeetPosition1.dx - 20.w, rightHandsFeetPosition1.dy + 5.w);var rightHandsFeetPosition3 = Offset(rightHandsFeetPosition2.dx - 15.w, rightHandsFeetPosition2.dy + 30.w);var rightHandsFeetPosition4 = Offset(rightHandsFeetPosition1.dx - 15.w, rightHandsFeetPosition3.dy + 15.w);var rightHandsFeetPosition5 = Offset(rightHandsFeetPosition4.dx - 15.w, rightHandsFeetPosition4.dy + 10.w);var rightHandsFeetPosition6 = Offset(rightHandsFeetPosition5.dx - 5.w, rightHandsFeetPosition5.dy + 20.w);var rightLastPosition = rightPath.getPositionFromPercent(1);var rightHandsFeetPosition7 = Offset(rightLastPosition.dx, rightLastPosition.dy);var rightHandsFeetPoints = [  rightHandsFeetPosition1,  rightHandsFeetPosition2,  rightHandsFeetPosition3,  rightHandsFeetPosition4,  rightHandsFeetPosition5,  rightHandsFeetPosition6,  rightHandsFeetPosition7,];var rightHandsFeetPath = createThreePath(rightHandsFeetPoints);canvas.drawPath(rightHandsFeetPath, _paint);

          效果如下:

          胡蘿卜葉

          蘿卜葉也是由一個 “3” 組成的,代碼如下:

          var point1 = Offset(leftHandsFeetPoints.first.dx + 20.w, leftHandsFeetPoints.first.dy - 5.w);var point2 = Offset(leftHandsFeetPoints.first.dx -5.w, leftHandsFeetPoints.first.dy -45.w);var point3 = Offset(leftHandsFeetPoints.first.dx  + 45.w, leftHandsFeetPoints.first.dy-45.w);var point4 = Offset(leftHandsFeetPoints.first.dx + 35.w, leftHandsFeetPoints.first.dy - 10.w);var point5 = Offset(leftHandsFeetPoints.first.dx + 40.w, leftHandsFeetPoints.first.dy -35.w);var point6 = Offset(rightFirstPosition.dx  + 0.w, leftHandsFeetPoints.first.dy-35.w);var point7 = Offset(leftHandsFeetPoints.first.dx + 50.w, leftHandsFeetPoints.first.dy - 5.w);var points = [point1, point2,  point3, point4, point5, point6, point7];Path radishLeafPath = createThreePath(points);canvas.drawPath(radishLeafPath, _paint)

          蘿卜葉的 “3” 以左右兩邊的手腳路徑的起點作為參考點進行一定單位的便宜,繪制效果如下:

          兔子的嘴也是由一個倒放的 “3” 組成的,代碼如下:

          var radishHeadMinYPosition = radishLeafPath.getMinYPosition();var mouthPosition1 = Offset(radishHeadMinYPosition.dx - 10.w, radishHeadMinYPosition.dy - 20.w);var mouthPosition2 = Offset(mouthPosition1.dx - 2.w, mouthPosition1.dy + 10.w);var mouthPosition3 = Offset(mouthPosition2.dx + 18.w, mouthPosition2.dy + 5.w);var mouthPosition4 = Offset(mouthPosition3.dx + 2.w, mouthPosition1.dy + 2.w);var mouthPosition5 = Offset(mouthPosition4.dx , mouthPosition4.dy + 10.w);var mouthPosition6 = Offset(mouthPosition5.dx + 18.w, mouthPosition5.dy + 2.w);var mouthPosition7 = Offset(mouthPosition6.dx + 2.w, mouthPosition1.dy);var mouthPoints = [  mouthPosition1,  mouthPosition2,  mouthPosition3,  mouthPosition4,  mouthPosition5,  mouthPosition6,  mouthPosition7,];var mouthPath = createThreePath(mouthPoints);canvas.drawPath(mouthPath, _paint);

          嘴的位置是以蘿卜葉進行參考的,在蘿卜葉上方一定位置,這里用到了 getMinYPosition 計算蘿卜葉 Path 的 Y 的最小點,該方法也是自定義擴展自 Path 的方法,實現如下:

          extension PathExt on Path{  Offset getMinYPosition(){    var pms = computeMetrics();    var pm = pms.first;    var minPosition = pm.getTangentForOffset(0)?.position;    for(int i = 0; i< pm.length; i++){      var position = pm.getTangentForOffset(i.toDouble())?.position;      if(minPosition == null || (position != null && position.dy < minPosition.dy)){        minPosition = position;      }    }    return minPosition ?? Offset.zero;  }}

          通過計算 Path 上所有的點,循環所有點取出 Y 值最小的點,也就是這里蘿卜葉的定點,然后進行一定單位的偏移調整,最終繪制出嘴的圖形,效果如下:

          眼睛

          眼鏡終于不是 “3” 了,只需要使用二次貝塞爾曲線就行了,代碼如下:

          var point1 = Offset(leftBodyPoints.first.dx - 5.w, leftBodyPoints.first.dy + 50.w);var point2 = Offset(point1.dx + 10.w, point1.dy - 13.w);var point3 = Offset(point1.dx + 20.w, point1.dy);Path leftEyesPath = Path();leftEyesPath.moveToPoint(point1);leftEyesPath.quadraticBezierToPoints([point2, point3]);canvas.drawPath(leftEyesPath, _paint);var rightFirstPosition = rightBodyPath.getPositionFromPercent(0);var point1 = Offset(rightFirstPosition.dx - 15.w, rightFirstPosition.dy + 50.w);var point2 = Offset(point1.dx + 10.w, point1.dy - 13.w);var point3 = Offset(point1.dx + 20.w, point1.dy);Path rightEyesPath = Path();rightEyesPath.moveToPoint(point1);rightEyesPath.quadraticBezierToPoints([point2, point3]);canvas.drawPath(rightEyesPath, _paint);

          眼睛的繪制使用的是二次貝塞爾曲線,只需三個點,即兩個端點和一個 控制曲線幅度的點。這里繪制眼睛分別以身體左邊起始點和右邊起始點作為參考點,因為身體右邊輪廓的 Path 是通過左邊的旋轉獲取,所以這里使用 getPositionFromPercent 獲取右邊 Path 的第一個點,然后進行一定單位的偏移,最終得到如下效果:

          胡蘿卜

          前面繪制了胡蘿卜葉,接下來就是繪制胡蘿卜的主體,胡蘿卜的主體主要是將兔子的左右手腳用曲線相連在中間形成一個封閉圖形就是胡蘿卜主體了。

          先看一下上半部分的曲線繪制:

          var radishTopPath = Path();var radishTopPosition1 = leftHandsFeetPath.getPositionFromPercent(0.07);var radishTopPosition2 = radishLeafPath.getPositionFromPercent(0).translate(0, -6.w);var radishTopPosition3 = radishLeafPath.getPositionFromPercent(1).translate(0, -9.w);var radishTopPosition4 = rightHandsFeetPath.getPositionFromPercent(0.07);radishTopPath.moveToPoint(radishTopPosition1);radishTopPath.cubicToPoints([radishTopPosition2, radishTopPosition3, radishTopPosition4]);canvas.drawPath(radishTopPath, _paint);

          胡蘿卜頂部曲線也是使用三次貝塞爾曲線進行繪制,起始點為左邊手腳的路徑的 0.07 位置的點,通過 getPositionFromPercent 獲取到具體點位坐標,其結束點為右邊手腳路徑的 0.07 位置,與左邊對稱。兩個曲線控制點已胡蘿卜葉的起始點和結束點作為參照進行一定單位的偏移,最終實現效果如下:

          接下來看底部曲線的繪制,實現思路與頂部曲線一致,不過底部采用的不是三次貝塞爾曲線,而是二次貝塞爾曲線,以左右手腳路徑上指定點(路徑上 0.9 位置)作為底部曲線的起始和結束點,曲線控制點以起始點坐標進行一定單位的偏移,代碼實現如下:

          var radishBottomPath = Path();var radishBottomPosition1 = leftHandsFeetPath.getPositionFromPercent(0.9);var radishBottomPosition2 = Offset(radishBottomPosition1.dx + 18.w, radishBottomPosition1.dy+40.w);var radishBottomPosition3 = rightHandsFeetPath.getPositionFromPercent( 0.9);radishBottomPath.moveToPoint(radishBottomPosition1);radishBottomPath.quadraticBezierToPoints([radishBottomPosition2, radishBottomPosition3]);canvas.drawPath(radishBottomPath, _paint);

          效果如下:

          上下封閉好后看起來就像一個胡蘿卜了,但是總感覺還差點啥,接下來給胡蘿卜上加點點綴,讓其看起來更像是個胡蘿卜。使用二次貝塞爾曲線在胡蘿卜內部繪制三條短曲線:

          var radishBodyPath1 = Path();var radishBodyPosition1 = leftHandsFeetPath.getPositionFromPercent( 0.3);var radishBodyPosition2 = Offset(radishBodyPosition1.dx + 5.w, radishBodyPosition1.dy-3.w);var radishBodyPosition3 = Offset(radishBodyPosition2.dx + 10.w, radishBodyPosition1.dy+3.w);radishBodyPath1.moveToPoint(radishBodyPosition1);radishBodyPath1.quadraticBezierToPoints([radishBodyPosition2, radishBodyPosition3]);var radishBodyPath2 = Path();var radishBodyPosition4 = rightHandsFeetPath.getPositionFromPercent( 0.7);var radishBodyPosition5 = Offset(radishBodyPosition4.dx - 5.w, radishBodyPosition4.dy-3.w);var radishBodyPosition6 = Offset(radishBodyPosition5.dx - 10.w, radishBodyPosition5.dy+3.w);radishBodyPath2.moveToPoint(radishBodyPosition4);radishBodyPath2.quadraticBezierToPoints([radishBodyPosition5, radishBodyPosition6]);var radishBodyPath3 = Path();var radishBodyPosition7 = rightHandsFeetPath.getPositionFromPercent( 0.78);var radishBodyPosition8 = Offset(radishBodyPosition7.dx - 3.w, radishBodyPosition7.dy-3.w);var radishBodyPosition9 = Offset(radishBodyPosition8.dx - 5.w, radishBodyPosition8.dy+3.w);radishBodyPath3.moveToPoint(radishBodyPosition7);radishBodyPath3.quadraticBezierToPoints([radishBodyPosition8, radishBodyPosition9]);canvas.drawPath(radishBodyPath1, _paint);canvas.drawPath(radishBodyPath2, _paint);canvas.drawPath(radishBodyPath3, _paint);

          分別以左邊手腳路徑的 0.3 、右邊手腳路徑的 0.7 和 0.78 作為曲線的起始點,再進行一定單位的偏移,最終繪制出胡蘿卜內部的三條曲線進行點綴,最終效果如下:

          尾巴

          經過上面的繪制后,這個兔子看著就像那么回事了,但還少一個尾巴,畢竟兔子不能沒有尾巴不是,尾巴同樣是一個三階貝塞爾曲線,代碼如下:

          var tailPath = Path();var tailPosition1 = rightBodyPath.getPositionFromPercent(0.8);var tailPosition2 = Offset(tailPosition1.dx + 35.w, tailPosition1.dy - 30.w);var tailPosition3 = Offset(tailPosition1.dx + 35.w, tailPosition1.dy + 40.w);var tailPosition4 = rightBodyPath.getPositionFromPercent(0.9);tailPath.moveToPoint(tailPosition1);tailPath.cubicToPoints([tailPosition2, tailPosition3, tailPosition4]);canvas.drawPath(tailPath, _paint);

          以右邊身體路徑的 0.8 位置為起始點,0.9 位置為結束點,中間以起始點偏移一定單位添加兩個控制點,最終實現尾巴的效果,如下圖:

          整體顏色填充

          圖形繪制完成后,接下來就是顏色的填充,首先對整個進行白色填充。這里采用的辦法是將小白兔最外層的路徑合并成一個 Path 然后對這個 Path 使用白色填充,代碼實現如下:

          var bodyBorderPath = Path();var positionFromPathPercent = earPath.getPositionFromPercent(0);bodyBorderPath  ..moveTo(positionFromPathPercent.dx, positionFromPathPercent.dy)  ..addPointsFromPath(earPath)  ..addPointsFromPath(rightBodyPath)  ..addPointsFromPath(rightHandsFeetPath.getPathFromPercent(0.9, 1), isReverse: true)  ..addPointsFromPath(radishBottomPath, isReverse: true)  ..addPointsFromPath(leftHandsFeetPath.getPathFromPercent(0.9, 1))  ..addPointsFromPath(leftBodyPath, isReverse: true)  ..addPath(tailPath, Offset.zero)  ..close();_paint.style = PaintingStyle.fill;_paint.color = Colors.white;canvas.drawPath(bodyBorderPath, _paint);

          創建 bodyBorderPath 然后獲取 earPath (耳朵路徑)的第一個點,將 bodyBorderPath 移動到這個點,然后調用 addPointsFromPath 方法將其他 Path 依次添加到 bodyBorderPath ,addPointsFromPath 為自定義擴展自 Path 的方法,實現如下:

          void addPointsFromPath(Path copyPath, {bool isReverse = false}){  var pms = copyPath.computeMetrics();  var pm = pms.first;  if(isReverse){    for(double i = pm.length; i > 0; i--){      var position = pm.getTangentForOffset(i.toDouble())?.position;      if(position != null ){        lineTo(position.dx, position.dy);      }    }  }else{    for(int i = 0; i< pm.length; i++){      var position = pm.getTangentForOffset(i.toDouble())?.position;      if(position != null ){        lineTo(position.dx, position.dy);      }    }  }}

          第一個參數為要添加的 Path,第二個參數 isReverse 表示是否反轉即將 Path 的點倒過來添加到當前 Path 中,具體實現為先計算出要添加的 Path 的點,然后循環每一個點使用 lineTo 將每一個點添加到當前 Path。

          rightHandsFeetPath、 leftHandsFeetPath 使用了 getPathFromPercent 方法截取 Path 的一部分添加到 bodyBorderPath 中,而 getPathFromPercent 也是自定義擴展自 Path 方法,代碼如下:

          Path getPathFromPercent( double startPercent, double endPercent){  var pms = computeMetrics();  var pm = pms.first;  var resultPath = pm.extractPath(pm.length * startPercent, pm.length * endPercent);  return resultPath;}

          代碼很簡單,計算 Path 的點,然后使用 extractPath 方法獲取指定開始位置到指定結束位置的路徑。

          最終實現效果如下:

          耳朵填充

          整體填充白色后,接下就為耳朵填充粉色,這里并不是將整個耳朵都填充為粉色,而是填充部分。代碼實現如下:

          _paint.color = Color(0xFFE79EC3);var leftFirstPosition = leftBodyPath.getPositionFromPercent(0);var rightFirstPosition = rightBodyPath.getPositionFromPercent(0);/// 左耳填充canvas.save();var leftEarRect = Rect.fromLTWH(leftFirstPosition.dx - 3.w, 25.w, 30.w, (leftFirstPosition.dy - 30.w));canvas.translate(leftEarRect.center.dx,  leftEarRect.center.dy);Path leftEarPath = Path();leftEarPath.addOval(Rect.fromLTWH(- leftEarRect.width /2, -leftEarRect.height/2, leftEarRect.width, leftEarRect.height));leftEarPath  = leftEarPath.transform(Matrix4.rotationZ(-pi/15).storage);canvas.drawPath(leftEarPath, _paint);canvas.restore();/// 右耳填充canvas.save();var rightEarRect = Rect.fromLTWH(rightFirstPosition.dx - 23.w, 25.w, 30.w, (rightFirstPosition.dy - 30.w));canvas.translate(rightEarRect.center.dx,  rightEarRect.center.dy);Path rightEarPath = Path();rightEarPath.addOval(Rect.fromLTWH(- rightEarRect.width / 2, - rightEarRect.height / 2, rightEarRect.width, rightEarRect.height));rightEarPath  = rightEarPath.transform(Matrix4.rotationZ(pi/10).storage);canvas.drawPath(rightEarPath, _paint);canvas.restore();

          首先調用 canvas.save() 將畫布保存,根據耳朵的起始點創建一個 Rect ,然后將畫布移動到該 Rect 的中心點,創建一個 Path 并添加一個橢圓,橢圓的 Rect 就是上面創建的 Rect ,然后將 Path 以 Z 軸旋轉一定角度使其與耳朵的形狀保持一個方向,然后將 Path 繪制出來,最后調用 canvas.restore() 恢復畫布。右耳的填充同理,最終實現效果如下:

          腮紅

          腮紅的繪制很簡單,就是在臉的左右兩邊各繪制一個粉色的圓,代碼如下:

          _paint.color = Color(0xFFE79EC3);var pointion1 = leftBodyPoints.first;Path leftFacePath = Path();Rect leftFaceRect = Rect.fromLTWH(position1.dx - 25.w - 15.w, position1.dy + 80.w - 15.w, 30.w, 30.w);leftFacePath.addOval(leftFaceRect);canvas.drawPath(leftFacePath, _paint);var rightFirstPosition = rightBodyPath.getPositionFromPercent(0);Path rightFacePath = Path();Rect rightFaceRect = Rect.fromLTWH(rightFirstPosition.dx + 25.w - 15.w, rightFirstPosition.dy + 80.w - 15.w, 30.w, 30.w);rightFacePath.addOval(rightFaceRect);canvas.drawPath(rightFacePath, _paint);

          在臉的位置創建一個 30.w 的正方形 Rect ,然后通過 Path 添加一個橢圓最后繪制出來即可,實現效果如下:

          胡蘿卜

          蘿卜的填充分為兩步,蘿卜葉和蘿卜體,蘿卜葉的填充相對比較簡單,只需使用填充模式繪制蘿卜葉的 Path 即可,代碼如下:

          _paint.style = PaintingStyle.fill;_paint.color = Colors.green;canvas.drawPath(radishLeafPath, _paint);

          效果如下:

          蘿卜體的填充需要先構建出蘿卜體的 Path ,之前繪制蘿卜體時時多個 Path 組合而成的,要對其填充首先要將這多個 Path 合成一個 Path,代碼如下:

          Path radishBorderPath = Path();var radishFistPosition = radishTopPath.getPositionFromPercent(0);radishBorderPath  ..moveTo(radishFistPosition.dx, radishFistPosition.dy)  ..addPointsFromPath(radishTopPath)  ..addPointsFromPath(rightHandsFeetPath)  ..addPointsFromPath(radishBottomPath, isReverse: true)  ..addPointsFromPath(leftHandsFeetPath, isReverse: true)  ..close();

          同樣采用的是 addPointsFromPath 方法,將頂部、底部的曲線以及左右手腳的線條合并為一個 Path,再對該 Path 進行填充繪制即可得到胡蘿卜的效果,繪制代碼如下:

          _paint.style = PaintingStyle.fill;_paint.color = Colors.orange;canvas.drawPath(radishBorderPath, _paint);

          效果如下:

          這樣一個可愛的抱著胡蘿卜的小白兔效果就繪制完成了。

          動畫

          圖形繪制完成后接下來就是添加動畫效果,動畫效果分為兩部分:線條的繪制動畫和顏色的填充動畫。動畫的繪制使用 AnimationController 結合 CustomPainter 來實現。通過 Animation 的不同進度繪制 Path 的不同長度,從而實現動畫效果。

          線條繪制動畫

          要實現線條的動畫效果,即線條的動態繪制,需要計算 Path 的路徑點,然后根據動畫的進度動態繪制 Path 的長度,由于這里 Path 比較多,如果每個都單獨使用一個動畫來控制的話太麻煩了,所以這里將所有的繪制線條的 Path 放到一個集合里用一個動畫來控制。

          首先在使用 RabbitPainter 的 Widget 中創建動畫:

          var bodyAniamtion = AnimationController(vsync: this, upperBound: 15.0)  ..duration = const Duration(seconds: 10);

          創建一個 AnimationController ,設置動畫的最大值為 15,為什么是 15 ?,因為 Path 的個數有 15 個,所以這里設置動畫的最大值為 15 , 然后設置動畫時長為 10 秒。

          在 RabbitPainter 中使用:

          /// 將所有線條path放入集合進行統一繪制var list = [  leftBodyPath,  rightBodyPath,  earPath,  leftHandsFeetPath,  rightHandsFeetPath,  radishLeafPath,  mouthPath,  leftEyesPath,  rightEyesPath,  radishTopPath,  radishBottomPath,  radishBodyPath1,  radishBodyPath2,  radishBodyPath3,  tailPath,];///繪制整體邊框動畫void drawBorder(Canvas canvas, List<Path> list){  int index = (bodyAnimation.value as double) ~/ 1 ;  double progress = bodyAnimation.value % 1;  for(int i = 0 ; i < index; i++){    var path = list[i];    canvas.drawPath(path, _paint);  }  if(index >= list.length){    return;  }  var path = list[index];  var pms = path.computeMetrics();  var pm = pms.first;  canvas.drawPath(pm.extractPath(0, progress * pm.length), _paint);}

          首先將所有 Path 放到 list 集合里,然后調用 drawBorder 方法,在 drawBorder 方法里,首先獲取動畫的值,并用動畫的值除以 1 取整,即獲取當前動畫執行到繪制那個 Path,然后用動畫的值除以 1 取余數,即獲取當前 Path 繪制的進度。獲取到這兩個值后先將已繪制完成的 Path 調用 canvas.drawPath 完整的繪制出來,然后取出當前正在繪制的 Path,計算 Path 的路徑點,然后使用 extractPath 根據動畫進度獲取當前繪制的長度,將其繪制出來,就形成了動態繪制的效果,效果如下:

          顏色填充動畫

          填充動畫如果還是按照線條動畫的邏輯來進行繪制則效果不太好,這里采用另一種辦法,獲取填充 Path 的 Bounds 即 Path 的范圍,通過 Path 的 getBounds 獲取,獲取的值是一個 Rect 類型即矩形,然后采用畫布的裁剪,先對畫布進行 Path 路徑的裁剪,然后再繪制 Rect 矩形的填充,此時就可以根據進度改變填充 Rect 的高度來實現動態填充效果,原理示意圖如下:

          如上,要繪制一個圓的填充,可以先繪制圓的邊框,然后繪制圓范圍矩形的填充,在通過畫布的裁剪將多余部分去除。

          根據以上原理來實現對整體白色填充的動畫,首先創建填充動畫的 Animation:

          var fillBodyAnimation = AnimationController(vsync: this)          ..duration = const Duration(seconds: 1);

          然后進行填充代碼的修改:

          /// 繪制整體白色填充canvas.save();canvas.clipPath(bodyBorderPath);var bodyRect = bodyBorderPath.getBounds();canvas.drawRect(bodyRect.clone(height: bodyRect.height * fillAnimation.value), _paint);canvas.restore();

          因為要對畫布進行裁剪,防止影響到其他的繪制,這里先調用 canvas.save() 對畫布進行保存,然后調用 clipPath 對畫布進行裁剪,即此時畫布只保留 Path 路徑區域。調用 Path 的 getBounds 方法獲取 Path 所在區域的 Rect ,再調用 drawRect 方法進行填充繪制,繪制的 Rect 高度根據動畫進度進行計算,最后調用 canvas.restore() 恢復畫布。

          這里用到了 Rect 的 clone 方法,該方法為自定義擴展 Rect 的方法,代碼如下:

          extension RectExt on Rect{  Rect clone({    double? left,    double? top,    double? right,    double? bottom,    double? width,    double? height}){    if(right != null || bottom != null){      return Rect.fromLTRB(left ?? this.left, top ?? this.top, right ?? this.right, bottom ?? this.bottom);    }    if(width != null || height != null){      return Rect.fromLTWH(left ?? this.left, top ?? this.top, width ?? this.width, height ?? this.height);    }    return Rect.fromLTRB(this.left, this.top, this.right, this.bottom);  }}

          主要作用是對 Rect 克隆,并修改其中某一個屬性。

          其余耳朵、腮紅、蘿卜的填充效果原理跟上面一樣,就不一一的貼代碼了,看一下最終填充動畫的效果圖:

          總結

          整個小白兔的效果主要通過繪制 7 個 “3” 的形狀組合而成,涉及到的技術主要是對 Path 和 Canvas 的使用,包括使用 Path 的貝塞爾曲線繪制 “3” 的形狀,使用 Path 路徑的計算獲取 Path 上指定的點或段,通過 Path 的計算實現動態繪制的動畫以及畫布的裁剪和平移等。通過對 Path 和 Canvas 的靈活使用最終實現我們想要的效果。

          源碼地址:flutter_rabbit[1]

          References

          [1] flutter_rabbit: https://github.com/loongwind/flutter_rabbit

          標簽:小白兔填顏色-

          網絡推廣與網站優化公司(網絡優化與推廣專家)作為數字營銷領域的核心服務提供方,其價值在于通過技術手段與策略規劃幫助企業提升線上曝光度、用戶轉化率及品牌影響力。這...

          在當今數字化時代,公司網站已成為企業展示形象、傳遞信息和開展業務的重要平臺。然而,對于許多公司來說,網站建設的價格是一個關鍵考量因素。本文將圍繞“公司網站建設價...

          在當今的數字化時代,企業網站已成為企業展示形象、吸引客戶和開展業務的重要平臺。然而,對于許多中小企業來說,高昂的網站建設費用可能會成為其發展的瓶頸。幸運的是,隨...

          360修復后藍屏怎么辦?1如果您下載了軟件、補丁、插件、驅動程序等。藍屏之前,可以先卸載;如果驅動不合適,可以下載驅動向導升級驅動。2.如果電腦有木馬,可以下載Win清理助手、金山衛士、360急救箱查殺木馬。3.如果藍屏關機不是經常發生,重啟電腦試試;如果它仍然沒有 t工作,請按下F8,直到高級選項出現,放開,并選擇 "最后正確的配置 "并進入修復。4.如果它不 如果無效,請嘗試還原系統或重新安裝...

          iwatch7的功能?與Series 6 (S6)相比,Apple Watch Series 7(簡稱s 7)新增三大功能:屏幕更大,可支持全鍵盤輸入;支持IP6X防塵,可以更好的應對極端環境;支持USB-C接口快充,提高充電速度。1.全鍵盤輸入蘋果重新設計了S7的屏幕,將邊框縮小了40%,并將外殼高度增加了1mm,從而獲得了更多的屏幕空間,顯示了更多的內容。具體數據是S7的分辨率達到396 x ...

          怎樣申請多個淘寶賬號?1.可以申請多個淘寶賬號,只要ID不重復,郵箱不重復,個人真實信息不限;2.也可以申請多個支付寶賬戶。只要郵箱地址不重復,個人真實信息同樣不限,都可以填寫。但是,實名一旦填寫就無法更改;3.一個身份證只能認證一個淘寶/支付寶賬號,其他不能認證。但可以授權其他賬號,掛在已認證的支付寶賬號下,即使不是自己的(不鼓勵授權給不重要或不知名的人,畢竟關系到錢和真實身份);4.正確填寫收...

          TOP
          国产初高中生视频在线观看|亚洲一区中文|久久亚洲欧美国产精品|黄色网站入口免费进人
          1. <nobr id="easjo"><address id="easjo"></address></nobr>

              <track id="easjo"><source id="easjo"></source></track>
              1. 
                

              2. <bdo id="easjo"><optgroup id="easjo"></optgroup></bdo>
              3. <track id="easjo"><source id="easjo"><em id="easjo"></em></source></track><option id="easjo"><span id="easjo"><em id="easjo"></em></span></option>