• Kotlin项目实战之手机影音---主界面tab切换、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,运行一下:

    准备四个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
  • 相关阅读:
    09-JS的事件流的概念(重点)
    08-jQuery的位置信息
    07-小米导航案例
    python-selector模块
    python--day9--异步IO、数据库、队列、缓存
    python--select多路复用socket
    python--gevent高并发socket
    python--协程
    python--进程锁、进程池
    python--多进程
  • 原文地址:https://www.cnblogs.com/webor2006/p/12909992.html
Copyright © 2020-2023  润新知