Kotlin Spinner 在 Activity 以及 Fragment 中的使用。
Spinner 在 Activity 中的案例非常普遍,这里直接列举一个例子,我们来看一下 Spinner 在 OCR (com.google.mlkit:text-recognition)中的应用。
我们在android studio中新建一个项目,如下为我的AndroidManifest.xml
, build_gradle (Project)
以及build_gradle (app)
的代码。注意,这个例子SDK Version 是32。
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.zjgwarehouseocr">
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.ZJGWarehouseOCR"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<meta-data
android:name="com.google.mlkit.vision.DEPENDENCIES"
android:value="ocr"/>
</application>
</manifest>
build_gradle (Project)
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'com.android.application' version '7.2.1' apply false
id 'com.android.library' version '7.2.1' apply false
id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
}
task clean(type: Delete) {
delete rootProject.buildDir
}
build_gradle (:app)
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
android {
compileSdk 32
defaultConfig {
applicationId "com.example.zjgwarehouseocr"
minSdk 28
targetSdk 32
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.navigation:navigation-fragment:2.5.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
implementation 'androidx.navigation:navigation-fragment-ktx:2.5.0'
implementation 'androidx.navigation:navigation-ui-ktx:2.5.0'
//MLKit
implementation 'com.google.mlkit:text-recognition:16.0.0-beta4'
implementation 'com.google.mlkit:text-recognition-chinese:16.0.0-beta4'
implementation 'com.google.mlkit:text-recognition-devanagari:16.0.0-beta4'
implementation 'com.google.mlkit:text-recognition-japanese:16.0.0-beta4'
implementation 'com.google.mlkit:text-recognition-korean:16.0.0-beta4'
}
<?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">
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:cropToPadding="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/test2" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="Recognized Text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView" />
<TextView
android:id="@+id/tv_content"
android:layout_width="0dp"
android:layout_height="200dp"
android:layout_marginTop="32dp"
android:textAlignment="center"
android:scrollbars="vertical"
app:layout_constraintBottom_toTopOf="@+id/btn_start"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
<Button
android:id="@+id/btn_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Start Recognition"
app:layout_constraintBottom_toTopOf="@+id/linearLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView"
app:layout_constraintVertical_bias="0.835" />
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyle"
android:layout_width="100dp"
android:layout_height="100dp"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/btn_start"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="409dp"
android:layout_height="68dp"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<Spinner
android:id="@+id/spinnerOcrLanguage"
android:layout_width="205dp"
android:layout_height="match_parent"
android:contentDescription="@string/app_name" />
<Spinner
android:id="@+id/spinnerLabel"
android:layout_width="205dp"
android:layout_height="match_parent"
android:layout_marginEnd="16dp"
android:contentDescription="@string/app_name" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
package com.example.zjgwarehouseocr
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.text.method.ScrollingMovementMethod
import android.util.Log
import android.view.View
import android.widget.*
import com.google.mlkit.vision.common.InputImage
import com.google.mlkit.vision.text.TextRecognition
import com.google.mlkit.vision.text.chinese.ChineseTextRecognizerOptions
import com.google.mlkit.vision.text.devanagari.DevanagariTextRecognizerOptions
import com.google.mlkit.vision.text.japanese.JapaneseTextRecognizerOptions
import com.google.mlkit.vision.text.korean.KoreanTextRecognizerOptions
import com.google.mlkit.vision.text.latin.TextRecognizerOptions
import java.util.ArrayList
class MainActivity : AppCompatActivity() {
private val TAG = "ML_kit_text_recognition"
private val label1 = R.drawable.test1
private val label2 = R.drawable.test2
// View Element:
private lateinit var btn: Button
private lateinit var tvContent:TextView
private lateinit var pb:ProgressBar
private lateinit var imageView:ImageView
private lateinit var spinnerLabel: Spinner
private lateinit var spinnerOcrLang: Spinner
// Bitmap:
private var selectedImage: Bitmap?= null
// OCR recognizer initialization
private var recognizer = TextRecognition.getClient(TextRecognizerOptions.Builder().build())
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Initialization
btn = findViewById(R.id.btn_start)
tvContent = findViewById(R.id.tv_content)
pb = findViewById(R.id.progressBar)
imageView = findViewById(R.id.imageView)
spinnerLabel = findViewById(R.id.spinnerLabel)
spinnerOcrLang = findViewById(R.id.spinnerOcrLanguage)
// 设置滚轮,当显示的文字太长时,支持滚轮拖动。
tvContent.setMovementMethod(ScrollingMovementMethod())
// Button - CLickListener
btn.setOnClickListener {
startTextRecognition()
pb.visibility = View.VISIBLE
}
imageView.setImageResource(label1)
selectedImage = BitmapFactory.decodeResource(resources, label1)
// Spinner
val content:Array<String> = arrayOf(LABEL1, LABEL2)
val adapter = ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, content)
spinnerLabel.adapter = adapter
spinnerLabel.onItemSelectedListener = object:AdapterView.OnItemSelectedListener{
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
tvContent.text = ""
if (position == 0)
{
imageView.setImageResource(label1)
selectedImage = BitmapFactory.decodeResource(resources,label1)
}
else
{
imageView.setImageResource(label2)
selectedImage = BitmapFactory.decodeResource(resources,label2)
}
}
override fun onNothingSelected(parent: AdapterView<*>?) {
}
}
val options: MutableList<String> = ArrayList()
options.add(TEXT_RECOGNITION_LATIN)
options.add(TEXT_RECOGNITION_CHINESE)
options.add(TEXT_RECOGNITION_DEVANAGARI)
options.add(TEXT_RECOGNITION_JAPANESE)
options.add(TEXT_RECOGNITION_KOREAN)
// Creating adapter for spinner
val dataAdapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, options)
spinnerOcrLang.adapter = dataAdapter
spinnerOcrLang.onItemSelectedListener = object:AdapterView.OnItemSelectedListener{
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
if (position == 0){
Log.i(TAG, "Using on-device Text recognition Processor for Latin")
recognizer = TextRecognition.getClient(TextRecognizerOptions.Builder().build())
}
else if(position == 1){
Log.i(TAG, "Using on-device Text recognition Processor for Chinese")
recognizer = TextRecognition.getClient(ChineseTextRecognizerOptions.Builder().build())
}
else if(position == 2){
Log.i(TAG, "Using on-device Text recognition Processor for Devanagari")
recognizer = TextRecognition.getClient(DevanagariTextRecognizerOptions.Builder().build())
}
else if(position == 3){
Log.i(TAG, "Using on-device Text recognition Processor for Japanese")
recognizer = TextRecognition.getClient(JapaneseTextRecognizerOptions.Builder().build())
}
else if(position == 4){
Log.i(TAG, "Using on-device Text recognition Processor for Korean")
recognizer = TextRecognition.getClient(KoreanTextRecognizerOptions.Builder().build())
}
}
override fun onNothingSelected(parent: AdapterView<*>?) {
TODO("Not yet implemented")
}
}
}
private fun startTextRecognition()
{
val inputImage = InputImage.fromBitmap(selectedImage!!, 0)
recognizer.process(inputImage)
.addOnSuccessListener {
pb.visibility = View.GONE
tvContent.text = it.text
Log.d(TAG, "Successful Recognition!")
}
.addOnFailureListener {
Log.d(TAG, "Fail Recognition!")
}
}
companion object {
private const val LABEL1 = "label 1"
private const val LABEL2 = "label 2"
private const val TEXT_RECOGNITION_LATIN = "Text Recognition Latin"
private const val TEXT_RECOGNITION_CHINESE = "Text Recognition Chinese"
private const val TEXT_RECOGNITION_DEVANAGARI = "Text Recognition Devanagari"
private const val TEXT_RECOGNITION_JAPANESE = "Text Recognition Japanese"
private const val TEXT_RECOGNITION_KOREAN = "Text Recognition Korean"
}
}
Kotlin 的官网也给出了例子:
val spinner: Spinner = findViewById(R.id.spinner)
// Create an ArrayAdapter using the string array and a default spinner layout
ArrayAdapter.createFromResource(
this,
R.array.planets_array,
android.R.layout.simple_spinner_item
).also { adapter ->
// Specify the layout to use when the list of choices appears
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
// Apply the adapter to the spinner
spinner.adapter = adapter
}
借助 createFromResource() 方法,您可以从字符串数组创建 ArrayAdapter。此方法的第三个参数是布局资源,其定义了所选选项在微调框控件中的显示方式。simple_spinner_item 布局是平台提供的默认布局,除非您想为微调框外观定义自己的布局,否则应使用此布局。
然后,您应调用 setDropDownViewResource(int),从而指定适配器用于显示微调框选择列表的布局(simple_spinner_dropdown_item 是平台定义的另一种标准布局)。
接着,通过调用 setAdapter() 将适配器应用到 Spinner。
在 fragment 中,我们无法直接使用 findViewById。需要先通过 onCreateView
函数进行初始化和调用。在 stackoverflow 中,我们找到一个类似的问题:The spinner doesn’t work in my Kotlin fragment。被选为正确的答案如下:
class Add : Fragment()
{
val types = arrayOf("simple User", "Admin")
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val t=inflater.inflate(R.layout.fragment_add, container, false)
val spinner = t.findViewById<Spinner>(R.id.spinner2)
spinner?.adapter = ArrayAdapter(activity?.applicationContext, R.layout.support_simple_spinner_dropdown_item, types) as SpinnerAdapter
spinner?.onItemSelectedListener = object :AdapterView.OnItemSelectedListener{
override fun onNothingSelected(parent: AdapterView<*>?) {
println("erreur")
}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
val type = parent?.getItemAtPosition(position).toString()
Toast.makeText(activity,type, Toast.LENGTH_LONG).show()
println(type)
}
}
return t
}
}
可以借鉴。最终用于我这边的代码被修改如下:
class HomeScanFragment : Fragment(R.layout.fragment_scan_home) {
private val binding: FragmentScanHomeBinding by viewBinding()
private val viewModel: HomeViewModel by viewModel()
private lateinit var spinnerOcrLang: Spinner
// OCR recognizer initialization
private var recognizer = TextRecognition.getClient(TextRecognizerOptions.Builder().build())
private val TAG = "ML_kit_text_recognition"
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupSpinner()
}
private fun setupSpinner() {
val ocrOptions: MutableList<String> = ArrayList()
ocrOptions.add(TEXT_RECOGNITION_CHINESE)
ocrOptions.add(TEXT_RECOGNITION_LATIN)
ocrOptions.add(TEXT_RECOGNITION_JAPANESE)
ocrOptions.add(TEXT_RECOGNITION_KOREAN)
val dataAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_list_item_1, ocrOptions)
spinnerOcrLang.adapter = dataAdapter
spinnerOcrLang.onItemSelectedListener = object: AdapterView.OnItemSelectedListener{
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
if (position == 0){
Log.i(TAG, "Using on-device Text recognition Processor for Latin")
recognizer = TextRecognition.getClient(ChineseTextRecognizerOptions.Builder().build())
}
else if(position == 1){
Log.i(TAG, "Using on-device Text recognition Processor for Chinese")
recognizer = TextRecognition.getClient(TextRecognizerOptions.Builder().build())
}
else if(position == 2){
Log.i(TAG, "Using on-device Text recognition Processor for Japanese")
recognizer = TextRecognition.getClient(JapaneseTextRecognizerOptions.Builder().build())
}
else if(position == 3){
Log.i(TAG, "Using on-device Text recognition Processor for Korean")
recognizer = TextRecognition.getClient(KoreanTextRecognizerOptions.Builder().build())
}
viewModel.selectOCRRecognizer(recognizer)
}
override fun onNothingSelected(parent: AdapterView<*>?) {
TODO("Not yet implemented")
}
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater!!.inflate(R.layout.fragment_scan_home, container, false)
spinnerOcrLang = view.findViewById<Spinner>(R.id.spinnerOcrLanguage)
return view
}
companion object {
private const val TEXT_RECOGNITION_LATIN = "Text Recognition Latin"
private const val TEXT_RECOGNITION_CHINESE = "Text Recognition Chinese"
private const val TEXT_RECOGNITION_JAPANESE = "Text Recognition Japanese"
private const val TEXT_RECOGNITION_KOREAN = "Text Recognition Korean"
}