Files
photobooth-app/14_FIX_PRESET_BUTTON.md

8.3 KiB

🎨 KẾ HOẠCH BỔ SUNG THUỘC TÍNH EDITOR & HIỂN THỊ TÊN ĐỘNG TRÊN TOP BAR

Mục tiêu: Đưa toàn bộ 14 thông số cấu hình vào rvAttributeIcons dạng danh sách cuộn ngang không giới hạn. Đồng bộ hóa việc chọn icon để hiển thị tên tiếng Việt tương ứng lên vùng văn bản phía trên ảnh và gán chính xác sự kiện vuốt trên ảnh cho thuộc tính đó.


📐 1. Cấu Trúc Lại XML Layout Để Thêm Dải Hiển Thị Tên (activity_main.xml)

Bám sát ảnh image.png, chúng ta cần thêm một TextView nằm ở dải đen phía trên bức ảnh để làm nhiệm vụ hiển thị tên nút đang chọn.

<!-- DẢI ĐEN PHÍA TRÊN ẢNH CAMERA (Như mô tả trong image.png) -->
<RelativeLayout
    android:id="@+id/layoutEditorTitleBar"
    android:layout_width="match_parent"
    android:layout_height="40dp"
    android:background="#000000"
    app:layout_bottom_toTopOf="@id/cameraContainer"
    app:layout_constraintTop_toBottomOf="@id/topBar">

    <!-- Văn bản hiển thị động tên nút đang được chọn -->
    <TextView
        android:id="@+id/tvCurrentAttributeName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="Brightness (Độ sáng)" <!-- Tên động hiển thị  đây -->
        android:textColor="#FF9800" <!-- Màu cam nổi bật hoặc trắng tùy chọn -->
        android:textSize="14sp"
        android:textStyle="bold" />
</RelativeLayout>


📁 2. Khai Báo Toàn Bộ Bản Đồ Thuộc Tính & Icons (Kotlin Model)

Để quản lý 14 nút thuộc tính một cách khoa học, chúng ta tạo một danh sách Model bao gồm: ID thuộc tính, Tên hiển thị (Tiếng Việt), và File định dạng Icon tương ứng trong res/drawable.

🔹 2.1. Định nghĩa Data Class cho nút thuộc tính

data class AttributeNode(
    val type: EditAttribute,
    val displayName: String,
    val iconResId: Int
)

enum class EditAttribute {
    BRIGHTNESS, CONTRAST, SATURATION, VIBRANCE, TEMPERATURE, TINT,        // Nhóm 1: Cơ bản
    CLARITY, DEHAZE, VIGNETTE_AMOUNT,                                    // Nhóm 2: Nâng cao
    FADED_BLACK_LEVEL, SHADOWS_TINT_R, SHADOWS_TINT_G, SHADOWS_TINT_B,   // Nhóm 3: Tone Curve
    GRAIN_AMOUNT, GRAIN_SIZE                                             // Nhóm 4: Nhiễu hạt
}

🔹 2.2. Khởi tạo mảng dữ liệu 14 nút đầy đủ

val editorAttributes = listOf(
    AttributeNode(EditAttribute.BRIGHTNESS, "Brightness (Độ sáng)", R.drawable.ic_attr_brightness),
    AttributeNode(EditAttribute.CONTRAST, "Contrast (Tương phản)", R.drawable.ic_attr_contrast),
    AttributeNode(EditAttribute.SATURATION, "Saturation (Bão hòa màu)", R.drawable.ic_attr_saturation),
    AttributeNode(EditAttribute.VIBRANCE, "Vibrance (Bão hòa thông minh)", R.drawable.ic_attr_vibrance),
    AttributeNode(EditAttribute.TEMPERATURE, "Temperature (Nhiệt màu)", R.drawable.ic_attr_temperature),
    AttributeNode(EditAttribute.TINT, "Tint (Sắc độ hồng/xanh)", R.drawable.ic_attr_tint),
    AttributeNode(EditAttribute.CLARITY, "Clarity (Độ rõ nét)", R.drawable.ic_attr_clarity),
    AttributeNode(EditAttribute.DEHAZE, "Dehaze (Độ trong suốt / Sương mù)", R.drawable.ic_attr_dehaze),
    AttributeNode(EditAttribute.VIGNETTE_AMOUNT, "Vignette Amount (Bo tối góc)", R.drawable.ic_attr_vignette),
    AttributeNode(EditAttribute.FADED_BLACK_LEVEL, "Faded Black Level (Độ mờ vùng đen)", R.drawable.ic_attr_fade),
    AttributeNode(EditAttribute.SHADOWS_TINT_R, "Shadows Tint R (Sắc Đỏ vùng tối)", R.drawable.ic_attr_tint_r),
    AttributeNode(EditAttribute.SHADOWS_TINT_G, "Shadows Tint G (Sắc Lục vùng tối)", R.drawable.ic_attr_tint_g),
    AttributeNode(EditAttribute.SHADOWS_TINT_B, "Shadows Tint B (Sắc Lam vùng tối)", R.drawable.ic_attr_tint_b),
    AttributeNode(EditAttribute.GRAIN_AMOUNT, "Grain Amount (Mật độ hạt)", R.drawable.ic_attr_grain_amount),
    AttributeNode(EditAttribute.GRAIN_SIZE, "Grain Size (Kích thước hạt)", R.drawable.ic_attr_grain_size)
)


💻 3. Xử Lý Logic Tương Tác & Đồng Bộ Trạng Thái Động

Khi gán danh sách này vào rvAttributeIcons dưới dạng LayoutManager cuộn ngang (LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)), người dùng có thể thoải mái vuốt lướt sang phải để tìm các nút bị ẩn như mũi tên chỉ dẫn trong ảnh image.png.

🔹 3.1. Sự kiện Click đổi màu và đồng bộ Tên lên Top Bar

Trong sự kiện bấm chọn một Icon thuộc tính bên trong Adapter của RecyclerView:

fun onAttributeSelected(selectedNode: AttributeNode) {
    // 1. Lưu lại thuộc tính đang chỉnh sửa hiện tại
    currentAttribute = selectedNode.type

    // 2. Cập nhật văn bản lên dải đen phía trên ảnh đúng theo thiết kế image_44ee6b.jpg
    binding.tvCurrentAttributeName.text = selectedNode.displayName

    // 3. Đọc giá trị hiện tại của thuộc tính này trong file JSON tạm thời để nạp lên thước đo
    val currentValue = getCurrentAttributeValue(selectedNode.type)
    binding.tvRulerValue.text = String.format("%.0f", currentValue * 100f)

    // 4. Ra lệnh cho Adapter vẽ lại (Notify) để đổi màu Icon được chọn thành MÀU CAM
    attributeAdapter.updateSelection(selectedNode.type)
}

🔹 3.2. Cập nhật hàm điều phối Vuốt trên ảnh (Swipe Logic Matrix)

Hàm tiếp nhận giá trị deltaX khi người dùng vuốt trên màn hình ảnh giờ đây sẽ phân nhánh chính xác sang cả 14 thông số, không còn tình trạng gán nhầm hàm:

private fun updateAttributeBySwipe(newValue: Float) {
    // newValue có dải giá trị từ -1.0f đến 1.0f sau khi đã chia cho 100
    when (currentAttribute) {
        EditAttribute.BRIGHTNESS -> editingPreset?.basic?.brightness = newValue
        EditAttribute.CONTRAST -> editingPreset?.basic?.contrast = newValue
        EditAttribute.SATURATION -> editingPreset?.basic?.saturation = newValue
        EditAttribute.VIBRANCE -> editingPreset?.basic?.vibrance = newValue
        EditAttribute.TEMPERATURE -> editingPreset?.basic?.temperature = newValue
        EditAttribute.TINT -> editingPreset?.basic?.tint = newValue
        
        EditAttribute.CLARITY -> editingPreset?.advanced?.clarity = newValue
        EditAttribute.DEHAZE -> editingPreset?.advanced?.dehaze = newValue
        EditAttribute.VIGNETTE_AMOUNT -> editingPreset?.advanced?.vignetteAmount = newValue.coerceIn(0f, 1f)
        
        EditAttribute.FADED_BLACK_LEVEL -> editingPreset?.toneCurve?.fadedBlackLevel = newValue.coerceIn(0f, 1f)
        EditAttribute.SHADOWS_TINT_R -> editingPreset?.toneCurve?.shadowsTintR = newValue.coerceIn(0f, 1f)
        EditAttribute.SHADOWS_TINT_G -> editingPreset?.toneCurve?.shadowsTintG = newValue.coerceIn(0f, 1f)
        EditAttribute.SHADOWS_TINT_B -> editingPreset?.toneCurve?.shadowsTintB = newValue.coerceIn(0f, 1f)
        
        EditAttribute.GRAIN_AMOUNT -> editingPreset?.grain?.grainAmount = newValue.coerceIn(0f, 1f)
        EditAttribute.GRAIN_SIZE -> editingPreset?.grain?.grainSize = newValue.coerceIn(0f, 1f)
    }

    // Luôn luôn gọi render cập nhật đồ họa thời gian thực lên Camera ngay lập tức
    applyLiveFilterToCamera(editingPreset)
}


📅 4. Các Đầu Việc Cần Triển Khai (Checklist)

  • Chuẩn bị 14 cặp Icons tương ứng: Tạo hoặc import các asset dạng vector đơn sắc trắng vào thư mục dự án trên Linux.
  • Cấu hình clipToPadding="false" cho RecyclerView: Thiết lập thuộc tính này cho thanh icon để khi cuộn, các icon ở hai đầu rìa có khoảng không trượt mượt mà, không bị cắt cụt đột ngột.
  • Kiểm thử liên kết: Đảm bảo khi mở màn hình Editor lên, mặc định app sẽ tự động giả lập click vào nút đầu tiên (Brightness), icon sáng cam và dải chữ phía trên hiển thị đúng chữ "Brightness (Độ sáng)" như ảnh image.png.