145 lines
7.4 KiB
Markdown
145 lines
7.4 KiB
Markdown
|
|
# 🎨 KẾ HOẠCH TÁI CẤU TRÚC UX: HOẠT HỌA DI CHUYỂN & DANH SÁCH CUỘN ĐỒNG HÀNG
|
|
|
|
Mục tiêu: Khi kích hoạt Frame hoặc Preset, nút đại diện sẽ dịch chuyển sang trái làm điểm neo. Danh sách các item sẽ trượt trên cùng một hàng ngang và chui xuống dưới nút neo khi lướt (Swipe). Nhấn giữ preset để mở shortcut chỉnh sửa.
|
|
|
|
---
|
|
|
|
## 📐 1. Cấu Trúc Lại XML Layout Tuyến Tính (`activity_main.xml`)
|
|
|
|
Để đạt được hiệu ứng cuộn "chui xuống dưới nút", nút Neo (Anchor) phải có thứ tự hiển thị nằm **trên** (Z-index cao hơn) và nằm đè lên `RecyclerView`. Ta sẽ sử dụng một `FrameLayout` hoặc `ConstraintLayout` để làm việc này tại khu vực viền xanh lá của file `image_2a8660.png`.
|
|
|
|
```xml
|
|
<!-- KHU VỰC ĐIỀU KHIỂN DÒNG (Khung viền xanh trong image_2a8660.png) -->
|
|
<FrameLayout
|
|
android:id="@+id/panelSelectionContainer"
|
|
android:layout_width="match_parent"
|
|
android:layout_height="70dp"
|
|
android:background="#000000"
|
|
app:layout_constraintTop_toBottomOf="@id/cameraContainer"
|
|
app:layout_constraintBottom_toTopOf="@id/zoomControls">
|
|
|
|
<!-- LỚP ĐÁY: Danh sách cuộn ngang dùng chung cho cả Frame và Preset -->
|
|
<!-- Thuộc tính paddingStart giúp phần tử đầu tiên không bị nút Neo che khuất lúc khởi tạo -->
|
|
<androidx.recyclerview.widget.RecyclerView
|
|
android:id="@+id/rvHorizontalTimeline"
|
|
android:layout_width="match_parent"
|
|
android:layout_height="match_parent"
|
|
android:orientation="horizontal"
|
|
android:clipToPadding="false"
|
|
android:paddingStart="80dp"
|
|
android:paddingEnd="16dp"
|
|
android:visibility="gone" />
|
|
|
|
<!-- LỚP TRÊN: Chứa 2 nút chức năng chính (Gốc ở giữa, động chuyển sang trái) -->
|
|
<ConstraintLayout
|
|
android:id="@+id/buttonsWrapper"
|
|
android:layout_width="match_parent"
|
|
android:layout_height="match_parent">
|
|
|
|
<!-- Nút Frame -->
|
|
<ImageView
|
|
android:id="@+id/btnMainFrame"
|
|
android:layout_width="50dp"
|
|
android:layout_height="50dp"
|
|
android:src="@drawable/ic_frame_default"
|
|
android:background="@null"
|
|
app:layout_constraintTop_toTopOf="parent"
|
|
app:layout_constraintBottom_toBottomOf="parent"
|
|
app:layout_constraintStart_toStartOf="parent"
|
|
app:layout_constraintEnd_toStartOf="@id/btnMainPreset"
|
|
app:layout_constraintHorizontal_chainStyle="packed"
|
|
android:layout_marginEnd="16dp"/>
|
|
|
|
<!-- Nút Presets Màu -->
|
|
<ImageView
|
|
android:id="@+id/btnMainPreset"
|
|
android:layout_width="50dp"
|
|
android:layout_height="50dp"
|
|
android:src="@drawable/ic_palette_default"
|
|
android:background="@null"
|
|
app:layout_constraintTop_toTopOf="parent"
|
|
app:layout_constraintBottom_toBottomOf="parent"
|
|
app:layout_constraintStart_toEndOf="@id/btnMainFrame"
|
|
app:layout_constraintEnd_toEndOf="parent" />
|
|
|
|
</ConstraintLayout>
|
|
</FrameLayout>
|
|
|
|
```
|
|
|
|
---
|
|
|
|
## 💻 2. Kịch Bản Hoạt Họa & Điều Khiển Trạng Thái (Kotlin)
|
|
|
|
### 🔹 2.1. Logic Di Chuyển Nút Ra Cạnh Trái (Animation)
|
|
|
|
Khi người dùng nhấn vào nút Frame, ta sẽ chạy một Animation dịch chuyển nút này về sát rìa trái màn hình, đồng thời ẩn nút còn lại và hiển thị `RecyclerView`.
|
|
|
|
```kotlin
|
|
private var isFrameModeActive = false
|
|
|
|
fun toggleFrameMode(activate: Boolean) {
|
|
if (activate) {
|
|
isFrameModeActive = true
|
|
// 1. Ẩn nút Preset màu đi
|
|
binding.btnMainPreset.animate().alpha(0f).setDuration(200).withEndAction {
|
|
binding.btnMainPreset.visibility = View.GONE
|
|
}
|
|
|
|
// 2. Dịch chuyển nút Frame sang trái (Dùng TransitionManager để mượt mà)
|
|
TransitionManager.beginDelayedTransition(binding.buttonsWrapper)
|
|
val params = binding.btnMainFrame.layoutParams as ConstraintLayout.LayoutParams
|
|
params.endToStart = ConstraintLayout.LayoutParams.UNSET // Hủy liên kết xích giữa
|
|
params.endToEnd = ConstraintLayout.LayoutParams.UNSET
|
|
binding.btnMainFrame.layoutParams = params
|
|
|
|
// 3. Hiển thị RecyclerView cùng hàng
|
|
binding.rvHorizontalTimeline.apply {
|
|
adapter = frameAdapter // Đổi sang dữ liệu của Frame
|
|
visibility = View.VISIBLE
|
|
alpha = 0f
|
|
animate().alpha(1f).setDuration(300).start()
|
|
}
|
|
} else {
|
|
// Logic phục hồi về trạng thái cân bằng ở giữa ban đầu như trong ảnh image_2a8660.png
|
|
}
|
|
}
|
|
|
|
```
|
|
|
|
> **Giải thích cơ chế "Chui xuống dưới":** Do `rvHorizontalTimeline` được khai báo trước trong `FrameLayout`, nó nằm ở lớp dưới. Nút `btnMainFrame` nằm ở lớp trên (`ConstraintLayout`). Khi bạn vuốt cuộn ngang `RecyclerView`, các item bên trong sẽ tự nhiên trượt và chui xuống dưới khu vực nút Frame đang đứng cố định ở góc trái.
|
|
|
|
---
|
|
|
|
## 🔄 3. Cơ Chế Nhấn Giữ Preset & Nút Chỉnh Sửa Nhỏ (Edit Shortcut)
|
|
|
|
Để giải quyết yêu cầu nhấn giữ mở nút Edit nhỏ mà không làm thay đổi preset gốc, chúng ta sẽ áp dụng cơ chế **Floating Context Button**.
|
|
|
|
### 🔹 3.1. Thiết kế Giao Diện Nút Edit Nhỏ trong Item của RecyclerView
|
|
|
|
Trong file layout thiết kế cho từng ô Preset nhỏ (`item_preset.xml`), ta thêm một nút icon cây bút nhỏ (`btnSmallEdit`) nằm đè ở góc trên bên phải của ô. Mặc định nút này sẽ ẩn (`android:visibility="gone"`).
|
|
|
|
### 🔹 3.2. Code bắt sự kiện Nhấn giữ (OnLongClickListener)
|
|
|
|
1. **Bước 1: Hiện nút Edit nhỏ:** Nhấn giữ Item.
|
|
Trong Adapter của Preset, gán sự kiện `setOnLongClickListener` cho ô màu. Khi kích hoạt, hiển thị nút `btnSmallEdit` (`View.VISIBLE`) kèm hiệu ứng rung nhẹ (Haptic Feedback) để thông báo cho người dùng.
|
|
|
|
|
|
2. **Bước 2: Mở bảng chỉnh sửa độc lập:** Chạm nút Edit.
|
|
Khi người dùng bấm vào nút `btnSmallEdit` nhỏ vừa hiện ra, app sẽ nhân bản (Clone) đối tượng `ColorPreset` hiện tại thành một thực thể tạm thời và mở bảng điều khiển Sliders (Vibrance, Clarity, Dehaze...).
|
|
|
|
|
|
3. **Bước 3: Lưu thành Preset mới:** Đặt tên & Ghi file.
|
|
Sau khi tinh chỉnh xong và ấn Save, một Dialog hiện lên yêu cầu nhập tên mới (Ví dụ: "Instax của Lộc"). App lưu file JSON mới này vào bộ nhớ riêng (`context.filesDir`) và giữ nguyên vẹn file preset gốc ban đầu.
|
|
|
|
|
|
---
|
|
|
|
## 📅 4. Các Đầu Việc Cần Làm Ngay (Checklist)
|
|
|
|
* [ ] **Cập nhật lại Code Layout:** Thiết lập `FrameLayout` bao bọc bên ngoài đúng như cấu trúc để tạo lớp đè cho hiệu ứng chui dưới nút.
|
|
* [ ] **Tạo File Hoạt Họa/Transition:** Viết logic hoàn chỉnh cho cả việc lật ngược trạng thái (khi người dùng bấm vào nút Neo lần nữa để đóng timeline, đưa 2 nút quay lại vị trí trung tâm cân bằng như ảnh `image_2a8660.png`).
|
|
* [ ] **Cập nhật ViewHolder trong Adapter:** Thêm tính năng ẩn/hiện nút `btnSmallEdit` dựa trên trạng thái click của từng item đơn lẻ.
|
|
|