Implement 2_PROJECT_ANDROID: Gradle configuration, layout setup, and CameraX initial logic
This commit is contained in:
@@ -0,0 +1,242 @@
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Bước 1: Khởi tạo các file build cấu hình Gradle cho Android
|
||||
|
||||
Để Android Studio có thể nhận diện và compile dự án, bạn cần tạo các file cấu hình bằng Kotlin DSL (`.gradle.kts`) tại thư mục `android-project`.
|
||||
|
||||
### 1. Tạo file `android-project/settings.gradle.kts`
|
||||
|
||||
File này dùng để khai báo tên dự án và quản lý kho chứa thư viện (Repositories).
|
||||
|
||||
```kotlin
|
||||
pluginManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "RetroPhotobooth"
|
||||
include(":app")
|
||||
|
||||
```
|
||||
|
||||
### 2. Tạo file `android-project/build.gradle.kts` (Của thư mục gốc Android)
|
||||
|
||||
```kotlin
|
||||
plugins {
|
||||
id("com.android.application") version "8.2.2" apply false
|
||||
id("org.jetbrains.kotlin.android") version "1.9.22" apply false
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### 3. Tạo file `android-project/app/build.gradle.kts` (Của module app)
|
||||
|
||||
Đây là nơi chúng ta khai báo SDK và các thư viện cần dùng (CameraX, Jetpack Compose hoặc XML Views). Ở đây mình cấu hình sẵn các dependency cho **CameraX**.
|
||||
|
||||
```kotlin
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.photobooth.app"
|
||||
compileSdk = 34
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.photobooth.app"
|
||||
minSdk = 26 // Android 8.0 trở lên để hỗ trợ tốt các tính năng đồ họa
|
||||
targetSdk = 34
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
buildFeatures {
|
||||
viewBinding = true // Bật ViewBinding để thao tác với giao diện dễ dàng
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("androidx.core:core-ktx:1.12.0")
|
||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||
implementation("com.google.android.material:material:1.11.0")
|
||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
||||
|
||||
// --- Thư viện CameraX Core ---
|
||||
val cameraVersion = "1.3.1"
|
||||
implementation("androidx.camera:camera-core:$cameraVersion")
|
||||
implementation("androidx.camera:camera-camera2:$cameraVersion")
|
||||
implementation("androidx.camera:camera-lifecycle:$cameraVersion")
|
||||
implementation("androidx.camera:camera-view:$cameraVersion")
|
||||
implementation("androidx.camera:camera-extensions:$cameraVersion")
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📸 Bước 2: Thiết lập Giao diện Chụp ảnh (XML Layout)
|
||||
|
||||
Tạo file giao diện hiển thị Camera preview và nút chụp ảnh tại đường dẫn:
|
||||
`android-project/app/src/main/res/layout/activity_main.xml` *(Bạn cần tạo thêm các thư mục `res/layout` nếu chưa có)*.
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res/auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#121212">
|
||||
|
||||
<!-- Màn hình xem trước Camera -->
|
||||
<androidx.camera.view.PreviewView
|
||||
android:id="@+id/viewFinder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintDimensionRatio="3:4"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/controlPanel" />
|
||||
|
||||
<!-- Bảng điều khiển phía dưới -->
|
||||
<RelativeLayout
|
||||
android:id="@+id/controlPanel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="120dp"
|
||||
android:background="#000000"
|
||||
app:layout_constraintBottom_toBottomOf="parent">
|
||||
|
||||
<!-- Nút chụp hình -->
|
||||
<ImageButton
|
||||
android:id="@+id/btnCapture"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
android:layout_centerInParent="true"
|
||||
android:background="@android:color/transparent"
|
||||
android:src="@android:drawable/ic_menu_camera"
|
||||
android:contentDescription="Capture Button" />
|
||||
</RelativeLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💻 Bước 3: Viết Code Logic Khởi Tạo CameraX (Kotlin)
|
||||
|
||||
Tạo file `MainActivity.kt` tại đường dẫn `android-project/app/src/main/java/com/photobooth/app/MainActivity.kt`. Đoạn code này thực hiện xin quyền Camera và kích hoạt kính ngắm.
|
||||
|
||||
```kotlin
|
||||
package com.photobooth.app
|
||||
|
||||
import android.Manifest
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.camera.core.CameraSelector
|
||||
import androidx.camera.core.Preview
|
||||
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.photobooth.app.databinding.ActivityMainBinding
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
private lateinit var cameraExecutor: ExecutorService
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
// Xin quyền Camera
|
||||
if (allPermissionsGranted()) {
|
||||
startCamera()
|
||||
} else {
|
||||
ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)
|
||||
}
|
||||
|
||||
cameraExecutor = Executors.newSingleThreadExecutor()
|
||||
}
|
||||
|
||||
private fun startCamera() {
|
||||
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
|
||||
|
||||
cameraProviderFuture.addListener({
|
||||
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
|
||||
|
||||
// Cấu hình Kính ngắm (Preview)
|
||||
val preview = Preview.Builder().build().also {
|
||||
it.setSurfaceProvider(binding.viewFinder.surfaceProvider)
|
||||
}
|
||||
|
||||
// Chọn Camera Trước (Mặc định cho Photobooth)
|
||||
val cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA
|
||||
|
||||
try {
|
||||
cameraProvider.unbindAll()
|
||||
cameraProvider.bindToLifecycle(this, cameraSelector, preview)
|
||||
} catch (xc: Exception) {
|
||||
Toast.makeText(this, "Không thể khởi động Camera", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}, ContextCompat.getMainExecutor(this))
|
||||
}
|
||||
|
||||
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
|
||||
ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
cameraExecutor.shutdown()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val REQUEST_CODE_PERMISSIONS = 10
|
||||
private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🏃♂️ Việc cần làm ngay lúc này của bạn:
|
||||
|
||||
1. **Mở Android Studio** trên Debian và thực hiện `Import Project`, trỏ tới thư mục `android-project`.
|
||||
2. Hệ thống sẽ tự động đồng bộ hóa (Gradle Sync).
|
||||
3. **Chạy thử nghiệm:** Bật Android Emulator (hoặc cắm điện thoại Android của bạn qua cổng USB bật gỡ lỗi ADB) để xem ứng dụng đã hiển thị được Camera lên màn hình chưa.
|
||||
|
||||
Khi luồng hiển thị Camera này chạy thành công, chúng ta sẽ chuyển sang code tính năng **Đếm ngược tự động chụp liên tiếp 4 tấm** và lưu file ảnh thô.
|
||||
@@ -5,19 +5,16 @@ plugins {
|
||||
|
||||
android {
|
||||
namespace = "com.photobooth.app"
|
||||
compileSdk = 33
|
||||
compileSdk = 34
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.photobooth.app"
|
||||
minSdk = 24
|
||||
targetSdk = 33
|
||||
minSdk = 26 // Android 8.0 trở lên để hỗ trợ tốt các tính năng đồ họa
|
||||
targetSdk = 34
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
useSupportLibrary = true
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@@ -30,48 +27,28 @@ android {
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
jvmTarget = "17"
|
||||
}
|
||||
buildFeatures {
|
||||
compose = true
|
||||
}
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "1.5.1"
|
||||
}
|
||||
packaging {
|
||||
resources {
|
||||
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
||||
}
|
||||
viewBinding = true // Bật ViewBinding để thao tác với giao diện dễ dàng
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("androidx.core:core-ktx:1.10.1")
|
||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
|
||||
implementation("androidx.activity:activity-compose:1.7.2")
|
||||
implementation(platform("androidx.compose:compose-bom:2023.06.01"))
|
||||
implementation("androidx.compose.ui:ui")
|
||||
implementation("androidx.compose.ui:ui-graphics")
|
||||
implementation("androidx.compose.ui:ui-tooling-preview")
|
||||
implementation("androidx.compose.material3:material3")
|
||||
implementation("androidx.core:core-ktx:1.12.0")
|
||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||
implementation("com.google.android.material:material:1.11.0")
|
||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
||||
|
||||
// CameraX dependencies for Sprint 1 Camera preview
|
||||
val cameraVersion = "1.3.0-rc01"
|
||||
// --- Thư viện CameraX Core ---
|
||||
val cameraVersion = "1.3.1"
|
||||
implementation("androidx.camera:camera-core:$cameraVersion")
|
||||
implementation("androidx.camera:camera-camera2:$cameraVersion")
|
||||
implementation("androidx.camera:camera-lifecycle:$cameraVersion")
|
||||
implementation("androidx.camera:camera-view:$cameraVersion")
|
||||
implementation("androidx.camera:camera-extensions:$cameraVersion")
|
||||
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
||||
androidTestImplementation(platform("androidx.compose:compose-bom:2023.06.01"))
|
||||
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
|
||||
debugImplementation("androidx.compose.ui:ui-tooling")
|
||||
debugImplementation("androidx.compose.ui:ui-test-manifest")
|
||||
}
|
||||
|
||||
@@ -1,44 +1,72 @@
|
||||
package com.photobooth.app
|
||||
|
||||
import android.Manifest
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.camera.core.CameraSelector
|
||||
import androidx.camera.core.Preview
|
||||
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.photobooth.app.databinding.ActivityMainBinding
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
private lateinit var cameraExecutor: ExecutorService
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState: Bundle?)
|
||||
setContent {
|
||||
MaterialTheme {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
color = MaterialTheme.colorScheme.background
|
||||
) {
|
||||
Greeting("Retro Photobooth")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
// Xin quyền Camera
|
||||
if (allPermissionsGranted()) {
|
||||
startCamera()
|
||||
} else {
|
||||
ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Greeting(name: String, modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
text = "Welcome to $name!",
|
||||
modifier = modifier
|
||||
)
|
||||
cameraExecutor = Executors.newSingleThreadExecutor()
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun GreetingPreview() {
|
||||
MaterialTheme {
|
||||
Greeting("Retro Photobooth")
|
||||
private fun startCamera() {
|
||||
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
|
||||
|
||||
cameraProviderFuture.addListener({
|
||||
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
|
||||
|
||||
// Cấu hình Kính ngắm (Preview)
|
||||
val preview = Preview.Builder().build().also {
|
||||
it.setSurfaceProvider(binding.viewFinder.surfaceProvider)
|
||||
}
|
||||
|
||||
// Chọn Camera Trước (Mặc định cho Photobooth)
|
||||
val cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA
|
||||
|
||||
try {
|
||||
cameraProvider.unbindAll()
|
||||
cameraProvider.bindToLifecycle(this, cameraSelector, preview)
|
||||
} catch (xc: Exception) {
|
||||
Toast.makeText(this, "Không thể khởi động Camera", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}, ContextCompat.getMainExecutor(this))
|
||||
}
|
||||
|
||||
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
|
||||
ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
cameraExecutor.shutdown()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val REQUEST_CODE_PERMISSIONS = 10
|
||||
private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res/auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#121212">
|
||||
|
||||
<!-- Màn hình xem trước Camera -->
|
||||
<androidx.camera.view.PreviewView
|
||||
android:id="@+id/viewFinder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintDimensionRatio="3:4"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/controlPanel" />
|
||||
|
||||
<!-- Bảng điều khiển phía dưới -->
|
||||
<RelativeLayout
|
||||
android:id="@+id/controlPanel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="120dp"
|
||||
android:background="#000000"
|
||||
app:layout_constraintBottom_toBottomOf="parent">
|
||||
|
||||
<!-- Nút chụp hình -->
|
||||
<ImageButton
|
||||
android:id="@+id/btnCapture"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
android:layout_centerInParent="true"
|
||||
android:background="@android:color/transparent"
|
||||
android:src="@android:drawable/ic_menu_camera"
|
||||
android:contentDescription="Capture Button" />
|
||||
</RelativeLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -1,5 +1,4 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
id("com.android.application") version "8.1.1" apply false
|
||||
id("org.jetbrains.kotlin.android") version "1.9.0" apply false
|
||||
id("com.android.application") version "8.2.2" apply false
|
||||
id("org.jetbrains.kotlin.android") version "1.9.22" apply false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user