【问题标题】:Set data in Custom view在自定义视图中设置数据
【发布时间】:2021-01-15 20:22:32
【问题描述】:

我正在构建一个自定义视图,它每 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>

【问题讨论】:

    标签: android xml android-layout kotlin android-view


    【解决方案1】:

    尝试在addView() 之后调用invalidate() 来告诉视图它应该重新渲染。另外你需要在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 等级别。然后应该将buffs提供给Fragment/Activity,然后在CustomView中设置。您也不会在诸如onDetachedFromWindow() 之类的视图破坏回调中取消请求,这可能会导致内存泄漏

    【讨论】:

    • 非常感谢 Mariusz,问题是我没有在 answerView 上设置文本。我知道我不应该在视图上执行用例,但是,我正在构建的是一个自定义视图的库,所以没有活动或片段,所以我想我不能(或者它没有多大作用感觉)使用 ViewModel?
    • 因此您可以将其标记为已接受的答案 ;) 还可以考虑将 useCase 移动到 ViewModel/Presenter 级别,这样您就可以在整个应用程序中获得干净的数据流,并且它会更易于测试:)跨度>
    • 即使库中没有 Activity 或 Fragment,我还能使用 ViewModel 吗?几天前我问过这个问题stackoverflow.com/questions/64080084/…
    • 好的,这改变了问题的上下文 :) 让我在这个问题中回答这个问题 :)
    • @HelenC 看看stackoverflow.com/questions/64080084/…,我给了我 5 美分 :)
    【解决方案2】:

    你不能在不重新创建新变量的情况下多次添加同一个视图,这并不像人们想象的那么简单。

    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)
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-12-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多