stock_keyboard_by_tun 1.0.0 copy "stock_keyboard_by_tun: ^1.0.0" to clipboard
stock_keyboard_by_tun: ^1.0.0 copied to clipboard

A highly customizable special stock numeric keyboard for trading dashboards with native selection and caret support.

Stock Keyboard by Tun (stock_keyboard_by_tun) #

Một package Flutter bàn phím số chuyên dụng có tính tùy biến cao, được thiết kế theo chuẩn Clean Architecture và hiệu năng cực cao. Package được tối ưu hóa hiển thị, ngăn ngừa vỡ giao diện (UI breakage protection), hỗ trợ rung phản hồi (haptic feedback), đa ngôn ngữ và tự động cuộn chống che lấp input field.


🌟 Tính Năng Nổi Bật (Key Features) #

  • 100% Stateless UI: Toàn bộ các UI component, phím bấm, thanh gợi ý được viết hoàn toàn bằng StatelessWidget giúp loại bỏ hoàn toàn việc rebuild dư thừa, đạt hiệu năng 60/120 FPS khi gõ nhanh.
  • Generic Custom Controller: Quản lý tập trung mọi trạng thái focus, ngôn ngữ (Việt/Anh), nhập liệu tại vị trí con trỏ và xóa ký tự lặp bằng CustomKeyboardController<T>.
  • Vibration Control (Haptic Feedback): Tùy chỉnh bật/tắt rung phản hồi khi bấm phím một cách linh hoạt tại runtime.
  • Font Size Customization & UI Protection: Cho phép tự do chỉnh size phím số (mặc định 25px) và phím chức năng. Nội dung tự động co giãn bằng FittedBox đảm bảo không bao giờ bị vỡ giao diện.
  • Auto-Avoiding View: Đi kèm CustomKeyboardAvoidingView tự động tính toán toạ độ và cuộn màn hình để bàn phím không che mất trường nhập liệu đang active.
  • Thanh Gợi Ý Đa Dạng (Suggestions Bar): Hỗ trợ hiển thị gợi ý nhanh theo định dạng tiền tệ hoặc tùy biến layout chip (bo tròn border radius, icon đi kèm, căn giữa hoặc cuộn ngang không lỗi overflow).

⚙️ Hướng Dẫn Cài Đặt (Installation) #

Thêm package vào file pubspec.yaml của bạn:

dependencies:
  stock_keyboard_by_tun:
    path: ./stock_keyboard_by_tun # Hoặc cấu hình git/pub.dev tùy thuộc môi trường của bạn

Sau đó chạy lệnh:

flutter pub get

🚀 Hướng Dẫn Sử Dụng (Usage Guide) #

Bước 1: Định nghĩa Enum đại diện cho các trường nhập liệu #

enum OrderFields { price, volume }

Bước 2: Khởi tạo Controllers, FocusNodes và CustomKeyboardController #

import 'package:flutter/material.dart';
import 'package:stock_keyboard_by_tun/stock_keyboard_by_tun.dart';

class OrderScreen extends StatefulWidget {
  const OrderScreen({super.key});

  @override
  State<OrderScreen> createState() => _OrderScreenState();
}

class _OrderScreenState extends State<OrderScreen> {
  final _priceController = TextEditingController();
  final _volumeController = TextEditingController();
  
  final _priceFocus = FocusNode();
  final _volumeFocus = FocusNode();

  late CustomKeyboardController<OrderFields> _keyboardController;

  @override
  void initState() {
    super.initState();

    _keyboardController = CustomKeyboardController<OrderFields>(
      controllers: {
        OrderFields.price: _priceController,
        OrderFields.volume: _volumeController,
      },
      focusNodes: {
        OrderFields.price: _priceFocus,
        OrderFields.volume: _volumeFocus,
      },
      layouts: {
        // Layout thập phân (Decimal) cho Giá
        OrderFields.price: KeyboardLayout.decimal(),
        // Layout số nguyên (Integer) cho Khối lượng
        OrderFields.volume: KeyboardLayout.integer(),
      },
      defaultLanguage: KeyboardLanguage.vi, // Mặc định hiển thị tiếng Việt
      enableHapticFeedback: true,           // Bật rung khi gõ phím
    );
  }

  @override
  void dispose() {
    _keyboardController.dispose();
    _priceController.dispose();
    _volumeController.dispose();
    _priceFocus.dispose();
    _volumeFocus.dispose();
    super.dispose();
  }

Bước 3: Thiết lập Giao diện với StockKeyboardOverlay (Khuyên dùng) #

Bọc giao diện của bạn bằng StockKeyboardOverlay để tự động quản lý bàn phím dưới dạng Overlay. Khi bàn phím custom xuất hiện, StockKeyboardOverlay sẽ tự động cập nhật MediaQueryData.viewInsets.bottom giúp Scaffold tự co giãn (resize) chiều cao màn hình và đẩy các trường nhập liệu lên trên mà không sợ bị đè/che khuất.

  @override
  Widget build(BuildContext context) {
    // Định nghĩa danh sách gợi ý cho từng trường nhập liệu
    final suggestions = {
      OrderFields.price: [
        const KeyboardSuggestionModel(displayLabel: 'LO', value: 'LO'),
        const KeyboardSuggestionModel(displayLabel: 'ATC', value: 'ATC'),
        KeyboardSuggestionModel(
          displayLabel: 'Sàn 60.9', 
          value: '60.9',
          leadingIcon: Icon(Icons.arrow_downward, size: 14, color: Colors.cyan),
        ),
      ],
      OrderFields.volume: [
        const KeyboardSuggestionModel(displayLabel: '100', value: '100'),
        const KeyboardSuggestionModel(displayLabel: '1,000', value: '1000'),
      ],
    };

    return Scaffold(
      // Bọc phần body bằng StockKeyboardOverlay để quản lý lớp phủ bàn phím
      body: StockKeyboardOverlay<OrderFields>(
        controller: _keyboardController,
        suggestions: suggestions,
        centerSuggestions: true, // Tự động căn giữa chips gợi ý
        theme: KeyboardTheme.light(
          keyFontSize: 26.0,
          specialKeyFontSize: 16.0,
        ),
        backspaceIcon: const Icon(Icons.backspace, size: 20),
        child: SingleChildScrollView(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            children: [
              TextField(
                controller: _priceController,
                focusNode: _priceFocus,
                decoration: const InputDecoration(labelText: 'Giá đặt'),
                showCursor: true,
                keyboardType: TextInputType.none, // Ẩn bàn phím hệ thống, giữ con trỏ native
              ),
              const SizedBox(height: 20),
              TextField(
                controller: _volumeController,
                focusNode: _volumeFocus,
                decoration: const InputDecoration(labelText: 'Khối lượng'),
                showCursor: true,
                keyboardType: TextInputType.none, // Ẩn bàn phím hệ thống, giữ con trỏ native
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Cách 2: Sử dụng StockKeyboardView thủ công trong Widget Tree #

Nếu không muốn dùng Overlay và muốn đặt bàn phím cố định ở cuối Layout (như trong một Column):

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          Expanded(
            child: CustomKeyboardAvoidingView<OrderFields>(
              scrollController: _scrollController,
              keyboardController: _keyboardController,
              keyboardHeight: 240.0,
              child: SingleChildScrollView(
                controller: _scrollController,
                child: Column(
                  children: [
                    // Các TextField...
                  ],
                ),
              ),
            ),
          ),
          // Khai báo thủ công ở cuối giao diện
          StockKeyboardView<OrderFields>(
            controller: _keyboardController,
            suggestions: suggestions,
            centerSuggestions: true,
            backspaceIcon: const Icon(Icons.backspace, size: 20),
          ),
        ],
      ),
    );
  }

🎨 Tùy Biến Giao Diện (Theme & Customization) #

1. Sử dụng Theme Đóng Gói Sẵn (Pre-packaged Themes) #

Package cung cấp sẵn 2 theme Light và Dark tối ưu độ tương phản cho ứng dụng Tài chính:

// Sáng (Light Mode)
theme: KeyboardTheme.light()

// Tối (Dark Mode)
theme: KeyboardTheme.dark()

2. Tùy biến mã màu tùy chỉnh #

Bạn có thể tự định nghĩa toàn bộ mã màu cho phím bấm, màu nền, màu chữ hành động thông qua Constructor:

final customTheme = KeyboardTheme(
  backgroundColor: Colors.grey.shade900,
  commonKeyColor: Colors.black85,
  specialKeyColor: Colors.black45,
  actionKeyColor: Colors.amber.shade700,
  textColor: Colors.white,
  actionTextColor: Colors.black,
  suggestionBackgroundColor: Colors.black85,
  suggestionChipColor: Colors.grey.shade800,
  suggestionTextColor: Colors.white,
  activeSuggestionChipColor: Colors.amber.shade700,
  activeSuggestionTextColor: Colors.black,
  keyFontSize: 28.0,
  specialKeyFontSize: 16.0,
);

🔋 Bật/Tắt Rung Động Phản Hồi (Haptic Feedback Control) #

Để tắt tính năng rung khi gõ phím từ cài đặt của người dùng, chỉ cần thay đổi giá trị enableHapticFeedback trên controller:

// Tắt rung hoàn toàn
_keyboardController.enableHapticFeedback = false;

// Bật rung trở lại
_keyboardController.enableHapticFeedback = true;

⚠️ Lưu ý quan trọng khi tích hợp (Important Notes) #

Khi tích hợp bàn phím custom vào dự án thực tế, lập trình viên cần lưu ý một số điểm đặc thù của Flutter và hệ điều hành sau để tránh lỗi giao diện và tối ưu trải nghiệm (UX):

1. Vấn đề với TextInputFormatter #

Do bàn phím custom ghi nhận nội dung bằng cách thay đổi giá trị của TextEditingController trực tiếp bằng code (TextEditingController.value = ...), cơ chế TextInputFormatter của Flutter sẽ không tự động được kích hoạt (vì formatter chỉ chạy khi nhập liệu qua kênh Input Method của hệ thống).

  • Giải pháp: Bạn cần thực hiện định dạng dữ liệu (ví dụ: định dạng tiền tệ) hoặc chặn giới hạn độ dài thủ công trước khi gán vào controller hoặc xử lý trong listener của controller.

2. Tránh đè lên bottomNavigationBar hoặc bottomSheet #

Nếu trang của bạn có bottomNavigationBar ở dưới cùng và bàn phím StockKeyboardView hiển thị cuối phần body. Khi trồi lên, bàn phím sẽ nằm ở trên thanh menu đó.

  • Giải pháp: Nên lắng nghe trạng thái mở/đóng bàn phím thông qua controller và ẩn bottomNavigationBar khi bàn phím đang hiển thị:
    final isKeyboardOpen = _keyboardController.focusedField != null;
      
    return Scaffold(
      body: ...
      bottomNavigationBar: isKeyboardOpen ? null : MyBottomBar(),
    );
    

3. Đóng bàn phím khi nhấn ra ngoài màn hình (Tap to Unfocus) #

Bàn phím custom hoạt động dựa trên trạng thái Focus. Để người dùng có thể chạm vào một khoảng trống bên ngoài để ẩn bàn phím:

  • Giải pháp: Bọc giao diện chính bằng GestureDetector và gọi hàm unfocus:
    GestureDetector(
      onTap: () {
        _keyboardController.unfocus();
      },
      child: Scaffold(...),
    )
    

4. Giải phóng tài nguyên (Memory Leak) #

CustomKeyboardController đăng ký listener vào các FocusNode được truyền vào.

  • Giải pháp: Phải gọi _keyboardController.dispose() trong phương thức dispose() của StatefulWidget để hủy đăng ký lắng nghe và giải phóng tài nguyên.

🧪 Chạy Kiểm Thử (Testing) #

Đảm bảo mã nguồn hoạt động chính xác bằng cách chạy bộ kiểm thử tĩnh và kiểm thử widget:

flutter analyze
flutter test
2
likes
160
points
4
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A highly customizable special stock numeric keyboard for trading dashboards with native selection and caret support.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

cupertino_icons, flutter

More

Packages that depend on stock_keyboard_by_tun