Kotlin における findViewById
今日の朝の散歩です。
雪は初めてではないが、いつもの散歩コースでもやっぱり景色は新鮮に感じるなという顔している柴犬です。
鼻先の頭に雪を付けてあたりを見渡す柴犬です。
概要
次の画像のように、表示画面の一番下にTextView を配置してインフォメーションを表示しようとしました。
TextView を findViewById() で次のように捕えようとしましたがエラーとなりました。
TextView ob;
ob = (TextView)findViewById(R.ID.XXX);
ob.setText("hogehoge");
上でダメなので次を試しました。
val ob = findViewById(R.id.tvinfo)
試した結果は次のようです。
このあと試行錯誤しましたので記録します。
目的の形
アプリ起動時は左の画像です。シャッター(Take Photo)をタップするとネットワークの情報がTextViewに表示されるようにしています。
右がシャッター(Take Photo)をタップした時の画像です。
撮影した画像
スマホのカメラ表示の範囲と撮影範囲が概ね合うようになりました。
activity_main.xml
TextView にスクロールを入れることにも結構手間取りました。
TextView のプロパティーの中でするものと思い込んでいろいろやってみました。ScrollView の中に入れ子にするとは思いませんでした。
<?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" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <androidx.camera.view.PreviewView android:id="@+id/viewFinder" android:layout_width="match_parent" android:layout_height="441dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> </androidx.camera.view.PreviewView> <Button android:id="@+id/image_capture_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:layout_marginEnd="30dp" android:elevation="2dp" android:text="@string/take_photo" app:layout_constraintEnd_toStartOf="@id/vertical_centerline" app:layout_constraintTop_toBottomOf="@+id/viewFinder" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/vertical_centerline" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" app:layout_constraintGuide_percent=".50" /> <ScrollView android:layout_width="match_parent" android:layout_height="0px" android:layout_marginTop="65dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/viewFinder" > <TextView android:id="@+id/tvinfo" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="TextView"/> </ScrollView> <Button android:id="@+id/video_capture_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="30dp" android:layout_marginTop="10dp" android:elevation="2dp" android:text="@string/start_capture" app:layout_constraintStart_toStartOf="@+id/vertical_centerline" app:layout_constraintTop_toBottomOf="@+id/viewFinder" /> </androidx.constraintlayout.widget.ConstraintLayout>
findViewById()の解決方法
build.gradle.kts (Module :App)で
android {
~
省略
~
buildFeatures {
viewBinding = true
}
}
viewBinding = true を加えていたので
viewBinding.tvinfo.text = "hogehoge"
とするだけでよかったです。
その後分かった記述方法
この記述でもいいようです。
var tvInfo = findViewById<View>(R.id.tvinfo) as TextView
tvInfo.SetText("hogehoge")
MainActivity.kt
私の備忘録ですので、MainActivity.kt の全文も掲げています。
また、所有している Synology 製の NAS へのFTPを使ってアップロード途中のコードも入っています。
NAS へのアップロードはまだ成功していないのですが、成功したら記録しようと思っています。
package siba.inu.android.cameraapp // apache FTPClient関係 commons-net-3.10.0.jar をインポート import android.Manifest import android.content.ContentValues import android.content.pm.PackageManager import android.net.ConnectivityManager import android.os.AsyncTask import android.os.Build import android.os.Bundle import android.provider.MediaStore import android.util.Log import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.camera.core.CameraSelector import androidx.camera.core.ImageAnalysis import androidx.camera.core.ImageCapture import androidx.camera.core.ImageCaptureException import androidx.camera.core.ImageProxy import androidx.camera.core.Preview import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.video.FallbackStrategy import androidx.camera.video.MediaStoreOutputOptions import androidx.camera.video.Quality import androidx.camera.video.QualitySelector import androidx.camera.video.Recorder import androidx.camera.video.Recording import androidx.camera.video.VideoCapture import androidx.camera.video.VideoRecordEvent import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.core.content.PermissionChecker import android.text.method.ScrollingMovementMethod import android.widget.TextView import org.apache.commons.net.ftp.FTP import org.apache.commons.net.ftp.FTPClient import siba.inu.android.cameraapp.databinding.ActivityMainBinding import java.io.File import java.io.FileInputStream import java.net.URL import java.nio.ByteBuffer import java.text.SimpleDateFormat import java.util.Locale import java.util.concurrent.ExecutorService import java.util.concurrent.Executors typealias LumaListener = (luma: Double) -> Unit class MainActivity : AppCompatActivity() { private lateinit var viewBinding: ActivityMainBinding private var imageCapture: ImageCapture? = null private var videoCapture: VideoCapture<Recorder>? = null private var recording: Recording? = null private lateinit var cameraExecutor: ExecutorService override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewBinding = ActivityMainBinding.inflate(layoutInflater) setContentView(viewBinding.root) // Request camera permissions if (allPermissionsGranted()) { startCamera() } else { ActivityCompat.requestPermissions( this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS) } // Set up the listeners for take photo and video capture buttons viewBinding.imageCaptureButton.setOnClickListener { takePhoto() } viewBinding.videoCaptureButton.setOnClickListener { captureVideo() } cameraExecutor = Executors.newSingleThreadExecutor() } private fun takePhoto() { // Get a stable reference of the modifiable image capture use case val imageCapture = imageCapture ?: return // Create time stamped name and MediaStore entry. val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US) .format(System.currentTimeMillis()) val contentValues = ContentValues().apply { put(MediaStore.MediaColumns.DISPLAY_NAME, name) put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg") if(Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraX-Image") } } // Create output options object which contains file + metadata val outputOptions = ImageCapture.OutputFileOptions .Builder(contentResolver, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues) .build() // Set up image capture listener, which is triggered after photo has // been taken imageCapture.takePicture( outputOptions, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback { override fun onError(exc: ImageCaptureException) { Log.e(TAG, "Photo capture failed: ${exc.message}", exc) } override fun onImageSaved(output: ImageCapture.OutputFileResults){ val msg = "Photo capture succeeded: ${output.savedUri}" Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show() Log.d(TAG, msg) //Fileオブジェクトの作成 val file = File("Pictures/CameraX-Image/${name}.jpg") //アップロード uploadFileToFtp(file, "xxxxxxxxxx", "xxxxxx", "xxxxxxxxxxx", "xxxxxxxxxxxxxxxxxxxxxxxxx") } } ) } // Implements VideoCapture use case, including start and stop capturing. private fun captureVideo() { val videoCapture = this.videoCapture ?: return viewBinding.videoCaptureButton.isEnabled = false val curRecording = recording if (curRecording != null) { // Stop the current recording session. curRecording.stop() recording = null return } // create and start a new recording session val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US) .format(System.currentTimeMillis()) val contentValues = ContentValues().apply { put(MediaStore.MediaColumns.DISPLAY_NAME, name) put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4") if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/CameraX-Video") } } val mediaStoreOutputOptions = MediaStoreOutputOptions .Builder(contentResolver, MediaStore.Video.Media.EXTERNAL_CONTENT_URI) .setContentValues(contentValues) .build() recording = videoCapture.output .prepareRecording(this, mediaStoreOutputOptions) .apply { if (PermissionChecker.checkSelfPermission(this@MainActivity, Manifest.permission.RECORD_AUDIO) == PermissionChecker.PERMISSION_GRANTED) { withAudioEnabled() } } .start(ContextCompat.getMainExecutor(this)) { recordEvent -> when(recordEvent) { is VideoRecordEvent.Start -> { viewBinding.videoCaptureButton.apply { text = getString(R.string.stop_capture) isEnabled = true } } is VideoRecordEvent.Finalize -> { if (!recordEvent.hasError()) { val msg = "Video capture succeeded: " + "${recordEvent.outputResults.outputUri}" Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT) .show() Log.d(TAG, msg) } else { recording?.close() recording = null Log.e(TAG, "Video capture ends with error: " + "${recordEvent.error}") } viewBinding.videoCaptureButton.apply { text = getString(R.string.start_capture) isEnabled = true } } } } } private fun startCamera() { val cameraProviderFuture = ProcessCameraProvider.getInstance(this) cameraProviderFuture.addListener({ // Used to bind the lifecycle of cameras to the lifecycle owner val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get() // Preview val preview = Preview.Builder() .build() .also { it.setSurfaceProvider(viewBinding.viewFinder.surfaceProvider) } val recorder = Recorder.Builder() .setQualitySelector(QualitySelector.from(Quality.HIGHEST, FallbackStrategy.higherQualityOrLowerThan(Quality.SD))) .build() videoCapture = VideoCapture.withOutput(recorder) imageCapture = ImageCapture.Builder().build() /* val imageAnalyzer = ImageAnalysis.Builder().build() .also { setAnalyzer( cameraExecutor, LuminosityAnalyzer { luma -> Log.d(TAG, "Average luminosity: $luma") } ) } */ // Select back camera as a default val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA try { // Unbind use cases before rebinding cameraProvider.unbindAll() // Bind use cases to camera cameraProvider.bindToLifecycle( this, cameraSelector, preview, imageCapture, videoCapture) } catch(exc: Exception) { Log.e(TAG, "Use case binding failed", exc) } }, 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 TAG = "CameraApp" private const val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS" private const val REQUEST_CODE_PERMISSIONS = 10 private val REQUIRED_PERMISSIONS = mutableListOf ( Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO ).apply { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) { add(Manifest.permission.WRITE_EXTERNAL_STORAGE) } }.toTypedArray() } override fun onRequestPermissionsResult( requestCode: Int, permissions: Array<String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == REQUEST_CODE_PERMISSIONS) { if (allPermissionsGranted()) { startCamera() } else { Toast.makeText(this, "Permissions not granted by the user.", Toast.LENGTH_SHORT).show() finish() } } } // NAS(FTPサーバー)にアップロード fun uploadFileToFtp(file: File, ftpServer: String, ftpUsername: String, ftpPassword: String, ftpDirectory: String) { try { activenet() val ftpClient = FTPClient() ftpClient.connect(ftpServer) ftpClient.login(ftpUsername, ftpPassword) ftpClient.enterLocalPassiveMode() ftpClient.setFileType(FTP.BINARY_FILE_TYPE) ftpClient.changeWorkingDirectory(ftpDirectory) val inputStream = FileInputStream(file) val fileName = file.name ftpClient.storeFile(fileName, inputStream) inputStream.close() ftpClient.logout() ftpClient.disconnect() } catch (e: Exception) { e.printStackTrace() //Toast.makeText(this, e.toString(), Toast.LENGTH_SHORT).show() //viewBinding.tvinfo.text = e.toString() } } fun activenet() { val connectivityManager = getSystemService(ConnectivityManager::class.java) val currentNetwork = connectivityManager.getActiveNetwork() val caps = connectivityManager.getNetworkCapabilities(currentNetwork) val linkProperties = connectivityManager.getLinkProperties(currentNetwork) viewBinding.tvinfo.text = linkProperties.toString() } } private class LuminosityAnalyzer(private val listener: LumaListener) : ImageAnalysis.Analyzer { private fun ByteBuffer.toByteArray(): ByteArray { rewind() // Rewind the buffer to zero val data = ByteArray(remaining()) get(data) // Copy the buffer into a byte array return data // Return the byte array } override fun analyze(image: ImageProxy) { val buffer = image.planes[0].buffer val data = buffer.toByteArray() val pixels = data.map { it.toInt() and 0xFF } val luma = pixels.average() listener(luma) image.close() } }
この件はここまでとします。