主界面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,运行一下:
准备四个Fragment:
接下来则需要处理Fragment的切换,先准备四个Fragment:
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的语法:
点击进去会定位到Fragment的一个方法:
关于这块的细节可以参考:https://www.cnblogs.com/webor2006/p/11218167.html,好,其它三个依葫芦画瓢:
处理Fragment切换显示:
接下来则来切换Fragment,先来对Fragment的实例的生成封装一下:
很显然应该将它设计成一个单例,好,此时对于Kotlin的单例写法又可以来复习一下了,跟Java写法一样,先将构造方法私有化:
在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已经用过一次了:
其中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,好接下来则就可以调用了:
报错了。。看一下啥错:
其实这个提示不是很准确,确实是由于非空的问题,由于我们封装的这个getFragment有可能为null,而看一下replace()中的这个参数:
所以此时用alt+enter看一下IDE的修复提示:
试一下第二个:
ok了,这时运行看一下:
那。。这个“!!”是啥语法来着,有点忘了,参考一个这篇https://blog.csdn.net/wuditwj/article/details/84302715所说,它表示如果为空则直接会抛异常,跟?还不一样。
home界面适配:
准备主布局:
接下来则先来处理HomeFragment, 先来看一下预期的效果长啥样:
很明显是一个列表,支持下拉刷新,上拉加载,所以先来准备一下布局:
<?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:
然后需要覆写相印的构造方法,这种有啥可提的,是因为在Kt中要通过IDE的提示来生成构造方法跟Java中操作有些区别,所以拿出来提一下这个细节,直接按alt+enter提示再覆写不就可以了,是的,看一下是否可以:
完全木有,要覆写构造得这样弄:
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 ) }
好,接下来则需要对它进行布局的关联,这里可以在初始化方法中来写:
其中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中的构造分为primary构造方法和secondary构造方法,具体可以参考https://www.cnblogs.com/webor2006/p/11197734.html:回到咱们代码来:
而对于初始化我们是在init代码块中写的,它是不管主构造还是次构造方法都会调用的,下面用程序来试验一下:
那,对于主构和次构造来说,还有这么一个区别,就是说在init代码块中是可以直接访问主构造中的参数的,而对于次构造是不能直接访问,啥意思,看下代码:
要想访问次构造的参数,则需要用成员变量给接一下,如下:
创建Adapter:
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,在子类中无法重写:
然后绑定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这个对象。。
而且按ctrl点击一下则会自动链到布局中相应的View上来,如下:
也就是对应在布局中声明的ViewID,666,连ButterKnife都省了,可见Koltin开发效果其实比用Java是高多了,当然前提是在建立在你会熟练的使用Koltin的前提之下的,所以这也是写这个项目的目标,好,回到正题,运行看一下效果:
居然报错了:
定位一下代码:
这里又是一个跟Java不一样的地方啦,对于平常我们写的TODO在Kotlin中如果不去掉是直接会报错的,意思是这块就是你忘掉的正常逻辑,所以要想不报错将此TODO去掉既可:
再运行:
获得首页网络数据:
接下来则来编写首页接口的请求了,这里先采用纯Okhttp库来,对于Retrofit的配合在未来再来加入。
添加Okhttp的依赖:
这里集成最新版本:
请求接口:
这里将请求地址封装到一个工具类中:
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; } }
其中音乐地址是在网上找的,然后接下来请求一下:
其中在Koltin是用object来进行回调处理的,添加网络权限:
运行:
这里先给个Toast提示一下:
咱们的toast是做UI线程的处理了的:
运行发现请求数据报403了。。
其实这里需要在请求时增加一个user-agent才行,如下:
然后自定义一下Application:
再运行,数据就可以正常的拉取下来了:
另外发现请求了两遍,其实是BaseFragment封装得有问题:
这里将其区分一下:
再运行就只会请求一次了:
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