iT邦幫忙

2021 iThome 鐵人賽

DAY 18
0
Mobile Development

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

Day18 - 使用ViewBinding取代Kotlin Android Extension

  • 分享至 

  • xImage
  •  

今天是預料之外的內容。

Kotlin在1.4.20-M2版本中棄用了Kotlin Android Extension,先前開發時沒特別注意到這個資訊,只有覺得Android Studio自某版本開始沒有自動加入apply plugin: 'kotlin-android-extensions'感到有些奇怪。最近了解了一下相關資訊後決定把目前在做的專案都改成使用ViewBinding

相關資訊

開始使用

在Gradle內加入

android {
    // ...
    buildFeatures {
        viewBinding true
    }
    // ...
}

並將有使用到kotlinx.android.synthetic的import都移除掉。

ViewBinding in Activity

先宣告Binding物件,Binding物件的名稱會根據layout.xml的名稱來自動產生,比如activity_main.xml就會產生為ActivityMainBinding

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

    }
}

接著在onCreate中呼叫Binding物件的inflate方法來初始化,並且將setContentView中傳入的內容改為inflate後的root。

最後使用到layout內的View時需從binding呼叫。

原本:

public fun showLoading(msg: String) {
    loadingLayout.visibility = View.VISIBLE
    loadingMsg.text = msg
}

修改後:

public fun showLoading(msg: String) {
    binding.loadingLayout.visibility = View.VISIBLE
    binding.loadingMsg.text = msg
}

ViewBinding in Fragment

class SearchArticleFragment : Fragment() {
    private var _binding: FragmentSearchArticleBinding? = null
    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        // Inflate the layout for this fragment
        _binding = FragmentSearchArticleBinding.inflate(inflater, container, false)
        return binding.root
    }
    
    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

Fragment的ViewBinding物件在inflate時需傳入所屬的parent。另外要注意在onDestroyView的時候將_binding設回null,避免Memory Leak。

ViewBinding in RecyclerView.Adapter

在RecyclerView.Adapter中使用時需要修改ViewHolder,將原本傳入View的部分改為傳入ViewBinding物件,super的部分則傳入binding.root即可:

inner class ViewHolder(private val binding: ItemSearchArticleResultBinding) :
    RecyclerView.ViewHolder(binding.root) {

    init {
        itemView.setOnClickListener {
            onArticleClickListener?.invoke(articleList[adapterPosition])
        }
    }

    fun bindView(article: Article) {
        binding.number.text = article.number
        binding.author.text = article.author
        binding.title.text = article.title
        binding.date.text = article.date
    }
}

RecyclerView.onCreateViewHolder對應修改:

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = ViewHolder(
    ItemSearchArticleResultBinding
        .inflate(LayoutInflater.from(parent.context), parent, false)
)

ViewBinding for dynamic View item

Day09時我用了PopupWindow來顯示看板的搜尋結果,過程中有另外inflate子View的部分,這邊在使用ViewBinding時也需要做一點修改,原本的程式碼連結內有就不重複貼了。

修改後程式碼
// ...
val boardResultBinding = LayoutSearchBoardResultBinding.inflate(
    LayoutInflater.from(requireContext()),
    null,
    false
)

val height = if (boardList.isEmpty()) {
    boardResultBinding.noResultLabel.visibility = View.VISIBLE
    boardResultBinding.recyclerView.visibility = View.GONE
    120f.dpToPx(requireContext())
} else {
    val adapter = SearchBoardResultAdapter(boardList)
    adapter.onBoardClickListener = { board ->
        popupWindow?.dismiss()
        binding.searchBoardInput.setText(board)
    }
    boardResultBinding.recyclerView.setHasFixedSize(true)
    boardResultBinding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
    boardResultBinding.recyclerView.adapter = adapter
    360f.dpToPx(requireContext())
}

popupWindow = PopupWindow(
    boardResultBinding.root, // view
    ViewGroup.LayoutParams.MATCH_PARENT, // width
    height, // height
    true // focusable
)
// ...

上一篇
Day17 - 解析推文
下一篇
Day19 - 讀取更多推文
系列文
花30天做個Android小專案30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言