iT邦幫忙

2021 iThome 鐵人賽

DAY 13
0
Mobile Development

花30天做個Android小專案系列 第 13

Day13 - 使用Chip和ChipGroup顯示搜尋項目

原本今天是想寫解析文章列表的,不過思考了一下,為了讓脈絡順一點,決定把今天的內容放到解析文章列表之前。

在前兩天分別做了以文章標題和文章作者來搜尋文章的部分,我打算使用ChipChipGroup來顯示搜尋的項目。

Layout

<!-- ... -->
<com.google.android.material.chip.ChipGroup
    android:id="@+id/titleChipGroup"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    app:layout_constraintEnd_toEndOf="@id/searchTitleInput"
    app:layout_constraintStart_toStartOf="@id/searchTitleInput"
    app:layout_constraintTop_toBottomOf="@id/searchTitleInput" />
<!-- ... -->
<com.google.android.material.chip.ChipGroup
    android:id="@+id/authorChipGroup"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    app:layout_constraintEnd_toEndOf="@id/searchAuthorInput"
    app:layout_constraintStart_toStartOf="@id/searchAuthorInput"
    app:layout_constraintTop_toBottomOf="@id/searchAuthorInput" />

在兩個EditText底下分別插入一個ChipGroup,用來存放ChipChip則在程式碼內動態新增。

Code

首先為了在確認有搜尋結果的狀態下再行紀錄,需要稍微改寫一下前兩天的搜尋方法。

前置修改

private suspend fun searchDetail(command: String): Boolean {
    // ...
    when (PttClient.getInstance().expect(searchTitleOrAuthorPattern)) {
        0 -> {
            // ...
            return false
        }
        1 -> {
            parseBoardArticle(PttClient.getInstance().getScreen())
            return true
        }
        else -> {
            // ...
            return false
        }
    }
}

private suspend fun searchDetailProcess(command: String): Boolean {
    when (PttClient.getInstance().expect(inBoardPattern)) {
        0 -> {
            // ...
            when (ret) {
                // ...
                2 -> { // "文章選讀" -> 在看板內
                    return searchDetail(command)
                }
            }
            return false
        }
        2 -> {
            return searchDetail(command)
        }
        else -> {
            // ...
            return false
        }
    }
}

主要是多回傳了布林值,並且把searchDetailProcess也改為suspend method,launch coroutine的部分提到click事件中處理。其餘的內容與前兩天差不多,就省略掉了。
以searchTitle的onClick事件為例

searchTitle.setOnClickListener {
    val word = searchTitleInput.text.toString()
    if (word.isEmpty()) return@setOnClickListener
    searchTitleInput.setText("")
    hideImm()

    viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
        val ret =
            withContext(Dispatchers.IO) { searchDetailProcess("/$word\r\n") }

        if (ret) {
            // Add chip.
        }
    }
}

Add chip

這部分的差異也只在填入的字串與目標ChipGroup,這邊就也只放searchTitle的部分了

val chip = Chip(it.context)
chip.isCheckable = false
chip.text = word
chip.textSize = 16f
chip.typeface = Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD)
chip.closeIcon =
    ResourcesCompat.getDrawable(
        resources,
        R.drawable.ic_baseline_clear_24,
        null
    )
chip.isCloseIconVisible = true
chip.setOnCloseIconClickListener {
    val text = (it as Chip).text
    searchTitleSet.remove(text)
    titleChipGroup.removeView(it)

    (requireActivity() as MainActivity).showLoading("")
    refreshSearch()
}
titleChipGroup.addView(chip)
searchTitleSet.add(word)

建立Chip其實也沒有什麼特殊的地方,頂多就是多放一個clear的icon並設close事件。並且加搜尋的項目記在searchTitleSet中,在refreshSearch方法內重新整理目前狀態。

refreshSearch

private val searchTitleSet = mutableSetOf<String>()
private val searchAuthorSet = mutableSetOf<String>()

private fun refreshSearch() {
    viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
        withContext(Dispatchers.IO) {
            PttClient.getInstance().send("qqqqqqqqqqqqq")
            delay(200L)
            if (searchTitleSet.isEmpty() && searchAuthorSet.isEmpty()) {
                PttClient.getInstance().printScreen()
                return@withContext
            }

            val board = searchBoardInput.text.toString().trim()
            if (board.isEmpty()) return@withContext
            PttClient.getInstance().send("s${board}\r\n")
            delay(200L)
            var ret = PttClient.getInstance().expect(inBoardPattern)
            while (ret == 1) {
                if (ret == 1) {
                    PttClient.getInstance().send("q")
                    delay(200L)
                    ret = PttClient.getInstance().expect(inBoardPattern)
                }
            }
            when (ret) {
                0 -> { // "【主功能表】" -> 在看板外,無法搜尋文章
                    Log.e(mTag, "Out of board.\n${PttClient.getInstance().getScreen()}")
                }
                2 -> { // "文章選讀" -> 在看板內
                    searchTitleSet.forEach {
                        searchDetail("/$it\r\n")
                    }
                    searchAuthorSet.forEach {
                        searchDetail("a$it\r\n")
                    }
                }
            }
        }

        (requireActivity() as MainActivity).dismissLoading()
    }

}

首先先送一串q將畫面重置到主功能表,接著是進入目前的看板,這段流程與searchDetailProcess差不多,接著就是把searchTitleSetsearchAuthorSet剩下的內容重新搜尋一遍就是。

Note

需要注意的是這邊目前有一個狀況,就是在ptt搜尋時,搜尋失敗相同的搜尋結果回傳的畫面是一致的,但是相同的搜尋結果會扣掉一次搜尋次數。這部分我還沒有處理的想法,後面再看看怎麼處理吧。

目前畫面

https://imgur.com/axuraEU.gif


上一篇
Day12 - 搜尋文章作者及合併方法
下一篇
Day14 - 解析看板文章及顯示
系列文
花30天做個Android小專案30

尚未有邦友留言

立即登入留言