以下出現之畫面皆為參照 FieC 公司開發的 Ahorro 所仿造出來的,該創意全為該公司所有,且本文之全部內容都與其公司無任何直接關係。
Ahorro - 輕鬆記帳,簡單理財
※只顯示部份程式碼
在上述的 app 中,有一個部份是可以透過滑動,來選取其支出或收入的類別
~中間的早餐123~
筆者選用 view pager 2 加上擁有 recycler view 的 fragment 來達到這個效果。這篇文主要就是來秀實作的
在有 view pager 的 activity 中,先取得資料的筆數。
@Dao
interface AccountingDao {
@Query("SELECT * FROM category_table")
fun getAllCategories(): LiveData<List<CategoryEntity>>
}
class AddNewViewModel(
application: Application
): AndroidViewModel(application) {
private val repository: Repository
var categories: LiveData<List<CategoryEntity>>
init {
val accountingDao = AccountingDatabase.getDatabase(application, viewModelScope).getAccountingDao()
repository = Repository(accountingDao)
categories= repository.getAllCategories()
}
因為我的這支程式,是採用前面所說的 mvvm 架構,所以就有了 adapter 的資料就從 view model 中取得。
val pagerType: ViewPager2 = findViewById(R.id.pager_type)
val typePagerAdapter = TypePagerAdapter(viewModel, this)
viewModel.categories.observe(this, Observer {
pagerType.adapter = typePagerAdapter
}
之所以要在 observe 裡面才設定 adpater 是為了避免資料為空
因為是要顯示 fragment,所以繼承於 fragmentStateAdapter()
class TypePagerAdapter(
val viewModel: AddNewViewModel,
activity: FragmentActivity
): FragmentStateAdapter(activity){
override fun getItemCount(): Int {
//類別總數除以10加1就會是總頁數
return viewModel.categories.value!!.size / 10 + 1
}
override fun createFragment(position: Int): Fragment {
//每一頁建立出 fragment,而且讓 fragment 本身也知道自己是第幾頁
return AddNewTypeListFragment(position)
}
}
這是每一個頁面的 fragment
class AddNewTypeListFragment(): Fragment() {
private lateinit var rvType: RecyclerView
private lateinit var recyclerAdapter: AddNewTypeRecyclerAdapter
private lateinit var viewModel: AddNewTypeListViewModel
private var page: Int= 0
constructor(page: Int) : this() {
this.page= page
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val root = inflater.inflate(R.layout.add_new_pager_type, container, false)
//viewModel
val factory = AddNewTypeListViewModelFactory(activity!!.application, page)
viewModel = ViewModelProvider(this, factory).get(AddNewTypeListViewModel::class.java)
//recyclerView
rvType= root.findViewById(R.id.add_type_rv_)
recyclerAdapter= AddNewTypeRecyclerAdapter(context!!, viewModel)
val gridLayoutManager = GridLayoutManager(context!!, 5).apply {
orientation= RecyclerView.VERTICAL
}
viewModel.pageCategories.observe(this, Observer {
with(rvType) {
adapter = recyclerAdapter
layoutManager = gridLayoutManager
}
})
return root
}
}
比較特別的是,我這裡就沒有用 linear layout,而是使用 GridLayoutManager,讓我可以顯示兩行資料。
~當資料沒有滿的時候~
class AddNewTypeListViewModel(application: Application, val page: Int): AndroidViewModel(application) {
private val repository: Repository
var pageCategories: LiveData<List<CategoryEntity>>
var selectedCategoryId: MutableLiveData<Int>
init {
val accountingDao = AccountingDatabase.getDatabase(application, viewModelScope).getAccountingDao()
repository = Repository(accountingDao)
selectedCategoryId= MutableLiveData(-1)
//started id = page* 10+ 1
//started id: 1 11 21
// page: 0 1 2
pageCategories= repository.getPageCategories(page* 10+ 1)
}
}
從本地資料庫分批取得資料給 recycler view
class TypeRecyclerAdapter(private val context: Context, private val viewModel: AddNewTypeListViewModel): RecyclerView.Adapter<TypeRecyclerAdapter.TypeViewHolder>() {
inner class TypeViewHolder(item: View): RecyclerView.ViewHolder(item){
var tvName: TextView = item.findViewById(R.id.add_type_item_tv_name)
var layoutItem: LinearLayout = item.findViewById(R.id.add_type_item_layout)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TypeRecyclerAdapter.TypeViewHolder {
return TypeViewHolder(
LayoutInflater.from(context).inflate(R.layout.add_new_pager_type_item, parent, false)
)
}
override fun getItemCount(): Int {
//一頁共十個
return viewModel.pageCategories.value!!.size
}
override fun onBindViewHolder(holder: TypeRecyclerAdapter.TypeViewHolder, position: Int) {
//設定一個項目的寬度為螢幕的五分之一
holder.layoutItem.layoutParams.width= context.resources.displayMetrics.widthPixels/ 5
//一個 item 的點擊
holder.layoutItem.setOnClickListener{
viewModel.selectedCategoryId.value= position
Log.v("position click", "$position")
}
// 這裡會發生 No adapter attached; skipping layout 的錯誤,但是可以成功顯示
try {
holder.tvName.text = "${viewModel.pageCategories.value!![position].name}${ viewModel.pageCategories.value!![position].id}"
} catch (e: Exception){
Log.e("EXCEPTION", "$e")
}
}
}
為了能夠讓每一的部分可以平均寬度,使用 displayMetrics.widthPixels/ 5 來設定每一個的寬度。