Author: doanquyet

  • Mô hình phát triển ứng dụng

    Mô hình phát triển ứng dụng

    Mục đích của blog này là cung cấp phân tích chi tiết về phân tích mô hình phát triển di động và các lựa chọn công nghệ, khuôn khổ khác nhau. Điều này sẽ giúp xác định con đường để chọn dựa trên các thông lệ kinh doanh và kỹ thuật.

    CÁC MÔ HÌNH PHÁT TRIỂN ỨNG DỤNG

    Cung cấp trải nghiệm di động đẳng cấp thế giới cho người dùng cuối liên quan đến các mô hình phát triển Ứng dụng khác nhau. Có 5 mô hình phát triển ứng dụng tồn tại:

    1. Phát triển ứng dụng nền tảng(Native App Development)
    2. Phát triển ứng dụng kết hợp (Hybrid App Development)
    3. Phát triển ứng dụng nền tảng chéo (Cross Platform App Developmen)
    4. Phát triển PWA (PWA Development)
    5. Phát triển ứng dụng dành cho máy tính (Desktop App Development)

    Native App Development
    Phát triển ứng dụng nền tảng liên quan đến việc tạo ra một ứng dụng di động chỉ phù hợp với một nền tảng. Trong trường hợp này, chúng ta sử dụng ngôn ngữ lập trình riêng cho hệ điều hành. Nó có thể là Java hoặc Kotlin cho Android và Swift / Objective-C cho iOS. Native Development có quyền truy cập vào bộ tính năng đầy đủ của thiết bị và do đó cho phép chúng tôi tận dụng chức năng nâng cao có hiệu suất cao.
    Công cụ sử dụng: XCode, ATOM, Android Studio, Android IDE.
    Ví dụ: Ghim sở thích, Cleartrip, v.v.

    Hybrid App Development
    Các ứng dụng kết hợp kết hợp các yếu tố web với các yếu tố di động. Vì vậy, bạn tạo một cơ sở mã bằng cách sử dụng các công nghệ web tiêu chuẩn (HTML, CSS, JavaScript). Sau đó, bạn bọc nó bên trong một vùng chứa gốc – WebView. Các ứng dụng kết hợp chạy trong một trình duyệt toàn màn hình, được gọi là webview, ẩn đối với người dùng. Thông qua các plugin gốc có thể tùy chỉnh, họ có thể truy cập các tính năng gốc của thiết bị di động cụ thể (chẳng hạn như máy ảnh hoặc ID cảm ứng) mà không cần mã lõi bị ràng buộc với thiết bị đó.
    Công cụ sử dụng: Ionic, Cordova, v.v.
    Ví dụ: Evernote, Gmail, v.v.

    Cross Platform App Development
    Phương pháp tiếp cận dành cho nhà phát triển đa nền tảng sử dụng công cụ kết xuất gốc. Cơ sở mã được viết bằng ngôn ngữ lập trình phổ biến (Phụ thuộc vào khuôn khổ) kết nối với các thành phần gốc thông qua cái gọi là cầu nối. Điều này cung cấp UX gần giống với nguyên bản. Ứng dụng đa nền tảng không có móc nối nền tảng. Chúng cung cấp chức năng liền mạch, dễ dàng triển khai và sản xuất hiệu quả về chi phí.
    Các công cụ sử dụng: React Native, NativeScripts, Flutter, v.v.
    Ví dụ: Uber Eats, GoogleAds, Alibaba, Instagram, v.v.

    Progressive Web App Development
    PWAs  cung cấp một cách tiếp cận thay thế cho phát triển ứng dụng di động truyền thống bằng cách bỏ qua việc phân phối cửa hàng ứng dụng và cài đặt ứng dụng. PWA là các ứng dụng web sử dụng một tập hợp các khả năng của trình duyệt – chẳng hạn như làm việc ngoại tuyến, chạy quy trình nền và thêm liên kết vào màn hình chính của thiết bị – để cung cấp trải nghiệm người dùng ‘giống ứng dụng’.
    Công cụ sử dụng: React, Angular JS, v.v.
    Ví dụ: Flipkart, Ali Express, Hầu hết được thấy trong nền tảng thương mại điện tử.

    Desktop App Development

    Phát triển ứng dụng dành cho máy tính đã tăng mức độ áp dụng trong những năm gần đây và có rất nhiều sự phát triển đang diễn ra để tạo ra ứng dụng máy tính đa nền tảng, như chuông, Slack, Spotify, v.v.
    Công cụ sử dụng: Electron cho ứng dụng máy tính đa nền tảng
    Ví dụ: Chime, Slack vv

    CÁCH LỰA CHỌN MÔ HÌNH PHÁT TRIỂN – SO SÁNH

    Trong phần này, chúng tôi sẽ phân tích một số lựa chọn khung và đánh giá chúng về kỹ thuật và phi kỹ thuật, đối với nghiên cứu này, chúng tôi đã chọn 3 mô hình phát triển:

    1. Phát triển ứng dụng nền tảng(iOS và Android)
    2. Ứng dụng kết hợp (Cordova)
    3. Ứng dụng đa nền tảng (React Native, Flutter)

    Phân tích kỹ thuật

    Hiệu suất ứng dụng

    Hình 1.png
    1. Ứng dụng Hyrbid: Hiệu suất của Ứng dụng kết hợp bị tụt hậu so với nền tảng phát triển ứng dụng khác vì đó là chế độ xem web được bao bọc, Vỏ ứng dụng đang tương tác với thành phần gốc và do đó làm giảm hiệu suất ứng dụng tổng thể, đã nói rằng điều này cũng có thể dựa vào cách chúng tôi viết mã chế độ xem web, tất cả chức năng gốc mà chúng tôi đang xây dựng, v.v.
    2. Ứng dụng nền tảng chéo: Nền tảng chéo cung cấp gần với hiệu suất ứng dụng gốc thực và đó là lý do tại sao hiệu suất tốt hơn so với kết hợp. Một trong những lý do là mã chúng ta viết trong khung chọn sẽ được biên dịch nguyên bản, có một chút khác biệt về hiệu suất tùy thuộc vào khung mà chúng ta chọn.
      2.1. Flutter: Flutter sử dụng khung công tác Dart có hầu hết các thành phần có sẵn nên thường không yêu cầu cầu nối để giao tiếp với các mô-đun gốc, Dark Framework sử dụng Skia một công cụ c ++ và tất cả mã được biên dịch sẽ ở trong skia để có hiệu suất tốt hơn.
      2.2. React Native: Kiến trúc React Native chủ yếu dựa vào kiến ​​trúc môi trường thời gian chạy JS, còn được gọi là cầu nối JavaScript. Mã JavaScript được biên dịch thành mã gốc trong thời gian chạy. Tóm lại, React Native sử dụng cầu nối JavaScript để giao tiếp với các mô-đun gốc.
    3. Ứng dụng Native: Bạn có thể không thể đánh bại các ngôn ngữ bản địa. Mã gốc được viết tốt sẽ luôn hoạt động hiệu quả hơn so với mã gốc đã được biên dịch hoặc mã kết hợp.

    Quyền truy cập tính năng gốc
    Hình 2.png

    1. Ứng dụng kết hợp: Hầu hết các ứng dụng kết hợp đều sử dụng wireova hoặc tụ điện cung cấp các API JS cho các ứng dụng vốn thường được gọi là thành phần gốc cho bất kỳ quyền truy cập tính năng Gốc nào, có rất nhiều plugin của bên thứ 3 được yêu cầu cho bất kỳ ứng dụng kết hợp nào để sử dụng cho quyền truy cập tính năng gốc, hầu hết của plugin tính năng gốc sẽ có thể sử dụng được nhưng nếu được yêu cầu, nhà phát triển ứng dụng cũng có thể viết các trình bao bọc của riêng mình xung quanh các chức năng gốc và sau đó đưa chúng vào mã.
    2. Ứng dụng nền tảng chéo: Nền tảng chéo cung cấp quyền truy cập tính năng gần giống như bản địa, rất khó sử dụng:
      2.1. Flutter: Flutter cung cấp các gói chính thức cho hầu hết các tính năng gốc phổ biến và dễ dàng có sẵn trong hệ sinh thái rộng lớn.
      2.2. React Native: React Native có tập hợp các gói bên thứ ba phong phú cũng như một số API có sẵn để truy cập tính năng gốc, dựa vào các plugin của bên thứ ba đi kèm với chi phí bảo trì.
    3. Ứng dụng Native: Bạn có thể không thể đánh bại các ngôn ngữ native, truy cập trực tiếp vào thành phần bản địa và tất cả phần phát hành thành phần mới của hệ điều hành sẽ có thể sử dụng đầu tiên trong Phát triển ứng dụng bản địa.

    Cơ sở mã đơn: Khả năng tái sử dụng
    Hình 3.png

    1. Ứng dụng Hyrbid: Ứng dụng kết hợp có khả năng phục hồi tuyệt vời vì đây là một chế độ xem web sử dụng HTML / CSS / JS (Tương tự như Web), nó đi kèm với cơ sở Mã đơn và mức độ khả năng phục hồi cao hơn.
    2. Cross Platform App: Cross Platform cũng có độ phục hồi tốt nhưng không bằng hybrid, vẫn còn một số đoạn mã sẽ được viết riêng cho iOS / Android.
      2.1. Flutter: Flutter widgets sử dụng thiết kế Material design trên cả hai nền tảng theo mặc định, có nghĩa là cùng một CX sẽ ở đó cho cả hai ứng dụng, nếu chúng ta cần thiết kế kiểu dáng khác nhau dựa trên nền tảng thì sẽ có một số công việc cần thiết dành riêng cho hệ điều hành.
      2.2. React Native: React Native cung cấp một số thành phần giao diện người dùng cơ bản nhưng hầu hết chúng cần tự tạo kiểu, mã có thể được khôi phục vì chúng tôi đang sử dụng langugae lập trình đơn lẻ nhưng khoảng ~ 15-20% mã cần phải dành riêng cho nền tảng.
    3. Ứng dụng Native: Rất khó để duy trì khả năng phục hồi trong phát triển ứng dụng gốc vì khung phát triển, ngôn ngữ khác nhau ở cả hai nền tảng.

    Học một lần, viết ở mọi nơi
    Hình 4.png

    1. Ứng dụng Hyrbid: Ứng dụng kết hợp tất cả đều được viết trong khung phát triển web và học một công nghệ giúp tạo ứng dụng trên tất cả các nền tảng.
    2. Ứng dụng đa nền tảng: Nền tảng chéo cũng sử dụng khái niệm tương tự, khung công tác khác nhau có các lựa chọn ngôn ngữ khác nhau nhưng ngôn ngữ duy nhất của nó được sử dụng để tạo ứng dụng trên tất cả các nền tảng. Ví dụ: Flutter sử dụng phi tiêu và React Native sử dụng JavaScript.
    3. Ứng dụng Native: Tốt trong việc phát triển ứng dụng Gốc như một khung phát triển, ngôn ngữ khác nhau ở cả hai nền tảng, chúng ta cần tìm hiểu sâu về cả ngôn ngữ và nền tảng, ví dụ: iOS sử dụng XCode + Swift, Android sử dụng Kotling / Java.

    Thành phần kiểu giao diện người dùng

    Hình 5.png
    1. Ứng dụng Hyrbid: Ứng dụng hỗn hợp có thành phần giao diện người dùng được tạo kiểu sẵn phụ thuộc vào khung giao diện người dùng bạn chọn, những cái nổi tiếng là ion. Những thành phần đó áp dụng cho cả hai nền tảng và mang lại trải nghiệm giống như bản địa về Kiểu giao diện người dùng.
    2. Ứng dụng đa nền tảng:
      2.1. Flutter: Flutter đã tích hợp sẵn các widget sử dụng thiết kế Material design và một số phong cách cupertino (cụ thể cho iOS) với tất cả các widget tích hợp này, bạn có thể tạo ứng dụng tuyệt vời mà chỉ cần tạo kiểu thủ công rất ít.
      2.2. React Native: Các bản soạn phong cách giao diện người dùng phong phú có sẵn, rất nhiều plugin của bên thứ ba có thể sử dụng được yêu cầu nhà phát triển ứng dụng cũng phải viết kiểu riêng của họ.
    3. Ứng dụng Native: Việc tuân thủ giao diện người dùng ứng dụng gốc là phổ biến và có thể sử dụng được cho tất cả các nền tảng gốc.

    Thư viện bên thứ ba / Hệ sinh thái
    Hình 6.png

    1. Tất cả các nền tảng: Tất cả các nền tảng đều đã trưởng thành và có sẵn hệ sinh thái tốt nếu nhà phát triển gặp khó khăn.

    Phân tích phi kỹ thuật

    Mức độ phổ biến / Hỗ trợ cộng đồng

    Hình 7.png
    1. Ứng dụng Hyrbid: Ứng dụng kết hợp có Mức độ phổ biến đã giảm dần trong một số năm khi các nhà phát triển trong gần như cùng thời gian có thể tạo ứng dụng giống như bản địa bằng cách chọn nền tảng phát triển ứng dụng khác.
    2. Ứng dụng nền tảng chéo: Nền tảng chéo đã cho thấy sự phổ biến to lớn sau khi ra mắt công khai phản ứng gốc vào năm 2015
      2.1. Flutter: Flutter được google ra mắt công khai vào khoảng năm 2017 và đã đạt được sự phát triển vượt bậc và rất được nhiều công ty lớn chấp nhận để tạo ứng dụng đa nền tảng.
      2.2. React Native: React Native dẫn đầu về phát triển ứng dụng, rất nhiều hỗ trợ từ cộng đồng, stackoverflow, github repositry (80k Stars trong github)
    3. Ứng dụng Native: Mặc dù việc phát triển ứng dụng khác đã phổ biến nhưng sự phát triển ứng dụng gốc vẫn đang bùng nổ ngày nay vì trải nghiệm và hiệu suất ứng dụng liền mạch. Rất nhiều công ty lớn đã thử sử dụng nền tảng kết hợp / chéo và cuối cùng đã tạo ra ứng dụng ở dạng bản địa. Ứng dụng ban đầu Ex Cleartrip là ứng dụng lai và sau đó được viết lại hoàn toàn bằng bản địa.
    Hình 8.png

    Thời gian đưa ra thị trường
    Hình 9.png

    1. Ứng dụng Hyrbid: Thời gian đưa ra thị trường sẽ ít hơn đối với việc phát triển Ứng dụng lai vì hệ số xem web và khả năng tái sử dụng của nó rất cao cũng như cơ sở mã duy nhất giúp thời gian đưa ra thị trường nhanh hơn.
    2. Ứng dụng Cross Platform: Thời gian tiếp thị trên nhiều nền tảng vẫn ít hơn so với ứng dụng gốc, lý do để đặt nó không ngang bằng với ứng dụng kết hợp là vẫn còn đường cong học tập ban đầu có thể làm tăng thời gian tiếp thị cho các ứng dụng này.
    3. Ứng dụng Native: Đường cong học tập, duy trì hai ngăn xếp khác nhau, cơ sở mã, v.v. khiến thời gian đưa ra thị trường chậm hơn tất cả các tùy chọn khác.

    Chi phí phát triển
    Hình 10.png

    1. Ứng dụng Hyrbid: Chi phí phát triển sẽ thấp hơn nhiều vì cùng một nhóm triển khai mã ứng dụng web có thể được sử dụng để tạo các trang web trong ứng dụng kết hợp.
    2. Ứng dụng Cross Platform: Chi phí đa nền tảng sẽ cao hơn một chút, vì sẽ có một số đường học tập về công nghệ mới và cũng yêu cầu một số kiến ​​thức phát triển ứng dụng gốc.
    3. Ứng dụng Native: Chi phí phát triển cao hơn vì bảo trì / phát triển hai cơ sở mã yêu cầu kiến ​​thức chuyên môn đặc biệt.

    Thuê mướn
    Hình 11.png

    1. Ứng dụng Hyrbid: Không cần thuê bất kỳ người mới nào vì cùng một nhà phát triển đang phát triển ứng dụng web có thể viết ứng dụng kết hợp.
    2. Ứng dụng đa nền tảng: có thể yêu cầu hoặc không vì nó yêu cầu phát triển khung cụ thể, chúng tôi đã thấy việc thuê tổ chức chỉ phản ứng nhà phát triển bản địa hoặc nhà phát triển chập chờn.
    3. Ứng dụng Native: Cần thuê hoặc duy trì hai nhóm dành riêng cho Android và iOS.

    Chi phí bảo trì
    Hình 12.png

    Thời gian phát triển
    Hình 13.png

    Trưởng thành
    Hình 14.png

    1. Ứng dụng Hyrbid: Ứng dụng kết hợp vẫn là ứng dụng đầu tiên ra đời sau khi bản địa vẫn chưa hoàn thiện về các tính năng truy cập bản địa.
    2. Ứng dụng nền tảng chéo: Nền tảng chéo đã cho thấy sự tuyệt vời và rất nhiều ứng dụng đa nền tảng như uber eat, instagram, quảng cáo goodle, alibaba được tạo ra ở đó ứng dụng trên ứng dụng đa nền tảng.
      2.1. Flutter: Flutter được google ra mắt công chúng vào khoảng năm 2017 và đã đạt được sự phát triển vượt bậc và nó rất được nhiều công ty lớn chấp nhận để tạo ra ứng dụng đa nền tảng, vì nó vẫn còn ở giai đoạn đầu, tôi chỉ xếp nó sau RN, điều này chắc chắn sẽ xảy ra trong thời gian tới.
      2.2. React Native: React Native dẫn đầu về phát triển ứng dụng, rất nhiều hỗ trợ từ cộng đồng, stackoverflow, github repositry (80k Stars trong github).
    3. Ứng dụng Native: Mặc dù việc phát triển ứng dụng khác đã phổ biến nhưng sự phát triển ứng dụng gốc vẫn đang bùng nổ ngày nay vì trải nghiệm và hiệu suất ứng dụng liền mạch. Các công ty phụ thuộc rất nhiều vào trải nghiệm di động chủ yếu chọn phát triển ứng dụng gốc như một cách thuần thục để tạo ứng dụng cho người dùng

    HIGH LEVEL DECISION TREE

    Hình 15.png
  • Stateful Builder có thể bước thay thế vị trí của Stateful Widget không?

    Để hiểu điều này trước tiên chúng ta hãy hiểu Stateful Builder là gì?

    StatefulBuilder là một widget có trạng thái có thể thay đổi (trạng thái có thể thay đổi), điều làm cho nó trở nên đặc biệt là chỉ xây dựng lại widget cụ thể được bao bọc trong Stateful Builder. Rất may là không có việc xây dựng lại toàn bộ widget, không có vấn đề gì widget được bao bọc trong Stateful Builder nằm trong Stateless hay stateful Widget.

    Bây giờ câu hỏi đặt ra là chúng ta biết rằng nó không thể xây dựng lại Stateless Widget cho đến khi có một phiên bản mới. (SetState không hoạt động)

    Stateful builder có sức mạnh của StateSetter chức năng được truyền cho trình xây dựng được sử dụng để gọi một quá trình xây dựng lại chính nó thay vì một trạng thái điển hình của toàn bộ Widget.

    StatefulBuilder(
    builder: (BuildContext context, StateSetter setState) => Checkbox(
      value: isChecked,
      onChanged: (value) {
        setState(() {
          isChecked = value;
        });
      },
    ),
    ),

    GHI NHỚ

    Bất kỳ biến nào đại diện cho trạng thái nên được giữ bên ngoài hàm build có nghĩa là Bạn phải khai báo biến bên ngoài phương thức build để thay đổi xảy ra.

    Như tôi đã đề cập ở trên, nó chỉ xây dựng lại một widget cụ thể. Nó chỉ cung cấp một thể hiện mới (State) cho widget cụ thể đó. vì vậy bạn đã gói tất cả widget (chế độ xem) mà bạn muốn thấy sự thay đổi trong StatefulBuilder.

    Để hiểu Tại sao Stateful Builder không thể thay thế một Stateful widget. Đầu tiên chúng ta hãy hiểu điều gì tạo nên sự khác biệt giữa Widget không trạng thái và Widget trạng thái.

    Bạn có nhận thấy trong phương pháp xây dựng ghi đè không trạng thái và mặt khác trong tiện ích con Stateful trước tiên sẽ ghi đè createState và sau đó ghi đè phương thức build? Điều xảy ra ngầm là khi widget không trạng thái nhận được một phiên bản mà hàm xây dựng của nó được gọi. và trong widget trạng thái trước tiên, hãy tạo thể hiện trạng thái để xây dựng hàm.Đó là cách setState đang hoạt động. Sau khi chạy setStatecreateState đưa ra một thể hiện mới để xây dựng lại phương thức và tiện ích con.

    Giờ chúng ta sẽ hiểu tại sao Stateful Builder không thể thay thế tiện ích Stateful.

    Nếu nó đang xây dựng lại phần cụ thể của cây widget. Tại sao chúng ta không thể sử dụng thay thế cho Stateful Widget?

    Nếu bạn lưu trữ bất kỳ trạng thái nào trong các biến cá thể trong widget không trạng thái, nó có thể bị mất bất cứ lúc nào vì hàm tạo cho widget không trạng thái có thể được gọi lặp lại, giống như phương thức build. Chỉ có Trạng thái phiên bản liên quan của tiện ích con Stateful được lưu giữ bởi Flutter.

    StatefulWidget vẫn được giữ nguyên trạng thái của nó ngay cả trong các lần xây dựng lại, đó là lý do tại sao đôi khi bạn thấy các hoạt động sai của TextEditorController trong Stateless Widget

    Hoặc nếu bạn không đủ khả năng sử dụng StatefulBuilder với Stateless Widget thì bạn phải lưu trạng thái bằng các khóa Toàn cục.

    Cảm ơn bạn đã đọc bài viết này nếu bạn thấy bất cứ điều gì có thể được cải thiện, vui lòng cho tôi biết, tôi rất muốn cải thiện.

  • Flutter Custom Radio Button với Custom Shapes

    Sự phát triển của Flutter đang trở nên phổ biến và ngày càng phổ biến hơn do có nhiều tùy biến, widget tùy chỉnh và cách tiếp cận rất dễ thực hiện. Hôm nay chúng ta sẽ tìm hiểu cách tạo nút radio tùy chỉnh Flutter với các hình dạng tùy chỉnh như hộp đựng hình vuông, hộp đựng hình tròn hoặc biểu tượng. Tôi đã cố gắng tạo ra các phương pháp khác nhau để chúng có thể được sử dụng theo nhu cầu và nhà phát triển sẽ học từ hướng dẫn này cũng có thể có ý tưởng tạo nút radio tùy chỉnh rung của riêng mình, với hình dạng hoặc tiện ích theo yêu cầu của mình.

    Ví dụ về Nút radio tùy chỉnh Flutter này sẽ tăng tốc độ phát triển của bạn và tiết kiệm thời gian của bạn bằng cách gọi nó trực tiếp như một phần tử con của bất kỳ tiện ích nào hoặc bằng cách thực hiện các phương thức tĩnh khi nó cần thiết. Bởi vì không có ứng dụng nào có thể được thực hiện mà không thực hiện bất kỳ widegt tùy chỉnh nào, tất cả các widget được tạo sẵn không phải lúc nào cũng giúp ích được. Vì vậy, học cách chế tạo widget tùy chỉnh là phần quan trọng nhất của quá trình phát triển Flagship.

    Hãy bắt đầu thực hiện từng bước để chúng ta có thể hiểu những gì cần phải làm để đạt được kết quả đầu ra mong muốn. Đầu tiên, chúng ta sẽ tạo widget home, nơi chúng ta sẽ hiển thị các nút radio tùy chỉnh.

    Nó là một widget toàn diện có khả năng xử lý tất cả các chức năng của nút radio, như hiển thị thiết kế tùy chỉnh, sự kiện chạm và lựa chọn, v.v.

    home_screen.dart

    class HomeScreen extends StatefulWidget {
      const HomeScreen({Key? key}) : super(key: key);
      @override
      _HomeScreenState createState() => _HomeScreenState();
    }
    class _HomeScreenState extends State {
      List radioOptions = [];
      @override
      void initState() {
        super.initState();
        radioOptions.add(RadioButtonModel(false, 'A', 'Option A'));
        radioOptions.add(RadioButtonModel(false, 'B', 'Option B'));
        radioOptions.add(RadioButtonModel(false, 'C', 'Option C'));
        radioOptions.add(RadioButtonModel(false, 'D', 'Option D'));
      }
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('Custom Check Box Example'),
          ),
          body: MyRadioButtonWidget(
            options: radioOptions,
            onItemSelected: (index) {
              print('I am index: $index');
            },
          ),
        );
      }
    }

    Giờ hãy tạo nút radio tùy chỉnh

    Lớp này chịu trách nhiệm xử lý tất cả các hoạt động của nút radio tùy chỉnh, như thiết kế, sự kiện và gọi lại. Nếu bạn nhận thấy, tôi đã thêm một chức năng gọi lại để trả về chỉ mục mục được nhấn hiện tại. Trong trường hợp bạn muốn làm cho lớp nút radio tùy chỉnh của mình độc lập và chung chung, bạn có thể tạo cho các sự kiện theo yêu cầu của mình.

    final Function(int)? onItemSelected;

    my_radio_button_widget.dart

    class MyRadioButtonWidget extends StatefulWidget {
      final List<RadioButtonModel>? options;
      final Function(int)? onItemSelected;
      const MyRadioButtonWidget({Key? key, this.options, this.onItemSelected})
          : super(key: key);
      @override
      createState() {
        return MyRadioButtonWidgetState();
      }
    }
    class MyRadioButtonWidgetState extends State<MyRadioButtonWidget> {
      @override
      Widget build(BuildContext context) {
        return ListView.builder(
          itemCount: widget.options!.length,
          itemBuilder: (BuildContext context, int index) {
            return InkWell(
              onTap: () {
                setState(() {
                  for (var element in widget.options!) {
                    element.isSelected = false;
                  }
                  widget.options![index].isSelected = true;
                  widget.onItemSelected!(index);
                });
              },
              child: CircleRadioButtonItem(widget.options![index]),
            );
          },
        );
      }
    }

    Radio Button Model

    class RadioButtonModel {
      bool isSelected;
      final String buttonText;
      final String text;
      RadioButtonModel(this.isSelected, this.buttonText, this.text);
    }

    Bây giờ chúng tôi sẽ tạo các hình dạng tùy chỉnh cho nút radio của chúng ta, ta có nhiều tùy chọn, bạn có thể tìm thấy ba lớp khác nhau để thể hiện phong cách khác nhau. bằng cách sử dụng ý tưởng này, bạn có thể tự tạo và sửa đổi các ví dụ đã cho theo ý muốn.

    Nút radio vuông

    Ở đây chúng tôi đang tạo hộp chứa hình vuông với văn bản bên trong nó.

    class SquareRadioButtonItem extends StatelessWidget {
      final RadioButtonModel _item;
      const SquareRadioButtonItem(this._item, {Key? key}) : super(key: key);
      @override
      Widget build(BuildContext context) {
        return Container(
          margin: const EdgeInsets.all(15.0),
          child: Row(
            mainAxisSize: MainAxisSize.max,
            children: [
              Container(
                height: 50.0,
                width: 50.0,
                child: Center(
                  child: Text(_item.buttonText,
                      style: TextStyle(
                          color: _item.isSelected ? Colors.white : Colors.black,
                          //fontWeight: FontWeight.bold,
                          fontSize: 18.0)),
                ),
                decoration: BoxDecoration(
                  color: _item.isSelected ? Colors.blueAccent : Colors.transparent,
                  border: Border.all(
                      width: 1.0,
                      color: _item.isSelected ? Colors.blueAccent : Colors.grey),
                  borderRadius: const BorderRadius.all(Radius.circular(2.0)),
                ),
              ),
              Container(
                margin: const EdgeInsets.only(left: 10.0),
                child: Text(_item.text),
              )
            ],
          ),
        );
      }
    }

    Nút radio tròn

    class CircleRadioButtonItem extends StatelessWidget {
      final RadioButtonModel _item;
      const CircleRadioButtonItem(this._item, {Key? key}) : super(key: key);
      @override
      Widget build(BuildContext context) {
        return Container(
          margin: const EdgeInsets.all(15.0),
          child: Row(
            mainAxisSize: MainAxisSize.max,
            children: [
              Container(
                height: 50.0,
                width: 50.0,
                child: Center(
                  child: Text(_item.buttonText,
                      style: TextStyle(
                          color: _item.isSelected ? Colors.white : Colors.black,
                          //fontWeight: FontWeight.bold,
                          fontSize: 18.0)),
                ),
                decoration: BoxDecoration(
                  shape: BoxShape.circle,
                  color: _item.isSelected ? Colors.blueAccent : Colors.transparent,
                  border: Border.all(
                      width: 1.0,
                      color: _item.isSelected ? Colors.blueAccent : Colors.grey),
                ),
              ),
              Container(
                margin: const EdgeInsets.only(left: 10.0),
                child: Text(_item.text),
              )
            ],
          ),
        );
      }
    }

    Icon Radio

    class IconRadioButtonItem extends StatelessWidget {
      final RadioButtonModel _item;
      const IconRadioButtonItem(this._item, {Key? key}) : super(key: key);
      @override
      Widget build(BuildContext context) {
        return Container(
          margin: const EdgeInsets.all(15.0),
          child: Row(
            mainAxisSize: MainAxisSize.max,
            children: [
              _item.isSelected
                  ? const Icon(Icons.circle)
                  : const Icon(Icons.circle_outlined),
              Container(
                margin: const EdgeInsets.only(left: 10.0),
                child: Text(_item.text),
              )
            ],
          ),
        );
      }
    }

  • [Flutter]Push Notification

    [Flutter]Push Notification

    0. Thêm dependency

    Thêm flutter_local_notifications: ^9.1.2 vào pubspec.yaml.

    Đừng quên sử dụng Pub get để tải về.

    1. Thiết lập hình ảnh biểu tượng của bạn

    Thêm hình ảnh vào đường dẫn này:

    icon path: PROJECT_NAME\android\app\src\main\res\drawable\icon.png

    2. Tạo một class NotificationService

    Bạn sẽ nhập lớp này vào main.dart.(Chúng tôi sẽ thực hiện việc này ở bước sau 3.) Mã của lớp này ở cuối bài viết này.

    3. Thực hiện một số thao tác khởi tạo

    trong main()

    4. Chức năng hiển thị cuộc gọi trực tiếp ở bất cứ đâu khi bạn muốn

    showNotification()là một hàm của lớp NotificationService mà chúng ta vừa tạo trong tệp notification_service.dart.

    Ví dụ: tôi muốn thông báo đẩy khi tôi nhấp vào một nút, sau đó tôi có thể gọi chức năng này trong _onPressed()nút. Mã ở dưới đây, hãy kiểm tra nó.

    main.dart

    import 'package:flutter/material.dart';
    import 'service/notification_service.dart';
    
    Future<void> main() async{
      // Step 3. Initialization
      WidgetsFlutterBinding.ensureInitialized();
      NotificationService notificationService = NotificationService();
      await notificationService.init();
    
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return const MaterialApp(
          home: MyHomePage(),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      const MyHomePage({Key? key}) : super(key: key);
    
      @override
      State<MyHomePage> createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      /// Step 4. For pusing notification.
      NotificationService notificationService = NotificationService();
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text("My Main Page"),
          ),
          body: const Text('hello world'),
          floatingActionButton: FloatingActionButton(
            onPressed: () async{
              /// When I click the button, the notification will be pushed.
              /// Step 4. just call this function anywhere you want.
              await notificationService.showNotification(0, 'This is title...', "Tis is body...",);
            },
            child: const Icon(Icons.radio_button_on),
          ),
        );
      }
    }

    notification_service.dart

    import 'package:flutter_local_notifications/flutter_local_notifications.dart';
    /// Step 2. Create a NotificationService class
    class NotificationService {
      // Singleton pattern, https://en.wikipedia.org/wiki/Singleton_pattern
      // 1.The _internal() construction is just a name often given to constructors
      // that are private to the class
      // 2.Use the factory keyword when implementing a constructor
      // that does not create a new instance of its class.
      NotificationService._internal();
      static final NotificationService _notificationService = NotificationService._internal();
      factory NotificationService() {
        return _notificationService;
      }
    
      // Configuration for platform-specific initialization settings.
      Future<void> init() async {
        // Specifies the default icon for notifications.
        // icon path: PROJECT_NAME\android\app\src\main\res\drawable\icon.png
        const AndroidInitializationSettings androidInitializationSettings =
          AndroidInitializationSettings("icon");
        const InitializationSettings initializationSettings =
          InitializationSettings(android: androidInitializationSettings);
        await flutterLocalNotificationsPlugin.initialize(initializationSettings);
      }
    
      // Some configurations for platform-specific notification's details.
      static const AndroidNotificationDetails _androidNotificationDetails =
        AndroidNotificationDetails(
          'ChannelId',
          'ChannelName',
          channelDescription: "Responsible for all local notifications",
          playSound: true,
          priority: Priority.high,
          importance: Importance.high,
        );
      final NotificationDetails notificationDetails =
        const NotificationDetails(android: _androidNotificationDetails);
    
      // Now we need to call the show() method of FlutterLocalNotificationsPlugin.
      // Show() is responsible for showing the local notification.
      final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
      FlutterLocalNotificationsPlugin();
    
      Future<void> showNotification(int id, String title, String body) async {
        await flutterLocalNotificationsPlugin.show(id, title, body, notificationDetails);
      }
    }
  • Custom Paint trong Flutter

    Lớp con CustomPainter ghi đè hai phương thức: paint()và shouldRepaint().

    Hãy để tôi hướng dẫn bạn qua mã để hiểu rõ hơn về những gì đang xảy ra ở đây. Như bạn có thể thấy giao diện người dùng mà chúng ta sẽ đạt được theo đó, tôi đã tạo một lớp CustomPaintExample chứa mã cho giao diện người dùng. Có hai yếu tố chính, hình dạng và hoạt ảnh của nút. Nút khi được nhấn sẽ tăng giá trị percentValue lên 10 và theo đó có thể thấy tiến trình trên đường viền nút đang tăng lên. Tiện ích này cũng được chia thành hai phần tử CustomPainterExample.buildShape() và CustomPainterExample.buildButtonAnimation() tôi đã tạo trong các lớp khác nhau CustomPainterExampleWidgets.

    Trong Class CustomPainterExampleWidgets, Phần tử CustomPainterExample.buildShape()trả về một ClipPath bao gồm clipper CustomShapeClass và phần tử còn lại CustomPainterExample.buildButtonAnimation() bao gồm một Container với CustomPaint là phần tử con của nó hiển thị ở giữa màn hình. . Phần trăm tăng được hiển thị bằng phương thức onPressed tại nút Raised.

    import 'package:flutter/material.dart';
    class CustomShapeClass extends CustomClipper<Path> {
      @override
      getClip(Size size) {
        // TODO: implement getClip
        var path = new Path();
        path.lineTo(0, size.height / 4.25);
        var firstControlPoint = new Offset(size.width / 4, size.height / 3);
        var firstEndPoint = new Offset(size.width / 2, size.height / 3 - 60);
        var secondControlPoint =
        new Offset(size.width - (size.width / 4), size.height / 4 - 65);
        var secondEndPoint = new Offset(size.width, size.height / 3 - 40);
        path.quadraticBezierTo(firstControlPoint.dx, firstControlPoint.dy,
            firstEndPoint.dx, firstEndPoint.dy);
        path.quadraticBezierTo(secondControlPoint.dx, secondControlPoint.dy,
            secondEndPoint.dx, secondEndPoint.dy);
        path.lineTo(size.width, size.height / 3);
        path.lineTo(size.width, 0);
        path.close();
        return path;
      }
      @override
      bool shouldReclip(CustomClipper oldClipper)
      {
        return true;
      }
    }

    CustomShapeClass kế thừa CustomClipper được minh họa ở trên Trong điều khiển này, các điểm điều khiển và điểm kết thúc được xác định trong đó đường cong bezier bậc hai được vẽ. Bây giờ hãy khai báo lõi của ứng dụng lớp ButtonPainter

    import 'dart:math';
    import 'package:flutter/cupertino.dart';
    import 'package:flutter/material.dart';
    class ButtonPainter extends CustomPainter
    {
      Color buttonBorderColor;
      Color progressColor;
      double percentage;
      double width;
      ButtonPainter({this.buttonBorderColor,this.progressColor,this.percentage,this.width});
      @override
      void paint(Canvas canvas, Size size) {
        Paint line = Paint()
          ..color = buttonBorderColor
          ..strokeCap = StrokeCap.round
          ..style = PaintingStyle.stroke
          ..strokeWidth = width;
        Paint complete =  Paint()
          ..color = progressColor
          ..strokeCap = StrokeCap.round
          ..style = PaintingStyle.stroke
          ..strokeWidth = width;
        Offset center  = Offset(size.width/2, size.height/2);
        double radius  = min(size.width/2,size.height/2);
        canvas.drawCircle(
            center,
            radius,
            line
        );
        double arcAngle = 2*pi* (percentage/100);
        canvas.drawArc(
            Rect.fromCircle(center: center,radius: radius),
            -pi/2,
            arcAngle,
            false,
            complete
        );
      }
      @override
      bool shouldRepaint(CustomPainter oldDelegate) {
        return true;
      }
    }

    Ở đây chúng ta nhận được các thuộc tính khác nhau lineColor, có nghĩa là màu cho đường đi, trên đó tiến trình sẽ được vẽ, completeColor cho tiến độ đã hoàn thành, completePercent cho mức tiến độ đã hoàn thành. Từ tiện ích con của nó, chúng tôi gửi thuộc tính này xuống, ở đây chúng tôi chỉ đơn giản là vẽ.

    ButtonPainter kế thừa lớp CustomPainter và chúng ta cần ghi đè paint phương thức của nó để vẽ đồ họa. Để vẽ, ngay từ đầu, chúng ta cần thông báo một thứ gọi là Paint , nó định hình tất cả các thuộc tính dự kiến ​​sẽ vẽ thứ gì đó trên màn hình. Vì vậy, chúng ta có hai đối tượng Paint ; đầu tiên là một dòng nói lên đường theo dõi mà tiến trình sẽ được vẽ trên đó. Chúng tôi cung cấp cho nó một màu sắc, được cung cấp dưới dạng thuộc tính, strokeCap ​​là hình tròn và chúng tôi yêu cầu style là PaintingStyle cho một trong những cung cấp. Vì vậy, ngoài ra, chúng tôi thông báo đối tượng Paint hoàn chỉnh cho tiến trình đã hoàn thành.

    Tóm lại, chúng ta phải vẽ một vòng tròn đầy đủ và sau đó vẽ một vòng cung trên đó, thế là đã hoàn thành.

    Offset center  = Offset(size.width/2, size.height/2);
    double radius  = min(size.width/2,size.height/2);

    Trong hai đường thẳng này, chúng tôi tính toán các hướng cho tâm của hình tròn và bán kính cơ bản của nó, tâm là chiều rộng và chiều cao của thùng chứa chia cho 2. Chúng tôi cần chính xác tâm của các hình tròn ở giữa. size là những gì chúng ta nhận được dưới dạng một tham số trong hàm paint, là kích thước của thùng chứa. radius Đây là mức tối thiểu của một nửa chiều rộng và chiều cao và bây giờ chúng tôi đang lấy mức tối thiểu của cả hai vì vùng chứa có thể không phải luôn luôn là hình vuông

    canvas.drawCircle(center,radius,line);

    Bây giờ chúng ta chỉ vẽ với hàm drawCircle được gọi trên đối tượng canvas được cung cấp, chúng ta chuyền vào tâm, bán kính và đối tượng Paint .

    canvas.drawArc(Rect.fromCircle(center: center,radius: radius),
        -pi/2,arcAngle,false,complete);

    Bây giờ, chúng ta tìm góc cho cung tròn đã hoàn thành, mà nó đã xuất hiện. Tôi không đi sâu vào tính toán hình học. Để gọi drawArc, chúng ta cần chỉ định một Rect , là hộp giới hạn của cung tròn, chúng ta nhận được Rect bao gồm các tham số của đường tròn mà chúng ta đã vẽ trước đó. Tại thời điểm đó, chúng ta cho một góc bắt đầu là -pi / 2 radian, hãy nhớ nó không phải là 0. Đỉnh là -pi/2, 0 là đích ngoài cùng bên phải của đường tròn. Chúng tôi cung cấp arcAngle tại điểm đó, đó là cung sẽ kéo dài bao nhiêu. Sau đó, chúng tôi chuyền vào false để nói rằng chúng tôi không muốn phần cuối của vòng cung được kết nối trở lại trung tâm và cuối cùng chúng chuyền vào complete. Đó là tất cả những gì chúng tôi mong đợi để vẽ bằng cách sử dụng CustomPaint.

    Bây giờ, hãy thêm các hình ảnh động.

    percentageAnimationController = new AnimationController(vsync: this,
        duration: new Duration(milliseconds: 1000))
      ..addListener((){setState(() {
          percentage=lerpDouble(percentage,newPercentage,
          percentageAnimationController.value);});});

    Chúng tôi thêm hoạt ảnh trên màn hình HomePage trong initState(). Sau đó, chúng tôi đã thêm một trình nghe trên AnimationController đó được gọi ở mỗi bước của hoạt ảnh và chúng tôi thay đổi tỷ lệ phần trăm. Để thay đổi tỷ lệ phần trăm, chúng tôi nhận được một giá trị được chèn với sự trợ giúp của hàm lerpDouble , giá trị nằm trong percentage và newPercentage lớn hơn giá trị ban đầu 10, chúng ta nhận dựa trên giá trị AnimationController’s.

    Chúng ta thêm dòng này vào nút onPressed để bắt đầu hoạt ảnh.

    percentageAnimationController.forward(from: 0.0);

    Bạn sẽ thấy mã đầy đủ trên GitHub và đây là một ví dụ về quy trình vòng tròn nhỏ để tích hợp với custom paint và video dưới đây cho biết cách CustomPaint sẽ hoạt động.

    Hy vọng blog này sẽ cung cấp cho bạn đầy đủ thông tin trong việc thử Custom Paint trong các dự án hiện tại của bạn Cảm ơn vì đã đọc bài viết này

  • [Flutter] Bố cục Layout (Phần 3)

    Container

    Một trong những Widget được sử dụng nhiều nhất – và vì những lý do này:

    Container as a layout tool

    Khi bạn không chỉ định height và width của Container, nó sẽ khớp với child kích thước của nó

    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(title: Text('Container as a layout')),
        body: Container(
          color: Colors.yellowAccent,
          child: Text("Hi"),
        ),
      );
    }

    Nếu bạn muốn kéo dài Container để khớp với cha của nó, hãy sử dụng double.infinity cho các thuộc tính heightwidth

    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(title: Text('Container as a layout')),
        body: Container(
          height: double.infinity,
          width: double.infinity,
          color: Colors.yellowAccent,
          child: Text("Hi"),
        ),
      );
    }

    Container as decoration

    Bạn có thể sử dụng thuộc tính color để làm thay đổi màu nền của Container bằng decoration và foregroundDecoration.

    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(title: Text('Container.decoration')),
        body: Container(
          height: double.infinity,
          width: double.infinity,
          decoration: BoxDecoration(color: Colors.yellowAccent),
          child: Text("Hi"),
        ),
      );
    }

    . . .

    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(title: Text('Container.foregroundDecoration')),
        body: Container(
          height: double.infinity,
          width: double.infinity,
          decoration: BoxDecoration(color: Colors.yellowAccent),
          foregroundDecoration: BoxDecoration(
            color: Colors.red.withOpacity(0.5),
          ),
          child: Text("Hi"),
        ),
      );
    }

    Container as Transform

    Nếu bạn không muốn sử dụng widget Transform để thay đổi bố cục của mình, bạn có thể sử dụng transform thuộc tính từ Container

    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(title: Text('Container.transform')),
        body: Container(
          height: 300,
          width: 300,
          transform: Matrix4.rotationZ(pi / 4),
          decoration: BoxDecoration(color: Colors.yellowAccent),
          child: Text(
            "Hi",
            textAlign: TextAlign.center,
          ),
        ),
      );
    }

    . . .

    BoxDecoration

    BoxDecoration thường được sử dụng trên tiện ích Container để thay đổi diện mạo của Container.

    image: DecorationImage

    Đặt một hình ảnh làm nền:

    Scaffold(
      appBar: AppBar(title: Text('image: DecorationImage')),
      body: Center(
        child: Container(
          height: 200,
          width: 200,
          decoration: BoxDecoration(
            color: Colors.yellow,
            image: DecorationImage(
              fit: BoxFit.fitWidth,
              image: NetworkImage(
                'https://flutter.io/images/catalog-widget-placeholder.png',
              ),
            ),
          ),
        ),
      ),
    );

    border: Border

    Chỉ định đường viền của Container sẽ trông như thế nào.

    Scaffold(
      appBar: AppBar(title: Text('border: Border')),
      body: Center(
        child: Container(
          height: 200,
          width: 200,
          decoration: BoxDecoration(
            color: Colors.yellow,
            border: Border.all(color: Colors.black, width: 3),
          ),
        ),
      ),
    );

    borderRadius: BorderRadius

    Cho phép làm tròn các góc của đường viền.

    borderRadius không hoạt động nếu shape trong decoration là BoxShape.circle

    Scaffold(
      appBar: AppBar(title: Text('borderRadius: BorderRadius')),
      body: Center(
        child: Container(
          height: 200,
          width: 200,
          decoration: BoxDecoration(
            color: Colors.yellow,
            border: Border.all(color: Colors.black, width: 3),
            borderRadius: BorderRadius.all(Radius.circular(18)),
          ),
        ),
      ),
    );

    shape: BoxShape

    BoxDecoration có thể là hình chữ nhật / hình vuông hoặc hình elip / hình tròn.

    Đối với bất kỳ hình dạng nào khác, bạn có thể sử dụng ShapeDecoration thay thế cho BoxDecoration

    Scaffold(
      appBar: AppBar(title: Text('shape: BoxShape')),
      body: Center(
        child: Container(
          height: 200,
          width: 200,
          decoration: BoxDecoration(
            color: Colors.yellow,
            shape: BoxShape.circle,
          ),
        ),
      ),
    );

    boxShadow: List<BoxShadow>

    Thêm bóng vào Container .

    Tham số này là một danh sách vì bạn có thể chỉ định nhiều bóng khác nhau và hợp nhất chúng lại với nhau.

    Scaffold(
      appBar: AppBar(title: Text('boxShadow: List<BoxShadow>')),
      body: Center(
        child: Container(
          height: 200,
          width: 200,
          decoration: BoxDecoration(
            color: Colors.yellow,
            boxShadow: const [
              BoxShadow(blurRadius: 10),
            ],
          ),
        ),
      ),
    );

    gradient

    Có ba loại gradient LinearGradient:, RadialGradientvà SweepGradient.

    Scaffold(
      appBar: AppBar(title: Text('gradient: LinearGradient')),
      body: Center(
        child: Container(
          height: 200,
          width: 200,
          decoration: BoxDecoration(
            gradient: LinearGradient(
              colors: const [
                Colors.red,
                Colors.blue,
              ],
            ),
          ),
        ),
      ),
    );

    . . .

    Scaffold(
      appBar: AppBar(title: Text('gradient: RadialGradient')),
      body: Center(
        child: Container(
          height: 200,
          width: 200,
          decoration: BoxDecoration(
            gradient: RadialGradient(
              colors: const [Colors.yellow, Colors.blue],
              stops: const [0.4, 1.0],
            ),
          ),
        ),
      ),
    );

    . . .

    Scaffold(
      appBar: AppBar(title: Text('gradient: SweepGradient')),
      body: Center(
        child: Container(
          height: 200,
          width: 200,
          decoration: BoxDecoration(
            gradient: SweepGradient(
              colors: const [
                Colors.blue,
                Colors.green,
                Colors.yellow,
                Colors.red,
                Colors.blue,
              ],
              stops: const [0.0, 0.25, 0.5, 0.75, 1.0],
            ),
          ),
        ),
      ),
    );

    backgroundBlendMode

    backgroundBlendMode là tài sản phức tạp nhất của BoxDecoration.
    Nó chịu trách nhiệm trộn các màu / độ chuyển màu với nhau BoxDecorationvà bất cứ thứ gì BoxDecorationở trên cùng.

    Với backgroundBlendModebạn có thể sử dụng một danh sách dài các thuật toán được chỉ định trong BlendModeenum.

    Đầu tiên, hãy đặt BoxDecoration là foregroundDecoration

    Scaffold(
      appBar: AppBar(title: Text('backgroundBlendMode')),
      body: Center(
        child: Container(
          height: 200,
          width: 200,
          foregroundDecoration: BoxDecoration(
            backgroundBlendMode: BlendMode.exclusion,
            gradient: LinearGradient(
              colors: const [
                Colors.red,
                Colors.blue,
              ],
            ),
          ),
          child: Image.network(
            'https://flutter.io/images/catalog-widget-placeholder.png',
          ),
        ),
      ),
    );

    . . .

    Material

    Đường viền với các góc cắt

    Scaffold(
      appBar: AppBar(title: Text('shape: BeveledRectangleBorder')),
      body: Center(
        child: Material(
          shape: const BeveledRectangleBorder(
            borderRadius: BorderRadius.all(Radius.circular(20)),
            side: BorderSide(color: Colors.black, width: 4),
          ),
          color: Colors.yellow,
          child: Container(
            height: 200,
            width: 200,
          ),
        ),
      ),
    );

    . . .

    Slivers

    SliverFillRemaining

    Widget này không thể thay thế khi bạn muốn căn giữa nội dung của mình ngay cả khi không có đủ dung lượng cho nó. Ví dụ

    Đủ không gian theo chiều dọc
    Scaffold(
      appBar: AppBar(title: Text('SliverFillRemaining')),
      body: CustomScrollView(
        slivers: [
          SliverFillRemaining(
            hasScrollBody: false,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: const [
                FlutterLogo(size: 200),
                Text(
                  'This is some longest text that should be centered'
                  'together with the logo',
                  textAlign: TextAlign.center,
                ),
              ],
            ),
          ),
        ],
      ),
    );

    Trong trường hợp không có đủ không gian cho nội dung được căn giữa, nội dung SliverFillRemaining sẽ có thể cuộn được:

    Nếu không có SliverFillRemaining nội dung sẽ tràn như thế này:

    Làm đầy không gian còn lại

    Ngoài việc hữu ích cho việc căn giữa nội dung của bạn, SliverFillRemaining nó sẽ lấp đầy không gian trống còn lại của khung nhìn. Để làm được điều đó, widget này phải được đặt vào CustomScrollView và cần phải là mảnh cuối cùng

    Trong trường hợp không có đủ dung lượng, tiện ích con sẽ có thể cuộn được:

    Scaffold(
      appBar: AppBar(title: Text('SliverFillRemaining')),
      body: CustomScrollView(
        slivers: [
          SliverList(
            delegate: SliverChildListDelegate(const [
              ListTile(title: Text('First item')),
              ListTile(title: Text('Second item')),
              ListTile(title: Text('Third item')),
              ListTile(title: Text('Fourth item')),
            ]),
          ),
          SliverFillRemaining(
            hasScrollBody: false,
            child: Container(
              color: Colors.yellowAccent,
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: const [
                  FlutterLogo(size: 200),
                  Text(
                    'This is some longest text that should be centered'
                    'together with the logo',
                    textAlign: TextAlign.center,
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );

    . . .

    SizedBox

    Đây là một trong những Widget đơn giản nhưng hữu ích nhất

    SizedBox dưới dạng ConstrainedBox

    SizedBox có thể hoạt động theo cách tương tự như ConstrainedBox

    SizedBox.expand(
      child: Card(
        child: Text('Hello World!'),
        color: Colors.yellowAccent,
      ),
    ),

    . . .

    SizedBox dưới dạng padding

    Khi cần thêm phần đệm hoặc lề, bạn có thể chọn Padding hoặc Container các widget. Nhưng chúng có thể dài dòng hơn và khó đọc hơn so với việc thêm một Sizedbox

    Column(
      children: <Widget>[
        Icon(Icons.star, size: 50),
        const SizedBox(height: 100),
        Icon(Icons.star, size: 50),
        Icon(Icons.star, size: 50),
      ],
    ),

    SizedBox như một đối tượng vô hình

    Nhiều khi bạn muốn ẩn / hiện một tiện ích phụ thuộc vào bool

    Widget build(BuildContext context) {
      bool isVisible = ...
      return Scaffold(
        appBar: AppBar(
          title: Text('isVisible = $isVisible'),
        ),
        body: isVisible 
          ? Icon(Icons.star, size: 150) 
          : const SizedBox(),
      );
    }

    Vì SizedBox có một hàm const tạo nên việc sử dụng const SizedBox()thực sự rất tối ưu**.

    ** Một giải pháp tối ưu hơn sẽ là sử dụng Opacity widget và thay đổi opacity giá trị thành 0.0. Hạn chế của giải pháp này là tiện ích đã cho sẽ chỉ ẩn, vẫn chiếm không gian.

    . . .

    Trên các nền tảng khác nhau, có những khu vực đặc biệt như Thanh trạng thái trên Android hoặc Notch trên iPhone X mà chúng tôi có thể tránh vẽ dưới.

    Giải pháp cho vấn đề này là SafeArea widget (ví dụ không có / có SafeArea)

    Widget build(BuildContext context) {
      return Material(
        color: Colors.blue,
        child: SafeArea(
          child: SizedBox.expand(
            child: Card(color: Colors.yellowAccent),
          ),
        ),
      );
    }

    Kết thúc phần 3!

  • [Flutter] Bố cục Layout (Phần 2)

    [Flutter] Bố cục Layout (Phần 2)

    Tiếp tục phần trước, ta sẽ tiếp tục tìm hiểu thêm các widget giúp điều chỉnh bố cục hiển thị.

    IntrinsicWidth and IntrinsicHeight

    Tất cả tiện ích con bên trong Hàng hoặc Cột có chiều cao/rộng bằng tiện ích con cao nhất/rộng nhất

    Trong trường hợp bạn có kiểu bố trí này:

    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(title: Text('IntrinsicWidth')),
        body: Center(
          child: Column(
            children: <Widget>[
              RaisedButton(
                onPressed: () {},
                child: Text('Short'),
              ),
              RaisedButton(
                onPressed: () {},
                child: Text('A bit Longer'),
              ),
              RaisedButton(
                onPressed: () {},
                child: Text('The Longest text button'),
              ),
            ],
          ),
        ),
      );
    }

    Nhưng bạn muốn có tất cả các nút theo nút rộng nhất , chỉ cần sử dụng :IntrinsicWidth

    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(title: Text('IntrinsicWidth')),
        body: Center(
          child: IntrinsicWidth(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: <Widget>[
                RaisedButton(
                  onPressed: () {},
                  child: Text('Short'),
                ),
                RaisedButton(
                  onPressed: () {},
                  child: Text('A bit Longer'),
                ),
                RaisedButton(
                  onPressed: () {},
                  child: Text('The Longest text button'),
                ),
              ],
            ),
          ),
        ),
      );
    }

    Trong trường hợp bạn gặp vấn đề tương tự nhưng bạn muốn có tất cả các widget có chiều cao theo widget cao nhất chỉ cần sử dụng kết hợp của IntrinsicHeight và Row .

    IntrinsicWidthIntrinsicHeight còn được sử dụng để định kích thước con của nó theo chiều rộng/cao nội tại tối đa của nó. Nó có thể hữu ích nếu chiều rộng/cao khả dụng là không giới hạn, nhưng bạn muốn đặt kích thước của tiện ích con thành chiều rộng nội tại của nó.

    . . .

    Stack

    Chồng các Widget lên nhau

    @override
    Widget build(BuildContext context) {
      Widget main = Scaffold(
        appBar: AppBar(title: Text('Stack')),
      );
    
      return Stack(
        fit: StackFit.expand,
        children: <Widget>[
          main,
          Banner(
            message: "Top Start",
            location: BannerLocation.topStart,
          ),
          Banner(
            message: "Top End",
            location: BannerLocation.topEnd,
          ),
          Banner(
            message: "Bottom Start",
            location: BannerLocation.bottomStart,
          ),
          Banner(
            message: "Bottom End",
            location: BannerLocation.bottomEnd,
          ),
        ],
      );
    }

    . . .

    Bạn có thể các widget của mình vào Positioned Widget để xác định vị trí hiện thị

    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(title: Text('Stack')),
        body: Stack(
          fit: StackFit.expand,
          children: <Widget>[
            Material(color: Colors.yellowAccent),
            Positioned(
              top: 0,
              left: 0,
              child: Icon(Icons.star, size: 50),
            ),
            Positioned(
              top: 340,
              left: 250,
              child: Icon(Icons.call, size: 50),
            ),
          ],
        ),
      );
    }

    . . .

    Nếu bạn không muốn đoán các giá trị trên cùng/dưới cùng, bạn có thể sử dụng LayoutBuilder để lấy chúng

    Widget build(BuildContext context) {
      const iconSize = 50;
      return Scaffold(
        appBar: AppBar(title: Text('Stack with LayoutBuilder')),
        body: LayoutBuilder(
          builder: (context, constraints) =>
            Stack(
              fit: StackFit.expand,
              children: <Widget>[
                Material(color: Colors.yellowAccent),
                Positioned(
                  top: 0,
                  child: Icon(Icons.star, size: iconSize),
                ),
                Positioned(
                  top: constraints.maxHeight - iconSize,
                  left: constraints.maxWidth - iconSize,
                  child: Icon(Icons.call, size: iconSize),
                ),
              ],
            ),
        ),
      );
    }

    . . .

    Expanded

    Expanded hoạt động với bố cục Flex\Flexbox và rất tốt để phân phối không gian giữa nhiều mục.

    Row(
      children: <Widget>[
        Expanded(
          child: Container(
            decoration: const BoxDecoration(color: Colors.red),
          ),
          flex: 3,
        ),
        Expanded(
          child: Container(
            decoration: const BoxDecoration(color: Colors.green),
          ),
          flex: 2,
        ),
        Expanded(
          child: Container(
            decoration: const BoxDecoration(color: Colors.blue),
          ),
          flex: 1,
        ),
      ],
    ),

    . . .

    ConstrainedBox

    Theo mặc định, hầu hết các tiện ích con sẽ sử dụng ít dung lượng nhất có thể:

    Card(child: const Text('Hello World!'), color: Colors.yellow)

    . . .

    ConstrainedBox cho phép tiện ích con sử dụng không gian còn lại như mong muốn.

    ConstrainedBox( 
      constraints: BoxConstraints.expand(),
      child: const Card(
        child: const Text('Hello World!'), 
        color: Colors.yellow,
      ), 
    ),

    . . .

    Bằng cách sử dụng BoxConstraints, bạn chỉ định lượng không gian mà một widget có thể có – bạn chỉ định minmax của heightwidth.

    BoxConstraints.expand sử dụng lượng không gian vô hạn (tất cả khả dụng) trừ khi được chỉ định:

    ConstrainedBox(
      constraints: BoxConstraints.expand(height: 300),
      child: const Card(
        child: const Text('Hello World!'), 
        color: Colors.yellow,
      ),
    ),

    Và nó giống như:

    ConstrainedBox(
      constraints: BoxConstraints(
        minWidth: double.infinity,
        maxWidth: double.infinity,
        minHeight: 300,
        maxHeight: 300,
      ),
      child: const Card(
        child: const Text('Hello World!'), 
        color: Colors.yellow,
      ),
    ),

    . . .

    Align

    Đôi khi bạn gặp khó khăn trong việc đặt tiện ích con của chúng tôi ở kích thước phù hợp – ví dụ: nó liên tục bị kéo căng khi bạn không muốn:

    Ví dụ ở trên xảy ra khi bạn có Column với CrossAxisAlignment.stretch và bạn chỉ muốn nút không bị kéo căng:

    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(title: Text('Align: without Align')),
        body: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: <Widget>[
            Align(
              child: RaisedButton(
                onPressed: () {},
                child: const Text('Button'),
              ),
            ),
          ],
        ),
      );
    }

    Khi tiện ích con của bạn không tuân theo các ràng buộc mà bạn cố gắng thiết lập, trước tiên hãy thử kết hợp với nó Align

    Kết thúc phần 2!

    . . .

  • [Flutter] Bố cục Layout (Phần 1)

    [Flutter] Bố cục Layout (Phần 1)

    Bạn cần các mẫu bố cục đơn giản cho Flutter?
    Tôi giới thiệu cho bạn tập hợp các đoạn mã bố cục Flutter của tôi. Tôi sẽ giữ cho nó ngắn gọn, dễ hiểu và đơn giản với vô số ví dụ trực quan.

    Row và Column

    MainAxisAlignment

    Row /*or Column*/( 
      mainAxisAlignment: MainAxisAlignment.start,
      children: <Widget>[
        Icon(Icons.star, size: 50),
        Icon(Icons.star, size: 50),
        Icon(Icons.star, size: 50),
      ],
    ),

    . . .

    Row /*or Column*/( 
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Icon(Icons.star, size: 50),
        Icon(Icons.star, size: 50),
        Icon(Icons.star, size: 50),
      ],
    ),

    . . .

    Row /*or Column*/( 
      mainAxisAlignment: MainAxisAlignment.end,
      children: <Widget>[
        Icon(Icons.star, size: 50),
        Icon(Icons.star, size: 50),
        Icon(Icons.star, size: 50),
      ],
    ),

    . . .

    Row /*or Column*/( 
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: <Widget>[
        Icon(Icons.star, size: 50),
        Icon(Icons.star, size: 50),
        Icon(Icons.star, size: 50),
      ],
    ),

    . . .

    Row /*or Column*/( 
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: <Widget>[
        Icon(Icons.star, size: 50),
        Icon(Icons.star, size: 50),
        Icon(Icons.star, size: 50),
      ],
    ),

    . . .

    Row /*or Column*/( 
      mainAxisAlignment: MainAxisAlignment.spaceAround,
      children: <Widget>[
        Icon(Icons.star, size: 50),
        Icon(Icons.star, size: 50),
        Icon(Icons.star, size: 50),
      ],
    ),

    . . .

    CrossAxisAlignment

    Bạn nên sử dụng CrossAxisAlignment.baseline nếu bạn yêu cầu căn chỉnh đường cơ sở của các văn bản khác nhau

    Row(
      crossAxisAlignment: CrossAxisAlignment.baseline,
      textBaseline: TextBaseline.alphabetic,
      children: <Widget>[
        Text(
          'Baseline',
          style: Theme.of(context).textTheme.display3,
        ),
        Text(
          'Baseline',
          style: Theme.of(context).textTheme.body1,
        ),
      ],
    ),

    . . .

    Row /*or Column*/( 
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        Icon(Icons.star, size: 50),
        Icon(Icons.star, size: 200),
        Icon(Icons.star, size: 50),
      ],
    ),

    . . .

    Row /*or Column*/( 
      crossAxisAlignment: CrossAxisAlignment.center,
      children: <Widget>[
        Icon(Icons.star, size: 50),
        Icon(Icons.star, size: 200),
        Icon(Icons.star, size: 50),
      ],
    ),

    . . .

    Row /*or Column*/( 
      crossAxisAlignment: CrossAxisAlignment.end,
      children: <Widget>[
        Icon(Icons.star, size: 50),
        Icon(Icons.star, size: 200),
        Icon(Icons.star, size: 50),
      ],
    ),

    . . .

    Row /*or Column*/( 
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: <Widget>[
        Icon(Icons.star, size: 50),
        Icon(Icons.star, size: 200),
        Icon(Icons.star, size: 50),
      ],
    ),

    Kết thúc phần 1!

  • Backdrop Filter Widget trong Flutter

    Tạo hiệu ứng làm mờ bằng tiện ích BackdropFilter trong ứng dụng Flutter của bạn

    Chúng ta sẽ xem cách triển khai chương trình demo của BackdropFilter và hướng dẫn bạn cách sử dụng tiện ích đó để tạo hiệu ứng mờ trong các ứng dụng Flutter của bạn.

    Backdrop Filter Widget Flutter đang sử dụng để thực hiện các tác động làm mờ trên ảnh, container và mọi tiện ích. Tiện ích Backdrop Filter được sử dụng với sự kết hợp của lớp ImageFilter . Nó áp dụng một bộ lọc trên tiện ích con hiện tại và làm cho tác động mờ bên dưới tiện ích con hiện tại.

    Làm mờ hình ảnh:

    Chúng ta sẽ tạo tác động làm mờ sẽ được áp dụng cho toàn bộ không gian của tiện ích con. Khi BackdropFilter áp dụng bộ lọc, phần lớn chúng ta cần sử dụng tiện ích Stack để thực thi. Tiện ích mà bộ lọc sẽ được áp dụng phải được đặt trước bộ lọc.

    Stack(
      fit: StackFit.expand,
      children: <Widget>[
        Image.asset("assets/devs.jpg",fit: BoxFit.contain,),
        Positioned.fill(
          child: Center(
            child: BackdropFilter(
              filter: ImageFilter.blur(
                sigmaX: 10.0,
                sigmaY: 10.0,
              ),
              child: Container(
                color: Colors.black.withOpacity(0.2),
    
              ),
            ),
          ),
        ),
      ],
    ),

    Vì bộ lọc sẽ bao phủ toàn bộ không gian gốc của nó, chúng ta cần bao bọc tiện ích con BackdropFilter làm phần tử con của Positioned.fill. Bạn cần chứa ImageFilter. Đối với tình huống này, bộ lọc thích hợp nhất có thể được sử dụng ImageFilter.blursigmaX và sigmaY kiểm soát độ lệch tiêu chuẩn phụ thuộc vào bộ lọc trên trục x và trục y riêng lẻ. Cả hai đều có ước tính mặc định là 0, có nghĩa là không có tác động nào được áp dụng. Để áp dụng bộ lọc trên trục x, hãy thay đổi ước tính sigmaX thành một số dương. Đối với trục y, sử dụng thuộc tính sigmaY . Con của BackdropFilter có thể là một Container có độ mờ che bóng dưới 1, bằng 0 là giá trị điển hình. Khi chúng tôi chạy ứng dụng, chúng tôi phải lấy đầu ra của màn hình như ảnh chụp màn hình bên dưới.

    Làm mờ văn bản:

    Ví dụ Blur này là cách làm cho bộ lọc được áp dụng cho một không gian cụ thể của hình ảnh. Thay vì Positioned. fill , sử dụng hàm tạo mặc định của tiện ích con Positioned  mà bạn có thể đặt phân tách từ trên cùng, trái, dưới và phải. Trong mọi trường hợp, điều đó là không đầy đủ. Như tôi đã soạn ở trên, Flutter sẽ áp dụng bộ lọc cho tất cả các khoảng trống bên trong clip của tiện ích con của nó. Theo đó, để áp dụng kênh trên một lãnh thổ cụ thể, bạn cần bao bọc chúng BackdropFilter dưới dạng con của bất kỳ Clip widget, nào , như ClipRect, ClipRRect, ClipOval, ClipPath hoặc CustomClipper.

    Stack(
      fit: StackFit.expand,
      children: <Widget>[
        Image.asset("assets/devs.jpg",fit: BoxFit.contain,),
        Positioned(
          top: 250,
          left: 0,
          right: 0,
          child: Center(
            child: ClipRRect(
              borderRadius: BorderRadius.circular(24),
              child: BackdropFilter(
                filter: ImageFilter.blur(
                  sigmaX: 10.0,
                  sigmaY: 10.0,
                ),
                child: Container(
                  padding: EdgeInsets.all(24),
                  color: Colors.white.withOpacity(0.5),
                  child: Text(
                    "Flutter Dev's",
                    style: TextStyle(
                      fontSize: 28,
                      fontWeight: FontWeight.bold,
                      color: Colors.white,
                    ),
                  ),
                ),
              ),
            ),
          ),
        ),
      ],
    ),

    Chúng ta sẽ thêm văn bản trên một vùng chứa với độ mờ màu và bán kính đường viền sẽ là hình tròn do ClipRRect(). Khi ta chạy ứng dụng, ta phải lấy đầu ra của màn hình như ảnh chụp màn hình bên dưới.

    Làm mờ hình ảnh & văn bản:

    Trong hiệu ứng mờ này, tất cả mọi thứ sẽ giống nhau. Chúng tôi sẽ thêm Stack () , bên trong chúng tôi sẽ thêm một widget Positioned với trên cùng, bên trái và bên phải. Chúng tôi sẽ áp dụng BackdropFilter() và thuộc tính con của nó, chúng tôi sẽ thêm một container có cùng văn bản và màu sắc với độ mờ. Chúng tôi sẽ xóa ClipRRect. Tất cả các vật dụng sẽ bị mờ.

    Stack(
      fit: StackFit.expand,
      children: <Widget>[
        Image.asset("assets/devs.jpg",fit: BoxFit.contain,),
        Positioned(
          top: 250,
          left: 0,
          right: 0,
          child: Center(
            child: BackdropFilter(
              filter: ImageFilter.blur(
                sigmaX: 10.0,
                sigmaY: 10.0,
              ),
              child: Container(
                padding: EdgeInsets.all(24),
                color: Colors.white.withOpacity(0.5),
                child: Text(
                  "Flutter Dev's",
                  style: TextStyle(
                    fontSize: 28,
                    fontWeight: FontWeight.bold,
                    color: Colors.white,
                  ),
                ),
              ),
            ),
          ),
        ),
      ],
    ),

    Khi chạy ứng dụng, ta sẽ thấy màn hình như ảnh chụp màn hình bên dưới.

    Kết luận:

    Trong bài viết này, tôi đã giải thích cấu trúc cơ bản của widget BackdropFilter trong Flutter, bạn có thể sửa đổi mã này theo sự lựa chọn của bạn.

  • [Flutter]Tạo Widget với Android Native bằng Platform Views

    [Flutter]Tạo Widget với Android Native bằng Platform Views

    Một ví dụ về tạo 2 widget được triển khai dưới Android Native

    Dưới Native

    Đầu tiên, chúng ta sẽ tạo 2 class FirstWidget.kt và SecondWidget.kt:

    • FirstWidget.kt : Sử dụng file .xml
    
    import android.content.Context
    import android.view.LayoutInflater
    import android.view.View
    import io.flutter.plugin.platform.PlatformView
    
    internal class FirstWidget(context: Context, id: Int, creationParams: Map<String?, Any?>?) : PlatformView {
        private val view: View
    
        override fun getView(): View {
            return view
        }
    
        init {
            view = LayoutInflater.from(context).inflate(R.layout.first_widget, null)
        }
    
        override fun dispose() {
        }
    }
    • first_widget.xml
    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        tools:context=".MainActivity"
        tools:showIn="@layout/first_widget">
    
        <TextView
            android:id="@+id/first_widget_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="FirstWidget from Android!"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    • SecondWidget.kt: 1 cách khác để tạo view
    import android.content.Context
    import android.graphics.Color
    import android.view.View
    import android.widget.TextView
    import io.flutter.plugin.platform.PlatformView
    
    internal class SecondWidget(context: Context, id: Int, creationParams: Map<String?, Any?>?) : PlatformView {
        private val textView = TextView(context)
    
        override fun getView(): View {
            return textView
        }
    
        override fun dispose() {}
    
        init {
            textView.textSize = 20f
            textView.setBackgroundColor(Color.rgb(255, 255, 255))
            textView.text = "Rendered on a native Android view (id: $id)"
        }
    }

    Tạo 2 class FirstWidgetFactory.kt và SecondWidgetFactory.kt:

    • FirstWidgetFactory.kt
    import android.content.Context
    import io.flutter.plugin.common.StandardMessageCodec
    import io.flutter.plugin.platform.PlatformView
    import io.flutter.plugin.platform.PlatformViewFactory
    
    class FirstWidgetFactory : PlatformViewFactory(StandardMessageCodec.INSTANCE) {
        override fun create(context: Context, viewId: Int, args: Any?): PlatformView {
            val creationParams = args as Map<String?, Any?>?
            return FirstWidget(context, viewId, creationParams)
        }
    }
    • SecondWidgetFactory.kt
    import android.content.Context
    import io.flutter.plugin.common.StandardMessageCodec
    import io.flutter.plugin.platform.PlatformView
    import io.flutter.plugin.platform.PlatformViewFactory
    
    class SecondWidgetFactory : PlatformViewFactory(StandardMessageCodec.INSTANCE) {
        override fun create(context: Context, viewId: Int, args: Any?): PlatformView {
            val creationParams = args as Map<String?, Any?>?
            return SecondWidget(context, viewId, creationParams)
        }
    }

    Tại MainActivity.kt:

    import io.flutter.embedding.android.FlutterActivity
    import io.flutter.embedding.engine.FlutterEngine
    import io.flutter.plugins.GeneratedPluginRegistrant
    
    class MainActivity : FlutterActivity() {
    
        override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
            GeneratedPluginRegistrant.registerWith(flutterEngine)
            flutterEngine
                    .platformViewsController
                    .registry
                    .registerViewFactory("com.example.flutter_app_demo1.FirstWidgetPlugin", FirstWidgetFactory())
            flutterEngine
                    .platformViewsController
                    .registry
                    .registerViewFactory("com.example.flutter_app_demo1.SecondWidgetPlugin", SecondWidgetFactory())
        }
    
    }

    Về phần Dart

    Tạo 2 file first_widget.dart và second_widget.dart:

    • first_widget.dart
    import 'package:flutter/foundation.dart';
    import 'package:flutter/gestures.dart';
    import 'package:flutter/material.dart';
    import 'package:flutter/rendering.dart';
    import 'package:flutter/services.dart';
    
    
    class FirstWidget extends StatefulWidget {
      const FirstWidget({
        Key? key,
      }) : super(key: key);
    
    
      @override
      State<StatefulWidget> createState() => _FirstWidgetState();
    }
    
    class _FirstWidgetState extends State<FirstWidget> {
       String viewType = 'com.example.flutter_app_demo1.FirstWidgetPlugin';
       Map<String, dynamic> creationParams = <String, dynamic>{};
      @override
      Widget build(BuildContext context) {
        if (defaultTargetPlatform == TargetPlatform.android) {
          return SizedBox(
            width: 200,
            child: PlatformViewLink(
              viewType: viewType,
              surfaceFactory:
                  (BuildContext context, PlatformViewController controller) {
                return AndroidViewSurface(
                  controller: controller as AndroidViewController,
                  gestureRecognizers:  const <Factory<OneSequenceGestureRecognizer>>{},
                  hitTestBehavior: PlatformViewHitTestBehavior.opaque,
                );
              },
              onCreatePlatformView: (PlatformViewCreationParams params) {
                return PlatformViewsService.initSurfaceAndroidView(
                  id: params.id,
                  viewType: viewType,
                  layoutDirection: TextDirection.ltr,
                  creationParams: creationParams,
                  creationParamsCodec: const StandardMessageCodec(),
                  onFocus: () {
                    params.onFocusChanged(true);
                  },
                )
                  ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
                  ..create();
              },
            ),
          );
        }
        return const Text('iOS platform version is not implemented yet.');
      }
    
    }
    • second_widget.dart
    import 'package:flutter/foundation.dart';
    import 'package:flutter/gestures.dart';
    import 'package:flutter/material.dart';
    import 'package:flutter/rendering.dart';
    import 'package:flutter/services.dart';
    
    class SecondWidget extends StatefulWidget {
      const SecondWidget({
        Key? key,
      }) : super(key: key);
    
      @override
      State<StatefulWidget> createState() => _SecondWidgetState();
    }
    
    class _SecondWidgetState extends State<SecondWidget> {
      String viewType = "com.example.flutter_app_demo1.SecondWidgetPlugin";
      Map<String, dynamic> creationParams = <String, dynamic>{};
      @override
      Widget build(BuildContext context) {
        if (defaultTargetPlatform == TargetPlatform.android) {
          return PlatformViewLink(
            viewType: viewType,
            surfaceFactory:
                (BuildContext context, PlatformViewController controller) {
              return AndroidViewSurface(
                controller: controller as AndroidViewController,
                gestureRecognizers:  const <Factory<OneSequenceGestureRecognizer>>{},
                hitTestBehavior: PlatformViewHitTestBehavior.opaque,
              );
            },
            onCreatePlatformView: (PlatformViewCreationParams params) {
              return PlatformViewsService.initSurfaceAndroidView(
                id: params.id,
                viewType: viewType,
                layoutDirection: TextDirection.ltr,
                creationParams: creationParams,
                creationParamsCodec: const StandardMessageCodec(),
                onFocus: () {
                  params.onFocusChanged(true);
                },
              )
                ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
                ..create();
            },
          );
        }
        return const Text('iOS platform version is not implemented yet.');
      }
    
    }
    

    Sau đó gọi lại 2 Widget vừa rồi tại main.dart

    @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('Flutter PlatformView Example'),
          ),
          body: Column(
            mainAxisSize: MainAxisSize.min,
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: const <Widget>[
              Card(
                child: SizedBox(
                  height: 200,
                  child: FirstWidget(),
                ),
              ),
              Card(
                child: SizedBox(
                  height: 200,
                  child: SecondWidget(),
                ),
              ),
            ],
          ),
        );
      }

    PlatformView

    PlatformView là một tính năng Flutter cần thực hiện để hiển thị Native-UIs toàn diện trên Android View / UIKitView.

    Nếu bạn muốn biết thêm thông tin về PlatformView, hãy xem tài liệu chính thức: