在自定义视图中设置数据
Set data in Custom view
我正在构建一个自定义视图,它每 6 秒向 Rest API 发出一个 HTTP 请求,并显示一个不同的问题及其可能的答案。在这种情况下,Buff 将是包含问题及其可能答案的对象。
现在,视图已注入,但是,在设置数据时(请参阅 setAnswer 函数),第一个视图显示答案列表最后一个元素的文本。其余视图不显示任何文本。
从 API
收到的 Buff 对象
根据这些数据,我应该按以下顺序显示一个问题和 3 个可能的答案:“没有进球!”、“一个进球!”、“两个或更多”。
{
"result": {
"id": 1,
"author": {
"first_name": "Ronaldo"
},
"question": {
"id": 491,
"title": "Kaio Jorge has 4 goals this tournament – I think he will score again today. What do you think?"
},
"answers": [
{
"id": 1163,
"buff_id": 0,
"title": "No goals!"
},
{
"id": 1164,
"buff_id": 0,
"title": "One goal!"
},
{
"id": 1165,
"buff_id": 0,
"title": "Two or more!"
}
]
}
}
目前是这样显示的
First element displays the text from the 3rd answer and the other two are empty
BuffView.kt(我的自定义视图)
class BuffView(context: Context, attrs: AttributeSet? = null): LinearLayout(context, attrs) {
private val apiErrorHandler = ApiErrorHandler()
private val getBuffUseCase = GetBuffUseCase(apiErrorHandler)
private var buffIdCount = 1
private val buffView: View
init {
buffView = inflate(context, R.layout.buff_view, this)
}
fun start() {
getBuff()
}
private fun getBuff() {
getBuffUseCase.invoke(buffIdCount.toLong(), object : UseCaseResponse<Buff> {
override fun onSuccess(result: Buff) {
displayBuff(result)
}
override fun onError(errorModel: ErrorModel?) {
//Todo: show error toast
Log.e("AppDebug", "onError: errorModel $errorModel")
}
})
val delay = 6000L
RepeatHelper.repeatDelayed(delay) {
if (buffIdCount < 5) {
buffIdCount++
getBuff()
}
}
}
private fun displayBuff(buff: Buff) {
setQuestion(buff.question.title)
setAuthor(buff.author)
setAnswer(buff.answers)
setCloseButton()
buffView.visibility = VISIBLE
}
private fun setQuestion(questionText: String) {
question_text.text = questionText
}
private fun setAuthor(author: Buff.Author) {
val firstName = author.firstName
val lastName = author.lastName
sender_name.text = "$firstName $lastName"
Glide.with(buffView)
.load(author.image)
.into(sender_image)
}
private fun setAnswer(answers: List<Buff.Answer>) {
val answersContainer = findViewById<LinearLayout>(R.id.answersContainer)
answersContainer.removeAllViews()
for(answer in answers) {
val answerView: View = LayoutInflater.from(answersContainer.context).inflate(
R.layout.buff_answer,
answersContainer,
false
)
answer_text?.text = answer.title
answersContainer.addView(answerView)
}
}
private fun setCloseButton() {
buff_close.setOnClickListener {
buffView.visibility = GONE
}
}
}
buff_view.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<include layout="@layout/buff_sender"/>
<include layout="@layout/buff_question"/>
<LinearLayout
android:id="@+id/answersContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"/>
</LinearLayout>
buff_answer.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/light_bg"
android:orientation="horizontal"
tools:ignore="RtlHardcoded">
<ImageView
android:id="@+id/answer_image"
android:layout_width="27dp"
android:layout_height="27dp"
android:layout_gravity="center_vertical"
android:src="@drawable/ic_generic_answer"
android:padding="4dp" />
<TextView
android:id="@+id/answer_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="4dp"
android:textColor="@color/test_color_dark"
tools:text="The keeper's right"
android:textStyle="bold" />
</LinearLayout>
尝试在 addView()
之后调用 invalidate()
告诉视图它应该 re-render。您还需要在 answerView
上设置文本,因此该方法应如下所示:
private fun setAnswer(answers: List<Buff.Answer>) {
val answersContainer = findViewById<LinearLayout>(R.id.answersContainer)
answersContainer.removeAllViews()
for(answer in answers) {
val answerView: View = LayoutInflater.from(answersContainer.context).inflate(
R.layout.buff_answer,
answersContainer,
false
)
answerView.answer_text?.text = answer.title
answersContainer.addView(answerView)
}
invalidate() // or invalidateSelf()
}
另一件事是,从自定义视图调用 api 是一种非常糟糕的做法。执行用例应该在 ViewModel/Presenter 等级别。然后应将 buff 提供给 Fragment/Activity,然后在 CustomView 中进行设置。您也没有取消某些 view-destroying 回调中的请求,例如 onDetachedFromWindow()
这可能导致内存泄漏
您不能在不重新创建新变量的情况下多次添加相同的视图,这并不像人们想象的那么简单。
private fun setAnswer(answers: List<Buff.Answer>) {
val answersContainer = findViewById<LinearLayout>(R.id.answersContainer)
var viewlist = ArrayList()
answersContainer.removeAllViews()
for(answer in answers) {
val answerView: View = LayoutInflater.from(answersContainer.context).inflate(
R.layout.buff_answer,
answersContainer,
false
)
answer_text?.text = answer.title
viewlist.add(answerView)
}
for(view in viewlist)
answersContainer.addView(view)
}
我正在构建一个自定义视图,它每 6 秒向 Rest API 发出一个 HTTP 请求,并显示一个不同的问题及其可能的答案。在这种情况下,Buff 将是包含问题及其可能答案的对象。
现在,视图已注入,但是,在设置数据时(请参阅 setAnswer 函数),第一个视图显示答案列表最后一个元素的文本。其余视图不显示任何文本。
从 API
收到的 Buff 对象根据这些数据,我应该按以下顺序显示一个问题和 3 个可能的答案:“没有进球!”、“一个进球!”、“两个或更多”。
{
"result": {
"id": 1,
"author": {
"first_name": "Ronaldo"
},
"question": {
"id": 491,
"title": "Kaio Jorge has 4 goals this tournament – I think he will score again today. What do you think?"
},
"answers": [
{
"id": 1163,
"buff_id": 0,
"title": "No goals!"
},
{
"id": 1164,
"buff_id": 0,
"title": "One goal!"
},
{
"id": 1165,
"buff_id": 0,
"title": "Two or more!"
}
]
}
}
目前是这样显示的 First element displays the text from the 3rd answer and the other two are empty
BuffView.kt(我的自定义视图)
class BuffView(context: Context, attrs: AttributeSet? = null): LinearLayout(context, attrs) {
private val apiErrorHandler = ApiErrorHandler()
private val getBuffUseCase = GetBuffUseCase(apiErrorHandler)
private var buffIdCount = 1
private val buffView: View
init {
buffView = inflate(context, R.layout.buff_view, this)
}
fun start() {
getBuff()
}
private fun getBuff() {
getBuffUseCase.invoke(buffIdCount.toLong(), object : UseCaseResponse<Buff> {
override fun onSuccess(result: Buff) {
displayBuff(result)
}
override fun onError(errorModel: ErrorModel?) {
//Todo: show error toast
Log.e("AppDebug", "onError: errorModel $errorModel")
}
})
val delay = 6000L
RepeatHelper.repeatDelayed(delay) {
if (buffIdCount < 5) {
buffIdCount++
getBuff()
}
}
}
private fun displayBuff(buff: Buff) {
setQuestion(buff.question.title)
setAuthor(buff.author)
setAnswer(buff.answers)
setCloseButton()
buffView.visibility = VISIBLE
}
private fun setQuestion(questionText: String) {
question_text.text = questionText
}
private fun setAuthor(author: Buff.Author) {
val firstName = author.firstName
val lastName = author.lastName
sender_name.text = "$firstName $lastName"
Glide.with(buffView)
.load(author.image)
.into(sender_image)
}
private fun setAnswer(answers: List<Buff.Answer>) {
val answersContainer = findViewById<LinearLayout>(R.id.answersContainer)
answersContainer.removeAllViews()
for(answer in answers) {
val answerView: View = LayoutInflater.from(answersContainer.context).inflate(
R.layout.buff_answer,
answersContainer,
false
)
answer_text?.text = answer.title
answersContainer.addView(answerView)
}
}
private fun setCloseButton() {
buff_close.setOnClickListener {
buffView.visibility = GONE
}
}
}
buff_view.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<include layout="@layout/buff_sender"/>
<include layout="@layout/buff_question"/>
<LinearLayout
android:id="@+id/answersContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"/>
</LinearLayout>
buff_answer.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/light_bg"
android:orientation="horizontal"
tools:ignore="RtlHardcoded">
<ImageView
android:id="@+id/answer_image"
android:layout_width="27dp"
android:layout_height="27dp"
android:layout_gravity="center_vertical"
android:src="@drawable/ic_generic_answer"
android:padding="4dp" />
<TextView
android:id="@+id/answer_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="4dp"
android:textColor="@color/test_color_dark"
tools:text="The keeper's right"
android:textStyle="bold" />
</LinearLayout>
尝试在 addView()
之后调用 invalidate()
告诉视图它应该 re-render。您还需要在 answerView
上设置文本,因此该方法应如下所示:
private fun setAnswer(answers: List<Buff.Answer>) {
val answersContainer = findViewById<LinearLayout>(R.id.answersContainer)
answersContainer.removeAllViews()
for(answer in answers) {
val answerView: View = LayoutInflater.from(answersContainer.context).inflate(
R.layout.buff_answer,
answersContainer,
false
)
answerView.answer_text?.text = answer.title
answersContainer.addView(answerView)
}
invalidate() // or invalidateSelf()
}
另一件事是,从自定义视图调用 api 是一种非常糟糕的做法。执行用例应该在 ViewModel/Presenter 等级别。然后应将 buff 提供给 Fragment/Activity,然后在 CustomView 中进行设置。您也没有取消某些 view-destroying 回调中的请求,例如 onDetachedFromWindow()
这可能导致内存泄漏
您不能在不重新创建新变量的情况下多次添加相同的视图,这并不像人们想象的那么简单。
private fun setAnswer(answers: List<Buff.Answer>) {
val answersContainer = findViewById<LinearLayout>(R.id.answersContainer)
var viewlist = ArrayList()
answersContainer.removeAllViews()
for(answer in answers) {
val answerView: View = LayoutInflater.from(answersContainer.context).inflate(
R.layout.buff_answer,
answersContainer,
false
)
answer_text?.text = answer.title
viewlist.add(answerView)
}
for(view in viewlist)
answersContainer.addView(view)
}