web analytics

Flutter Project : ทำ The Matrix Wallpaper

cover2

 

สวัสดีครับ ช่วงนี้เหนื่อยๆ เครียดๆ วันนี้เลยหาอะไรทำแก้เครียดเสียหน่อย ไม่รู้อะไรดลใจที่เห็นหน้าจอ Desktop ของผมที่ตั้งเป็นรูป The Matrix เอาไว้  ก็เลยได้ความคิดว่า ลองทำ The Matrix Wallpaper ใน Flutter กันดีกว่า ทำสนุกๆแบบง่ายๆ

ขอแปะรูป Desktop บันทึกไว้หน่อย

j1

 

เริ่มต้น

เริ่มต้นจากแสดง Text เป็นแบบตาราง แสดงให้เต็มจอ โดยลอง fix เลข row , col ไปก่อน
ใส่พื้นหลังสีดำ ตัวหนังสือสีเขียว

 

class _MyMatrixPageState extends State<MyMatrixPage> {

  static const int ROW = 54;
  static const int COL = 32;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Container(color: Colors.black, child: buildMatrixTable())
    );
  }

  Column buildMatrixTable() {
    List<Widget> listRow = List();
    for (int row = 0; row < ROW; row++) {
      List<Widget> listCol = List();
      for (int col = 0; col < COL; col++) {
        listCol.add(Container(width: 12, height: 12,
            child: Text("A", style: TextStyle(color: Color(0xff3b6d43)))));
      }
      listRow.add(Row(children: listCol));
    }
    return Column(children: listRow);
  }
}

 

จะได้แบบนี้

1

 

สร้างคลาสสำหรับเก็บค่าแต่ละช่อง ในที่นี้ผมใช้ชื่อว่า Pixel ก็เก็บแค่ตัวหนังสือในช่อง

class Pixel {
  String data;

  Pixel({this.data = ""});
}

 

ประกาศให้ Pixel เป็น array 2 มิติ ชื่อ matrixTable

class _MyMatrixPageState extends State<MyMatrixPage> {

  static const int ROW = 54;
  static const int COL = 32;

  List<List<Pixel>> matrixTable;

 

ทำการ initMatrixTable ก็คือสร้าง instance ให้กับตารางให้ครบตาม row , col
แล้วก็ผมเพิ่ม method การสุ่มค่าด้วย แล้วกำหนดค่าให้ Pixel

  initState() {
    initMatrixTable();
    super.initState();
  }

  String randomData() {
    const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    Random rand = Random();
    return chars[rand.nextInt(chars.length)];
  }

  void initMatrixTable() {
    matrixTable = List();
    for (int row = 0; row < ROW; row++) {
      List<Pixel> list = List();
      for (int col = 0; col < COL; col++) {
        list.add(Pixel(data: randomData()));
      }
      matrixTable.add(list);
    }
  }

 

จากนั้นที่ build() ก็กำหนด matrixTable ให้กับ Text

Column buildMatrixTable() {
    ...
            child: Text(matrixTable[row][col].data,
    ...
  }

 

ได้ผลลัพธ์เป็นแบบนี้

2

 

 

ทำให้เคลื่อนไหว

ต่อมาทำส่วนให้ มีแถวเคลื่อนที่ลงมาจากข้างบนลงด้านล่าง
สร้างคลาส Line โดยจะเก็บตำแหน่งของหัวเอาไว้ โดยหัวจะอยู่ด้านล่างนะ ก็คือ index ของ row จะ +1 เรื่อยๆ จนสิ้นสุด

class Line {
  int rowHead;
  int colHead;
  int size;

  Line({this.rowHead = 0, this.colHead = 0, this.size = 10});

  moveDown() {
    rowHead = rowHead + 1;
  }

}

 

จากนั้นเพิ่ม Timer ให้ทำงานวนไปตามเวลาที่กำหนด ผมลองกำหนด 500 ms ก่อน แล้วก็เพิ่ม ตัวแปร Line
ตอนนี้ราจะลองก่อน 1 แถว โดยทุกๆเวลาที่กำหนดจะรัน method process() ซึ่งจะทำการขยับแถวลงมา พร้อมสุ่มค่าใหม่ให้กับ Pixel

class _MyMatrixPageState extends State<MyMatrixPage> {

  ...

  Timer timer;
  Line line;

  initState() {
    initMatrixTable();
    line = Line(rowHead: 10, colHead: 10, size: 10);

    timer = Timer.periodic(
        Duration(milliseconds: 500), (Timer t) => process());
    super.initState();
  }

  process() {
    setState(() {
      initMatrixTable();
      addLineToMatrixTable(matrixTable, line);
      line.moveDown();
    });
  }

  void addLineToMatrixTable(List<List<Pixel>> matrixTable, Line line) {
    for (int i = 0; i < line.size; i++) {
      int row = line.rowHead - i;
      if (row >= 0) {
        matrixTable[row][line.colHead].data = randomData();
      }
    }
  }

  void initMatrixTable() {
    matrixTable = List();
    for (int row = 0; row < ROW; row++) {
      List<Pixel> list = List();
      for (int col = 0; col < COL; col++) {
        list.add(Pixel());
      }
      matrixTable.add(list);
    }
  }

  String randomData() {
    const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    Random rand = Random();
    return chars[rand.nextInt(chars.length)];
  }

 

ลองรัน แถวเลื่อนลงมาแล้ว

a1

 

เพิ่มแถว

เพิ่มแถวให้มากขึ้น โดยเปลี่ยนเป็น List<Line> แทน

class _MyMatrixPageState extends State<MyMatrixPage> {

  ...
  List<Line> listLine;

 

เพิ่มการ initLine โดยวนลูปเพิ่ม Line เข้าไปใน list พร้อมสุ่มตำแหน่ง และขนาด

  initState() {
    ...
    initLine();
    ...
    super.initState();
  }

  void initLine() {
    listLine = List();
    for (int i = 0; i < 30; i++) {
      listLine.add(Line(
          rowHead: randomNumber(max: ROW),
          colHead: randomNumber(max: COL),
          size: randomNumber(min: 10, max: 20)));
    }
  }

  int randomNumber({int min = 0, int max = 10}) {
    Random rand = Random();
    return rand.nextInt(max) + min;
  }

 

  process() {
    setState(() {
      initMatrixTable();

      for (Line line in listLine) {
        addLineToMatrixTable(matrixTable, line);
        line.moveDown();
      }
    });
  }

 

ดูเหมือน Matrix มากขึ้นอีกนิด

a2

 

เพิ่มเงา

Matrix เรายังไม่เนียนเท่าไหร่ เพราะเรากำหนดสีตัว Text ตายตัว ดังนั้นน่าจะปรับสีไล่เฉดสักหน่อย
เพิ่มตัวแปร color ให้ Pixel

class Pixel {
  String data;
  Color color;

  Pixel({this.data = "", this.color = Colors.black});
}

 

กำหนดสีในแต่ละ Index ของแถวไล่สีจากเข้มไปจาง
โดยใน Flutter จะกำหนด Alpha ได้จากค่าหลังตัว x เช่น 0xAARRGGBB

class _MyMatrixPageState extends State<MyMatrixPage> {

  ...

  List<Color> color = [
    Color(0xff3b6d43),
    Color(0xff3b6d43),
    Color(0xff3b6d43),
    Color(0xff3b6d43),
    Color(0xee3b6d43),
    Color(0xdd3b6d43),
    Color(0xcc3b6d43),
    Color(0xbb3b6d43),
    Color(0xaa3b6d43),
    Color(0x993b6d43),
    Color(0x883b6d43),
    Color(0x773b6d43),
    Color(0x663b6d43),
    Color(0x553b6d43),
    Color(0x443b6d43),
    Color(0x333b6d43),
    Color(0x223b6d43)
  ];

 

ปรับให้ตอน addLineToMatrixTable เพิ่มสีลงไปด้วย

  void addLineToMatrixTable(List<List<Pixel>> matrixTable, Line line) {
    for (int i = 0; i < line.size; i++) {
      int row = line.rowHead - i;
      if (row >= 0 && row < ROW) {
        matrixTable[row][line.colHead].data = randomData();

        // Add 
        if(i < color.length) {
          matrixTable[row][line.colHead].color = color[i];
        }else{
          matrixTable[row][line.colHead].color = color.last;
        }
      }
    }
  }

 

จากนั้น ก็เอาสีของ matrixTable ไปใส่ Text ด้วย

 Column buildMatrixTable() {
    ...
            child: Text(matrixTable[row][col].data,
                style: TextStyle(color: matrixTable[row][col].color)))); 
      ...
  }

 

มาแล้ว Matrix

a3

 

เริ่มแถวใหม่ เมื่อจบ

ตอนนี้แถวเมื่อเลื่อนมาจนสุดก็จะหายไปเลย เพราะ index มันเลยค่าของ ROW
ดังนั้นเรามาปรับให้เมื่อแถวเลื่อนลงมาจนสุดก็ ลบแถวนั้นแล้วสุ่มแถวใหม่ลงมาใน list แทน
แค่นี้ Matrix เราจะรันไปเรื่อยๆแล้ว

  process() {
    setState(() {
      initMatrixTable();

      for (Line line in listLine) {
        addLineToMatrixTable(matrixTable, line);
        line.moveDown();

        if (line.rowHead - line.size >= ROW) {
          listLine.remove(line);
          listLine.add(randomLine(maxRow: 1));
        }
      }
    });
  }

  Line randomLine({int maxRow = ROW, int maxCol = COL}) {
    return Line(
        rowHead: randomNumber(max: maxRow),
        colHead: randomNumber(max: maxCol),
        size: randomNumber(min: 10, max: 20));
  }

 

เพิ่อความสวยงาม ผมชอบสีสดๆ เลยขอปรับเป็นสีสดๆขึ้นหน่อย

  List<Color> color = [
    Color(0xff10d630),
    Color(0xff10d630),
    Color(0xff10d630),
    Color(0xff10d630),
    Color(0xee10d630),
    Color(0xdd10d630),
    Color(0xcc10d630),
    Color(0xbb10d630),
    Color(0xaa10d630),
    Color(0x9910d630),
    Color(0x8810d630),
    Color(0x7710d630),
    Color(0x6610d630),
    Color(0x5510d630),
    Color(0x4410d630),
    Color(0x3310d630),
    Color(0x2210d630)
  ];

 

ได้แบบนี้

a4

ทำให้รองรับทุกขนาดหน้าจอ

ตอนนี้เรากำหนด row ,col ตายตัว ทำให้เมื่อไปรันบนแท็บเลต มันเลยแสดงไม่ถูกต้อง

3

 

ปรับให้ ค่า ROW ,COL มาจากขนาดความกว้าง สูง ของจอ
โดยหารกับ 12 เพราะ ช่องนึงมัน 12×12

~/ คือ การหารแล้ว cast เป็น int 

  Widget buildMatrixTable() {
    ROW = MediaQuery
        .of(context)
        .size
        .height ~/ 12;

    COL = MediaQuery
        .of(context)
        .size
        .width ~/ 12;

    if (matrixTable != null) {
      ...

      return Column(children: listRow);
    } else {
      return Container();
    }
  }

 

ปัญหาต่อมาคือ ในเมื่อเราไม่รู้ row ,col ทำให้ไม่สามารถ initMatrixTable ใน initState ได้

  initState() {
    timer = Timer.periodic(
        Duration(milliseconds: 250), (Timer t) => process());
    super.initState();
  }

 

ให้มาทำใน process() แทน

  process() {
    if (matrixTable == null ) {
      initMatrixTable();
      initLine();
    }

    ...
  }

 

มาแล้ว แต่ดูมันโล่งๆ เพราะเรากำหนดจำนวน Line ตายตัว ตอน initLine

4

 

ดังนั้น น่าจะปรับจำนวนตาม ความกว้างของเจอ สัก 2 เท่าของ COL กำลังดี

  void initLine() {
    listLine = List();
    for (int i = 0; i < COL * 2; i++) {
      listLine.add(randomLine(maxRow: ROW, maxCol: COL));
    }
  }

a5

 

ทำให้เลื่อนไม่เท่ากัน

เพิ่มเติมอีกนิด ตอนนี้แถวทุกแถวมันเลื่อนในอัตราที่เท่ากัน ก็ปรับให้มีการสุ่มการเลื่อน
20% เลื่อน 2 ช่อง
60 % เลื่อน 1 ช่อง
20% ไม่เลื่อน

  process() {
    ...

    setState(() {
      initMatrixTable();

      for (Line line in listLine) {
        addLineToMatrixTable(matrixTable, line);

        Random random = Random();
        int number = random.nextInt(100);
        if (number < 50) {
          line.moveDown(move: 2);
        }  else if (number < 80) {
          line.moveDown();
        } else {
          //
        }
    ...
}

 

แถวมันเลื่อนไม่เท่ากันก็จริง แต่อัตราการอัพเดทแถวยังเท่ากัน ดังนั้นเลยปรับให้มี process เพื่มอีกตัว ให้เวลามันเหลื่อมๆกันนิดหน่อย

  initState() {
    timer = Timer.periodic(Duration(milliseconds: 500), (Timer t) => process());
    timer2 = Timer.periodic(Duration(milliseconds: 300), (Timer t) => process());
    super.initState();
  }

a6

 

 

จบแล้ว

บล็อกนี้เป็นโปรเจคสนุกๆ เล็กๆ ง่ายๆ เกี่ยวกับการสร้าง Matrix Wallpaper ครับ จริงๆแล้ววิธีการนี้น่าจะยังไม่ดีเท่าไหร่ เพราะต้องวนลูปจำนวนมาก แล้วก็จริงๆอยากจะทำ matrixtable ซ้อนไปด้านหลังอีกชั้น แล้วเอา Stack ครอบ จะได้เหมือน 3 มิติ มากขึ้น แต่ว่าดูเหมือนคอมผมจะเอาไม่ไหวแล้ว พอเท่านี้ก่อนละกัน หวังว่าจะเป็นไอเดียให้กับผู้อ่านครับ (:

โค้ดอยู่ที่ Github ครับ
https://github.com/benznest/the-matrix-wallpaper-flutter