Preface: Why will write this article? The main reason is that the technical group often has friends asking how to draw staff, and there is basically no relevant demo on the Internet, which feels like a very unpopular demand point. In addition, I have some immature project planning for music and staff, so I hope to maintain it as a complete open source project. So on the first day of the Mid-Autumn festival holiday, take a moment to simply draw a staff.

rendering

knowledge

  1. Note is actually a font: akvo, draw staff, is actually display this font;
  2. Involving Flutter canvas drawing and font drawing;
  3. Music related knowledge 🎵 [which is missing from this article]

Implementation steps

1. Basic needs

Starting from the simplest services, there are at least these requirements: plan the length of the line spectrum according to the duration, display the corresponding note at the corresponding time point, and highlight the display…… when playing the corresponding note But limited to the author of music is utterly ignorant, so the realization of staff only two requirements: five lines + notes 🤣. The author of this only do uniform in the staff display notes.

2. Draw a staff

Very simple, draw a line according to the length of the incoming note. See the code:

/// Draw five baselines
class BaseLinePainter extends CustomPainter {
  final double lineSpace; // The gap between each line
  final double rowSpace; // The gap of each staff
  final int noteLength; // Note length

  BaseLinePainter(this.lineSpace, this.rowSpace, this.noteLength);

  @override
  voidpaint(Canvas canvas, Size size) { Paint _paint = Paint() .. color = Colors.black .. isAntiAlias =false. style = PaintingStyle.stroke .. strokeWidth =1.0;

    double w = size.width;
    int rowCount = (noteLength / 6).ceil(); // Each line contains 6 notes rounded up
    double h = 0;

    for (int c = 0; c < rowCount; c++) {
      canvas.save();
      h = c * (4 * lineSpace + rowSpace);
      for (int i = 0; i < 5; i++) {
        Path path = new Path();
        path.moveTo(0, h + i * lineSpace); path.lineTo(w, h + i * lineSpace); canvas.drawPath(path, _paint); } canvas.restore(); }}@override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
Copy the code

Draw notes

Drawing notes is actually drawing text, and the font is akvo, so we need to import the font package in YAML in advance

flutter:
  fonts:
    - family: akvo
      fonts:
        - asset: assets/fonts/akvo.ttf
Copy the code

To draw a line above, we specify a line of staff to draw 6 notes. Here is the drawing code:

/// Note drawing method
class NotesPainter extends CustomPainter {
  final List<StaffModel> noteList; // Note list
  final double lineSpace; // The gap between each line
  final double rowSpace; // The gap of each staff

  NotesPainter(this.noteList, this.lineSpace, this.rowSpace);

  @override
  void paint(Canvas canvas, Size size) {

    double cellWidth = size.width / 6; // The width of each note
    double rowTotalHeight = lineSpace * 4 + rowSpace;
    final textStyle = ui.TextStyle(
      fontFamily: 'akvo',
      fontSize: 24.// We need to calculate the font size according to the space between each line of the staff
      color: Colors.brown,
    );
    for (int i = 0; i < noteList.length; i++) {
      canvas.save();
      int rowCount = (i / 6).floor(); / / how many rows
      int lineCount = (i % 6); / / which columns
      Offset noteOffset = Offset(lineCount * cellWidth + (cellWidth - 12) / 2,
          rowCount * rowTotalHeight + (noteList[i].level - 1) * lineSpace);

      finalparagraphBuilder = ui.ParagraphBuilder(ui.ParagraphStyle()) .. pushStyle(textStyle) .. addText(noteList[i].note);final paragraph = paragraphBuilder.build()..layout(ui.ParagraphConstraints(width: cellWidth));
      canvas.drawParagraph(paragraph, noteOffset);
      canvas.restore();
    }
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
Copy the code

To encapsulate the widget

Just pass in the note model

class Staff extends StatelessWidget {
  Staff(this.size, {Key? key}) : super(key: key);
  final Size size;
  final List<StaffModel> _list = [
    StaffModel('e'.1),
    StaffModel('e'.2),
    StaffModel('g'.3),
    StaffModel('e'.4),
    StaffModel('h'.4),
    StaffModel('f'.0),
    StaffModel('e'.1),
    StaffModel('e'.1),
    StaffModel('1'.1),
    StaffModel('2'.3),
    StaffModel('3'.4),
    StaffModel('4'.2),
    StaffModel('5'.3),
    StaffModel('6'.2),
    StaffModel('e'.1),
    StaffModel('g'.1),
    StaffModel("\ $".3),
    StaffModel('e'.2),
    StaffModel('g'.3),
    StaffModel('e'.4),
    StaffModel('h'.4),
    StaffModel('f'.0)];@override
  Widget build(BuildContext context) {
    return RepaintBoundary(
      child: CustomPaint(
        size: size,
        painter: BaseLinePainter(12.24, _list.length),
        child: RepaintBoundary(
          child: CustomPaint(
            size: size,
            painter: NotesPainter(_list, 12.24(), ((), ((), ((), ((), (() }}Copy the code

Related resources and code

Notes are in the AKVO font package, and below is the akVO key comparison table;As for fonts, you can download them from the Internet, but you can’t find them:Pan.baidu.com/s/1HVRHWi1K…Extraction code: 62DQ

Here is the complete code:

class Staff extends StatelessWidget {
  Staff(this.size, {Key? key}) : super(key: key);
  final Size size;
  final List<StaffModel> _list = [
    StaffModel('e'.1),
    StaffModel('e'.2),
    StaffModel('g'.3),
    StaffModel('e'.4),
    StaffModel('h'.4),
    StaffModel('f'.0),
    StaffModel('e'.1),
    StaffModel('e'.1),
    StaffModel('1'.1),
    StaffModel('2'.3),
    StaffModel('3'.4),
    StaffModel('4'.2),
    StaffModel('5'.3),
    StaffModel('6'.2),
    StaffModel('e'.1),
    StaffModel('g'.1),
    StaffModel("\ $".3),
    StaffModel('e'.2),
    StaffModel('g'.3),
    StaffModel('e'.4),
    StaffModel('h'.4),
    StaffModel('f'.0)];@override
  Widget build(BuildContext context) {
    return RepaintBoundary(
      child: CustomPaint(
        size: size,
        painter: BaseLinePainter(12.24, _list.length),
        child: RepaintBoundary(
          child: CustomPaint(
            size: size,
            painter: NotesPainter(_list, 12.24(), ((), ((), ((), ((), (() }}class StaffModel {
  final String note; / / notes.
  final int level; // Note display levels 1, 2, 3, 4, 5

  StaffModel(this.note, this.level);
}
Copy the code

Write in the last

As I have always emphasized, the writing of staff this time is purely a simple technical pre-research, and the usability of the code is not strong, for only one reason: there is no research in the field of music and notes. I need to know something about this, or at least how to draw a staff properly, to make a good wheel. Therefore, I very much hope that:

  1. In the field of music has the relevant understanding, can let me take 😋;
  2. At the same time, I think there is commercial/technical value to explore in this field. I will go deep into open source, and I am looking for people with lofty ideals to join us.

Let’s talk about what’s going on. The writing of 😄 special effects should take a while, the previous several special effects UI will be optimized and released as pub during the National Day holiday. I have been reloading Android recently. Next, I will record the process of learning Android from 0 to 0.5 as a Flutter developer and share it with you.

Good, younger brother teach fish to swim, hope can study progress together!!