①在AS虚拟机运行,搜索框只能输入数字,有查询结果,无法切换英文或中文输入
②安装到手机运行,搜索框输入英文或中文无反应,依旧只能输入数字查询,
③强行写完代码有太多的?.操作,感觉不对劲。我有尝试把override fun onActivityCreated替换为override fun onAttach,直接启动不了,看文档好像要配合onViewCreate使用,这段代码我就不懂该怎么拆分了。
附上这段代码的原文
使用MVVM架构,logic包存放业务逻辑相关代码,含dao,model ,network3个子包;ui包含
palce,weather2个子包,分别对应2个主要界面
dependencies {
implementation 'androidx.core:core-ktx:1.10.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.recyclerview:recyclerview:1.3.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.1'
implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
implementation 'androidx.fragment:fragment-ktx:1.5.7'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:name=".SunnyWeatherApplication"
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:supportsRtl="true"
android:theme="@style/Theme.SunnyWeather"
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>
</application>
</manifest>
package com.sunnyweather.android
import android.annotation.SuppressLint
import android.app.Application
import android.content.Context
class SunnyWeatherApplication : Application() {
companion object {
const val Token = "MNqVXy86vYXurQrD"
@SuppressLint("StaticFieldLeak")
lateinit var context: Context
}
override fun onCreate() {
super.onCreate()
context = applicationContext
}
}
package com.sunnyweather.android.logic.model
import android.location.Location
import com.google.gson.annotations.SerializedName
data class PlaceResponse(val status: String, val places: List<Place>)
data class Place(val name: String, val location: Location,
@SerializedName("formatted_address") val address: String)
data class Location(val lng: String, val lat: String)
package com.sunnyweather.android.logic.network
import com.sunnyweather.android.SunnyWeatherApplication
import com.sunnyweather.android.logic.model.PlaceResponse
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Query
interface PlaceService {
@GET("v2/place?token=${SunnyWeatherApplication.Token}&lang=zh_CN")
fun searchPlaces(@Query("query") query: String): Call<PlaceResponse>
}
ackage com.sunnyweather.android.logic.network
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
object ServiceCreator {
private const val BASE_URL = "https://api.caiyunapp.com"
private val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
fun <T> create(serviceClass: Class<T>): T = retrofit.create(serviceClass)
inline fun <reified T> create(): T = create(T::class.java)
}
enqueue(object : Callback<T>, retrofit2.Callback<T>)
override fun onResult(result: T)
##SunnyWeatherNetwork.kt
package com.sunnyweather.android.logic.network
import org.chromium.base.Callback
import retrofit2.Call
import retrofit2.Response
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
object SunnyWeatherNetwork {
private val placeService = ServiceCreator.create<PlaceService>()
suspend fun searchPlaces(query: String) = placeService.searchPlaces(query).await()
private suspend fun <T> Call<T>.await(): T {
return suspendCoroutine { continuation ->
enqueue(object : Callback<T>, retrofit2.Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
val body = response.body()
if (body != null) continuation.resume(body)
else continuation.resumeWithException(
RuntimeException("response body is null")
)
}
override fun onFailure(call: Call<T>, t: Throwable) {
continuation.resumeWithException(t)
}
override fun onResult(result: T) {
TODO("Not yet implemented")
}
})
}
}
}
package com.sunnyweather.android.logic
import androidx.lifecycle.liveData
import com.sunnyweather.android.logic.model.Place
import com.sunnyweather.android.logic.network.SunnyWeatherNetwork
import kotlinx.coroutines.Dispatchers
object Repository {
fun searchPlaces(query: String) = liveData(Dispatchers.IO) {
val result = try {
val placeResponse = SunnyWeatherNetwork.searchPlaces(query)
if (placeResponse.status == "ok") {
val places = placeResponse.places
Result.success(places)
} else {
Result.failure(RuntimeException("response status is ${placeResponse.status}"
))
}
} catch (e: Exception) {
Result.failure<List<Place>>(e)
}
emit(result)
}
}
package com.sunnyweather.android.ui.place
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.switchMap
import com.sunnyweather.android.logic.Repository
import com.sunnyweather.android.logic.model.Place
class PlaceViewModel : ViewModel() {
private val searchLiveData = MutableLiveData<String>()
val placeList = ArrayList<Place>()
val placeLiveData = searchLiveData.switchMap() {
query -> Repository.searchPlaces(query)
}
fun searchPlaces(query: String) {
searchLiveData.value = query
}
}
package com.sunnyweather.android.ui.place
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.RecyclerView
import com.sunnyweather.android.R
import com.sunnyweather.android.logic.model.Place
class PlaceAdapter(private val fragment: Fragment, private val placeList: List<Place>) :
RecyclerView.Adapter<PlaceAdapter.ViewHolder>() {
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val placeName: TextView = view.findViewById(R.id.placeName)
val placeAddress: TextView = view.findViewById(R.id.placeAddress)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.place_item,
parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val place = placeList[position]
holder.placeName.text = place.name
holder.placeAddress.text = place.address
}
override fun getItemCount() = placeList.size
}
PlaceFragment.kt
package com.sunnyweather.android.ui.place
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import android.widget.ImageView
import android.widget.Toast
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.sunnyweather.android.R
class PlaceFragment : Fragment() {
private val viewModel by lazy { ViewModelProvider(this)[PlaceViewModel::class.java] }
private lateinit var adapter: PlaceAdapter
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_place, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val recyclerView = activity?.findViewById<RecyclerView>(R.id.recyclerView)
val searchPlaceEdit = activity?.findViewById<EditText>(R.id.searchPlaceEdit)
val bgImageView = activity?.findViewById<ImageView>(R.id.bgImageView)
val layoutManager = LinearLayoutManager(activity)
if (recyclerView != null) {
recyclerView.layoutManager = layoutManager
}
adapter = PlaceAdapter(this, viewModel.placeList)
if (recyclerView != null) {
recyclerView.adapter = adapter
}
searchPlaceEdit?.addTextChangedListener { editable ->
val content = editable.toString()
if (content.isNotEmpty()) {
viewModel.searchPlaces(content)
} else {
recyclerView?.visibility = View.GONE
bgImageView?.visibility = View.VISIBLE
viewModel.placeList.clear()
adapter.notifyDataSetChanged()
}
}
viewModel.placeLiveData.observe(viewLifecycleOwner, Observer { result ->
val places = result.getOrNull()
if (places != null) {
recyclerView?.visibility = View.VISIBLE
bgImageView?.visibility = View.GONE
viewModel.placeList.clear()
viewModel.placeList.addAll(places)
adapter.notifyDataSetChanged()
} else {
Toast.makeText(activity, "未能查询到任何地点", Toast.LENGTH_SHORT).show()
result.exceptionOrNull()?.printStackTrace()
}
})
}
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:windowBackground">
<ImageView
android:id="@+id/bgImageView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:contentDescription="@string/imageview"
android:src="@drawable/bg_place" />
<FrameLayout
android:id="@+id/actionBarLayout"
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="@color/colorPrimary">
<EditText
android:id="@+id/searchPlaceEdit"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_gravity="center_vertical"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:hint="@string/address"
android:background="@drawable/search_bg"
android:autofillHints="editText"
android:inputType="numberSigned" />
</FrameLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/actionBarLayout"
android:visibility="gone"/>
</RelativeLayout>
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView 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="130dp"
android:layout_margin="12dp"
app:cardCornerRadius="4dp">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="18dp"
android:layout_gravity="center_vertical">
<TextView
android:id="@+id/placeName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?android:attr/textColorPrimary"
android:textSize="20sp"/>
<TextView
android:id="@+id/placeAddress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:textColor="?android:attr/textColorPrimary"
android:textSize="14sp"/>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/placeFragment"
android:name="com.sunnyweather.android.ui.place.PlaceFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
parent="Theme.MaterialComponents.Light.NoActionBar"
1.你的布局文件里面的EditText有个 android:inputType="numberSigned",这句话导致只能输入数字,这个属性是定义输入框输入的类型,去掉就行。
2.onAttach和onViewCreated和onActivityCreated方法都是fragment的生命周期,一般都在onActivityCreated或onViewCreated方法做相关逻辑,确保fragment加载完成才能操作相关逻辑