Source code : gitbub
1.先使用Android Studio 創建一個Empty Activity
2.在build.gradle 加入以下code再重新sync
android {
buildFeatures{
dataBinding = true
viewBinding = true
}
}
dependencies {
...
implementation "androidx.fragment:fragment-ktx:1.5.5"
implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.0"))
}
3. 新增兩個檔案
MainFragment.kt 在ui資料夾下,
MainViewModel在 viewModel資料夾下.
MainFragment.kt 內容如下:
package com.sqtek.recyclerviewmvvm.ui
import androidx.lifecycle.ViewModelProvider
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import com.sqtek.recyclerviewmvvm.R
import com.sqtek.recyclerviewmvvm.databinding.FragmentMainBinding
import com.sqtek.recyclerviewmvvm.viewModel.MainViewModel
class MainFragment : Fragment() {
companion object {
fun newInstance() = MainFragment()
}
private lateinit var viewModel: MainViewModel
private lateinit var viewDataBinding: FragmentMainBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
viewDataBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_main ,container, false)
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
viewDataBinding.pViewModel = viewModel //pViewModel 要與對應的xxx.xml中data<>定義的name一致
return viewDataBinding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel.updateTitle()
}
}
MainViewModel.kt 內容如下:
package com.sqtek.recyclerviewmvvm.viewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class MainViewModel : ViewModel() {
var title: MutableLiveData<String> = MutableLiveData()
fun updateTitle() {
title.value = "SQTek for Ethan"
}
}
fragment_main.xml 內容如下: 記得要改成layout
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".ui.MainFragment">
<data>
<variable
name = "pViewModel"
type = "com.sqtek.recyclerviewmvvm.viewModel.MainViewModel"
/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:text="@{pViewModel.title}" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
4. 在MainActivity.kt 加入以下code去呼叫MainFragment顯示畫面
package com.sqtek.recycleview
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.sqtek.recycleview.ui.MainFragment
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
loadFragment()
}
private fun loadFragment() {
val fragmentManager = this.supportFragmentManager
val transaction = fragmentManager.beginTransaction()
transaction.replace(R.id.container, MainFragment.newInstance())
transaction.commit()
}
}
5. 在activity_main.xml加入FrameLayout
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
6.完成基本MVVM架構之後, 只要在MainViewModel修改updateTitle()就會在UI上看見效果
7.增加一個Course data class並存於model/資料夾下,用來存放顯示資料欄位
package com.sqtek.recyclerviewmvvm.model
data class Course(var name: String, val imageUrl: Int, val courseUrl: String, val category: String, val desc: String)
8.修改fragment_main.xml加入RecyclerView元件, 移除原本textview與data
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".ui.MainFragment">
<data>
<variable
name = "pViewModel"
type = "com.sqtek.recyclerviewmvvm.viewModel.MainViewModel"
/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black">
<TextView
android:id="@+id/txTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@{pViewModel.title}"
android:textColor="@color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent"
tools:itemCount="8"
tools:listitem="@layout/card_view_layout"
app:layout_constraintEnd_toEndOf="parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintStart_toStartOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
9. 增加一個card_view_layout.xml顯示每個item的內容
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name = "pViewModel"
type = "com.sqtek.recyclerviewmvvm.model.Course"/>
</data>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
app:cardCornerRadius="5dp">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={pViewModel.name}"//有=代表雙向binding
android:textSize="16sp"/>
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/imageview"
android:layout_width="match_parent"
android:layout_height="160dp"
android:scaleType="centerCrop"
app:imageResource="@{pViewModel.imageUrl}"
android:layout_marginTop="50dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="40dp"
android:gravity="center_vertical"
android:text="@{pViewModel.name}"
android:layout_marginTop="160dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:textAppearance="@style/TextAppearance.AppCompat.Large"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{pViewModel.desc}"
android:layout_marginTop="200dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:textAppearance="@style/TextAppearance.AppCompat.Large"/>
</androidx.cardview.widget.CardView>
</layout>
10. 增加一個MainAdapter.kt將資料塞到recyclerview
package com.sqtek.recyclerviewmvvm.ui
import android.annotation.SuppressLint
import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.annotation.NonNull
import androidx.recyclerview.widget.RecyclerView
import com.sqtek.recyclerviewmvvm.databinding.CardViewLayoutBinding
import com.sqtek.recyclerviewmvvm.model.Course
class MainAdapter: RecyclerView.Adapter<MainAdapter.ViewHolder>() {
var courses = mutableListOf<Course>()
private val TAG = "MainAdapter"
@SuppressLint("NotifyDataSetChanged")
fun setCourseList(course: List<Course>) {
this.courses = course.toMutableList()
Log.d(TAG, "setCourseList: $courses")
notifyDataSetChanged()
}
@NonNull
override fun onCreateViewHolder(@NonNull parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder.from(parent)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.apply {
bind(courses[position])
}
}
override fun getItemCount(): Int {
Log.d(TAG, "getItemCount: ${courses.size}")
return courses.size
}
companion object {
@JvmStatic
@BindingAdapter("loadImage")
fun loadImage(thumbs: ImageView, url: String) {
Glide.with(thumbs)
.load(url)
.circleCrop(
.placehol)der(R.drawable.ic_launcher_foreground)
.error(R.drawable.ic_launcher_foreground)
.fallback(R.drawable.ic_launcher_foreground)
.into(thumbs)
}
}
class ViewHolder(val binding: CardViewLayoutBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(course: Course) {
binding.pViewModel= course
}
companion object {
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = CardViewLayoutBinding.inflate(layoutInflater, parent, false)
return ViewHolder(binding)
}
}
}
}
11.修改MainViewModel加入一些假的資料
package com.sqtek.recyclerviewmvvm.viewModel
import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.sqtek.recyclerviewmvvm.R
import com.sqtek.recyclerviewmvvm.model.Course
class MainViewModel : ViewModel() {
var title: MutableLiveData<String> = MutableLiveData()
val courseList = MutableLiveData<List<Course>>()
fun updateTitle() {
title.value = "SQTek for Ethan"
}
fun getAllCourse() {
val title = arrayOf<String>(
"【Highlands, Islands, Cities: The Magic of Scotland】",
"【Study Finds Why Gardening Is Good for You】",
"【French Bulldog Becomes Most Popular US Dog Breed】",
"【Three-Year Cruise Visits 135 Countries for \$85 a Day】",
"【'I'm Terribly Sorry': How to Apologize in English】",
"【Japanese Scientists Create Mice with Cells from Two Males】",
"【Google's AI chatbot Bard takes on Microsoft's ChatGPT】",
"【Archaeologists Find Earliest Evidence of Horse Riding】")
val imageId = arrayOf<Int>(
R.drawable.course1, R.drawable.course2, R.drawable.course3,
R.drawable.course4, R.drawable.course5, R.drawable.course6,
R.drawable.course7, R.drawable.course8
))
val articleUrl = arrayOf<String>(
"https://engoo.com.tw/app/daily-news/article/highlands-islands-cities-the-magic-of-scotland/RuaCPMUKEe2Pj58I1puG_w",
"https://engoo.com.tw/app/daily-news/article/study-finds-why-gardening-is-good-for-you/UhFf6LkLEe221f9tVn_GlQ",
"https://engoo.com.tw/app/daily-news/article/french-bulldog-becomes-most-popular-us-dog-breed/GyuvJMc0Ee2GvBOw-NIEew",
"https://engoo.com.tw/app/daily-news/article/three-year-cruise-visits-135-countries-for-85-a-day/1f2UHMK6Ee2lN59yxslasw",
"https://engoo.com.tw/app/daily-news/article/im-terribly-sorry-how-to-apologize-in-english/QY1m7sc0Ee2GLHdMNyUqPg",
"https://engoo.com.tw/app/daily-news/article/japanese-scientists-create-mice-with-cells-from-two-males/-FCkIsS5Ee279ouyuxjQkA",
"https://engoo.com.tw/app/daily-news/article/googles-ai-chatbot-bard-takes-on-microsofts-chatgpt/zzeQ_MjMEe2Z5Qcx9XxsIw",
"https://engoo.com.tw/app/daily-news/article/archaeologists-find-earliest-evidence-of-horse-riding/PLMOHLxdEe2OOytrSUBaAA")
val name = arrayOf<String>(
"Highlands, Islands, Cities: The Magic of Scotland",
"Study Finds Why Gardening Is Good for You",
"French Bulldog Becomes Most Popular US Dog Breed",
"Three-Year Cruise Visits 135 Countries for \$85 a Day",
"'I'm Terribly Sorry': How to Apologize in English",
"Japanese Scientists Create Mice with Cells from Two Males",
"Google's AI chatbot Bard takes on Microsoft's ChatGPT",
"Archaeologists Find Earliest Evidence of Horse Riding")
val desc = arrayOf<String>(
"Harry Potter author JK Rowling often wrote her books in Scotland — and if it's magic you're looking for, this might just be a good place to find it.",
"Most gardeners will probably say gardening is good for you. It gets you out in the fresh air, getting lots of sunshine and exercise while watching things grow.",
"For the first time in over 30 years, the US has a new favorite dog breed, according to the American Kennel Club.",
"Would you like to take a cruise around the world with enough time on land to visit everything from the Great Wall of China to the pyramids of Egypt, the bars of Barcelona and the ice-covered rock of Antarctica?",
"It's said that being polite costs nothing. But when we want to apologize — to say we're sorry — it can sometimes be hard to choose the best words.",
"Japanese scientists have created baby mice with two males for the first time by turning their stem cells into female cells in a laboratory.",
"Google has announced it will allow more people to interact with \"Bard,\" its artificial intelligence (AI) chatbot.",
"Archaeologists believe they have found the earliest direct evidence of horseback riding in 5,000-year-old human skeletons in central Europe.")
val list = mutableListOf<Course>()
(0..7).forEach {
list.add(Course(title[it], imageId[it], articleUrl[it], name[it], desc[it]))
}
Log.d("Ethan", "getAllCourse: $list")
courseList.postValue(list)
}
}
12. 修改MainFragment
package com.sqtek.recyclerviewmvvm.ui
import androidx.lifecycle.ViewModelProvider
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import com.sqtek.recyclerviewmvvm.R
import com.sqtek.recyclerviewmvvm.databinding.FragmentMainBinding
import com.sqtek.recyclerviewmvvm.viewModel.MainViewModel
class MainFragment : Fragment() {
companion object {
fun newInstance() = MainFragment()
}
private val TAG = "MainFragment"
private lateinit var viewModel: MainViewModel
private lateinit var viewDataBinding: FragmentMainBinding
private lateinit var mainAdapter: MainAdapter
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
viewDataBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_main ,container, false)
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
viewDataBinding.lifecycleOwner = requireActivity()
mainAdapter = MainAdapter()
mainAdapter.setHasStableIds(true)
viewDataBinding.recyclerview.adapter = mainAdapter
viewModel.getAllCourse()
return viewDataBinding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
Log.d(TAG, "onActivityCreated()")
viewModel.updateTitle()
viewModel.movieList.observe(viewLifecycleOwner, Observer {
Log.d(TAG, "onActivityCreated()::observe: $it")
mainAdapter.setCourseList(it)
})
}
}
13.運行結果
14. 處理按下謀個課程時,跳轉到課程詳細頁面. (記得要將viewmodel 加入adapter中, 不然按下item會沒作用), 先在AndroidManifeat.xml加入網路權限
<uses-permission android:name="android.permission.INTERNET" />
15. 修改card_view_layout.xml 增加viewmode以及按下item後要處理的fun
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name = "pViewModel"
type = "com.sqtek.recyclerviewmvvm.model.Course"/>
<variable
name = "ViewModel"
type = "com.sqtek.recyclerviewmvvm.viewModel.MainViewModel"/>
</data>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
app:cardCornerRadius="5dp"
android:onClick="@{() -> ViewModel.openItem(pViewModel)}">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={pViewModel.name}"
android:textSize="18sp"/>
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/imageview"
android:layout_width="match_parent"
android:layout_height="160dp"
android:scaleType="centerCrop"
app:imageResource="@{pViewModel.imageUrl}"
android:layout_marginTop="60dp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:text="@{pViewModel.name}"
android:layout_marginTop="230dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:text="@tools:sample/full_names"
android:background="#E1303F9F"
android:paddingStart="8dp"
android:textColor="@color/white"
android:textAppearance="@style/TextAppearance.AppCompat.Large"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{pViewModel.desc}"
android:layout_marginTop="290dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:text="@tools:sample/full_names"
android:paddingStart="8dp"
android:textSize="14sp"
android:textAppearance="@style/TextAppearance.AppCompat.Large"/>
</androidx.cardview.widget.CardView>
</layout>
16. 修改MainViewModel.kt 新增一個fun openItem(course: Course) 處理按下的動作以及新增一個openItemEvent通知MainFragment有item被按下
package com.sqtek.recyclerviewmvvm.viewModel
import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.sqtek.recyclerviewmvvm.R
import com.sqtek.recyclerviewmvvm.model.Course
import com.sqtek.recyclerviewmvvm.ui.Event
class MainViewModel : ViewModel() {
private val TAG = "MainFragment"
//var title: MutableLiveData<String> = MutableLiveData()
val courseList = MutableLiveData<List<Course>>()
val openItemEvent: MutableLiveData<Event<Course>> = MutableLiveData()
//fun updateTitle() {
// title.value = "SQTek for Ethan"
//}
fun getAllCourse() {
val title = arrayOf<String>(
"【Highlands, Islands, Cities: The Magic of Scotland】",
"【Study Finds Why Gardening Is Good for You】",
"【French Bulldog Becomes Most Popular US Dog Breed】",
"【Three-Year Cruise Visits 135 Countries for \$85 a Day】",
"【'I'm Terribly Sorry': How to Apologize in English】",
"【Japanese Scientists Create Mice with Cells from Two Males】",
"【Google's AI chatbot Bard takes on Microsoft's ChatGPT】",
"【Archaeologists Find Earliest Evidence of Horse Riding】")
val imageId = arrayOf<Int>(
R.drawable.course1, R.drawable.course2, R.drawable.course3,
R.drawable.course4, R.drawable.course5, R.drawable.course6,
R.drawable.course7, R.drawable.course8
)
val articleUrl = arrayOf<String>(
"https://engoo.com.tw/app/daily-news/article/highlands-islands-cities-the-magic-of-scotland/RuaCPMUKEe2Pj58I1puG_w",
"https://engoo.com.tw/app/daily-news/article/study-finds-why-gardening-is-good-for-you/UhFf6LkLEe221f9tVn_GlQ",
"https://engoo.com.tw/app/daily-news/article/french-bulldog-becomes-most-popular-us-dog-breed/GyuvJMc0Ee2GvBOw-NIEew",
"https://engoo.com.tw/app/daily-news/article/three-year-cruise-visits-135-countries-for-85-a-day/1f2UHMK6Ee2lN59yxslasw",
"https://engoo.com.tw/app/daily-news/article/im-terribly-sorry-how-to-apologize-in-english/QY1m7sc0Ee2GLHdMNyUqPg",
"https://engoo.com.tw/app/daily-news/article/japanese-scientists-create-mice-with-cells-from-two-males/-FCkIsS5Ee279ouyuxjQkA",
"https://engoo.com.tw/app/daily-news/article/googles-ai-chatbot-bard-takes-on-microsofts-chatgpt/zzeQ_MjMEe2Z5Qcx9XxsIw",
"https://engoo.com.tw/app/daily-news/article/archaeologists-find-earliest-evidence-of-horse-riding/PLMOHLxdEe2OOytrSUBaAA")
//val courseResponse = CourseResponse()
val name = arrayOf<String>(
"Highlands, Islands, Cities: The Magic of Scotland",
"Study Finds Why Gardening Is Good for You",
"French Bulldog Becomes Most Popular US Dog Breed",
"Three-Year Cruise Visits 135 Countries for \$85 a Day",
"'I'm Terribly Sorry': How to Apologize in English",
"Japanese Scientists Create Mice with Cells from Two Males",
"Google's AI chatbot Bard takes on Microsoft's ChatGPT",
"Archaeologists Find Earliest Evidence of Horse Riding")
val desc = arrayOf<String>(
"Harry Potter author JK Rowling often wrote her books in Scotland — and if it's magic you're looking for, this might just be a good place to find it.",
"Most gardeners will probably say gardening is good for you. It gets you out in the fresh air, getting lots of sunshine and exercise while watching things grow.",
"For the first time in over 30 years, the US has a new favorite dog breed, according to the American Kennel Club.",
"Would you like to take a cruise around the world with enough time on land to visit everything from the Great Wall of China to the pyramids of Egypt, the bars of Barcelona and the ice-covered rock of Antarctica?",
"It's said that being polite costs nothing. But when we want to apologize — to say we're sorry — it can sometimes be hard to choose the best words.",
"Japanese scientists have created baby mice with two males for the first time by turning their stem cells into female cells in a laboratory.",
"Google has announced it will allow more people to interact with \"Bard,\" its artificial intelligence (AI) chatbot.",
"Archaeologists believe they have found the earliest direct evidence of horseback riding in 5,000-year-old human skeletons in central Europe.")
val list = mutableListOf<Course>()
(0..7).forEach {
list.add(Course(title[it], imageId[it], articleUrl[it], name[it], desc[it]))
}
Log.d(TAG, "getAllCourse: $list")
courseList.postValue(list)
}
fun openItem(course: Course) {
Log.d(TAG, "openItem: $course")
openItemEvent.value = Event(course)
}
}
17. 修改MainFragment, 將MainViewModel加入MainAdapter中才能處理再MainFragment按下item的動作, 在處理openItemEvent 呼叫detailfragment顯示課程詳細頁面
package com.sqtek.recyclerviewmvvm.ui
import androidx.lifecycle.ViewModelProvider
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import com.sqtek.recyclerviewmvvm.R
import com.sqtek.recyclerviewmvvm.databinding.FragmentMainBinding
import com.sqtek.recyclerviewmvvm.model.Course
import com.sqtek.recyclerviewmvvm.viewModel.MainViewModel
class MainFragment : Fragment() {
companion object {
fun newInstance() = MainFragment()
}
private val TAG = "MainFragment"
private lateinit var viewModel: MainViewModel
private lateinit var viewDataBinding: FragmentMainBinding
private lateinit var mainAdapter: MainAdapter
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
viewDataBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_main ,container, false)
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
viewDataBinding.lifecycleOwner = requireActivity()
mainAdapter = MainAdapter(viewModel)
//mainAdapter.setHasStableIds(true)
//viewDataBinding.viewModel = viewModel
viewDataBinding.recyclerview.adapter = mainAdapter
viewModel.getAllCourse()
return viewDataBinding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
Log.d(TAG, "onActivityCreated()")
//viewModel.updateTitle()
viewModel.courseList.observe(viewLifecycleOwner, Observer {
Log.d(TAG, "onActivityCreated()::observe $it")
mainAdapter.setCourseList(it)
})
viewModel.openItemEvent.observe(viewLifecycleOwner, Observer { event ->
event.getContentIfNotHandled()?.let {
val course: Course = it
Log.d(TAG, "openItemEvent()")
requireActivity().supportFragmentManager
.beginTransaction()
.replace(R.id.container, DetailFragment.newInstance(course))
.commit()
}
})
}
}
18.修改MainAdapter的class新增帶入MainViewModel以及修改viewholder的bind fun
package com.sqtek.recyclerviewmvvm.ui
import android.annotation.SuppressLint
import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.annotation.NonNull
import androidx.recyclerview.widget.RecyclerView
import com.sqtek.recyclerviewmvvm.databinding.CardViewLayoutBinding
import com.sqtek.recyclerviewmvvm.model.Course
import com.sqtek.recyclerviewmvvm.viewModel.MainViewModel
class MainAdapter(private val viewModel: MainViewModel): RecyclerView.Adapter<MainAdapter.ViewHolder>() {
var courses = mutableListOf<Course>()
private val TAG = "MainAdapter"
@SuppressLint("NotifyDataSetChanged")
fun setCourseList(course: List<Course>) {
this.courses = course.toMutableList()
Log.d(TAG, "setCourseList: $courses")
notifyDataSetChanged()
}
@NonNull
override fun onCreateViewHolder(@NonNull parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder.from(parent)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.apply {
bind(viewModel, courses[position])
}
}
override fun getItemCount(): Int {
Log.d(TAG, "getItemCount: ${courses.size}")
return courses.size
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
class ViewHolder(val binding: CardViewLayoutBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(viewModel: MainViewModel,course: Course) {
binding.pViewModel= course
binding.viewModel =viewModel
binding.executePendingBindings()
}
companion object {
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = CardViewLayoutBinding.inflate(layoutInflater, parent, false)
return ViewHolder(binding)
}
}
}
}
19. 新增Event.kt 處理event
package com.sqtek.recyclerviewmvvm.ui
open class Event<out T>(private val content: T) {
var hasBeenHandled = false
private set // Allow external read but not write
/**
* Returns the content and prevents its use again.
*/
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
}
/**
* Returns the content, even if it's already been handled.
*/
fun peekContent(): T = content
}
20. 新增DetailFragment.kt與fragment_course_detail.xml
DetailFragment.kt
package com.sqtek.recyclerviewmvvm.ui
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.activity.OnBackPressedCallback
import androidx.databinding.DataBindingUtil
import com.sqtek.recyclerviewmvvm.R
import com.sqtek.recyclerviewmvvm.databinding.FragmentCourseDetailBinding
import com.sqtek.recyclerviewmvvm.model.Course
/**
* A simple [Fragment] subclass.
* Use the [DetailFragment.newInstance] factory method to
* create an instance of this fragment.
*/
class DetailFragment(item: Course) : Fragment() {
val item: Course = item
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val viewDataBinding: FragmentCourseDetailBinding = DataBindingUtil.inflate(
inflater, R.layout.fragment_course_detail, container, false
)
val webView = viewDataBinding.root.findViewById(R.id.webView) as WebView
webView.settings.javaScriptEnabled = true
webView.webViewClient = WebViewClient()
webView.loadUrl(item.courseUrl)
webView.settings.setSupportZoom(true)
return viewDataBinding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
requireActivity().supportFragmentManager
.beginTransaction()
.replace(R.id.container, MainFragment.newInstance(), null)
.commit()
}
})
}
companion object {
@JvmStatic
fun newInstance(item: Course) = DetailFragment(item)
}
}
fragment_course_detail.xm
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.DetailFragment">
<WebView
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="50dp"
android:layout_marginBottom="10dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp">
</WebView>
</FrameLayout>
</layout>
21. 完成