Files

10 KiB

🎨 KẾ HOẠCH ĐIỀU CHỈNH: BẢNG SLIDERS FLOATING & HIỂN THỊ TRANSPARENT FRAME

Mục tiêu: Đưa bảng chỉnh sửa thành một khối mờ floating nằm gọn bên trong khung hình camera, cho phép cuộn dọc nội dung sliders. Đồng thời, cấu trúc lại hệ thống hiển thị để phần trong suốt (Transparent) của file Frame PNG lọt lòng đúng luồng camera.


📐 1. Cấu Trúc Lại Phân Tầng Giao Diện (activity_main.xml)

Để bảng điều khiển nằm lọt vào bên trong phần hình ảnh, chúng ta phải đưa nó vào làm con trực tiếp của FrameLayout quản lý camera (cameraContainer). Thứ tự khai báo trong XML sẽ quyết định tầng hiển thị (Z-index):

[FrameLayout: cameraContainer] (Tỉ lệ 3:4)
  ├── Lớp 1 (Đáy): PreviewView (Luồng camera gốc từ phần cứng)
  ├── Lớp 2 (Giữa): ImageView (Khung ảnh PNG trong suốt)
  └── Lớp 3 (Trên cùng): CardView / ScrollView (Bảng Sliders mờ floating)

💻 Đoạn mã XML chỉnh sửa chi tiết:

<!-- KHU VỰC HÌNH ẢNH CAMERA CHÍNH (Tỉ lệ 3:4) -->
<FrameLayout
    android:id="@+id/cameraContainer"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    app:layout_constraintDimensionRatio="3:4"
    app:layout_constraintTop_toBottomOf="@id/topBar">

    <!-- LỚP 1: Kính ngắm Camera -->
    <androidx.camera.view.PreviewView
        android:id="@+id/viewFinder"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <!-- LỚP 2: Khung Frame PNG (Phần ruột trong suốt sẽ tự động lộ Camera ở Lớp 1 ra) -->
    <ImageView
        android:id="@+id/imgFrameOverlay"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="fitCenter" />

    <!-- LỚP 3: BẢNG SLIDERS FLOATING (Nằm lọt vào giữa hình ảnh như image_2be399.jpg) -->
    <com.google.android.material.card.MaterialCardView
        android:id="@+id/cardAdvancedSliders"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="24dp" <!-- Tạo khoảng trống lọt lòng xung quanh giống ảnh mẫu -->
        app:cardCornerRadius="12dp"
        app:cardElevation="8dp"
        android:visibility="gone" <!-- Mặc định ẩn -->
        app:strokeWidth="0dp"
        android:backgroundTint="#A6000000"> <!-- Màu nền đen mờ tỉ lệ nén Alpha mịn -->

        <!-- ScrollView cho phép cuộn lên xuống tự do để chỉnh tất cả các thông số -->
        <ScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fillViewport="true"
            android:scrollbars="vertical">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                android:padding="16dp">

                <!-- Tiêu đề & Nút X (Đóng) -->
                <RelativeLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginBottom="12dp">
                    <TextView
                        android:id="@+id/tvEditPresetName"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="Chỉnh sửa: Instax Nắng Chiều"
                        android:textColor="#FFFFFF"
                        android:textStyle="bold"
                        android:textSize="15sp" />
                    <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"
                        app:tint="#FFFFFF" />
                </RelativeLayout>

                <!-- ================= DANH SÁCH CÁC CỤM SLIDERS ĐỂ CUỘN ================= -->
                <!-- 1. VIBRANCE -->
                <TextView
                    android:id="@+id/tvLabelVibrance"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="Vibrance (Bão hòa thông minh): 0.0"
                    android:textColor="#FFFFFF"
                    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" />

                <!-- 2. CLARITY -->
                <TextView
                    android:id="@+id/tvLabelClarity"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="Clarity (Độ rõ nét vùng trung tính): 0.0"
                    android:textColor="#FFFFFF"
                    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" />

                <!-- 3. DEHAZE -->
                <TextView
                    android:id="@+id/tvLabelDehaze"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="Dehaze (Độ trong suốt / Sương mù): 0.0"
                    android:textColor="#FFFFFF"
                    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" />

                <!-- Bổ sung thêm các Slider khác (Brightness, Saturation, Grain) vào đây để cuộn... -->

                <!-- NÚT LƯU THÀNH PRESET MỚI -->
                <Button
                    android:id="@+id/btnSaveCustomPreset"
                    android:layout_width="match_parent"
                    android:layout_height="48dp"
                    android:layout_marginTop="16dp"
                    android:text="LƯU THÀNH PRESET MỚI"
                    android:textColor="#FFFFFF"
                    android:backgroundTint="#FF9800"
                    android:textStyle="bold"
                    android:insetTop="0dp"
                    android:insetBottom="0dp"
                    app:cornerRadius="4dp" />

            </LinearLayout>
        </ScrollView>
    </com.google.android.material.card.MaterialCardView>
</FrameLayout>


💻 2. Kịch Bản Xử Lý Kỹ Thuật (Kotlin)

🔹 2.1. Hiện thực hóa việc hiện thị Frame suốt (Transparent Layer)

Để luồng camera hiện lên ở phần ruột trong suốt của khung hình:

  1. Các file asset khung ảnh (ví dụ: instaxframe.png) thiết kế trên phần mềm đồ họa phải được đục lỗ rỗng (Alpha channel = 0) ở khu vực hiển thị ảnh chụp.
  2. Trong Android, khi người dùng chọn mẫu từ Timeline, ta nạp trực tiếp bằng luồng luân chuyển nhẹ:
val bitmap = BitmapFactory.decodeStream(assets.open("frames/instaxframe.png"))
binding.imgFrameOverlay.setImageBitmap(bitmap)

Do cơ chế xếp chồng tầng của FrameLayout, luồng CameraX hoạt động ở viewFinder (Lớp 1) sẽ tự động xuyên thấu và hiển thị mượt mà qua các vùng trống của imgFrameOverlay (Lớp 2).

🔹 2.2. Logic cập nhật Text hiển thị chỉ số thời gian thực (Real-time Value Indicator)

Bám sát thiết kế hiển thị số ở nhãn text (ví dụ: Vibrance (...): -0.23), ta cập nhật chuỗi text liên tục khi con trỏ di chuyển:

binding.sliderVibrance.addOnChangeListener { slider, value, fromUser ->
    // 1. Cập nhật chữ hiển thị giá trị số thực tế
    binding.tvLabelVibrance.text = String.format("Vibrance (Bão hòa thông minh): %.2f", value)
    
    // 2. Cập nhật Object tạm thời
    editingPreset?.basic?.vibrance = value
    
    // 3. Đẩy sang GPU render lập tức lên ViewFinder
    applyLiveFilterToCamera(editingPreset)
}


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

  • Kiểm tra độ trong suốt Asset: Đảm bảo tất cả các file khung ảnh trong thư mục assets/frames/ là định dạng PNG-24 hỗ trợ Transparent hoàn toàn.
  • Cập nhật hiệu ứng hiển thị Card: Khi mở bảng sliders, áp dụng hiệu ứng phóng to nhẹ từ tâm (ScaleAnimation) thay vì trượt từ dưới lên để phù hợp hơn với kiểu giao diện hộp Floating Card cố định.
  • Tối ưu hóa ScrollView: Cài đặt thuộc tính ngăn chặn xung đột vuốt (Touch Interception) giữa thanh cuộn của ScrollView và các con trỏ trượt của Slider Mechanism của Material Component, giúp việc kéo thông số không bị trượt nhầm làm cuộn cả bảng.