Distance and collect images auto

This commit is contained in:
SaiD 2025-11-25 23:06:17 +05:30
parent 3e5a8772d7
commit 6de6ef8bf8
6 changed files with 402 additions and 129 deletions

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@ -1,14 +1,17 @@
package com.example.animalrating
import android.Manifest
import android.content.ContentValues
import android.content.pm.ActivityInfo
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import android.graphics.Matrix
import android.os.Build
import android.os.Bundle
import android.provider.MediaStore
import android.util.Log
import android.widget.Button
import android.util.Size
import android.widget.ImageView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
@ -23,6 +26,7 @@ import androidx.core.content.ContextCompat
import com.example.animalrating.ml.CowAnalyzer
import com.example.animalrating.ui.SilhouetteOverlay
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.util.concurrent.Executors
@ -41,6 +45,9 @@ class CameraProcessor : AppCompatActivity(), CowAnalyzer.CowListener {
private var orientation: String? = null
private var currentMask: Bitmap? = null
private var savedMaskBitmap: Bitmap? = null
private var isPhotoTaken = false
private var matchThreshold = 75
private var algorithm = HomeActivity.ALGORITHM_HAMMING
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -49,6 +56,11 @@ class CameraProcessor : AppCompatActivity(), CowAnalyzer.CowListener {
cowName = intent.getStringExtra("COW_NAME")
orientation = intent.getStringExtra("ORIENTATION")
// Load settings
val prefs = getSharedPreferences("AnimalRatingPrefs", MODE_PRIVATE)
matchThreshold = prefs.getInt("THRESHOLD", 75)
algorithm = prefs.getString("ALGORITHM", HomeActivity.ALGORITHM_HAMMING) ?: HomeActivity.ALGORITHM_HAMMING
// Set orientation based on selected view
if (orientation == "front" || orientation == "back") {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
@ -61,33 +73,7 @@ class CameraProcessor : AppCompatActivity(), CowAnalyzer.CowListener {
segmentationOverlay = findViewById(R.id.segmentationOverlay)
savedMaskOverlay = findViewById(R.id.savedMaskOverlay)
frameProcessor = FrameProcessor { maskBitmap ->
runOnUiThread {
if (maskBitmap != null && (orientation == "front" || orientation == "back")) {
try {
val matrix = Matrix()
matrix.postRotate(90f)
val rotatedBitmap = Bitmap.createBitmap(
maskBitmap, 0, 0, maskBitmap.width, maskBitmap.height, matrix, true
)
currentMask = rotatedBitmap
segmentationOverlay.setImageBitmap(rotatedBitmap)
} catch (e: Exception) {
Log.e("CameraProcessor", "Error rotating mask", e)
currentMask = maskBitmap
segmentationOverlay.setImageBitmap(maskBitmap)
}
} else {
currentMask = maskBitmap
segmentationOverlay.setImageBitmap(maskBitmap)
}
}
}
val btnSave = findViewById<Button>(R.id.btnSave)
btnSave.setOnClickListener {
saveBitmask()
}
frameProcessor = FrameProcessor()
val silhouetteId = intent.getIntExtra("SILHOUETTE_ID", 0)
overlay.setSilhouette(silhouetteId)
@ -98,6 +84,9 @@ class CameraProcessor : AppCompatActivity(), CowAnalyzer.CowListener {
}
private fun takePhoto() {
if (isPhotoTaken) return
isPhotoTaken = true
val imageCapture = imageCapture ?: return
val name = cowName ?: "unknown"
@ -121,6 +110,7 @@ class CameraProcessor : AppCompatActivity(), CowAnalyzer.CowListener {
override fun onError(exc: ImageCaptureException) {
Log.e("MainActivity", "Photo capture failed: ${exc.message}", exc)
Toast.makeText(baseContext, "Photo capture failed", Toast.LENGTH_SHORT).show()
isPhotoTaken = false
}
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
@ -139,18 +129,57 @@ class CameraProcessor : AppCompatActivity(), CowAnalyzer.CowListener {
}
}
// Save to public gallery
saveToGallery(file)
val msg = "Saved as $filename"
Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
finish()
} catch (e: Exception) {
Log.e("MainActivity", "Error rotating image", e)
Log.e("MainActivity", "Error saving image", e)
Toast.makeText(baseContext, "Error saving image", Toast.LENGTH_SHORT).show()
isPhotoTaken = false
}
}
}
)
}
private fun saveToGallery(file: File) {
val values = ContentValues().apply {
put(MediaStore.Images.Media.DISPLAY_NAME, file.name)
put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/AnimalRating")
put(MediaStore.Images.Media.IS_PENDING, 1)
}
}
val collection = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
} else {
MediaStore.Images.Media.EXTERNAL_CONTENT_URI
}
try {
val uri = contentResolver.insert(collection, values)
uri?.let {
contentResolver.openOutputStream(it)?.use { out ->
FileInputStream(file).use { input ->
input.copyTo(out)
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
values.clear()
values.put(MediaStore.Images.Media.IS_PENDING, 0)
contentResolver.update(it, values, null, null)
}
}
} catch (e: Exception) {
Log.e("CameraProcessor", "Error saving to gallery", e)
}
}
private fun loadSavedMask() {
val side = orientation ?: "unknown"
@ -160,12 +189,19 @@ class CameraProcessor : AppCompatActivity(), CowAnalyzer.CowListener {
if (file.exists()) {
try {
val savedBitmap = BitmapFactory.decodeFile(file.absolutePath)
savedMaskBitmap = savedBitmap // Store reference for comparison
// Apply green color filter or process pixels
// Apply green color filter for visualization (on original size)
val greenMask = applyGreenColor(savedBitmap)
savedMaskOverlay.setImageBitmap(greenMask)
savedMaskOverlay.alpha = 0.5f
// Scale to 640x480 (or flipped for portrait) for comparison
val isPortrait = (side == "front" || side == "back")
val width = if (isPortrait) 480 else 640
val height = if (isPortrait) 640 else 480
savedMaskBitmap = Bitmap.createScaledBitmap(savedBitmap, width, height, true)
} catch (e: Exception) {
Log.e("CameraProcessor", "Error loading saved mask", e)
}
@ -181,69 +217,13 @@ class CameraProcessor : AppCompatActivity(), CowAnalyzer.CowListener {
for (i in pixels.indices) {
val alpha = (pixels[i] shr 24) and 0xff
if (alpha > 10) {
// Set to Green with original alpha or fixed alpha
// Set to Green with original alpha
pixels[i] = Color.argb(alpha, 0, 255, 0)
}
}
return Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888)
}
private fun saveBitmask() {
val mask = currentMask
if (mask == null) {
Toast.makeText(this, "No mask available to save", Toast.LENGTH_SHORT).show()
return
}
val side = orientation ?: "unknown"
val filename = "${side}_mask.png"
val file = File(filesDir, filename)
try {
FileOutputStream(file).use { out ->
mask.compress(Bitmap.CompressFormat.PNG, 100, out)
}
Toast.makeText(this, "Bitmask saved as $filename", Toast.LENGTH_SHORT).show()
// Reload to show the newly saved mask immediately
loadSavedMask()
} catch (e: Exception) {
Log.e("CameraProcessor", "Error saving bitmask", e)
Toast.makeText(this, "Error saving bitmask", Toast.LENGTH_SHORT).show()
}
}
// Function to calculate Hamming distance between two bitmasks
fun calculateHammingDistance(mask1: Bitmap, mask2: Bitmap, threshold: Int): Boolean {
if (mask1.width != mask2.width || mask1.height != mask2.height) {
Log.w("CameraProcessor", "Masks dimensions mismatch: ${mask1.width}x${mask1.height} vs ${mask2.width}x${mask2.height}")
return false
}
val width = mask1.width
val height = mask1.height
val pixels1 = IntArray(width * height)
val pixels2 = IntArray(width * height)
mask1.getPixels(pixels1, 0, width, 0, 0, width, height)
mask2.getPixels(pixels2, 0, width, 0, 0, width, height)
var distance = 0
for (i in pixels1.indices) {
val isSet1 = (pixels1[i] ushr 24) > 0
val isSet2 = (pixels2[i] ushr 24) > 0
if (isSet1 != isSet2) {
distance++
}
}
Log.d("CameraProcessor", "Hamming distance: $distance")
return distance <= threshold
}
private val requestPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
if (granted) startCamera()
@ -256,13 +236,14 @@ class CameraProcessor : AppCompatActivity(), CowAnalyzer.CowListener {
val cameraProvider = providerFuture.get()
val preview = androidx.camera.core.Preview.Builder().build()
preview.setSurfaceProvider(previewView.surfaceProvider)
preview.surfaceProvider = previewView.surfaceProvider
imageCapture = ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
.build()
val analyzer = ImageAnalysis.Builder()
.setTargetResolution(Size(640, 480))
.setBackpressureStrategy(
ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST
)
@ -284,18 +265,27 @@ class CameraProcessor : AppCompatActivity(), CowAnalyzer.CowListener {
}
override fun onFrame(imageProxy: ImageProxy) {
val current = frameProcessor.processFrame(imageProxy)
val savedMask = savedMaskBitmap
val threshold = 80000
Log.d("MatchingMasks", "Comparing masks: ${current?.width} ${savedMask?.width}")
if (savedMask != null && current != null) {
// Perform comparison
Log.d("MatchingMasks", "Comparing masks")
if (calculateHammingDistance(savedMask, current, threshold)) {
takePhoto()
} else {
Toast.makeText(this, "Masks do not match. Please align better.", Toast.LENGTH_SHORT).show()
}
if (isPhotoTaken) {
imageProxy.close()
return
}
val isPortrait = (orientation == "front" || orientation == "back")
frameProcessor.processFrame(imageProxy, savedMaskBitmap, isPortrait, matchThreshold, algorithm)
.addOnSuccessListener { result ->
runOnUiThread {
if (result.mask != null) {
currentMask = result.mask
segmentationOverlay.setImageBitmap(result.mask)
}
}
if (result.isMatch) {
takePhoto()
}
}
.addOnFailureListener { e ->
Log.e("CameraProcessor", "Frame processing error", e)
}
}
}
}

View File

@ -2,16 +2,25 @@ package com.example.animalrating
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.Matrix
import android.util.Log
import androidx.camera.core.ExperimentalGetImage
import androidx.camera.core.ImageProxy
import com.google.android.gms.tasks.Task
import com.google.android.gms.tasks.TaskCompletionSource
import com.google.mlkit.vision.common.InputImage
import com.google.mlkit.vision.segmentation.subject.SubjectSegmentation
import com.google.mlkit.vision.segmentation.subject.SubjectSegmenterOptions
import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.math.sqrt
class FrameProcessor(private val onMaskReady: (Bitmap?) -> Unit) {
data class SegmentationResult(
val mask: Bitmap?,
val isMatch: Boolean
)
class FrameProcessor {
private val isProcessing = AtomicBoolean(false)
private val processingExecutor = Executors.newSingleThreadExecutor()
@ -26,25 +35,34 @@ class FrameProcessor(private val onMaskReady: (Bitmap?) -> Unit) {
private val segmenter = SubjectSegmentation.getClient(options)
@ExperimentalGetImage
fun processFrame(imageProxy: ImageProxy): Bitmap? {
Log.d("MatchingMasks", "Here")
fun processFrame(
imageProxy: ImageProxy,
savedMask: Bitmap?,
isPortrait: Boolean,
thresholdPercent: Int = 75,
algorithm: String = HomeActivity.ALGORITHM_HAMMING
): Task<SegmentationResult> {
val taskCompletionSource = TaskCompletionSource<SegmentationResult>()
if (!isProcessing.compareAndSet(false, true)) {
imageProxy.close()
return null
taskCompletionSource.setResult(SegmentationResult(null, false))
return taskCompletionSource.task
}
val mediaImage = imageProxy.image
if (mediaImage == null) {
isProcessing.set(false)
imageProxy.close()
return null
taskCompletionSource.setResult(SegmentationResult(null, false))
return taskCompletionSource.task
}
val inputImage = InputImage.fromMediaImage(mediaImage, 0)
var bitmapMask: Bitmap? = null
segmenter.process(inputImage)
.addOnSuccessListener(processingExecutor) { result ->
var bitmapMask: Bitmap? = null
val subject = result.subjects.firstOrNull()
val mask = subject?.confidenceMask
@ -57,9 +75,8 @@ class FrameProcessor(private val onMaskReady: (Bitmap?) -> Unit) {
val fullWidth = inputImage.width
val fullHeight = inputImage.height
// Ensure buffer has enough data
if (mask.remaining() >= maskWidth * maskHeight) {
val colors = IntArray(fullWidth * fullHeight) // Initialized to 0 (TRANSPARENT)
val colors = IntArray(fullWidth * fullHeight)
mask.rewind()
for (y in 0 until maskHeight) {
@ -73,24 +90,172 @@ class FrameProcessor(private val onMaskReady: (Bitmap?) -> Unit) {
}
}
}
bitmapMask = Bitmap.createBitmap(colors, fullWidth, fullHeight, Bitmap.Config.ARGB_8888)
onMaskReady(bitmapMask)
val rawBitmap = Bitmap.createBitmap(colors, fullWidth, fullHeight, Bitmap.Config.ARGB_8888)
// Rotate if needed
bitmapMask = if (isPortrait) {
try {
val matrix = Matrix()
matrix.postRotate(90f)
Bitmap.createBitmap(rawBitmap, 0, 0, rawBitmap.width, rawBitmap.height, matrix, true)
} catch (e: Exception) {
Log.e("FrameProcessor", "Error rotating mask", e)
rawBitmap
}
} else {
rawBitmap
}
} else {
Log.e("FrameProcessor", "Mask buffer size mismatch. Expected ${maskWidth * maskHeight}, got ${mask.remaining()}")
onMaskReady(null)
Log.e("FrameProcessor", "Mask buffer size mismatch")
}
} else {
onMaskReady(null)
}
// Calculate match
var isMatch = false
if (bitmapMask != null && savedMask != null) {
// Scale current mask to match saved mask dimensions (640x480) for comparison
val comparisonMask = if (bitmapMask.width != savedMask.width || bitmapMask.height != savedMask.height) {
try {
Bitmap.createScaledBitmap(bitmapMask, savedMask.width, savedMask.height, true)
} catch (e: Exception) {
Log.e("FrameProcessor", "Error scaling mask for comparison", e)
null
}
} else {
bitmapMask
}
if (comparisonMask != null) {
isMatch = when (algorithm) {
HomeActivity.ALGORITHM_EUCLIDEAN -> calculateEuclideanDistance(savedMask, comparisonMask, thresholdPercent)
HomeActivity.ALGORITHM_JACCARD -> calculateJaccardSimilarity(savedMask, comparisonMask, thresholdPercent)
else -> calculateHammingDistance(savedMask, comparisonMask, thresholdPercent)
}
}
}
taskCompletionSource.setResult(SegmentationResult(bitmapMask, isMatch))
}
.addOnFailureListener { e ->
Log.e("FrameProcessor", "Subject Segmentation failed", e)
onMaskReady(null)
taskCompletionSource.setException(e)
}
.addOnCompleteListener { _ ->
isProcessing.set(false)
imageProxy.close()
}
return bitmapMask
return taskCompletionSource.task
}
private fun calculateHammingDistance(mask1: Bitmap, mask2: Bitmap, thresholdPercent: Int): Boolean {
if (mask1.width != mask2.width || mask1.height != mask2.height) {
return false
}
val width = mask1.width
val height = mask1.height
val pixels1 = IntArray(width * height)
val pixels2 = IntArray(width * height)
mask1.getPixels(pixels1, 0, width, 0, 0, width, height)
mask2.getPixels(pixels2, 0, width, 0, 0, width, height)
var distance = 0
for (i in pixels1.indices) {
val isSet1 = (pixels1[i] ushr 24) > 0
val isSet2 = (pixels2[i] ushr 24) > 0
if (isSet1 != isSet2) {
distance++
}
}
val totalPixels = width * height
val validThreshold = thresholdPercent.coerceIn(1, 100)
val allowedDistance = (totalPixels.toLong() * (100 - validThreshold)) / 100
return distance <= allowedDistance
}
private fun calculateEuclideanDistance(mask1: Bitmap, mask2: Bitmap, thresholdPercent: Int): Boolean {
if (mask1.width != mask2.width || mask1.height != mask2.height) {
return false
}
val width = mask1.width
val height = mask1.height
val pixels1 = IntArray(width * height)
val pixels2 = IntArray(width * height)
mask1.getPixels(pixels1, 0, width, 0, 0, width, height)
mask2.getPixels(pixels2, 0, width, 0, 0, width, height)
var sumSq = 0L
for (i in pixels1.indices) {
// Simple binary comparison for Euclidean distance on masks
// Treat existence of pixel as 255, non-existence as 0
val val1 = if ((pixels1[i] ushr 24) > 0) 255 else 0
val val2 = if ((pixels2[i] ushr 24) > 0) 255 else 0
val diff = val1 - val2
sumSq += diff * diff
}
val euclideanDistance = sqrt(sumSq.toDouble())
val maxDistance = sqrt((width * height).toDouble()) * 255.0
val validThreshold = thresholdPercent.coerceIn(1, 100)
val allowedDistance = maxDistance * (100 - validThreshold) / 100.0
return euclideanDistance <= allowedDistance
}
private fun calculateJaccardSimilarity(mask1: Bitmap, mask2: Bitmap, thresholdPercent: Int): Boolean {
if (mask1.width != mask2.width || mask1.height != mask2.height) {
return false
}
val width = mask1.width
val height = mask1.height
val pixels1 = IntArray(width * height)
val pixels2 = IntArray(width * height)
mask1.getPixels(pixels1, 0, width, 0, 0, width, height)
mask2.getPixels(pixels2, 0, width, 0, 0, width, height)
var intersection = 0
var union = 0
for (i in pixels1.indices) {
// mask1 is Saved Mask (Inverse: Subject is Transparent/0, Background is Black/255)
// NOTE: Wait, the saved mask was inverted originally?
// Let's stick to generic "isSet" logic.
// In previous turns, I assumed mask1 was "Inverse" and mask2 was "Normal".
// But for general algorithms, let's normalize.
// In CameraProcessor, we load the saved mask. It is likely the inverted one.
// Let's assume:
// Saved Mask (mask1): Alpha < 128 means subject (because it's inverted)
// Current Mask (mask2): Alpha > 0 means subject
val alpha1 = (pixels1[i] ushr 24) and 0xFF
val isSubject1 = alpha1 < 128
val alpha2 = (pixels2[i] ushr 24) and 0xFF
val isSubject2 = alpha2 > 0
if (isSubject1 && isSubject2) {
intersection++
}
if (isSubject1 || isSubject2) {
union++
}
}
if (union == 0) return false
val jaccardIndex = (intersection.toDouble() / union.toDouble()) * 100
return jaccardIndex >= thresholdPercent
}
}

View File

@ -2,20 +2,82 @@ package com.example.animalrating
import android.content.Intent
import android.os.Bundle
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.SeekBar
import android.widget.Spinner
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
class HomeActivity : AppCompatActivity() {
companion object {
const val ALGORITHM_HAMMING = "Hamming Distance"
const val ALGORITHM_EUCLIDEAN = "Euclidean Distance"
const val ALGORITHM_JACCARD = "Jaccard Similarity"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_home)
setupUI()
}
private fun setupUI() {
// Navigation buttons
findViewById<Button>(R.id.btnViewGallery).setOnClickListener {
startActivity(Intent(this, GalleryActivity::class.java))
}
findViewById<Button>(R.id.btnSelectCow).setOnClickListener {
startActivity(Intent(this, CowSelectionActivity::class.java))
saveSettingsAndStart()
}
// Algorithm Spinner
val spinner = findViewById<Spinner>(R.id.spinnerAlgorithm)
val algorithms = listOf(ALGORITHM_HAMMING, ALGORITHM_EUCLIDEAN, ALGORITHM_JACCARD)
val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, algorithms)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spinner.adapter = adapter
// Set default selection from preferences or intent
val prefs = getSharedPreferences("AnimalRatingPrefs", MODE_PRIVATE)
val savedAlg = prefs.getString("ALGORITHM", ALGORITHM_HAMMING)
spinner.setSelection(algorithms.indexOf(savedAlg))
// Threshold SeekBar
val seekBar = findViewById<SeekBar>(R.id.seekBarThreshold)
val tvThreshold = findViewById<TextView>(R.id.tvThresholdValue)
val savedThreshold = prefs.getInt("THRESHOLD", 75)
seekBar.progress = savedThreshold
tvThreshold.text = "$savedThreshold%"
seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
tvThreshold.text = "$progress%"
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {}
override fun onStopTrackingTouch(seekBar: SeekBar?) {}
})
}
}
private fun saveSettingsAndStart() {
val spinner = findViewById<Spinner>(R.id.spinnerAlgorithm)
val seekBar = findViewById<SeekBar>(R.id.seekBarThreshold)
val selectedAlgorithm = spinner.selectedItem.toString()
val threshold = seekBar.progress
// Save to preferences
val prefs = getSharedPreferences("AnimalRatingPrefs", MODE_PRIVATE)
prefs.edit().apply {
putString("ALGORITHM", selectedAlgorithm)
putInt("THRESHOLD", threshold)
apply()
}
startActivity(Intent(this, CowSelectionActivity::class.java))
}
}

View File

@ -15,7 +15,7 @@
android:textSize="32sp"
android:textStyle="bold"
android:textColor="#333333"
android:layout_marginBottom="48dp"/>
android:layout_marginBottom="32dp"/>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
@ -30,6 +30,64 @@
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Settings"
android:textStyle="bold"
android:layout_marginBottom="8dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Algorithm:"
android:layout_marginEnd="8dp"/>
<Spinner
android:id="@+id/spinnerAlgorithm"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Threshold: "/>
<TextView
android:id="@+id/tvThresholdValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="75%"/>
</LinearLayout>
<SeekBar
android:id="@+id/seekBarThreshold"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="100"
android:progress="75"/>
</LinearLayout>
<TextView
android:layout_width="wrap_content"

View File

@ -29,12 +29,4 @@
android:alpha="0.9"
android:elevation="10dp"/>
<Button
android:id="@+id/btnSave"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Save"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginBottom="32dp"/>
</FrameLayout>