Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据 home界面适配: 获得首页网络数据:

主界面tab切换:

添加点击事件:

接下来需要处理一下主界面TAB的切换了,这里先添加BottomBar的监听事件:

class MainActivity : BaseActivity(), ToolBarManager {

    override fun getLayoutId(): Int {
        return R.layout.activity_main
    }

    override val toolbar by lazy {
        println("MainActivity layz initMainToolBar()")
        find<Toolbar>(R.id.toolbar)
    }

    override fun initData() {
        super.initData()
        initMainToolBar()
    }

    override fun initListeners() {
        super.initListeners()
        bottomBar.setOnTabSelectListener {
            when (it) {
                R.id.tab_home -> toast("首页")
                R.id.tab_mv -> toast("MV")
                R.id.tab_vbang -> toast("v榜")
                R.id.tab_yuedan -> toast("悦单")
            }
        }
    }
}

其中it代表当前选中的是第几个TAB,运行一下:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

准备四个Fragment:

接下来则需要处理Fragment的切换,先准备四个Fragment:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

package com.kotlin.musicplayer.ui.fragment

import android.graphics.Color
import android.view.Gravity
import android.view.View
import android.widget.TextView
import com.kotlin.musicplayer.base.BaseFragment

/**
 * 首页
 */
class HomeFragment : BaseFragment() {
    override fun initView(): View? {
        val textView = TextView(context)
        textView.gravity = Gravity.CENTER
        textView.setTextColor(Color.RED)
        textView.text = javaClass.simpleName
        return textView
    }

}

其中为啥TextView构造时可以直接写context呢?这里再来复习一下Kotlin的语法:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

点击进去会定位到Fragment的一个方法:
Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

关于这块的细节可以参考:https://www.cnblogs.com/webor2006/p/11218167.html,好,其它三个依葫芦画瓢:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

处理Fragment切换显示:

接下来则来切换Fragment,先来对Fragment的实例的生成封装一下:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

很显然应该将它设计成一个单例,好,此时对于Kotlin的单例写法又可以来复习一下了,跟Java写法一样,先将构造方法私有化:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

在Kotlin中想要将其变为单例就可以使用伴生对象,关于它可以参考:https://www.cnblogs.com/webor2006/p/11210181.html, 如下:

package com.kotlin.musicplayer.utils

/**
 * 管理Fragment的util类
 */
class FragmentUtil private constructor() {
    companion object {
        val fragmentUtil by lazy { FragmentUtil() }
    }
}

其中by lazy再来复习一下,在之前https://www.cnblogs.com/webor2006/p/12637282.html已经用过一次了:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

其中by lazy也是线程安全的,所以就已经是线程安全的单例了,接下来里面封装一个函数来根据tabId来生成对应的Fragment,如下:

/**
 * 管理Fragment的util类
 */
class FragmentUtil private constructor() {

    val homeFragment by lazy { HomeFragment() }
    val mvFragment by lazy { MvFragment() }
    val vbangFragment by lazy { VBangFragment() }
    val yueDanFragment by lazy { YueDanFragment() }

    companion object {
        val fragmentUtil by lazy { FragmentUtil() }
    }

    fun getFragemnt(tabId: Int): BaseFragment? {
        when (tabId) {
            R.id.tab_home -> return homeFragment
            R.id.tab_mv -> return mvFragment
            R.id.tab_vbang -> return vbangFragment
            R.id.tab_yuedan -> return yueDanFragment
        }
        return null
    }
}

同样对于每一个Fragment实例的生成也采用by lazy,好接下来则就可以调用了:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

报错了。。看一下啥错:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

其实这个提示不是很准确,确实是由于非空的问题,由于我们封装的这个getFragment有可能为null,而看一下replace()中的这个参数:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

所以此时用alt+enter看一下IDE的修复提示:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

试一下第二个:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

ok了,这时运行看一下:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

那。。这个“!!”是啥语法来着,有点忘了,参考一个这篇https://blog.csdn.net/wuditwj/article/details/84302715所说,它表示如果为空则直接会抛异常,跟?还不一样。

准备主布局:

接下来则先来处理HomeFragment, 先来看一下预期的效果长啥样:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

很明显是一个列表,支持下拉刷新,上拉加载,所以先来准备一下布局:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

准备列表Item布局:

然后再来准备列表显示的Item:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据: 

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

然后需要覆写相印的构造方法,这种有啥可提的,是因为在Kt中要通过IDE的提示来生成构造方法跟Java中操作有些区别,所以拿出来提一下这个细节,直接按alt+enter提示再覆写不就可以了,是的,看一下是否可以:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

完全木有,要覆写构造得这样弄:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

package com.kotlin.musicplayer.ui.widget

import android.content.Context
import android.util.AttributeSet
import android.widget.RelativeLayout

class HomeItemView : RelativeLayout {
    constructor(context: Context?) : super(context)
    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    )
}

好,接下来则需要对它进行布局的关联,这里可以在初始化方法中来写:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

其中item_home就是用的一个卡片布局,布局这块不多说:

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:cardCornerRadius="5dp"
    app:cardElevation="5dp"
    app:cardUseCompatPadding="true">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="300dp"
        android:padding="10dp">

        <ImageView
            android:id="@+id/img_bg"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@mipmap/default_splash_bg"
            android:scaleType="centerCrop" />

        <ImageView
            android:id="@+id/image"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_margin="10dp"
            android:src="@mipmap/home_page_live" />

        <TextView
            android:id="@+id/tv_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_toRightOf="@id/image"
            android:maxLines="1"
            android:text="歌单"
            android:textColor="#00ff00"
            android:textSize="25sp" />

        <TextView
            android:id="@+id/tv_desc"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_above="@id/tv_title"
            android:layout_toRightOf="@id/image"
            android:maxLines="1"
            android:text="简介"
            android:textColor="#fff"
            android:textSize="20sp" />
    </RelativeLayout>
</androidx.cardview.widget.CardView>

用到了一张新的资源图:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

在继续往下编写之前,这里又得来复习一个知识点,对于Kotlin中的构造分为primary构造方法和secondary构造方法,具体可以参考https://www.cnblogs.com/webor2006/p/11197734.html:回到咱们代码来:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

而对于初始化我们是在init代码块中写的,它是不管主构造还是次构造方法都会调用的,下面用程序来试验一下:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

那,对于主构和次构造来说,还有这么一个区别,就是说在init代码块中是可以直接访问主构造中的参数的,而对于次构造是不能直接访问,啥意思,看下代码:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

要想访问次构造的参数,则需要用成员变量给接一下,如下:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

创建Adapter:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

package com.kotlin.musicplayer.adapter

import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.kotlin.musicplayer.ui.widget.HomeItemView

/**
 * 首页列表Adapter
 */
class HomeAdapter : RecyclerView.Adapter<HomeAdapter.HomeHolder>() {
    class HomeHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeHolder {
        return HomeHolder(HomeItemView(parent?.context))
    }

    override fun getItemCount(): Int {
        return 20
    }

    override fun onBindViewHolder(holder: HomeHolder, position: Int) {
        TODO("Not yet implemented")
    }
}

绑定数据:

在写HomeFragment绑定数据代码之前,先来修改一下BaseFragment,有几个protected方法木有声明open,在子类中无法重写:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

然后绑定Adapter到RecycleView上,体现一下Kotlin的写法,明显比Java的要清爽:

package com.kotlin.musicplayer.ui.fragment

import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import com.kotlin.musicplayer.R
import com.kotlin.musicplayer.adapter.HomeAdapter
import com.kotlin.musicplayer.base.BaseFragment
import kotlinx.android.synthetic.main.fragment_home.*

/**
 * 首页
 */
class HomeFragment : BaseFragment() {
    override fun initView(): View? {
        return View.inflate(context, R.layout.fragment_home, null)
    }

    override fun initListeners() {
        super.initListeners()
        recyclerView.layoutManager = LinearLayoutManager(context)
        val adapter = HomeAdapter()
        recyclerView.adapter = adapter
    }
}

是不是很神奇,居然木有findViewById直接就可以用recyclerView这个对象。。

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

而且按ctrl点击一下则会自动链到布局中相应的View上来,如下:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

也就是对应在布局中声明的ViewID,666,连ButterKnife都省了,可见Koltin开发效果其实比用Java是高多了,当然前提是在建立在你会熟练的使用Koltin的前提之下的,所以这也是写这个项目的目标,好,回到正题,运行看一下效果:

居然报错了:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

定位一下代码:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

这里又是一个跟Java不一样的地方啦,对于平常我们写的TODO在Kotlin中如果不去掉是直接会报错的,意思是这块就是你忘掉的正常逻辑,所以要想不报错将此TODO去掉既可:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

再运行: 

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

获得首页网络数据:

接下来则来编写首页接口的请求了,这里先采用纯Okhttp库来,对于Retrofit的配合在未来再来加入。

添加Okhttp的依赖:

这里集成最新版本:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

请求接口:

这里将请求地址封装到一个工具类中:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

package com.kotlin.musicplayer.utils;

import android.util.Log;

public class URLProviderUtils {

    /**
     * 获取首页的url
     *
     * @param offset 数据偏移量
     * @param size   返回数据的条目个数
     * @return url
     */
    public static String getHomeUrl(int offset, int size) {
        String url = "http://tingapi.ting.baidu.com/v1/restserver/ting?from=android&version=5.6.5.0&method=baidu.ting.artist.getSongList&format=json&order=2&tinguid=7988&artistid=7988"
                + "&offset=" + offset
                + "&limits=" + size;
        Log.i("Main_url", url);
        return url;
    }
}

其中音乐地址是在网上找的,然后接下来请求一下:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

其中在Koltin是用object来进行回调处理的,添加网络权限:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

运行:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据: 

这里先给个Toast提示一下:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

咱们的toast是做UI线程的处理了的:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

运行发现请求数据报403了。。

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

其实这里需要在请求时增加一个user-agent才行,如下:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

然后自定义一下Application:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

再运行,数据就可以正常的拉取下来了:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

另外发现请求了两遍,其实是BaseFragment封装得有问题:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据:

这里将其区分一下:

Kotlin项目实战之手机影音---主界面tab切换、home界面适配、获得首页网络数据
home界面适配:
获得首页网络数据: 

再运行就只会请求一次了:

2020-05-19 15:27:19.474 29816-29816/com.kotlin.musicplayer I/System.out: ToolBarManager.initMainToolBar()
2020-05-19 15:27:19.474 29816-29816/com.kotlin.musicplayer I/System.out: MainActivity layz initMainToolBar()
2020-05-19 15:27:20.214 29816-29889/com.kotlin.musicplayer I/System.out: 获取数据成功:{"songlist":[{"artist_id":"19165","all_artist_ting_uid":"7988,340525851,340528104,340528105","all_artist_id":"19165,664639518,664750681,664750682","language":"u82f1u8bed","publishtime":"2018-12-31","album_no":"16","versions":"","pic_big":"http://qukufile2.qianqian.com/data2/pic/39006488e5d44cba364d9104c49eedbf/664751367/664751367.jpg@s_2,w_150,h_150","pic_small":"http://qukufile2.qianqian.com/data2/pic/39006488e5d44cba364d9104c49eedbf/664751367/664751367.jpg@s_2,w_90,h_90","country":"u6b27u7f8e","area":"2","lrclink":"","hot":"0","file_duration":"541","del_status":"0","resource_type":"0","resource_type_ext":"2","copy_type":"1","relate_status":"0","all_rate":"96,224,128,320,flac","has_mv_mobile":0,"toneid":"0","bitrate_fee":"{"0":"129|-1","1":"-1|-1"}","biaoshi":"lossless,vip,perm-1","info":"","has_filmtv":"0","si_proxycompany":"u770bu89c1u7f51u7edcu79d1u6280uff08u4e0au6d77uff09u6709u9650u516cu53f8","song_id":"664750725","title":"The Psychedelic Interlude","ting_uid":"7988","author":"Damage,Deep N Beeper,Deep N Beeper feat. Damage & Bobby Dangler,Bobby Dangler","album_id":"664750684","album_title":"Submerged","is_first_publish":0,"havehigh":2,"charge":0,"has_mv":0,"learn":0,"song_source":"web","piao_id":"0","korean_bb_song":"0","mv_provider":"0000000000","listen_total":"0","pic_radio":"http://qukufile2.qianqian.com/data2/pic/39006488e5d44cba364d9104c49eedbf/664751367/664751367.jpg@s_2,w_300,h_300","pic_s500":"http://qukufile2.qianqian.com/data2/pic/39006488e5d44cba364d9104c49eedbf/664751367/664751367.jpg@s_2,w_500,h_500","pic_premium":"http://qukufile2.qianqian.com/data2/pic/39006488e5d44cba364d9104c49eedbf/664751367/664751367.jpg@s_2,w_500,h_500","pic_huge":"http://qukufile2.qianqian.com/data2/pic/39006488e5d44cba364d9104c49eedbf/664751367/664751367.jpg@s_2,w_1000,h_1000","album_500_500":"http://qukufile2.qianqian.com/data2/pic/39006488e5d44cba364d9104c49eedbf/664751367/664751367.jpg@s_2,w_500,h_500","album_800_800":"","album_1000_1000":"http://qukufile2.qianqian.com/data2/pic/39006488e5d44cba364d9104c49eedbf/664751367/664751367.jpg@s_2,w_1000,h_1000"},{"artist_id":"19165","all_artist_ting_uid":"7988,340525851,340528094,212302,340528100,340528101","all_artist_id":"19165,664639518,664750671,7330727,664750677,664750678","language":"u82f1u8bed","publishtime":"2018-12-31","album_no":"6","versions":"","pic_big":"http://qukufile2.qianqian.com/data2/pic/39006488e5d44cba364d9104c49eedbf/664751367/664751367.jpg@s_2,w_150,h_150","pic_small":"http://qukufile2.qianqian.com/data2/pic/39006488e5d44cba364d9104c49eedbf/664751367/664751367.jpg@s_2,w_90,h_90","country":"u6b27u7f8e","area":"2","lrclink":"","hot":"0","file_duration":"360","del_status":"0","resource_type":"0","resource_type_ext":"2","copy_type":"1","relate_status":"0","all_rate":"96,224,128,320,flac","has_mv_mobile":0,"toneid":"0","bitrate_fee":"{"0":"129|-1","1":"-1|-1"}","biaoshi":"lossless,vip,perm-1","info":"","has_filmtv":"0","si_proxycompany":"u770bu89c1u7f51u7edcu79d1u6280uff08u4e0au6d77uff09u6709u9650u516cu53f8","song_id":"664750715","title":"Collateral Damage","ting_uid":"7988","author":"Damage,Deep N Beeper,Onepacman,JT,Deep N Beeper feat. Damage, JT, Onepacman & The Paranaut,The Paranaut","album_id":"664750684","album_title":"Submerged","is_first_publish":0,"havehigh":2,"charge":0,"has_mv":0,"learn":0,"song_source":"web","piao_id":"0","korean_bb_song":"0","mv_provider":"0000000000","listen_total":"0","pic_radio":"http://qukufile2.qianqian.com/data2/pic/39006488e5d44cba364d9104c49eedbf/664751367/664751367.jpg@s_2,w_300,h_300","pic_s500":"http://qukufile2.qianqian.com/data2/pic/39006488e5d44cba364d9104c49eedbf/664751367/664751367.jpg@s_2,w_500,h_500","pic_premium":"http://qukufile2.qianqian.com/data2/pic/39006488e5d44cba364d9104c49eedbf/664751367/664751367.jpg@s_2,w_500,h_500","pic_huge":"http://qukufile2.qianqian.com/data2/pic/39006488e5d44cba364d9104c49eedbf/6