Files
photobooth-app/8_SLIDER_TABLE.md
T

9.1 KiB

🎨 KẾ HOẠCH PHÁT TRIỂN: BẢNG SLIDERS TÙY CHỈNH MÀU THỜI GIAN THỰC (REAL-TIME OVERLAY)

Mục tiêu: Khi bấm nút Edit nhỏ trên một Preset, một bảng chỉnh sửa mờ (Semi-transparent Overlay) sẽ trượt lên từ phía dưới, đè lên kính ngắm camera. Mọi thao tác kéo trượt của người dùng sẽ cập nhật trực tiếp vào bộ xử lý đồ họa phần cứng để thay đổi màu ảnh ngay lập tức (WYSIWYG).


📐 1. Cấu Trúc Thành Phần Giao Diện (XML Layout)

Để bảng điều khiển này có thể phủ (Overlay) lên hình ảnh camera mà không làm dịch chuyển hay thu nhỏ kính ngắm, chúng ta sẽ định nghĩa nó như một lớp trên cùng nằm trong FrameLayout chính của ứng dụng.

🔹 1.1. Bổ sung giao diện ẩn vào activity_main.xml

Bảng này mặc định sẽ ẩn đi (android:visibility="gone") và được gom nhóm trong một thẻ NestedScrollView để người dùng có thể cuộn lên/xuống nếu có nhiều thông số.

<!-- Chèn thêm vào phân tầng cao nhất của FrameLayout chứa camera hoặc Layout gốc -->
<LinearLayout
    android:id="@+id/layoutAdvancedSliders"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:background="#D9000000" <!-- Nền đen mờ 85% để vừa nhìn thấy camera phía sau vừa  chữ -->
    android:padding="20dp"
    android:clickable="true"
    android:focusable="true"
    android:visibility="gone"
    app:layout_constraintBottom_toTopOf="@id/panelSelectionContainer">

    <!-- Thanh tiêu đề & Nút thao tác nhanh -->
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="16dp">
        <TextView
            android:id="@+id/tvEditPresetName"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Chỉnh sửa: Instax Warm"
            android:textColor="#FFFFFF"
            android:textStyle="bold"
            android:textSize="16sp" />
        <ImageButton
            android:id="@+id/btnCancelEdit"
            android:layout_width="32dp"
            android:layout_height="32dp"
            android:layout_alignParentEnd="true"
            android:background="@null"
            android:src="@drawable/ic_close_white" />
    </RelativeLayout>

    <!-- KHU VỰC CÁC THANH KÉO (SLIDERS) -->
    <!-- 1. Thanh chỉnh VIBRANCE -->
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Vibrance (Bão hòa thông minh)"
        android:textColor="#CCCCCC"
        android:textSize="12sp" />
    <com.google.android.material.slider.Slider
        android:id="@+id/sliderVibrance"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:valueFrom="-1.0"
        android:valueTo="1.0"
        android:value="0.0"
        app:thumbColor="#FF9800"
        app:trackColorActive="#FF9800"
        app:trackColorInactive="#333333" />

    <!-- 2. Thanh chỉnh CLARITY -->
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Clarity (Độ rõ nét vùng trung tính)"
        android:textColor="#CCCCCC"
        android:textSize="12sp"
        android:layout_marginTop="8dp" />
    <com.google.android.material.slider.Slider
        android:id="@+id/sliderClarity"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:valueFrom="-1.0"
        android:valueTo="1.0"
        android:value="0.0"
        app:thumbColor="#FF9800"
        app:trackColorActive="#FF9800"
        app:trackColorInactive="#333333" />

    <!-- 3. Thanh chỉnh DEHAZE -->
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Dehaze (Độ trong suốt / Sương mù)"
        android:textColor="#CCCCCC"
        android:textSize="12sp"
        android:layout_marginTop="8dp" />
    <com.google.android.material.slider.Slider
        android:id="@+id/sliderDehaze"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:valueFrom="-1.0"
        android:valueTo="1.0"
        android:value="0.0"
        app:thumbColor="#FF9800"
        app:trackColorActive="#FF9800"
        app:trackColorInactive="#333333" />

    <!-- NÚT LƯU THÀNH PRESET MỚI -->
    <Button
        android:id="@+id/btnSaveCustomPreset"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:text="LƯU THÀNH PRESET MỚI"
        android:textColor="#FFFFFF"
        android:backgroundTint="#FF9800"
        android:textStyle="bold" />
</LinearLayout>


💻 2. Kịch Bản Tương Tác & Đồng Bộ Thời Gian Thực (Kotlin)

🔹 2.1. Chuẩn bị biến lưu trữ tạm thời (Clone Object)

Khi người dùng bấm nút Edit nhỏ từ Item trong RecyclerView, chúng ta không chỉnh sửa trực tiếp lên file gốc mà nhân bản dữ liệu ra một biến tạm:

private var editingPreset: ColorPreset? = null

fun openAdvancedSliders(selectedPreset: ColorPreset) {
    // Clone đối tượng thông qua cơ chế sao chép dữ liệu (Deep Copy hoặc JSON String conversion)
    editingPreset = selectedPreset.copy(
        basic = selectedPreset.basic.copy(),
        advanced = selectedPreset.advanced.copy()
    )

    // Cập nhật giá trị hiện tại lên các thanh Slider trực quan
    binding.sliderVibrance.value = editingPreset!!.basic.vibrance
    binding.sliderClarity.value = editingPreset!!.advanced.clarity
    binding.sliderDehaze.value = editingPreset!!.advanced.dehaze

    // Hiển thị bảng kèm hoạt họa trượt lên mượt mà (Slide up animation)
    binding.layoutAdvancedSliders.apply {
        visibility = View.VISIBLE
        translationY = this.height.toFloat()
        animate().translationY(0f).setDuration(300).start()
    }
}

🔹 2.2. Lắng nghe thay đổi của Sliders và cập nhật Camera (Real-time Pipeline)

  1. Bước 1: Người dùng kéo Slider: Lắng nghe sự kiện. Gán trình lắng nghe addOnChangeListener cho từng Slider (ví dụ: sliderVibrance). Khi người dùng di chuyển ngón tay, giá trị float (value) thay đổi liên tục.

  2. Bước 2: Cập nhật thông số vào Object tạm: Cập nhật Biến số. Cập nhật ngay giá trị mới vào biến tạm thời: editingPreset?.basic?.vibrance = value.

  3. Bước 3: Đẩy thông số vào Khung hình Camera: Render GPU. Gọi hàm applyLiveFilterToCamera(editingPreset). Hàm này sẽ chuyển đổi toàn bộ thông số của editingPreset thành một ma trận cấu hình hoặc nạp trực tiếp vào OpenGL Shader (hoặc GPUImage) đang liên kết với PreviewView của CameraX. Màn hình camera sẽ thay đổi sắc độ và cấu trúc ảnh ngay lập tức.


💾 3. Quy Trình Đổi Tên Và Lưu File Độc Lập

Khi bấm nút btnSaveCustomPreset, ứng dụng sẽ thực hiện các bước sau để tránh đè lên preset mặc định:

  1. Hiển thị Dialog nhập tên: Mở một AlertDialog tối giản với một ô EditText để người dùng đặt tên cho bộ lọc của họ (Ví dụ mặc định gợi ý sẵn: [Tên cũ] - Custom).
  2. Sinh ID duy nhất: Sử dụng UUID.randomUUID().toString() để tạo ID mới cho preset này. Đổi thuộc tính themeCategory thành "User Custom".
  3. Lưu file JSON vật lý: Sử dụng thư viện GSON để chuyển đổi Object editingPreset vừa tạo thành chuỗi JSON và ghi vào thư mục lưu trữ nội bộ của ứng dụng trên điện thoại:
val file = File(context.filesDir, "preset_${newId}.json")
file.writeText(gson.toJson(editingPreset))

  1. Cập nhật lại thanh trượt ngoài màn hình chính: Đóng bảng slider nâng cao, nạp lại danh sách preset từ thư mục chứa file để item mới vừa tạo xuất hiện ngay lập tức trên thanh Timeline ngang.

📅 4. Các Đầu Việc Cần Làm Tiếp Theo (Checklist)

  • Cài đặt thư viện đồ họa thời gian thực: Cấu hình thư viện xử lý ảnh (như GPUImage cho Android) chạy song song với PreviewView của CameraX để nhận các lệnh cập nhật thông số liên tục từ Slider mà không bị giật lag khung hình.
  • Thiết kế hoạt họa đóng/mở: Viết các tệp Animation (slide_up.xml, slide_down.xml) để tạo hiệu ứng chuyển cảnh mượt mà cho bảng trượt Overlay.
  • Kiểm tra giới hạn kéo: Thiết lập tính năng hiển thị số thực tế (ví dụ: +0.25, -0.10) ngay cạnh tên Slider khi người dùng đang kéo để họ nhận biết được mức độ tăng giảm chính xác.