如何应对 Android 面试官 -> 运用 Jetpack 写一个音乐播放器(二)音乐列表

前言


本章继续,运用前面讲过的 Jetpack 组件结合 MVVM + Kotlin 在完成一个音乐播放器;

数据填充


我们接下来进行 Base 类和 Activity 的数据填充;

SharedViewModel 功能填充

kotlin 复制代码
class SharedViewModel : ViewModel() {

    // 添加监听(可以弹上来的监听)
    val timeToAddSlideListener = MutableLiveData<Boolean>()

    // 可以控制播放详情 点击/back 掉下来
    // 播放详情中左手边滑动图标(点击的时候),与 MainActivity back 是会 set(true)
    val closeSlidePanelIfExpanded = MutableLiveData<Boolean>()

    // 活动关闭的一些记录(播放条缩小一条与扩大展开)通知给控制者的(扩展的)
    val activityCanBeClosedDirectly = MutableLiveData<Boolean>()

    // openMenu 打开菜单的时候会 set触发---> 改变 openDrawer.setValue(aBoolean); 的值
    val openOrCloseDrawer = MutableLiveData<Boolean>()

    // 开启和关闭卡片相关的状态,如果发生改变会和 allowDrawerOpen 挂钩
    val enableSwipeDrawer = MutableLiveData<Boolean>()
}

MainActivity 功能填充

我们首先来初始化 MainActivityViewModel 和 ActivityMainBinding

kotlin 复制代码
class MainActivity : BaseActivity() {

    var mainBinding: ActivityMainBinding? = null // 当前 MainActivity 的布局
    var mainActivityViewModel: MainActivityViewModel? = null  // 当前 Activity 关联的 ViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 绑定 DataBinding 与 ViewModel 结合
        mainActivityViewModel = getActivityViewModelProvider(this).get(MainActivityViewModel::class.java)
        mainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        mainBinding?.lifecycleOwner = this
        mainBinding?.setVm(mainActivityViewModel)
        
        // 
        setOberve()
    }
}

接下来,我们来给 SharedViewModel 中定义的各个状态设置 observe

kotlin 复制代码
private fun setObserve() {

    // 共享 (观察) 活动关闭的一些记录(播放条 缩小一条 与 扩大展开)
    mSharedViewModel.activityCanBeClosedDirectly.observe(this, {
        // ... 业务逻辑的
    })

    // 监听(发送 打开菜单的指令 1)
    mSharedViewModel.openOrCloseDrawer.observe(this, { aBoolean ->
        mainActivityViewModel?.allowDrawerOpen.value = aBoolean // 触发,就会改变 --> 观察(打开菜单)
    })

    // 监听(发送 打开菜单的指令 2)
    mSharedViewModel.enableSwipeDrawer.observe(this, { aBoolean ->
        mainActivityViewModel?.allowDrawerOpen.value = aBoolean // 触发抽屉控件关联的值
    })
}

接下来,我们来给 MainActivityViewModel 中定义变量

kotlin 复制代码
class MainActivityViewModel : ViewModel() {

    // 首页需要记录抽屉布局的情况
    @JvmField
    val openDrawer = MutableLiveData<Boolean>()

    // 响应的效果,都让抽屉控件干了
    @JvmField // @JvmField消除了变量的getter方法
    val allowDrawerOpen = MutableLiveData<Boolean>()

    // 构造代码块
    init {
        allowDrawerOpen.value = true
    }
}

接下来,我们需要在 Activity 可见的时候,触发 timeToAddSlideListener 的状态变化,这里我们复现下 onWindowFoucsChanged 方法

kotlin 复制代码
override fun onWindowFocusChanged(hasFocus: Boolean) {
    super.onWindowFocusChanged(hasFocus)
    // 此字段只要发生了改变,就会添加监听(可以弹上来的监听)
    mSharedViewModel.timeToAddSlideListener.value = true // 触发改变
}

BindingAdapter

上一章中,我们有在 activity_main.xml 中设置了一些并不属于 xml 范围内的属性,例如:

这是 DataBinding 的能力,我们来进行 DrawerLayout 的 DataBinding 填充

kotlin 复制代码
object DrawerBindingAdapter {

    // 『打开抽屉』与『关闭抽屉』
    @JvmStatic // 代表是静态函数
    @BindingAdapter(value = ["isOpenDrawer"], requireAll = false)
    fun openDrawer(drawerLayout: DrawerLayout, isOpenDrawer: Boolean) {
        if (isOpenDrawer && !drawerLayout.isDrawerOpen(GravityCompat.START)) {
            drawerLayout.openDrawer(GravityCompat.START)
        } else {
            drawerLayout.closeDrawer(GravityCompat.START)
        }
    }

    // 允许抽屉的 打开 与 关闭
    @JvmStatic // 代表是静态函数
    @BindingAdapter(value = ["allowDrawerOpen"], requireAll = false)
    fun allowDrawerOpen(drawerLayout: DrawerLayout, allowDrawerOpen: Boolean) {
        drawerLayout.setDrawerLockMode(if (allowDrawerOpen) DrawerLayout.LOCK_MODE_UNLOCKED else DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
    }
}

PS:这里需要注意,因为是 Kotlin,需要我们引入 apply plugin: 'kotlin-kapt' 才能编译通过;

MainFragment

我们先来填充 xml 中的布局:

ini 复制代码
<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="click"
            type="com.xiangxue.puremusic.ui.page.MainFragment.ClickProxy" />

        <variable
            name="vm"
            type="com.xiangxue.puremusic.bridge.state.MainViewModel" />

    </data>

    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:overScrollMode="never"
        android:background="@color/black">

        <com.google.android.material.appbar.AppBarLayout
            android:id="@+id/appbar_layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:fitsSystemWindows="true"
            android:theme="@style/AppTheme">

            <!-- 折叠工具栏布局 -->
            <com.google.android.material.appbar.CollapsingToolbarLayout
                android:id="@+id/collapse_layout"
                android:layout_width="match_parent"
                android:layout_height="275dp"
                android:fitsSystemWindows="true"
                app:contentScrim="?attr/colorPrimary"
                app:layout_scrollFlags="scroll|exitUntilCollapsed">

                <!-- 背景 图片
                   android:src="@drawable/bg_home"
                 -->
                <androidx.appcompat.widget.AppCompatImageView
                    android:id="@+id/iv_bg"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:fitsSystemWindows="true"
                    android:scaleType="centerCrop"
                    android:src="@drawable/bg_home"
                    app:layout_collapseMode="parallax" />

                <androidx.constraintlayout.widget.ConstraintLayout
                    android:id="@+id/toolbar"
                    drawable_radius="@{8}"
                    drawable_solidColor="@{0x88ffffff}"
                    drawable_strokeColor="@{0x33666666}"
                    drawable_strokeWidth="@{1}"
                    android:layout_width="match_parent"
                    android:layout_height="48dp"
                    android:layout_marginStart="12dp"
                    android:layout_marginTop="37dp"
                    android:layout_marginEnd="12dp"
                    android:layout_marginBottom="12dp">

                    <androidx.appcompat.widget.AppCompatImageView
                        android:id="@+id/iv_menu"
                        android:layout_width="24dp"
                        android:layout_height="24dp"
                        android:layout_marginStart="12dp"
                        android:background="?attr/selectableItemBackgroundBorderless"
                        android:onClick="@{()->click.openMenu()}"
                        android:scaleType="centerInside"
                        android:src="@drawable/ic_menu_black_48dp"
                        app:layout_constraintBottom_toBottomOf="parent"
                        app:layout_constraintLeft_toLeftOf="parent"
                        app:layout_constraintTop_toTopOf="parent" />

                    <androidx.appcompat.widget.AppCompatImageView
                        android:id="@+id/iv_icon"
                        onClickWithDebouncing="@{()->click.search()}"
                        android:layout_width="24dp"
                        android:layout_height="24dp"
                        android:background="?attr/selectableItemBackgroundBorderless"
                        android:scaleType="centerInside"
                        android:src="@drawable/ic_music_note_black_48dp"
                        app:layout_constraintBottom_toBottomOf="parent"
                        app:layout_constraintRight_toLeftOf="@+id/tv_app"
                        app:layout_constraintTop_toTopOf="parent" />

                    <TextView
                        android:id="@+id/tv_app"
                        onClickWithDebouncing="@{()->click.search()}"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="@string/app_name"
                        android:textSize="18sp"
                        android:textStyle="bold"
                        android:textColor="@color/my_c2"
                        app:layout_constraintBottom_toBottomOf="parent"
                        app:layout_constraintLeft_toLeftOf="parent"
                        app:layout_constraintRight_toRightOf="parent"
                        app:layout_constraintTop_toTopOf="parent" />

                    <androidx.appcompat.widget.AppCompatImageView
                        android:id="@+id/iv_search"
                        onClickWithDebouncing="@{()->click.search()}"
                        android:layout_width="24dp"
                        android:layout_height="24dp"
                        android:layout_marginEnd="12dp"
                        android:background="?attr/selectableItemBackgroundBorderless"
                        android:scaleType="centerInside"
                        android:src="@drawable/ic_search_black_48dp"
                        app:layout_constraintBottom_toBottomOf="parent"
                        app:layout_constraintRight_toRightOf="parent"
                        app:layout_constraintTop_toTopOf="parent" />

                </androidx.constraintlayout.widget.ConstraintLayout>

            </com.google.android.material.appbar.CollapsingToolbarLayout>

            <!-- MainFragment 初始化页面的标记,初始化选项卡和页面 -->
            <com.google.android.material.tabs.TabLayout
                android:id="@+id/tab_layout"
                initTabAndPage="@{vm.initTabAndPage}"
                android:layout_width="match_parent"
                android:layout_height="48dp"
                android:fitsSystemWindows="true"
                app:tabBackground="@color/my_c1"
                app:tabIndicatorColor="@color/gray"
                app:tabIndicatorHeight="4dp"
                app:tabSelectedTextColor="@color/gray"
                app:tabTextColor="@color/light_gray">

                <com.google.android.material.tabs.TabItem
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="@string/recently" />

                <com.google.android.material.tabs.TabItem
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="@string/best_practice" />

            </com.google.android.material.tabs.TabLayout>

        </com.google.android.material.appbar.AppBarLayout>

        <!-- 下面展示 横向切换:
             1."最近播放区域" 其实就是 音乐列表 系列Item
             2."其他信息区域" 其实就是 WebView 展示网页信息而已
        -->
        <androidx.viewpager.widget.ViewPager
            android:id="@+id/view_pager"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:overScrollMode="never"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">

            <!-- 1."我的歌曲区域" 其实就是『 音乐列表 』系列Item
                 RecyclerView 的每一个 Item 布局 == adapter_play_item.xml
             -->
            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/rv"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:clipToPadding="false"
                app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
                tools:listitem="@layout/adapter_play_item"
                android:visibility="visible"
                />

            <!-- 2."其他信息区域" 其实就是 WebView 展示网页信息而已 -->
            <androidx.core.widget.NestedScrollView
                android:layout_width="match_parent"
                android:layout_height="match_parent">

                <WebView
                    android:id="@+id/web_view"
                    pageAssetPath="@{vm.pageAssetPath}"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:clipToPadding="false"
                    android:visibility="visible"
                    android:background="#B4D9DD"/>

            </androidx.core.widget.NestedScrollView>

        </androidx.viewpager.widget.ViewPager>

    </androidx.coordinatorlayout.widget.CoordinatorLayout>

</layout>

接下来,我们在 MainFragment 中初始化 DataBinding 和 ViewModel

kotlin 复制代码
class MainFragment  : BaseFragment(){

    // 首页 DataBinding
    private var mainBinding: FragmentMainBinding? = null
    // 首页 Fragment的ViewModel
    private var mainViewModel : MainViewModel? = null 
    // 音乐资源相关的 VM Request ViewModel
    private var musicRequestViewModel: MusicRequestViewModel? = null
    // 适配器
    private var adapter: SimpleBaseBindingAdapter<TestAlbum.TestMusic?, AdapterPlayItemBinding?>? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 初始化 ViewModel
        mainViewModel = getFragmentViewModelProvider(this).get(MainViewModel::class.java)
        musicRequestViewModel = getFragmentViewModelProvider(this).get(MusicRequestViewModel::class.java)
    }

    
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        val view: View = inflater.inflate(R.layout.fragment_main, container, false)
        // 绑定DataBinding 与 ViewModel 关联
        mainBinding = FragmentMainBinding.bind(view)
        // 设置点击事件,布局就可以直接绑定
        mainBinding?.click = ClickProxy()
        // 设置 VM,就可以实时数据变化
        mainBinding?.setVm(mainViewModel) 
        return view
    }
}

这里用到了 ClickProxy 这个是一个点击代理类,我们可以把它定义在 MainFramgent 中最为一个内部类来处理;

kotlin 复制代码
// 处理所有点击事件
inner class ClickProxy {
    // 当在首页点击 "菜单" 的时候,直接导航到 ---> 菜单的Fragment界面
    // 间接通过共享 VM 触发到 openDrawer 触发到 @BindingAdapter
    fun openMenu() { 
        sharedViewModel.openOrCloseDrawer.value = true 
    } 

    // 当在首页点击 "搜索图标" 的时候,直接导航到 ---> 搜索的Fragment界面
    fun search() = nav().navigate(R.id.action_mainFragment_to_searchFragment)
}

接下来,我们来填充 MainViewModel 支持 initTabAndPage

less 复制代码
class MainViewModel : ViewModel() {

    // MainFragment 初始化页面的标记 初始化选项卡和页面
    @JvmField
    val initTabAndPage = ObservableBoolean()

    // MainFragment "其他信息" 里面的 WebView需要加载的网页链接路径
    @JvmField
    val pageAssetPath = ObservableField<String>()

}

PS:ViewModel 中什么时候使用 LiveData 什么时候使用 ObservableField,

LiveData 应用于更新并不频繁的场景,而 ObservableField 使用于较为频繁的场景来防止抖动;

我们给 TabLayout 和 WebView 设置了 BindingAdapter,那么我们来完善这个 BindingAdapter

initTabAndPage

kotlin 复制代码
object TabPageBindingAdapter {

    // MainFragment 初始化页面的标记,初始化选项卡和页面
    @JvmStatic
    @BindingAdapter(value = ["initTabAndPage"], requireAll = false)
    fun initTabAndPage(tabLayout: TabLayout, initTabAndPage: Boolean) {
        if (initTabAndPage) {
            val count = tabLayout.tabCount
            val title = arrayOfNulls<String>(count)
            for (i in 0 until count) {
                val tab = tabLayout.getTabAt(i)
                if (tab != null && tab.text != null) {
                    title[i] = tab.text.toString()
                }
            }
            val viewPager: ViewPager = tabLayout.rootView.findViewById(R.id.view_pager)
            if (viewPager != null) {
                viewPager.adapter = CommonViewPagerAdapter(count, false, title)
                tabLayout.setupWithViewPager(viewPager)
            }
        }
    }

    // 在选项卡上添加选定的侦听器(备用的功能)
    @BindingAdapter(value = ["tabSelectedListener"], requireAll = false)
    fun tabSelectedListener(tabLayout: TabLayout, listener: OnTabSelectedListener?) {
        tabLayout.addOnTabSelectedListener(listener)
    }
}

pageAssetPath

kotlin 复制代码
object WebViewBindingAdapter {

    /**
     * 加载WebView,固定加载 Assets 目录下的资源
     */
    @JvmStatic
    @SuppressLint("SetJavaScriptEnabled")
    @BindingAdapter(value = ["pageAssetPath"], requireAll = false)
    fun loadAssetsPage(webView: WebView, assetPath: String) {
        webView.webViewClient = object : WebViewClient() {
            override fun shouldOverrideUrlLoading(
                view: WebView,
                request: WebResourceRequest
            ): Boolean {
                val uri = request.url
                val intent = Intent(Intent.ACTION_VIEW, uri)
                intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
                Utils.getApp().startActivity(intent)
                return true
            }
        }
        webView.scrollBarStyle = View.SCROLLBARS_INSIDE_OVERLAY
        val webSettings = webView.settings
        webSettings.javaScriptEnabled = true
        webSettings.defaultTextEncodingName = "UTF-8"
        webSettings.setSupportZoom(true)
        webSettings.builtInZoomControls = true
        webSettings.displayZoomControls = false
        webSettings.useWideViewPort = true
        webSettings.loadWithOverviewMode = true
        val url = "file:///android_asset/$assetPath"
        webView.loadUrl(url)
    }
}

接下来,我们来填充 MainFragment 音乐列表界面,按照多状态管理原则,这个 Fragment 除了 MainViewModel 之后,还需要一个请求音乐数据列表的 MusicRequestViewModel

kotlin 复制代码
class MusicRequestViewModel : ViewModel() {

    var freeMusicesLiveData : MutableLiveData<TestAlbum>? = null
        get() {
            if (field == null) {
                field = MutableLiveData()
            }
            return field
        }
        private set

    fun requestFreeMusics() {
        HttpRequestManager.getFreeMusic(freeMusicesLiveData)
    }
}

接下来,我们进行适配器的填充,让数据在 View 上渲染出来;

kotlin 复制代码
class MainFragment  : BaseFragment() {
    
    
    // 我们操作布局,不去传统方式操作,全部使用 Databind
    private var mainBinding: FragmentMainBinding? = null
    // 首页Fragment的 ViewModel
    private var mainViewModel : MainViewModel? = null 
    // 音乐资源相关的 ViewModel 用来做网络请求,获取音乐数据
    private var musicRequestViewModel: MusicRequestViewModel? = null

    // 适配器
    private var adapter: SimpleBaseBindingAdapter<TestAlbum.TestMusic?, AdapterPlayItemBinding?>? = null
   
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // MainFragment初始化页面的标记,初始化选项卡和页面
        mainViewModel?.initTabAndPage.set(true)
        // 加载 WebView
        mainViewModel?.pageAssetPath.set("JetPack 之 WorkManager.html")

        // 展示数据,适配器里面的的数据 展示出来
        // 设置设配器(item 的布局和适配器的绑定)
        adapter = object : SimpleBaseBindingAdapter<TestAlbum.TestMusic?, AdapterPlayItemBinding?>(context, R.layout.adapter_play_item) {
            override fun onSimpleBindItem(
                binding: AdapterPlayItemBinding?,
                item: TestAlbum.TestMusic?,
                holder: RecyclerView.ViewHolder?) {
                
                // 标题
                binding?.tvTitle?.text = item?.title 
                // 名字
                binding?.tvArtist?.text = item?.artist?.name
                // 左右的图片
                Glide.with(binding?.ivCover?.context)
                     .load(item?.coverImg).into(binding.ivCover)

                // 点击Item
                binding.root.setOnClickListener { v ->
                    Toast.makeText(mContext, "播放音乐", Toast.LENGTH_SHORT).show()
                }
            }
        }
        mainBinding?.rv.adapter = adapter

        // 请求数据
        musicRequestViewModel?.requestFreeMusics()

        // observe
        // 音乐资源的 VM
        musicRequestViewModel?.freeMusicesLiveData?.observe(viewLifecycleOwner, { musicAlbum: TestAlbum? ->
            if (musicAlbum != null && musicAlbum.musics != null) {
                // 这里特殊:直接更新UI
                // 数据加入适配器
                adapter?.list = musicAlbum.musics 
                adapter?.notifyDataSetChanged()
            }
        })
    }
}

接下来,我们来看下适配器是如何实现的 SimpleBaseBindingAdapter 可以看到这个适配器是一个抽象类,实现了 onSimpleBindItem 方法;

scala 复制代码
public abstract class SimpleBaseBindingAdapter<M, B extends ViewDataBinding> extends BaseBindingAdapter {

    private final int layout;

    public SimpleBaseBindingAdapter(Context context, int layout) {
        super(context);
        this.layout = layout;
    }

    @Override
    protected @LayoutRes int getLayoutResId(int viewType) {
        return this.layout;
    }

    protected abstract void onSimpleBindItem(B binding, M item, RecyclerView.ViewHolder holder);

    @Override
    protected void onBindItem(ViewDataBinding binding, Object item, RecyclerView.ViewHolder holder) {
        //noinspection unchecked
        onSimpleBindItem((B) binding, (M) item, holder);
    }
}

AdapterPlayItemBinding 则是 xml 生成的 DataBinding adapter_play_item.xml

ini 复制代码
<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>
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/root_view"
        android:layout_width="match_parent"
        android:layout_height="72dp"
        android:orientation="vertical"
        tools:background="@color/light_gray">

        <!-- 正方形的图片 -->
        <androidx.appcompat.widget.AppCompatImageView
            android:id="@+id/iv_cover"
            android:layout_width="56dp"
            android:layout_height="56dp"
            android:layout_marginStart="12dp"
            android:scaleType="centerCrop"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:src="@drawable/bg_home" />

        <!-- 歌曲名字信息,与,歌曲描述信息等 -->
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toRightOf="@+id/iv_cover"
            app:layout_constraintTop_toTopOf="parent">

            <TextView
                android:id="@+id/tv_title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="12dp"
                android:textSize="18sp"
                android:textColor="@color/white"
                tools:text="title" />

            <TextView
                android:id="@+id/tv_artist"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="12dp"
                android:layout_marginTop="4dp"
                android:textSize="14sp"
                android:textColor="@color/white"
                tools:text="summary" />

        </LinearLayout>

        <!-- 右的图标,播放的状态图标哦 -->
        <net.steamcrafted.materialiconlib.MaterialIconView
            android:id="@+id/iv_play_status"
            android:layout_width="36dp"
            android:layout_height="36dp"
            android:layout_marginEnd="12dp"
            android:background="?attr/selectableItemBackgroundBorderless"
            android:scaleType="center"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:materialIcon="music_note"
            app:materialIconColor="@color/gray"
            app:materialIconSize="28dp" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

接下来,我们来完善下『网络请求』模块 HttpRequestManager

kotlin 复制代码
object HttpRequestManager : ILoadRequest, IRemoteRequest {

    // 仓库:liveData: MutableLiveData<TestAlbum>?  已经和  Request VM 的 LiveData是 同一份了
    override fun getFreeMusic(liveData: MutableLiveData<TestAlbum>?) {
        val gson = Gson()
        val type = object : TypeToken<TestAlbum?>(){}.type
        // 这里模拟网络请求了,加载了本地的一个 json 文件,后面有条件的可以修改成实际的网络请求;
        val testAlbum =
            gson.fromJson<TestAlbum>(Utils.getApp().getString(R.string.free_music_json), type)
        liveData?.value = testAlbum
    }

    override fun getLibraryInfo(liveData: MutableLiveData<List<LibraryInfo>>?) {
        val gson = Gson()
        val type = object : TypeToken<List<LibraryInfo?>?>() {}.type
        // 这里模拟网络请求了,加载了本地的一个 json 文件,后面有条件的可以修改成实际的网络请求;
        val list =
            gson.fromJson<List<LibraryInfo?>>(Utils.getApp().getString(R.string.library_json), type)
        liveData?.value = list as List<LibraryInfo>?
    }
}

我们还差一项需要进行完善,点击事件的 BindingAdapter

less 复制代码
object CommonBindingAdapter {

    @JvmStatic
    @BindingAdapter(value = ["onClickWithDebouncing"], requireAll = false)
    fun onClickWithDebouncing(view: View?, clickListener: View.OnClickListener?) {
        ClickUtils.applySingleDebouncing(view, clickListener)
    }
}

到这,首页的音乐列表,我们就填充完了;

欢迎三连


来都来了,点个关注,点个赞吧,你的支持是我最大的动力~

相关推荐
雨白2 天前
Hilt 入门指南:从 DI 原理到核心用法
android·android jetpack
我命由我123452 天前
Android 开发 - Android JNI 开发关键要点
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
alexhilton3 天前
在Jetpack Compose中创建CRT屏幕效果
android·kotlin·android jetpack
峰哥的Android进阶之路3 天前
viewModel机制及原理总结
android jetpack
我命由我123454 天前
Android WebView - loadUrl 方法的长度限制
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
Coffeeee4 天前
面试被问到Compose的副作用不会,只怪我没好好学
android·kotlin·android jetpack
Frank_HarmonyOS7 天前
Android APP 的压力测试与优化
android jetpack
QING6188 天前
Jetpack Compose 条件布局与 Layout 内在测量详解
android·kotlin·android jetpack
Lei活在当下8 天前
【现代 Android APP 架构】09. 聊一聊依赖注入在 Android 开发中的应用
java·架构·android jetpack
bqliang9 天前
Jetpack Navigation 3:领航未来
android·android studio·android jetpack