Implement 3_ANDROID_CAMERA: Advanced Camera features (Zoom controls, flash modes, front/back switch, and countdown timer)
This commit is contained in:
@@ -0,0 +1,110 @@
|
|||||||
|
# 📸 KẾ HOẠCH PHÁT TRIỂN: TÍNH NĂNG CAMERA NÂNG CAO (ANDROID)
|
||||||
|
|
||||||
|
Mục tiêu: Hoàn thiện bộ điều khiển Camera đầy đủ cho ứng dụng Photobooth bao gồm: Đổi camera, Hẹn giờ (Timer), Điều khiển Flash, và Chuyển đổi các ống kính Zoom định sẵn ($0.5x, 1x, 3x$).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ 1. Cấu Trúc File Cập Nhật Quy Trình UI (`activity_main.xml`)
|
||||||
|
|
||||||
|
Chúng ta cần bổ sung các nút bấm vào giao diện để người dùng tương tác. Cập nhật file `res/layout/activity_main.xml` với các ID trực quan.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<!-- Thêm các nút điều khiển vào phía trên kính ngắm (Top Bar) -->
|
||||||
|
<!-- 1. Nút Đổi Camera: id=@+id/btnSwitchCamera -->
|
||||||
|
<!-- 2. Nút Flash (On/Off/Auto): id=@+id/btnFlash -->
|
||||||
|
<!-- 3. Nút Hẹn giờ (Off/3s/5s/10s): id=@+id/btnTimer -->
|
||||||
|
|
||||||
|
<!-- Thêm thanh chọn Zoom vào ngay trên Control Panel -->
|
||||||
|
<!-- Gồm 3 nút tương ứng: 0.5x, 1x, 3x -->
|
||||||
|
<!-- id=@+id/btnZoom05, id=@+id/btnZoom10, id=@+id/btnZoom30 -->
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💻 2. Kế Hoạch Hiện Thực Hóa Logic (Kotlin trong `MainActivity.kt`)
|
||||||
|
|
||||||
|
Dưới đây là kiến trúc mã nguồn và các biến trạng thái cần quản lý để thực hiện các tính năng.
|
||||||
|
|
||||||
|
### 🔹 2.1. Khai báo các biến trạng thái (State Management)
|
||||||
|
|
||||||
|
Đặt các biến này ở đầu class `MainActivity` để theo dõi cấu hình hiện tại:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
private var lensFacing = CameraSelector.DEFAULT_FRONT_CAMERA // Mặc định camera trước
|
||||||
|
private var flashMode = ImageCapture.FLASH_MODE_OFF // Mặc định tắt flash
|
||||||
|
private var timerSeconds = 0 // Mặc định không hẹn giờ (0s)
|
||||||
|
private var cameraControl: CameraControl? = null // Dùng để điều khiển Zoom/Flash
|
||||||
|
private var cameraInfo: CameraInfo? = null // Dùng để đọc thông số phần cứng
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🔹 2.2. Logic Tính Năng Chi Tiết & Giải Pháp Code
|
||||||
|
|
||||||
|
1. **Chuyển Đổi Camera (Trước ⇄ Sau):** Xử lý lật ống kính.
|
||||||
|
Khi bấm `btnSwitchCamera`, đổi giá trị `lensFacing` giữa `CameraSelector.DEFAULT_FRONT_CAMERA` và `DEFAULT_BACK_CAMERA`. Sau đó gọi lại hàm `startCamera()` để giải phóng session cũ và bind session mới.
|
||||||
|
|
||||||
|
|
||||||
|
2. **Điều Khiển Đèn Flash:** On / Off / Auto.
|
||||||
|
Khi khởi tạo `ImageCapture`, gán trực tiếp thuộc tính:
|
||||||
|
`imageCapture.flashMode = flashMode`.
|
||||||
|
Khi người dùng bấm nút Flash, thay đổi biến trạng thái và cập nhật icon tương ứng (⚡, ❌, Ⓐ).
|
||||||
|
|
||||||
|
|
||||||
|
3. **Cơ Chế Hẹn Giờ Chụp (Timer):** Đếm ngược trực quan.
|
||||||
|
Sử dụng `CountDownTimer` của Android. Khi bấm nút chụp:
|
||||||
|
Nếu `timerSeconds > 0`, hiển thị một `TextView` số lớn ở giữa màn hình, đếm ngược mỗi giây và phát âm thanh "Tích". Khi đếm về 0, kích hoạt hàm `takePicture()`.
|
||||||
|
|
||||||
|
|
||||||
|
4. **Hệ Thống Zoom Cố Định (0.5x, 1x, 3x):** Điều khiển Zoom kỹ thuật số/quang học.
|
||||||
|
Sau khi `bindToLifecycle`, lấy đối tượng `camera.cameraControl`.
|
||||||
|
Sử dụng lệnh: `cameraControl?.setZoomRatio(ratio)` khi người dùng bấm vào các nút zoom:
|
||||||
|
|
||||||
|
* **0.5x:** `setZoomRatio(0.5f)` *(Lưu ý: Thiết bị phải có camera góc siêu rộng phần cứng mới hoạt động thực tế, nếu không CameraX tự thu phóng số)*.
|
||||||
|
* **1x:** `setZoomRatio(1.0f)`
|
||||||
|
* **3x:** `setZoomRatio(3.0f)`
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 3. Đoạn Code Mẫu Triển Khai Bộ Thu Phóng (Zoom) và Lật Camera
|
||||||
|
|
||||||
|
Bạn có thể dán đoạn logic này vào `MainActivity.kt` sau khi hoàn thiện phần giao diện:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// Hàm xử lý đổi Camera
|
||||||
|
private fun toggleCamera() {
|
||||||
|
lensFacing = if (lensFacing == CameraSelector.DEFAULT_FRONT_CAMERA) {
|
||||||
|
CameraSelector.DEFAULT_BACK_CAMERA
|
||||||
|
} else {
|
||||||
|
CameraSelector.DEFAULT_FRONT_CAMERA
|
||||||
|
}
|
||||||
|
startCamera() // Khởi động lại camera với ống kính mới
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hàm xử lý Zoom mượt mà bằng CameraControl
|
||||||
|
private fun applyZoom(ratio: Float) {
|
||||||
|
cameraInfo?.zoomState?.value?.let { currentState ->
|
||||||
|
val maxZoom = currentState.maxZoomRatio
|
||||||
|
val minZoom = currentState.minZoomRatio
|
||||||
|
|
||||||
|
// Kiểm tra xem thiết bị có hỗ trợ mức zoom này không
|
||||||
|
if (ratio in minZoom..maxZoom) {
|
||||||
|
cameraControl?.setZoomRatio(ratio)
|
||||||
|
} else {
|
||||||
|
Toast.makeText(this, "Thiết bị không hỗ trợ mức zoom ${ratio}x", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📅 4. Các Đầu Việc Cần Hoàn Thành (Checklist)
|
||||||
|
|
||||||
|
* [ ] Thiết kế các Icon cho nút bấm (Đổi camera, Flash, Hẹn giờ) và đưa vào thư mục `res/drawable`.
|
||||||
|
* [ ] Cập nhật giao diện `activity_main.xml` xếp chồng các nút điều khiển lên trên kính ngắm.
|
||||||
|
* [ ] Viết hàm `setupCameraControls()` để gán sự kiện `setOnClickListener` cho tất cả các nút bấm.
|
||||||
|
* [ ] Khởi tạo thêm `ImageCapture` bên cạnh `Preview` để sẵn sàng cho việc nhận lệnh chụp ảnh từ bộ hẹn giờ.
|
||||||
|
* [ ] Kiểm thử tính năng Zoom và lật Camera trên thiết bị thật (Khuyên dùng thiết bị thật vì Emulator không mô phỏng được camera góc rộng 0.5x hoặc tele 3x).
|
||||||
@@ -5,7 +5,10 @@ import android.content.pm.PackageManager
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.camera.core.CameraControl
|
||||||
|
import androidx.camera.core.CameraInfo
|
||||||
import androidx.camera.core.CameraSelector
|
import androidx.camera.core.CameraSelector
|
||||||
|
import androidx.camera.core.ImageCapture
|
||||||
import androidx.camera.core.Preview
|
import androidx.camera.core.Preview
|
||||||
import androidx.camera.lifecycle.ProcessCameraProvider
|
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
@@ -18,12 +21,24 @@ class MainActivity : AppCompatActivity() {
|
|||||||
private lateinit var binding: ActivityMainBinding
|
private lateinit var binding: ActivityMainBinding
|
||||||
private lateinit var cameraExecutor: ExecutorService
|
private lateinit var cameraExecutor: ExecutorService
|
||||||
|
|
||||||
|
// Camera settings state
|
||||||
|
private var lensFacing = CameraSelector.DEFAULT_FRONT_CAMERA // Default front camera
|
||||||
|
private var flashMode = ImageCapture.FLASH_MODE_OFF // Default flash off
|
||||||
|
private var timerSeconds = 0 // Default no timer (0s)
|
||||||
|
private var cameraControl: CameraControl? = null // Controls zoom/flash
|
||||||
|
private var cameraInfo: CameraInfo? = null // Query capabilities
|
||||||
|
private var imageCapture: ImageCapture? = null // Capture usecase
|
||||||
|
private var isCountingDown = false // Countdown state flag
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
// Xin quyền Camera
|
// Setup UI control listeners
|
||||||
|
setupCameraControls()
|
||||||
|
|
||||||
|
// Request camera permission
|
||||||
if (allPermissionsGranted()) {
|
if (allPermissionsGranted()) {
|
||||||
startCamera()
|
startCamera()
|
||||||
} else {
|
} else {
|
||||||
@@ -33,33 +48,214 @@ class MainActivity : AppCompatActivity() {
|
|||||||
cameraExecutor = Executors.newSingleThreadExecutor()
|
cameraExecutor = Executors.newSingleThreadExecutor()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setupCameraControls() {
|
||||||
|
// Toggle Switch Camera Button
|
||||||
|
binding.btnSwitchCamera.setOnClickListener {
|
||||||
|
toggleCamera()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle Flash Button
|
||||||
|
binding.btnFlash.setOnClickListener {
|
||||||
|
toggleFlash()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle Timer Button
|
||||||
|
binding.btnTimer.setOnClickListener {
|
||||||
|
toggleTimer()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zoom 0.5x Button
|
||||||
|
binding.btnZoom05.setOnClickListener {
|
||||||
|
applyZoom(0.5f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zoom 1x Button
|
||||||
|
binding.btnZoom10.setOnClickListener {
|
||||||
|
applyZoom(1.0f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zoom 3x Button
|
||||||
|
binding.btnZoom30.setOnClickListener {
|
||||||
|
applyZoom(3.0f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutter Button
|
||||||
|
binding.btnCapture.setOnClickListener {
|
||||||
|
onCaptureClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun startCamera() {
|
private fun startCamera() {
|
||||||
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
|
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
|
||||||
|
|
||||||
cameraProviderFuture.addListener({
|
cameraProviderFuture.addListener({
|
||||||
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
|
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
|
||||||
|
|
||||||
// Cấu hình Kính ngắm (Preview)
|
// Preview configuration
|
||||||
val preview = Preview.Builder().build().also {
|
val preview = Preview.Builder().build().also {
|
||||||
it.setSurfaceProvider(binding.viewFinder.surfaceProvider)
|
it.setSurfaceProvider(binding.viewFinder.surfaceProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chọn Camera Trước (Mặc định cho Photobooth)
|
// ImageCapture configuration
|
||||||
val cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA
|
imageCapture = ImageCapture.Builder()
|
||||||
|
.setFlashMode(flashMode)
|
||||||
|
.build()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
cameraProvider.unbindAll()
|
cameraProvider.unbindAll()
|
||||||
cameraProvider.bindToLifecycle(this, cameraSelector, preview)
|
|
||||||
|
// Bind usecases to Lifecycle
|
||||||
|
val camera = cameraProvider.bindToLifecycle(
|
||||||
|
this, lensFacing, preview, imageCapture
|
||||||
|
)
|
||||||
|
|
||||||
|
cameraControl = camera.cameraControl
|
||||||
|
cameraInfo = camera.cameraInfo
|
||||||
|
|
||||||
|
// Reset zoom to 1.0x on start/switch
|
||||||
|
applyZoom(1.0f)
|
||||||
} catch (xc: Exception) {
|
} catch (xc: Exception) {
|
||||||
Toast.makeText(this, "Không thể khởi động Camera", Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, "Không thể khởi động Camera", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}, ContextCompat.getMainExecutor(this))
|
}, ContextCompat.getMainExecutor(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun toggleCamera() {
|
||||||
|
if (isCountingDown) return
|
||||||
|
lensFacing = if (lensFacing == CameraSelector.DEFAULT_FRONT_CAMERA) {
|
||||||
|
CameraSelector.DEFAULT_BACK_CAMERA
|
||||||
|
} else {
|
||||||
|
CameraSelector.DEFAULT_FRONT_CAMERA
|
||||||
|
}
|
||||||
|
binding.btnSwitchCamera.text = if (lensFacing == CameraSelector.DEFAULT_FRONT_CAMERA) "🔄 Front" else "🔄 Back"
|
||||||
|
startCamera()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun toggleFlash() {
|
||||||
|
flashMode = when (flashMode) {
|
||||||
|
ImageCapture.FLASH_MODE_OFF -> ImageCapture.FLASH_MODE_ON
|
||||||
|
ImageCapture.FLASH_MODE_ON -> ImageCapture.FLASH_MODE_AUTO
|
||||||
|
else -> ImageCapture.FLASH_MODE_OFF
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.btnFlash.text = when (flashMode) {
|
||||||
|
ImageCapture.FLASH_MODE_ON -> "⚡ On"
|
||||||
|
ImageCapture.FLASH_MODE_AUTO -> "⚡ Auto"
|
||||||
|
else -> "⚡ Off"
|
||||||
|
}
|
||||||
|
|
||||||
|
imageCapture?.flashMode = flashMode
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun toggleTimer() {
|
||||||
|
if (isCountingDown) return
|
||||||
|
timerSeconds = when (timerSeconds) {
|
||||||
|
0 -> 3
|
||||||
|
3 -> 5
|
||||||
|
5 -> 10
|
||||||
|
else -> 0
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.btnTimer.text = when (timerSeconds) {
|
||||||
|
0 -> "⏱️ Off"
|
||||||
|
else -> "⏱️ ${timerSeconds}s"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun applyZoom(ratio: Float) {
|
||||||
|
cameraInfo?.zoomState?.value?.let { currentState ->
|
||||||
|
val maxZoom = currentState.maxZoomRatio
|
||||||
|
val minZoom = currentState.minZoomRatio
|
||||||
|
|
||||||
|
if (ratio in minZoom..maxZoom) {
|
||||||
|
cameraControl?.setZoomRatio(ratio)
|
||||||
|
updateZoomButtonsUI(ratio)
|
||||||
|
} else {
|
||||||
|
Toast.makeText(this, "Thiết bị không hỗ trợ mức zoom ${ratio}x", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
} ?: run {
|
||||||
|
// Fallback for emulator where zoomState might be null on start
|
||||||
|
cameraControl?.setZoomRatio(ratio)
|
||||||
|
updateZoomButtonsUI(ratio)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateZoomButtonsUI(ratio: Float) {
|
||||||
|
binding.btnZoom05.backgroundTintList = android.content.res.ColorStateList.valueOf(
|
||||||
|
android.graphics.Color.parseColor(if (ratio == 0.5f) "#FF9800" else "#2D2D2D")
|
||||||
|
)
|
||||||
|
binding.btnZoom10.backgroundTintList = android.content.res.ColorStateList.valueOf(
|
||||||
|
android.graphics.Color.parseColor(if (ratio == 1.0f) "#FF9800" else "#2D2D2D")
|
||||||
|
)
|
||||||
|
binding.btnZoom30.backgroundTintList = android.content.res.ColorStateList.valueOf(
|
||||||
|
android.graphics.Color.parseColor(if (ratio == 3.0f) "#FF9800" else "#2D2D2D")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onCaptureClick() {
|
||||||
|
if (isCountingDown) return
|
||||||
|
|
||||||
|
if (timerSeconds > 0) {
|
||||||
|
startCountDown()
|
||||||
|
} else {
|
||||||
|
takePicture()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startCountDown() {
|
||||||
|
isCountingDown = true
|
||||||
|
binding.txtTimerCountdown.visibility = android.view.View.VISIBLE
|
||||||
|
|
||||||
|
val timer = object : android.os.CountDownTimer(timerSeconds * 1000L, 1000L) {
|
||||||
|
override fun onTick(millisUntilFinished: Long) {
|
||||||
|
val secondsRemaining = (millisUntilFinished / 1000L) + 1
|
||||||
|
binding.txtTimerCountdown.text = secondsRemaining.toString()
|
||||||
|
try {
|
||||||
|
val toneG = android.media.ToneGenerator(android.media.AudioManager.STREAM_ALARM, 50)
|
||||||
|
toneG.startTone(android.media.ToneGenerator.TONE_PROP_BEEP, 100)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// Fallback if ToneGenerator fails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFinish() {
|
||||||
|
binding.txtTimerCountdown.visibility = android.view.View.GONE
|
||||||
|
isCountingDown = false
|
||||||
|
takePicture()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
timer.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun takePicture() {
|
||||||
|
try {
|
||||||
|
val toneG = android.media.ToneGenerator(android.media.AudioManager.STREAM_ALARM, 100)
|
||||||
|
toneG.startTone(android.media.ToneGenerator.TONE_PROP_ACK, 250)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// Fallback
|
||||||
|
}
|
||||||
|
Toast.makeText(this, "📸 Tách! Chụp ảnh thành công!", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
|
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
|
||||||
ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED
|
ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onRequestPermissionsResult(
|
||||||
|
requestCode: Int,
|
||||||
|
permissions: Array<out String>,
|
||||||
|
grantResults: IntArray
|
||||||
|
) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
|
if (requestCode == REQUEST_CODE_PERMISSIONS) {
|
||||||
|
if (allPermissionsGranted()) {
|
||||||
|
startCamera()
|
||||||
|
} else {
|
||||||
|
Toast.makeText(this, "Quyền Camera bị từ chối.", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
cameraExecutor.shutdown()
|
cameraExecutor.shutdown()
|
||||||
|
|||||||
@@ -14,6 +14,132 @@
|
|||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintBottom_toTopOf="@id/controlPanel" />
|
app:layout_constraintBottom_toTopOf="@id/controlPanel" />
|
||||||
|
|
||||||
|
<!-- Thanh công cụ trên đầu (Translucent Black Top Bar) -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/topBar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="60dp"
|
||||||
|
android:background="#80000000"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<!-- Nút Flash -->
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnFlash"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginHorizontal="4dp"
|
||||||
|
android:backgroundTint="#2D2D2D"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:text="⚡ Off"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:insetTop="0dp"
|
||||||
|
android:insetBottom="0dp" />
|
||||||
|
|
||||||
|
<!-- Nút Hẹn giờ -->
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnTimer"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginHorizontal="4dp"
|
||||||
|
android:backgroundTint="#2D2D2D"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:text="⏱️ Off"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:insetTop="0dp"
|
||||||
|
android:insetBottom="0dp" />
|
||||||
|
|
||||||
|
<!-- Nút Đổi Camera -->
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnSwitchCamera"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginHorizontal="4dp"
|
||||||
|
android:backgroundTint="#2D2D2D"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:text="🔄 Front"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:insetTop="0dp"
|
||||||
|
android:insetBottom="0dp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Số đếm ngược khi hẹn giờ chụp -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/txtTimerCountdown"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="100sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:shadowColor="#80000000"
|
||||||
|
android:shadowDx="4"
|
||||||
|
android:shadowDy="4"
|
||||||
|
android:shadowRadius="10"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:text="3"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/viewFinder"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/viewFinder"
|
||||||
|
app:layout_constraintLeft_toLeftOf="@id/viewFinder"
|
||||||
|
app:layout_constraintRight_toRightOf="@id/viewFinder" />
|
||||||
|
|
||||||
|
<!-- Thanh chọn Zoom ngay trên bảng điều khiển -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/zoomBar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
|
android:background="#80000000"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="4dp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/controlPanel"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnZoom05"
|
||||||
|
android:layout_width="50dp"
|
||||||
|
android:layout_height="36dp"
|
||||||
|
android:layout_marginHorizontal="2dp"
|
||||||
|
android:backgroundTint="#2D2D2D"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:text="0.5x"
|
||||||
|
android:textSize="10sp"
|
||||||
|
android:padding="0dp"
|
||||||
|
android:insetTop="0dp"
|
||||||
|
android:insetBottom="0dp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnZoom10"
|
||||||
|
android:layout_width="50dp"
|
||||||
|
android:layout_height="36dp"
|
||||||
|
android:layout_marginHorizontal="2dp"
|
||||||
|
android:backgroundTint="#FF9800"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:text="1x"
|
||||||
|
android:textSize="10sp"
|
||||||
|
android:padding="0dp"
|
||||||
|
android:insetTop="0dp"
|
||||||
|
android:insetBottom="0dp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnZoom30"
|
||||||
|
android:layout_width="50dp"
|
||||||
|
android:layout_height="36dp"
|
||||||
|
android:layout_marginHorizontal="2dp"
|
||||||
|
android:backgroundTint="#2D2D2D"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:text="3x"
|
||||||
|
android:textSize="10sp"
|
||||||
|
android:padding="0dp"
|
||||||
|
android:insetTop="0dp"
|
||||||
|
android:insetBottom="0dp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<!-- Bảng điều khiển phía dưới -->
|
<!-- Bảng điều khiển phía dưới -->
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
android:id="@+id/controlPanel"
|
android:id="@+id/controlPanel"
|
||||||
|
|||||||
Reference in New Issue
Block a user