下载预览

  1. Github:https://github.com/kirikaTowa/FarawayPlayer 部分网络接口可能已过期
  2. 补充资料:MediaStore.MediaColumns 与 MediaStore.Audio.AudioColumns,从字段来看,父MediaColumn更加的全面,子AudioColumns多了id与key等字段。
    2.1 从官网我们可以看到,AudioColumns是继承自MediaColumns的,

一:boat

  1. 采用MVP框架
    该项目来自黑马程序员的视频教学。由于原网络接口不太稳定,找了新的接口及类封装方式。按照自己的喜好体系化梳理结构。本人的每次提交的gihub源码都可以在对应一级标题中跳转查看。
  2. 使用Genymotion(或直连手机)
  3. anko库
    1)封装过了一些SDK方法,使调用更加的方便toast(R.string.message)取代 Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();

implementation “org.jetbrains.anko:anko:0.10.8”`

  1. github
    1)git add .
    2)git commit -m "First Commit."
    3)git push origin master
  2. jetpack安卓官方

二:First Cmt【okhttp获取数据并显示】

1.项目结构

  1. M层:
    1)第一个Model目录目前存放了一个bean类 接收回调的网络数据
    2)第五个Util目录目前存放了一个utils
  2. V层:
    1)第二个Base目录中分别定义了Activity和Fragment基类
    2)第四个UI目录都继承自Base类
    3)第六个Widget类型,小组件模块
    a.Recycleview的基础组件
    b.一些通用组
  3. P层:
    1)第一个Adapter是做了Home页的RecycleView绑定

2.抽象类BaseActivity分析


1.onCreate三个普通方法子类可不复写
2.由于目前还不知道要传入那个layout 可写一个抽象方法子类实现通过id传 获取对应layout的id进行设置ContentView

    //创建抽象方法以供获取Layoutabstract fun getLayoutid():Int

3.对消息处理最好启用线程 toast容易报错

    protected fun myToast(msg:String){runOnUiThread{toast(msg)}}

4.由于做了入场动画效果,可以封装一个方法用于开启新活动并自动关闭当前活动(这里的写法是调用的anko库封装的Intent)
内联(reified与内联相当于具体化一个泛型T并getName)inline标记可以致辞reified获取泛型类型【面向BaseActivity子类都开放】

   inline  fun <reified T:BaseActivity>startActivityAndFinish(){startActivity<T>()finish()}

2.5.BaseFragment抽取

具有三个较为明显的父类方法

  1. onCreate(普通初始化)
    1)写一个init()方法,不一定要实现
    2)加了个debug(BACT也可以加)

  2. onCreateView
    1)用于实现静态布局初始化这里return了一个方法
    2)这个自己写的方法是抽象的,子类调用返回其View

  3. oncreateActivity方法,
    1)调用 initListener()和 initData()进行动态数据绑定
    2)最后封装一个Mytoast方法处理线程问题

3.入场动画分析

1.首先继承了Base方法 这里就不复写oncreate方法,复写getlayoutid方法传入动画layout,配置全屏动画并且设置无ActionBar 追加全屏显示

    <!-- Base application theme. --><style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"><!-- Customize your theme here. --><item name="colorPrimary">@color/colorPrimary</item><item name="colorPrimaryDark">@color/colorPrimaryDark</item><item name="colorAccent">@color/colorAccent</item></style><style name="AppTheme.FullScreen"><item name="windowNoTitle">true</item><item name="android:windowFullscreen">true</item></style><style name="pop"><item name="android:windowEnterAnimation">@anim/pop_enter</item><item name="android:windowExitAnimation">@anim/pop_exit</item></style>
 <activityandroid:name=".ui.activity.SplashActivity"android:theme="@style/AppTheme.FullScreen">

2.复写initdatas方法,利用ViewCompat的静态方法进行数据拉伸回来及持续时间(layout设置放大为1.5倍
ViewCompat.animate(imageView).scaleX(1.0f).scaleY(1.0f).setListener(this).setDuration(2000)(这里的imageview是)

3.ViewPropertyAnimatorListener继承接口回调进入主函数,销毁动画界面

4.主界面布局分析

  1. 首先观察需求
    1)顶部是一个ToolBar文件
    2)底部是一个bottombar组件
    3)中间用了权重为1的FrameLayout容器 【bottombar组件要用】
  2. 实现布局方法
    1)写一个xml的layout文件,MLayout include就行
    <include layout="@layout/toolbar"/>
    2)底部使用Bottom组件并指定tabs.xml

implementation ‘com.roughike:bottom-bar:2.3.1’

  1. 主界面动态适配ToolBar(由于需求三个组件都是动态交互的,所以不能写成静态的)
    1)M层Utils包下建立ToolBarManager接口用于管理类
    a.新建一个toolbar对象
    b.写一个初始化Main主页标题信息
    kotlin接口是可以写实现的,java不行
    2)MACT中实现接口并继承toolbar对象并惰性加载【确保线程安全和初始化时toolbar时才执行(findviewbyid)】布局toolbar对象,再直接实现无参方法即可(复写在initdata方法里)
override val toolbar by lazy{find<Toolbar>(R.id.tb_toolbar)}
//初始化toolbar查看效果override fun initDate() {initMainToolBar()}
  1. 为ToolBar添加Manu
    1)Menu文件夹中写一个文件menu
    2)ToolBar初始化Main主页方法中通过inflate动态R.id.menu生成View
    3)设置菜单监听
    a.先复写监听ToolBar
    b.再监听具体Item内容
    c.点击跳转到设置界面(相比于传统写法,这种写法很有意思)
    4)java中若接口的方法只有一个,可以直接省去实现接口参数,直接复写方法就好了
 toolbar.setOnMenuItemClickListener
//也可以
//a->//构造a进行捕捉it 下面取值$aprintln("item=$it")//it是隐含的选中MenuItemtoolbar.context.startActivity(Intent(toolbar.context,SettingActivity::class.java))true}
toolbar.setOnMenuItemClickListener(object : Toolbar.OnMenuItemClickListener{override fun onMenuItemClick(item: MenuItem?): Boolean {when(item?.itemId){R.id.setting->  {toolbar.context.startActivity(Intent(toolbar.context,SettingActivity::class.java))}return true;}
  1. 为新建的设置动态添加项目
    1)layout和toolbar的操作是一样的
    2)区别在于初始化时initdata时除了加载settingtoolbar显示,还需加载新的控件(按钮控件)
    3)运用PreferenceFragment或PreferenceActivity组件实现,由于SettingActivity已经继承了BaseActivity,所以这里使用Fragment
  2. 创建SettingFragment继承PreferenceFragment
    1)既然是Fragment显示需要onCreateView方法内部
    addPreferencesFromResource(R.xml.setting)设定该Fragment的布局
    2)设定setting文件层级目录
    a.PreferenceScreen
    b.Preference
    c.SwitchPreferen
  3. 将装配好的Fragment引入activity_setting布局中
    借助<Fragment标签,在initdata中
    1)通过SharePreference对象保存
    2)通过该对象的getBoolean进行选中判断
  4. 类似的 主界面的BottomBar切换
  5. MACT中initlistener中设置bottombar监听(同样含it,进行Container替换)
  6. M层Utils包下设置一个FragmentUtils切换类,根据id获得相应的Fragment
    Setting中只有一个布局可以直接layout设定,而MainLayout是根据选择replace的,要设置对应的Fragment进行替换

5.建立对应的Fragment与对应的xml文件

  1. 将要装填条目,所以布局会用到RecycleView(大)
  2. 必然建立一个adapter进行适配(小条目适配)
    1)adapter三个方法
    2)onBindViewHolder绑定的布局中条目较多时,widget封装类HomeItemView继承RleativeLayout,复写三个相关构造方法,定义次构造器可以不写方法体,无{},但要调主构造方法,这样不论调什么方法都会执行init{}
    3)init初始化,inflate动态加载一个布局(item_home.xml)(小条目界面)用了卡片布局加载,看起来挺舒服的

implementation ‘androidx.cardview:cardview:1.0.0’

6.发送网络请求(使用okhttp自封框架)

安卓8.0需要对http类型特殊处理,https正常方法1
e.printStackTrace()和log调试很好用,e.printStackTrace()可能会造成死锁

compile ‘com.squareup.okhttp3:okhttp:3.8.1’

  1. 既然是获取数据,HomeFragment就继承父类的initData(),写一个方法(loadDatas)专门调用获取网络数据功能填loadData()
  2. okhttp的使用
    1)需要一个路径
    2)需要一个okhttpclient对象
    3)request请求,将path传入
    4)通过client发送request并进行结果回调
    a.成功的回调
    a1)拿到string数据val result = response?.body()?.string()
    b2)postman跑接口,观察xml还是json格式,转换后写一个类用于对应储存
    b.失败的回调(提示交互)
  3. 拿到数据,Homeadapter进行界面更新
    1)定义list泛型<>
    2)更新,clean,addall,刷新
    3)Android界面的更新只能在主线程总进行,所有在HomeFragment的成功回调中还得使用handler或runOnMainThread
    4)Util包下封装一个方法ThreadUtil静态类(object),写一个方法运行主线程即可
    5)回到HFment中,静态调用主线程方法,复写run方法,内部使用之前适配惰性加载的adapter对象进行 adapter类的内部刷新
    6)来到adapter刷新页面,在onBindViewHolder方法中根据位置进行条目的具体信息通过holder.itemview绑定
    a)拿到数据
    b)拿到itemview
    7)接下来有个是数据对应取出与装载,由于之前写了homeview的类,这边就可以在里面调用赋值了
    a.Title
    b.author
    c.image
    8)通过Picasso来将获取到的图片(http流式图片)转化为正常图片显示

implementation ‘com.squareup.picasso:picasso:2.5.2’

7.下拉刷新与上拉加载更多

  1. 现在用的接口默认加载10条数据,type为起始页。
  2. 使用刷新控件,google自带,加入fragment_homeSwipeRefreshLayout 将Recycleview包裹住,下拉就有效果了。
    1)refreshLayout.setColorSchemeColors(Color.RED, Color.YELLOW, Color.GREEN)放在fragment的initlistener里动态监听刷新状态颜色。
    2)刷新监听,由于要初始化数据,所有监听内部调用 Loaddatas()方法和初始化一样。
    3)不管成功失败都要设置刷新控件隐藏refreshLayout.isRefreshing=false 回调一定要用之前封装好的Util中主线程运行类 动态改变View在子线程会报错
  3. 上拉加载更多
    1)view显示,在最后一条记录后,用那个圆形转圈圈进度条好看
    a.条目数量可以在adapter的getcount方法中获得
    b.鉴于这个进度条其他也可能用到,可以封装到widget中
    2)新建Loadmoreviw继承自RelativeLayout,inflate一个布局,该布局内是一个<ProgressBar
    a.回到homeadapter的onbindview中,复习第四个方法getItemViewType进行boolean的判断是否到了最后一条
    b.这个return的值是onCreateViewHolder()中的viewtype,内部写一个when或者 if判断调用loadmore或原本的Homeitemview进行条目显示
    c.到了条目显示这一步,自然会联动数据的装载onBindViewHolder()中,加一个判断,是最后一条就不装载,就不用调用方法进行数据绑定了
    3)回到,通过okhttp加载更多数据HomeFragment中,initlistener监听列表滑动,复写滑动状态改变的方法有两种,这边使用只触发一次的onScrollStateChanged(其中滚动状态有三种)
    4)Recycleview中只有linnerLayout才会有列表的最后一条概念,在列表处于空闲状态监听,判断最后的条目是否为最终可见条目,是的话就要调用loadmore()方法进行more数据加载。
    a.loadmore()和loaddatas(),内部方法差不多,只不过改变type的量级
    b.到了的刷新列表,loaddatas()中的update()方法内要先clear()堆栈,但loadmore()是绝对不应该clean,可以再写一个loadmore()方法调用,内部addall(list)与notifyDataSetChanged()即可。

二:[Second Cmt]首页MVP封装

Home界面的后端VP分离

  1. View包下创建HomeView接口,负责home界面和presenter交互,HomeFragment继承接口并重写方法。
    1)onError(一个错误信息)
    2)loadSuccess(拿到list数据)
    3)loadmore(拿到list数据)

  2. 接下来在presenter包下写一个接口HomePresenter控制数据流 供实现类HomePresenterImpl调用(一个接口一个实现类,明确模块)
    1)该接口定义了loaddata和loadmore方法,供实现类继承
    2)HomePresenterImpl实现类继承后,将HomeFragment的两个方法拿过来复写,HF调用新建一个实现类对象调用就好了。由于这是p层,加载成功失败的消息要UiI更新
    3)V层消息更新,之前的HF继承homeview,在实现类中加主构造方法var一个新HomeVIiew对象,进行实现类与HF绑定。HF中复写了三个方法,利用该对象完成界面的更新。

三:[Third Cmt]网络架构的封装,抽取重复代码

网络架构的抽取封装,MVP框架中,网络操作是在Model层中的,【Second Cmt】中网络操作处于实现类P层,所以要进行抽取,loadmore和loaddata中有大量重复的代码

参照Volley的结构

  1. 每个网络请求都是一个request
    1)包含一个请求方式
    2)一个请求地址
    3)结果的回调
  2. 定义一个专门的 网络请求类进行add请求

2.创建net

  1. 定义所有请求的基类MRequest
    1)主构造器
    a.一个url地址
    b. 一个接口对象回调
  2. 创建responseHandler接口
    1)定义了成功和失败方法
    2)失败一个反馈,而成功每次返回的结果不一样,指定一个泛型
    3)由于每次请求的封装类不同,MRequest也指定一个泛型
    open class MRequest<RESPONSE>(val url: String,val handler: ResponseHandler<RESPONSE>)
  3. 刚刚提到了一个管理类 现在来实现以下
    1)创建NetManage类,发送网络请求,由于每次发送请求都要用到这个对象,可以做成单例模式,创建伴生对象并且惰性加载
    2)定义一个发送的方法sendRequest方法,并定义泛型,参数写MRequst
    fun <RESPONSE>sendRequest(req:MRequest<RESPONSE>)
    a.将【FST CMT】6.2的okhttp内容【SCD CMT】封装在Impl里移过来
    b.去除val path,基类已经定义了,实现类传一下就好了(req.url)
    c.okhttpclient创建一次就好了,拿出来放外面,惰性加载一下
    3)更改回调方式主构造器定义了ResponseHandler进行回调
    a. 获取失败时的:homeView.onError(e?.message)改为 req.handler.onError(e?.message)
    b1.请求成功时Gson这边需要更改,原本类型写死,这边得用泛型,说到泛型,就刚好可以放到MRequest基类中,需求每个界面都要对应的request,那一定也有对应的类型。写一个parseResult方法··········,调用之后返回一个解析后的集合,储存就行了
    b2.MRequest中首先要获取泛型类型【翻译成中文大白话就知道怎么实现的了】
    val type=(this.javaClass.genericSuperclass as ParameterizedType).getActualTypeArguments()[0]
    b3. 原成功回调homeView.loadMore(userBeanList)改为req.handler.onSuccess(parseResult)
  4. 创建实现类HomeRequest,这种写法很好用(,)里面的两个参数,一个int,一个handler接口对象,继承主类时(,)利用两个参数进行初始化主构造器
    例如:class HomeRequest(type:Int,handler: ResponseHandler<HomeItemBeanSuccess>) :MRequest<HomeItemBeanSuccess>(YWJHURLProviderUtils.getHomeUrl(type),handler) { }
  5. 回到HomePresenterImpl,okhttp框架被拿走此刻调用就行了,loaddata和loadmore就type参数不一样。
    a. 定义request,第二个参数,定义匿名内部类(这是真的好用)
    a1.匿名内部类实现onError
    a2.匿名内部类实现onSuccess
    a3.不知道大家还记不记得咱们网络框架原本调用的homeview被req的方法取代了,现在复写方法,调用homeview一波搞定。
    a4.值得一提的是咱们换了接口拿到的是大类,这边传的是list,所以回调的resule要getresult,将我们装好list的每条数据类加载进来
    b.网络请求管理类发送request(静态调用)
  6. 进一步简化
    1)MRequest定义一个方法。内部发送获取this(req)IMPL初始化时直接调用就可以了
    2)IMPL中的loaddatas和loadmore,回调接口方面是重合的,第二个参数匿名内部类全部用this替换,通过当前类实现接口就好了。
    3)类实现回调是没问题,但是成功回调要判断是初始化还是加载更多
    a.解决方法就是将他们分别写个标识符,随方法一起传过去
    b.NetManager中回调回来就行了(ps 发现基类主构造器val了之后 ,后面的不复写直接用就可以了)
    c.这个时候我们发现这两个常量也可以放到Presenter接口中,但这个kotlin不能直接定义常量,还是用到伴生对象,impl实现静态调用即可
    d.其实直接传1,2也行。不过这样写更安全
**  val request=HomeRequest(1,object :ResponseHandler<HomeItemBeanSuccess>{//实现两个方法override fun onError(msg: String?) {//netmanage中处理过线程问题 所以处理时都是在主线程中homeView.onError(msg)}override fun onSuccess(result: HomeItemBeanSuccess) {homeView.loadMore(result.getResult())}}).execute()//匿名内部类**

四:[Fourth Cmt]悦单Fragment封装

1.通过网络架构的封装和基类的抽取后,到这步已经非常简单了,拿接口就行了,值得一提的是kotlin的很多地方用习惯了真的比java更简洁舒服。

  1. 这次的数据bean类层数很多,泛型定义注意,在获取request阶段传第一层bean类,接收对应的信息
  2. 成功后,adapter和实现View拿数据该拿啥型写啥型
    提到了response有很多层,这次内部封装了list在第三层,所以View中也不必直接用list装起来1.(第一层bean类,浪费空间,也没必要过早的取出来,费时费力 2.省去了实现类与Fragment的写法问题和麻烦)
  3. 补充接口信息:
    1)limit限制条目数
    2)offset偏移量,从第几条开始
    a)loadmore首次传参 设定limt=3 而我们接口返回数据下标是从(offset=0)开始的 返回三条数据,(0,1,2 那么下次应该为3 ) 打印当前adapter.itemCount=4(这个是从1开始,而且适配了多一条进度条)算上进度条是第四条,所以这种写法presenter.LoadMore(adapter.itemCount-1)

userBeanList: List<YueDanBean.ResultBean.PlaylistsBean>?改为 response: YueDanBean?

2.梳理结构

  1. 首先的YueDanFragment继承了BaseFragment与HomeView(都是三方法)
    1)继承了BaseFragment复写方法 //初始绑定
    a)initView()//初始化layout
    b)initData()//利用p层初始化数据
    c)initListener()//监听上拉与下拉滚动条,刷新与加载更多(通过presenter实现类对象)
    2)继承乐单View复写方法//结合RecycleView动态更新
    a) onError()
    b) onloadSuccess()//上拉刷新(1c中有监听 对应recycleview的监听换了写法,可以有上下文自动推断类型)
    c) loadMore()//下拉加载更多
  2. P层实现类
    1) 调用M层网络请求的发送以及实现回调
    2)回调结果中调用YuedanView接口,HomeFragment负责继承和实现

五:[Fifth Cmt]基类再次提取封装

1.观察两个Fragment的共同点

1.view
1)列表
2)下拉刷新
3)上拉加载更多
2.data
1)初始化数据
2)刷新数据
3)加载个更多数据
3.抽取 首页 乐单 定义基类将其实现界面代码拷贝进去,从基类角度分析哪些在基类保留,那些在子类实现

2.开始

基类抽取 ,还能实现显示样式的统一修改

HomeView->BaseView
Adapter->BaselistAdapter
Presenter->BaselistPresenter

  1. base包下加了BaseListFragment类,用于所有具有下拉刷新与上拉加载更多列表的基类。把HomeFragment的代码全考过来,那个HomeView要让子类实现,用个泛型,然后loadsuccess和loadmore也用泛型

  2. base包下加了接口BaseView<泛型>定义
    a)把三个方法考过来(上拉刷新和下拉加载更多列表界面的基类)
    b)将loadMore和loadSuccess内部定义该泛型类,接口不用var出来
    c)原本两个接口直接把内部实现方法去掉,继承BaseView传入自己对应的bean类

  3. base包下加了接口BaseListPresenter定义
    1)发现两个实现类的presenter接口内容是一样的
    a.一个伴随判断标识,两个接口方法,之前写的解绑ondestory放这里面ok
    b.原两个接口也不用删,继承一下就好了

  4. 定义了BaseListAdapter方法(所有下拉刷新和上拉加载更多的基类)
    1)子类代码考过来
    a.改变adapter和holder名称
    b.加泛型<>
    b1)Bean类泛型
    b2)adapter泛型
    2)onbindviewholder有个setdata方法用于设置数据,但泛型又不知道具体方法
    a.写个抽象方法,让子类传个itemview和data数据
    b.create中的设定对应条目也不知道是哪个条目,写个抽象方法getitemview让子类整活(参数是一个上下文context)
    3)子类adapter实现起来就贼舒服了
    a.改写两个adapter继承这个,然后对应泛型初始化,复写两个抽象方法,搞定,美滋滋。
    5.回到BaseListView,继承写好的BaseView,再整个泛型
    a)loadSuccess自然要用到泛型,这个时候刷新就有问题了,刷新有可能是集合有可能是bean类,原本当前的泛型返回值,在updateList(getlist(response)),写getlist这一个抽象方法,让子类实现,拿到对应的list数据(地址对象转为具体参数)

6.更改原适配惰性加载的adapter,还是定义一个抽象方法获取适配器继承自BaseListadapter加个泛型取adapter,让子类实现,子类初始化柱构造器就要写出来
1)ITEMBEAN泛型
2)ITEMVIEW:View规定是View泛型
3)presenter也一样

7.改写原两个Fragment,搞定”
整着整着接口爆炸了,妈耶okhttp返回错误-460,okhttp和浏览器没问题,这。。。。百度一堆花里胡哨的都没用,最后贴吧老哥给的这个搞定了,关于网景具体故事的走这边传送门
ps:返回有点区别,原homebean java写的,实现类返回的时候用了getresult方法,打印成功的返回值是数据,而yuedan直接返回对象
影响结果:home的getlist直接返回就行,而乐单的要返回这个对象的list数据。

.addHeader(“User-Agent”,“Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:0.9.4)”)

六:[sixth Cmt]MV适配

1.android.support.design.widget.TableLayout不自动跳出这个控件。。
result.data?.list原bean类有tostring自动会调用,没tostring会调用地址

1:需求分析,MV和榜单有所区别,MV是有分类的,根据选择的分类切换对应的V榜,使以上层列表条目(TabLayout控件),下层页面条目(ViewPager,控制子Fragment:ViewPagerFragment)

  1. 建立MVP架构(与前两个不一样)
    1)建立MvFragment(内部的layout套娃包含上下) 复写初始三方法,initdata中利用实现类获取网络数据
    2)创建MvVeiw,之后p层写接口和实现类,Fragment继承BF和MView
    3)Fragment惰性加载并实现加载数据成功/失败接口,loaddatas()调用实现类
    4)实现类利用定义并修改网络请求发送数据并执行网络请求成功/失败回调(mvView,创建对应的成功,失败方法),回到3继承的MvFragment复写方
    a.网络请求不需要typt
    b.列表的URl不需要传参
    5)创建MvPagerAdapter,继承FragmentPagerAdapter,要做切换,实现一参构造方法,复写三个方法
    a.getItemFragment传参不能用构造方法

a11)这里新建个ViewPagerFragment****继承BaseFragment,然后return这个对象(动态text用作切换测试,如果直接return viewFragmetn那会报错,套娃把自己套进去什么鬼)
a12)当然,想加点标识 可以先新建对象,通过新建budle对(put,get/string传一个键值对)随后fragment设置arguments属性为该bundle,然后那边取出来,(和intentputextra差不多)
a21)第二种传参
a22)Fragment传一个context,然后 val fragment=Fragment.instantiate(context,MvPagerFragment::class.java.name,bundle),这种方法提高了V和P层的耦合,没使用
a23)其构造方法中加入list集合
b.getitem方法
c.getPageTitle方法
6)建立新的MvPagerFragment和对应的Fragment_mv的layout用于下面的切换

override fun initView(): View? {val tv= TextView(context)tv.gravity= Gravity.CENTERtv.setTextColor(Color.RED)tv.text=javaClass.simpleName+name//text显示的文字为类名return tv}
  1. ViewPager进行匹配
    1)回到MvFragment中,成功就回调 对FragmentPagerAdapter进行初始化(list,childfragmentmanager)在fragment中管理fragment
    2)viewPager对应的set一个返回的fragment
  2. TabLayout进行适配
    1)MvFragment中加入success方法tabLayout.setupWithViewPager(viewPager)上层监听,适配条目滚动fragment
    2)已经实现同步滚动,下面将标题显示起来,通过c的第三个方法设置Title
  3. 将每个界面的效果图展现出来,并且具有上拉刷新和下拉加载更多
    1)要将MvPagerFragment继承具有上拉刷新和下拉加载更多的类(BaseListFragment),观察YueDanFragment,看bean类,view,adapter的需求(见2)
    2)widget中创建每个条目view,xml做适配
    3)实现BLF类的4个方法,
    a. 创建第一个adapter,和乐单那个一
    b.创建listview与实现类
    c.getitem返回具体数据list
    4)实现类创建request,调用netmanage,这边传参多一个可变参数,接口问题以实现选取不同列表,发送接口的sort不同,loaddate和loadmore改写一下就好了
    5)改写MPagerPragment,要有数据交互了,继承(BaseListFragment)并且书写主构造器,实现三个方法,因为要做切换,init初始化拿到传过来的codesort数据,并且在实现类传入即可。
    6)Widget具体条目适配一下就好了
    (这个接口图片失效了)
  4. 接下来设置点击事件(RecycleView)
    1)《第一行代码》中介绍了RV没有setonItemClickListener(),我们可以自己根据需求对子项进行监听即抛去子项点击监听,点击事件右具体的View注册
    a. onCreateViewHolder中定义的holder对象,可以setonClickListener监听其内部的任意属性(例如文字或图片)
    b. 监听内部复写onClick方法即可
    2)BaseListAdapter实现
    a. 来到BaseListFragment 里面有个getSpecialAdapter(),我们发现,所有显示条目的fragment,其adapter都是这个的子类
    b.进入BaseListAdapter中,进入onBindView可以直接获取每个条目的View,(这里的holder只有一个属性view)那就给这里的itemView进行监听设置,setonClickListener()方法.
    3)回到BaseListFragment
    a. 复写initlistener方法,super.不能删,还要用父类实现进度条,接着往下写,调用2)写好的函数
    adapter.setMyLiserner
    标准写法:
//参数里面是个函数,a是里面传递的参数,就是data,返回Unitadapter.setMyListener({a->Unit}

Fragment与adapter的RycyleView点击事件都在这里
写完后,**发现a是里面**最后一个参数**,就可以去掉括号()**,又发现函数只有一个参数,我们连这个参数都可以省略不写,。。。。。日天了,还没完,使用it就是我们传递的参数,也就是adapter那个listener,设置的databean类

打一下:printlin("it=$it")ok能取到

  1. 创建VedioPlayActivity作为播放MV的活动
    1)建立layout,三个button,一个VideoView控件
  2. 修改点击内容,anko式start并且传数据(键值对)
    startActivity<VideoPlayerActivity>("item" to it)
    但未经序列化直接传递bean是不被允许键值对形式的,需要序列化
  3. 序列化bean类,建立VedioPlayBean类,继承Parcelable,
    1)复写方法即可完成序列化
    2)还有一个好处就是原bean类实在是tm冗杂了,这边取需要的进行 主构造器构造就好了
    改写:
 adapter.setMyListener {//a->Unit//it//代表传递回来的data数据 打印是Infobean类 下面类型不匹配val videoPlayBean= VideoPlayBean(it.userid,it.title,it.url)startActivity<VideoPlayerActivity>("item" to videoPlayBean)}
  1. 来到VedioPlayActivity VideoView(继承SurfaceView)
    1)拿到Url设置路径
    2)prepared监听
    3)start设置播放,三个按钮实现功能即可
    4)不过这里我用的接口没有视频url回调,就写死找了一个天行九歌的,见样例

  1. 由于设置路径后是异步请求,太大的视频没加载出来自己start就GG了,观察生命周期与prepared()监听一下
videoView.setOnPreparedListener{videoView.start()

七:[Seventh Cmt]MV测试,响应本地或应用外视频

ps:没事别手贱修改github的描述文件,改了push错了,百度一个多小时都不行,。。最后删库建新的库才好。。

  1. VideoVeiw虽然好用,但是很多特性是不支持的,比如alpha,旋转等等,引入TextureView
  2. 建立TextureVideoPlayerActivity,并设定 对应的layout,控件更改为TextureView,按钮也先扔掉,不然实在是太丑了,发现是全屏播放
  3. 关于扔掉按钮,原本VideoView直接拿到,直接start什么的都ok,还有isplaying()方法,但这个不行,他是调用mediaplayer的,得改写medioplayer。
  4. initdata设置监听.surfaceTextureListener通过this传递对象,并且通过当前类实现方法
    1)onSurfaceTextureSizeChanged VIEW大小改变时
    2)Updated 视图更新
    3)Destroyed 视图销毁 ,不实现会报错 原因是,这个有bool类型返回
    a.true就不继续渲染 且声明大多数app应返回true
    b. false调用release 销毁texture资源
  5. Available 视图可用显示界面时 但还需要解码 显示视频画面的界面
    设定mideaplayer就放在这实现,而且不能在这里面定义,以前可以,现在就没用了,播放步骤:
    1)设置播放源
    2)设定surface源,传一个函数,参数就是我们拿到的surface,应该是绑定mediaplayer和textureView
    3)异步准备
    4)监听完成就令mediaplayer启动
    override fun onSurfaceTextureAvailable(surface: SurfaceTexture?, width: Int, height: Int) {//加非空校验很重要的videoPlayBean?.let {//视图可用显示界面 但还需要解码 显示视频画面的界面// mediaPlayer.setDataSource(videoPlayBean.url)mediaPlayer.setDataSource("http://vodkgeyttp8.vod.126.net/cloudmusic/MjUyNDMxNzEw/0645927d1d174202121abf592a085c45/2b0d0faaf2c6d0feccd40b31ac668586.mp4?wsSecret=c8e0631dde1e900924976be4577b85fc&wsTime=1586933449")mediaPlayer.setSurface(Surface(surface))//设置显示画面mediaPlayer.prepareAsync()//异步准备mediaPlayer.setOnPreparedListener {mediaPlayer.start()}}}
  1. start后可以设置这个属性:textureView.rotation=100f 进行旋转角度,。。。画面太美不敢看,这边就不展示了。返回后发现还会有声音延续,是destory哪个方法,没有进行生命周期的管理,关闭一下就好了
    1)非空验证
    2)stop方法
    3)release方法

mediaPlayer不支持硬件解码,高精也不支持,如mkv,flv,rmvb。。。。。要想做得用第三方组件

  1. 引入Vitamio
    目的使用他们封装好,新增方法的MideaPlayer解码替换系统的,且方法保持不变,但是呢他是继承SurfaceView(不能在滑动界面显示)的,仍有局限,最惨的是,这家伙官网都炸了,额, 上传Github后,利用as测试,as的import module挺好用的,不过。。现在的高阶安卓根本不适用。。还有导入其他包时注意最小兼容。
  2. B站的ijkplayer非常好用,就用这个
    1)解压,查看包结构
    a.首先可以可以一些x86,x64,arm64等等这些平台依赖(生成对应平台的so库)
    2)查看播放View控件-IjkVideoView,点击查看具体
    a.发现他是继承FragmentLayout,神奇【视频播放就两个控件,一个是SurfaceView还有一个是TextureViw】,其实他就做了个包裹显示,并非真正的播放操作
    b.向下找发现了IRenderView(接口)(ctrl+H)显示
    b1)实现类一:SurfaceRenderView(继承SurfaceView)
    b2)实现类二:TextureRenderViw(继承TextureView)
    c.向下查看解码方式IMdeiaPlayer(接口)解码
    c1)AndroidMediaPlayer(安卓自带)
    c2)IjkMediaPlayer(自封装的)
    c3)IjkExoMediaPlayer(谷歌提供数字加密解码)
    3)应用仓库直接搜索依赖就好了,支持库也可以一同加进来,然后layout替换ijkvideoview,回activity修改导包
    4)和之前一样,复写onDestory(),返回时videoview调用stopPlayback()
  3. 原本的三个按钮外界,非常不美观,而且适配还非常有问题,。。所以我们再用一个框架JiaoZiVideoPlayer
    1)同样的煎饼果子来一套,把prepared监听去掉,修改为自己官网提供的播放路径,鉴于网易云接口老失效,这边就引用B站-友利郁野 的钢琴,弹得非常好哦 压缩到自己土豆服务器上,测试会糊挺多,不过满足需求。点击播放,美滋滋,最后在清单文件配置横屏自动旋转,等等
  4. 响应本地的播放视频,安卓清单文件加入action.View接收广播系列,在原类中定义data接收数据,并取出.path具体路径,测试应用外的网络请求不需要.path直接(清单文件定义了http即可)data.tostring(),加一个if ok
  5. 补充videoview继承Surfaceview相当于创建新窗口 ,我们视频刷新比较高,而我们只会刷新当前窗体,并不会影响应用窗体的重新绘制,效率较高,使用MediaPlayer进行视频解码。Texture功能比较强大如旋转。

七:[Eighth Cmt]显示详情,buttongroup与viewpager双向切换绑定

  1. 首先修改layout,加入RadioGroup组和三个对应Radiobutton
  2. 双向绑定设计监听,复写initlistener方法
    1)建立新的adapter:VideoPagerAdapter继承FragmentPagerAdapter,传入fm,再复写内部两个方法
    a.获取Fragment
    b.返回共几条
    2)新建adapter对象,并监听ButtonGroup改变状态方法(setOnCheckedChangeListener),发现内部没有待实现方法,且参数只有一个直接打开设置监听,然后写一个lambda表达式,通过buttongroup和对应i来进行viewpager的切换
    3)双向绑定,所以 还要监听ViewPager(addOnPageChangeListener),发现内部有三个待实现的方法,就使用匿名内部类,该三方法分别是
    a.onPageScrollStateChanged 滑动(左右)状态 改变的回调
    b.onPageScrolled 滑动的回调
    c.onPageSelected 滑动状态改变 选中状态改变 做滑动下面 同步更改上面
    c1)判断c中的position,并set group的check即可同步

八:[Ninth Cmt]Media媒体库与异步查询

Boat:MediaProvider

九:[Ninth Cmt]乐单条目View

Boat:AsyncQueryHandler,CursorAdapter

  1. 目前查询数据是放在Fragment中的,当然最好是放在Utils里岂不是美滋滋
  2. 做界面适配,老套路
    1)条目View,Widget目录下创建VbangItemView,继承RelativeLayout
    a.复写三个次构造方法
    b.init方法中动态inflate中动态加载布局
    2)当前结果是cursur对象,以前是bean(listview做适配继承baseadapter,定义一个list集合,装载的是每个条目的bean)转换非常麻烦,但好在安卓有一个CursorAdapter新建adapter继承。
    3)类似FragmentPagerAdapter,这个是实现两参的构造器,复写两个方法,做Vbang界面的列表适配器
    a. newView() 创建条目view,绑定布局,return Widget生成的条目VBangItemView(),将我们的context参数传进去。
    b.bindView() 条目view初始化与数据绑定。参数自带View,kotlin也不需要通过holder进行中间持有,其游标会转到自适应的位置,好用
    b1)getview中新建了view as VbangItemView绑定,那这边新建itemview赋值转化一下。
    b2)数据在cursor里,但我们内部用游标进行查数据是相对麻烦的,使用找bean类。
    b3)建立AudiBean,初始化cursor查询到的四个参数主构造器
    b4)创建伴生对象,内置静态方法,实现cursor到bean类的转换
    c.b中分三步实现
    c1)创建bean类对象
    c2)判断cursor是否为空
    c3)解析cursor并设置到bean对象中
    4)补充:CursorAdapter是继承BaseAdapter(有四个未实现方法)CurosrAdapter已自动实现(数据类型转换与游标移动)
    a.getitemid()
    b.getitem()
    c.getcount()
    d.getview()
    5 )虽然他没有适配ViewHolder(中间持有),但是kotlin可以直接拿到控件ID,所有也可以不用绑定。data是string类型,getstring()内部传递一个下标(int类型,data在那列),通过cursor的获取列下标(传入列名),其他数据同样方式拿。

audioBean.data=cursor.getString(cursor.getColumnIndex((MediaStore.Audio.Media.DATA)))
做截取的时候可以通过这种方式:
audioBean.display_name=audioBean.display_name.substring(0,audioBean.display_name.lastIndexOf("."))//截取到 最后一个.结束如:夕日坂-初音.flac
解析完成之后,返回bean类就可以了
6)adapter就可以获取对应数据
val itemBean= cursor?.let { AudioBean.getAudioBean(it) }//好用
7)对应的itemview(b1中)绑定的VbangItemView,这里setData得到的bean类数据就好
8)VbangItemView创建对应方法,是视图和数据绑定,方法内部和之前的一样
9)//text包下下的Formatter 安卓api 格式化M,G...后缀名 sizev.text=Formatter.formatFileSize(context,itemBean.size)

  1. 回到YuedanFragment声明,initlistener赋值adapter,并赋值给listview拿到了数据,并绑定界面,条目view(VbangItemView)中也进行了数据设置回到Fragment中进行adapter的创建解析,初始化是两个参数,第一个就当前context,第二个目前还没有(cursor)就先传个null,先不设定。
  2. 我们在查询这步才拿到cursor,所以呢,在Eighth Cmt中查询里的cookie设空的部分现在改为adapter ,回调的时候通过cooike将adapter设置对应的cursor,来到查询完成回调后
    (cookie as VbangAdapter).swapCursor(cursor)先设置了数据源,再刷新
  3. (cookie as VbangAdapter).notifyDataSetChanged()//数据源变化时调用刷新,但adapter创建时给的是null,而我们的目的是给adapter传cursor数据,在此之前是绝对没有数据变化的。
  4. 补充(1),理论上这套应该ok了,但是运行炸了,原因是cursoradapater继承baseadapter中自动实现的4个方法中getitemid是主键,而我们没获取到,查询语句多加一条就好了。
  5. 注意内存泄漏问题 ,其一是Io操作没有关流,其二是cursor操作,还有就是发送广播没有反注册,handler消息没有移除等等。
  6. 当前我们在滑动时都要用到cursor对象,界面存在,cursor都需要,界面销毁时需要关闭cursor,fragment中复写ondestory方法,先获取adapter中的cursor设置为null
    adapter?.changeCursor(null)//先自动获取到adapter中的cursor并关闭,再将传入的null替换原cursor//内部有swapcursor帮其操作。完成生命周期
  7. 接下来做权限申请的优化
  8. 点击音乐条目跳转到播放界面
    1)initListener中不仅可以设置adapter(条目变化)
    2)还可以设置条目点击事件,ListView还更方便了一点,对应的adapter直接设置setOnItemClickListener//四个参数 int是position long就是id
    a.创建对应activity并设置跳转,原监听处设置点击startactivity,并传递两个参数
    b.参数1:播放列表,供上下首切换 参数2:当前点击条目的position
    c.获取数据集合,之前都是list与bean类,这边通过处参数i利用adapter拿到当前位置的cursor数据
    d.定义一个方法audiobean中,传入当前cursor获取整个数据,存入list,具体实现,和之前那个传入cursor,获取单个的对应。
    d1)创建list
    d2)通过传入的cursor将游标移动到-1,因为0开始,判断cursor是否为空,let表达式;走nextitemit.moveToPosition(-1)
    d3)while循环走一个,初始化bean一个,list add()一个,最后返回list就好了
    回到c传入数据即可startActivity<AudioPlayerActivity>("list" to list,"position" to i)//已拿到对应参数,播放列表集合用于上下首切换
    e.又是熟悉的地方,listbean类需要序列化,继承Parcelable接口,实现各种方法,静态对象合并一下ok
    3)来到AudioPlayerActivity,将传过来的数据取出
    a.initdata中拿数据
    a1)intent.getParcelableArrayListExtra(“list”)
    a2)intent.getIntExtra(“position”,-1)//第二个参数是默认值,-1代表没有获取到,打印一下发现都是对的上的。

九:[Ninth Cmt]具体播放页面

Boat:不得不说,做适配的时候Miv4t画师的适配图实在是太棒了

  1. 修改布局layout,分成top ,middle,buttom三个模块
  2. 简单播放音乐和视频类似,获取position后 ,但这个代码在当前Activity中的话,按个返回或者切换就没了。。所以要利用安卓第二大组件:Service(其一是他优先级高,其二是被误杀后会尝试重新启动//即非软件主动销毁,与后面空指针异常的联系,不过高办好、本好像更了默认,不会重新启动),与绑定上一曲,下一曲
    override fun initDate() {val list=intent.getParcelableArrayListExtra<AudioBean>("list")val position=intent.getIntExtra("position",-1)//-1代表没有获取到//println("111111111111list=$list 2222222222positin=$position") 队列与位置正确//播放音乐//1.创建进入idle状态val mediaPlayer=MediaPlayer()//2.设置播放路径mediaPlayer.setDataSource(list.get(position).data)//3.异步准备mediaPlayer.prepareAsync()//3.监听准备mediaPlayer.setOnPreparedListener{mediaPlayer.start()}}
  1. 新建服务类继承Service测试
    1)实现onBind()方法,继承自IBind,返回bind对象,可以定义Binder()类
    2)复写方法,并且注册清单文件
    a.onCreate//1开启服务 2绑定服务(之前destory了会先create,绑定后裔操作界面的更新)
    b.onStartCommand//2开启服务
    c.onBind//2绑定intent服务
    d.onUnbind//1解绑服务
    e.onDestory//停止服务 2解绑服务
    直接绑定服务,退出界面时发现会执行c和e,所以单纯用服务是不行的,需要混合启动方式:先开启服务,再绑定服务,执行服务方法后退出发现只是解绑而不会销毁。手动调停止才停止(先绑定再开启也行)服务就会在后台一直运行,手动点停止才会停止。

ps服务类太吓人了,手机的清除内存对他根本没用,活动管了仍然在运行服务,点击其他歌曲后两首歌会一起放,所以安卓软件光靠一键加速是没用的,得重启

  1. 建立与Utils同级的包Service
    1)建立AudioService类继承Service
    a. 复写onBind()方法,绑定对应类型的binder
    b. 注册清单文件
    c. 将对应Activityity中音乐播放代码移至Service中,原位置代码改为开启服务,必须的是list和position,
    c1)val intent= Intent(this,AudioService::class.java)//對應發件人和收件人
    c2)使用putExtra键值对方式传list和position过去
    c3)开启与绑定服务混合启动通过intent,绑定Activity服务时,第二个参数对应新写的 AudioConnection()类(惰性加载),这个是要实现继承ServiceConnection接口,其中的两个方法分别是连接成功和意外断开连接
         //先开启startService(intent)//再绑定bindService(intent,conn, Context.BIND_AUTO_CREATE)//第二个参数是接口connection,第三个参数是绑定后若未创建则会自动创建出来

c4)intent优化,原本上一级拿到两个参数测试,这一级封装intent转发,那c1和c2直接省略=getintent就好了。

 var intent=intent//这是getintent 和1中一样取从VBangFragment中的数据//修改intent.setClass(this,AudioService::class.java)

d.来到AudioService中(流程和3.2)一样
d1)先复写onCreate方法(print测试挺好的,这里测试了一下get不到intent)
d2)复写onStartCommand,执行startService时会调用,这里的参数就是拿到之前传的intent,接下来开始播放音乐
d3)复写onBind()//参数也有intent
ps:这时都能拿到intent就要看那个更合适了。多次开启服务会多次执行d2,而多次绑定服务只会执行一次d3,所以采用d2方法,比如播放列表更新之类的。
d4)定义两个字段保存 Arraylist和position,d2 就可以拿出来了
d5)为了上下一曲等等,和MediaPlayer,Service交互,是通过Binder对象,(作为d3的拓展),自己写个Binder类和对象在Activity,Service交互岂不是美滋滋
d5a. 定义AudioBinder()类继承Binder(),也放在最外面惰性加载对象,不然(d2 d3根本拿不到)
d5b. 创建playItem方法用于播放,其他的移过来,不过medaiplayer放在最外层初始化,一个服务一个medaiplayer就好了
d5c. 复写onprepared方法进行监听(是让当前的AudioBinder()实现接口,监听this美滋滋)
e. 对象方法都有了,在onStartCommand中通过我们的bind对象执行playitem()方法,在onBind()中,返回我们的对象binder就完成绑定了。Binder放外面惰性加载确保能拿到。然后播放OK。
5)回到Activity中复写onDestory方法,解绑一下unbindService(conn)
6)返回的Binder保存一下,并且防止其他类看到,写一个接口Iservice当前ABinder实现一下,后续和Service是所有交互都由接口完成,Activity中整一个对应对象Iservice实现接口
a. 主函数新建这个对象,在1)c3中有个回调拿到IBinder,在这就可以通过IService保存连接后的绑定AudioBinder状态,之后通过这个实现后续的其他操作
b. 我们发现Activity的intent只是起到连接前AC和Service的中转站作用,

 override fun initDate() {//println("1111  positin=$position")var intent=intent//这是getintent 和1中一样取从VBangFragment中的数据//修改目标走向intent.setClass(this, AudioService::class.java)bindService(intent,conn, Context.BIND_AUTO_CREATE)//第二个参数是接口connection//回到AudioService进行播放startService(intent)}

7)老版本,外界强制销毁时会报错,Service中playItem()方法内部设置播放路径报空指针异常,打印发现list为空(onStartCommant()中进行赋值,此时initet为null,所以这个方法拿不到数据,原因是onstartCommand开始执行了一次,杀死进程执行了一次,而这次intent为null会报异常,原因2.当中提到了,自动重启
a. 设置OnStartCommand这个方法的返回值查看一下。发现内部会根据版本不同的判断
b.START_STRICKY=1(粘性的,被强制杀死会尝试重新启动,但不会传递原本的intent 报空指针原因)
c. START_Not_STRICKY=2(非粘性的,不会尝试重新启动,选择直接返回这个,省的被说流氓软件)
d. START_REDELIVER_INTENT=3(和1类似,但是会传递intent)

十:[Tenth Cmt]播放页面适配与动画效果

Boat:缺陷待完,首先堆叠多首一起播放,其次是只能点击一次进入播放页

1. 处理点击播放和暂停的切换

  1. 来到AudioPlayerActivity
    1)复写initlistener,监听按钮必不可少,先设置state按钮点击监听,并且用传this的方法,让当前Activity实现onClick接口,原因是目前播放暂停可以写里面,但后期还有上下一曲,播放状态等等,里面用when表达式看id,更加便捷目前只有state按钮控制播放状态
    2)定义updatePlayState方法,供when(点击state播放暂停图标)调用,具体操作如下
    a. 更新播放状态(操纵Service中的mediaplayer->在service中实现绑定服务返回的service接口,t通过九当中new的一个Iservice接口的绑定对象.
    b. 我们拿到对象但是没方法,于是原接口中写一个就好了,updatePlayState(),好了这个对象调用,我们发现Service报错了
    c. 原因是我们自己的AudioBinder是继承iService绑定的,这边要继承所有接口,继承一下,ok
    d. 在Service中实现
    d1)获取当前播放状态,这里再定义一个方法isPlaying(),方便以后复用,就直接eturn mediaPlayer?.isPlayingbool类型即可
    d2)切换播放状态,返回值非空,后进行判断,1就说明点了暂停,执行pause(),0就执行start()
    3)改变播放状态的图标updatePlayStateBtn(),供复用,而且和改变播放实现代码的对应,组个cp?
    a. 方法内呢无参,还是我们的iService先调用isPlaying(),根据状态改成相应的即可

2. 界面音乐标题,动画以及进度的更新

  1. 业务逻辑在播放音乐之后,我们AudioBinder类里的监听onprepared方法才是完成播放的,medaiplayerhou,写个方法通知界面更新notifyUpdateUi()
    1)在SerVice通知Activity中更新,之前是Activity通过iservice绑定主动到service中拿东西,而现在是service要主动发信号给activity,实现方法
    a. Activity中定义一个接口,用iservice传到service,然后再通过这个接口将数据回传,额。。有点麻烦
    b. 通过handler,Activity中定义一个handler,再通过handler与iService将handler传给service,开始播放时通过handler传递消息就中了。a和b耦合性较高,Service要持有Activity一个变量的引用,可能会内存泄漏,即销毁某个时,变量还存在
    c. 广播方式。Activity中定义广播接收者,Service那个方法中发送广播,接收即更新,退出关闭广播接收者,岂不是美滋滋
    d. EventBus应用类广播(导个jar包,也能搜到)4个特性美滋滋
    2)首先Acitvity中初始化initData注册一下EventBus.getDefault().register(this)this指当前class,和广播一样,销毁时反注册一下EventBus.getDefault().unregister(this)
    a. Activity中定义接收器onEvent,方法名有4种模式
    a1)PostThread:啥线程中发送,接收端啥线程中执行发送
    a2)MainThread:主线程执行
    a3)Background:后台队列依次执行
    a4)Async:后台并行执行
    b. 由于数据比较简单,而且会操作界面UI数据,所以直接主线程了fun onEventMainThread(itembean:AudioBean)指定接收的Bean类
    3)回到1)中方法写EventBus.getDefault().post(position?.let { list?.get(it) }) list里面装的是bean类,通过position拿到对应的bean对象。
  2. 怎么实现的呢,我们initdata中注册了bus,然后会扫描当前类中的onEvent方法,并进行保存,我们Service中传bean类,就会在保存的onEvent方法中找到参数的bean类的对应方法就OK了美滋滋
  3. 在bus接收器中取出来 设置一下就好了,值得一提的是动画设置,再次调用方法改变播放/暂停显示按钮。
    1)首先动画其实是多张图片交替animation-list动画帧与内部的ltem图片组,和botton图片组
    2)更新播放状态后开启动画切换
    drawable = audio_anim.drawable as AnimationDrawablesrc方式拿到并转化为动画类型的drawable,之后**drawable.start()**即可ps:若非src而是background方式设置背景 则拿到应该:audio_anim.getbackground,暂停的话调用stop方法即可
    3)drawable扔外边去,再实现一下动画的同步暂停,咱们有两个方法,一更新播放状态,和更新图标状态,那果断在图标状态更新,两个ss一加,搞定。
  4. initlistener再把返回按钮加个click侦测,finish()搞定
  5. 实现进度的显示,其一是实时变化的进度,其二是总进度
    1)总之得先拿到总时长数据
    a. 通过我们绑定的iService对象写个getduration()方法获取(iservice来个抽象方法,自己的service对应binder实现return mediaplayer。duration即可),记录一下
    b. 不过这里获取到方法是毫秒,嗯,待会写个方法转一下就好了,Utils包下建立StringUtil静态类,写个方法一转。
    result=String.format("%02d:%02d",min,sec)//不足两位以0补齐这里注意一下就好了。
    2.1)随后要获取当前进度,嗯,写个方法,和总时长类似startUpadateProgress(),先同样写一个方法getprogress()返回mediaPlayer.currentPosition,这里同样是毫秒
    2.2) 更新进度数据
    a. 虽然是对界面进行改变,由于和前面的方法没啥交集,主要是写里面太冗杂了,再写个方法拉倒 updateProgress(),里面传的是获取到的progress
    b. 对应settext搞定
    2.3)定时获取进度,ntime更新啊,方法其一呢就是一直获取,然后设置进程睡眠 ,这个呢发证法。其二呢,安卓handler也能做到
    a. 定义个handler,再讲EventBus提到过handler,这里就可以用了,发送一个延时空消息 handler.sendEmptyMessageDelayed(MSG_PROGRESS,1000)//发送空消息并有一定的延时,第一个参数是标志变量,自己外层定义一下
    b. 定义handler用匿名内部类方式打开,复写接收方法handleMessage(msg:Message),然后when等于咱标志量,相等再执行**startUpadateProgress()**方法,完成循环调用
    3)回到2.2)中,将毫秒传过去之后,搞定
 progress.text = StringUtil.parseDuration(pro)+"/"+ StringUtil.parseDuration(duration)

十一:[Eleventh Commit]播放列表与上下一曲

  1. 处理进度条同步
    1)在onEventMainThread()即EventBus中获取总进度后,通过max属性就可以给咱进度条设置最大值
    2)我们实时改变进度时间的方法中updateProgress()通setprogress()就好了
    3)点击暂停后停止进度条滚动
    a. 这个和放动画的类似(只不过是将操作变为handler消息(咱定义的标志))移除和添加即可,在相同位置

  2. 处理内存泄漏问题,发送延时消息传一个message,其中有target handler作为类指针引用,若退出界面,消息任在消息队列中,handler也任然持有,咱们是定义在activity中的,对应这个AC也无法销毁。解决方法两种:
    1)handler放静态伴生对象里,但是呢由于匿名类里有调用外部方法,其他方法也要改为静态,比较麻烦
    2)退出界面,清空handler消息即可,两个都可以走正常的销毁流程,ondestory方法中
    handler.removeCallbacksAndMessages(null) 内部一个token参数,null就是全清空

  3. 进度条交互
    1)首先当然要给进度条设置一个箭头,和back那些一样,来到initlistener
    progress_sk.setOnSeekBarChangeListener() 发现内部参数是对应listener还有三个未实现方法,不能打开解决方法其一是内部类,其二是传this,让当前活动对象来实现,和之前state对象一样,是不是有内味了,总共三个方法:
    a.onProgressChanged(),其中参数进度一改变就回调 改变后进度和ture//用户手指改变 false代码式改变
    b.onStartTrackingTouch() 触碰seekbar的回调
    c.onStopTrackingTouch() 手指离开seekbar回调
    2)我们要监听进度条的拖动改变,就是在a方法中进行,分三个阶段
    a. 侦测用户操作,如果是代码使进度条变化就直接return
    b. 更新播放进度,通过我们的Service绑定,将我们的progress参数传递过去,任然是接口加上静态方法,再通过Service里自己定义的Binder类定义seekTo(),通过mediaPlayer实现一下传参,音乐就能调进度了,最后将当前进程数字同步,调用updateProgress就好了

  4. 设置自动播放下一首
    1)基于算法上就是当现在的时间等于总时长就切歌,但是安卓的mediaplayer贴心的提功力setOnCompletionListener方法,咱传this用类实现回调onCompletion,再封装这个方法autoPlayNext(),里面用when根据模式切歌就行,随机调用Random(),这里kotlin的random报错了,于是改用javarandom,更改position,最后调用playItem()即可。

  5. 切换播放模式,现在APATY的initlistener的mode控件设置点击事件,在之前写好的click回调的when中同样写个方法updatePlayMode()
    1)修改service中的逻辑模式
    a. service操作和之前一样,接口写个方法updatePlayMode(),iService调用,Service写具体的逻辑模块
    b.传入当前模式,when写一下all,singer,random三模式的相互转换
    2)修改界面模式图标
    a.定义个方法据当前播放模式updatePlayModeBtn() 修改图标,其中也分两步
    a1)获取播放模式,仍然是写个方法,通过service实现getPlayMode ,操作也很简单,到了service就可以直接return mode了。回到activity,报红,做个为空判断,iService为空就不管他了。
    a2)设置对应图标,分三种情况按拿到的写图标就行了,这里有个问题,service中的三状态拿不到,把那三个改为静态对象,搞定。

  6. 播放模式状态不保存,未同步service中的模式
    1)打开service会执行playitem,会有通知更新的方法,在eventbus说过,回调更新界面也可以更新播放模式,方法加上去,尝试了一下,可行。关闭之后就不行了,内存中服务都销毁了。
    2)保存到本地,方法1:文件式保存,方法2:sharepreference保存
    a.定义名称与私有路径,在最外层惰性加载一下,
    ** val sp by lazy{getSharedPreferences(“config”,Context.MODE_PRIVATE)}**
    b.保存播放模式,UpdatePlayMod中负责切换,切换后保存**sp.edit().putInt(“mode”,mode).commit()**键值对
    c.打开更新,oncreate方法
    mode=sp.getInt(“mode”,1) 如果拿不到播放模式就默认为1模式即可。
    7. 设置上一曲和下一曲,同样设置监听,写方法,iService实现,这里留了个坑,随机的上一曲是没有记录的,上下一曲都是随机

  7. 如果media已经存在就先释放掉,不然new对象会堆积,这里我也不太理解,我只定义了一个变量,居然不会被覆盖掉,在playItem做重置,和之前的videoview类似

  8. 点击继续播发现重新开始了,onStartCommand()中加入判断思想,如果不是同一首再设置获取和播放,(返回Activity销毁但service不会销毁,但是界面都没了)bind方法里有play和更新界面notifyUI,主动通知即可,但进度和数值有问题,原因是初始化时混合启动,绑定开启顺序都可以,但这里有问题,应该先绑定在开启,是因为没拿到iservice接口就更新的缘故,先绑定就能拿到binder

  9. 做完了8可能会报错,原因是刚开始点第一个下标也是0,咱初始化也是0。。会认为相等就不再执行获取list,所以后面取就会报空指针异常,修改默认值为-2或者null

  10. 通过popwindow显示播放列表(理想效果和网易云一样最好),同样的先定义一个监听,方法,处理onclick
    1)界面的显示,所以写个activity的方法 showPlayList(),这个弹出界面使用的是popwindo

    2) Widget包下创建popwindow,继承popwindow窗体,复写init方法
    a1)设置布局
    找到对应的界面context,由于是控件找不到,使用主构造器传一下,写个布局R.layout.pop_playlist,里面是一个listview
    a2)设置好之后设置contentView展示出来
    b.代码中设置宽度和高度(不设置显示不出来),通过windowmanager和point()
    3)回到1的方法中,新建主构造器对象,并且传入当前context,showAsDropDown()方法是设定弹出左下方到当前参数view的左上方,要想移到底下,设定y偏移量为参考控件的高度即可。
    4)回到widget中设定焦点(isFocusable)和外部点击(isOutsideTouchable都为true),实现设置setBackgroundDrawable(ColorDrawable())能够响应返回按钮(适配低版本点击返回按钮里面写了适配dismiss)

十一:[Twelvth Commit]

  1. 适配播放列表,和以前一样
    1)widget下适配条目View,并写对应布局ps:设置最大一行很重要
    2)来到定义的PopWindow中,init方法里获取listview,由于只是控件,(非fragment、activity)不能直接拿到id,得findviewbyid
    3)做adapter适配,复写4个方法,注意getView的操作,其中kotlin形参不可修改,还有convertView缓存机制,这边定义了条目PopListItemView就不需要viewholder了。返回前对应的itemView选择setData()在我们的桃木中实现。怎么拿到adapter呢,让外部主构造器传递即可
    4)来到AudioPlayerActivity方法中, showPlayList()展示播放列表定义一个对应的adapter,数据需要列表集合,写个抽象方法,让Service整,返回list就管,注意这边改写了空判断
  2. 点击播放列表条目就播放歌曲
    1)在设置adapter后设置条目点击事件,listView的sOCL,方法内需要一个listener对象来执行回调
    2)乐单Activity中,创建PlayListPopWindow处修改主构造器,传this,让当前类实现回调
    3)和之前一样,通过iService写个方法用position传递,实现很有意思,用了注释
    this@AudioService.position=position//注解this注解,原本是实现的bean类,现在指向到AudioService
  3. 设置弹入/出动画animationStyle =R.style.pop指定文件,添加进入和退出动画
    1)进入设置translate平移,设定y方向平移和时间1s
    2)退出弄成相反就好
  4. 改变弹出背景颜色,播放列表弹出popwindow是新窗体覆盖在应用程序窗体上,弹出修改应用程序透明度即可
    1)这是界面的操作,来到activity,showplaylist方法中通过showAsDropDown显示,来到对应PlayListPopWindow组件复写这个方法,super不要去掉,再复写dismiss,具体的动画操作放这里面。
    2)主构造器拿到window,activity传window,拿到Ac的窗口,获取不透明度,分别两个方法设置一下就好了,ps,复写方法里要拿到参数不能放init里面
  5. 增设自定义通知
  6. 回到FarawayPlayer,显示通知栏
    1)在Service中onPrepared()准备完成后,通知界面更新后也要通知显示“通知栏”,先将6中的show方法及相关核心代码拷贝过来
    2)getNotification()获取具体Notification对象时,生成的对象报错了NotificationCompat.Builder(this),原因是这里的this指binder对象(在这里面),应指定为Service,参数改为this@AudioService用了kotlin中的注解
    3)service中是有list数据和position的,所以标题什么的直接拿出来就好了
    4)延时加载的点击事件中,这里的Intent操作,service,不需要操作界面,而其中的this也要指向service【后面也改为对应的getService即可】获取Service操作就好了,不用显示界面,只有点击主体才跳转到播放界面
  7. 设置通知栏点击事件,点击通知栏主体跳转到播放界面
    1)这里崩了,原因是空指针异常,原intent传递list现在拿不到了
    a. 进入Activity方法中会先执行initdate()方法,再通过绑定和开启服务(传递intent(VbangFragment中)),而点击主体创建的intent是不一样的,会再次执行onStartCommand,咱们数据是从intent获取的,此时当然获取不到,返回-2,list也获取不到,所以报null【即进入AC后仍然会进入Service,但这个intent获取不到数据】
    2)那就好办了,每个intent都传了from标志,OSC方法加个when判断,非vbang的就不获取和播放,但刷新界面,原本的显示条目都没了,那好整,和再次点击条目一样,用绑定的binder,执行更新UI方法就好了。
    3)新问题:点击条进入的是栈结构,单片直退,多片重叠,解决单片直退问题呢,先启动主界面,再启动播放界面,所以启动改为Activitys,结合数组
    4)但播放界面APAcitivty会堆叠多次,应设定SimpleTask启动模式(见第一行代码)
 private fun getPendingIntent(): PendingIntent? {val intentM = Intent(this@AudioService, MainActivity::class.java)//点击主体进入当前界面中val intentA = Intent(this@AudioService,AudioPlayerActivity::class.java)//点击主体进入当前界面中intentA.putExtra("from",FROM_CONTENT)val intents= arrayOf(intentM,intentA)val pendingIntent = PendingIntent.getActivities(this@AudioService,1,intents, PendingIntent.FLAG_UPDATE_CURRENT)return pendingIntent}
  1. 通知栏点击事件,点击上下一曲以及暂停的切换
    1)[7.2]中when判断把其他的代码补全,由于写过方法,通过binder调用即可,美滋滋,但是呢暂停涉及图标变化,虽然功能实现了,但界面和通知栏的图标都还没变【原因就是:
    a. 原本的AC中点击state,调用service的updatePlayState()和AC的界面更新
    b. 在servie中的通知栏只能完成播放暂停核心代码,无法干涉界面】
    c.我们之前更新状态通过EventBus,是不是有内味了,这个消息发过去,AC会自动根据播放状态进界面更新,但此时我们还需要更新通知栏的状态
    2)更新通知图标有点小麻烦,因为不是在同一个线程里面
    a.找到显示通知栏的showNotification()方法,将manager显现的第二个参数getnotification对象具现出来,和他的manager一样,放到最外面service层,这样更新通知栏的图标都那到对象了,岂不是手到擒来
    3)显示是通过咱点击播放start和pause方法后notification?.contentView?.setImageViewResource()设定对应图片
    4)最后一步通过manager重新notify当前id即可。要想没提示音设置低等级即可

安卓8.0,往后要设置channelid测试了一下,setsmallicon有些机型没用,而原image最好使用drawble中的才管用。

十二:[Thirteenth Commit]静态多行歌词【绘制画面与切换】

1. 需求【重点是求每行的x和y】

  1. 歌词拖动,滚动,播放到哪一行,哪一行居中
  2. 拖动更新较难,实现滚动也较难
  3. 先做播放到哪行,哪行居中 ,绘制多行居中文本
  4. 先绘制居中,再绘制多行文本,再层级向上完成

2.绘制单行居中文本

  1. 歌词是自定义View,不能用单纯的TextView实现,自定义两种
    1)自定义组合View
    2)继承View measure layout draw
  2. 这里选择继承View绘制一下内容就好了
    1)Widget下建立 LyricView继承View,复写三个次级构造方法
    2)查看View绘制流程【View 画布 画笔】
    a. measure方法进行测量
    b. layout方法进行布局
    c. draw方法进行绘制,其中有6个步骤,View调第三步ondraw方法绘制主体,ViewGroup将孩子绘制出来

    3)复写ondraw方法,参数是一个画布canvas
    a. 定义显示的文字,通过canvas的.drawText方法,传四个参数,前两个是坐标,第三个是定义的文字,第四是个是画笔,(0,0)任然是左上角那个店,显示文本的左下角定位在这个点上
    b. 声明抗锯齿画笔val paint by lazy{Paint(Paint.ANTI_ALIAS_FLAG)}
  3. 找到middle其中引入的歌词布局,改为我们刚写的LyricView控件,因为咱们是继承了View,发现就是在左上角
  4. 根据公式要拿到四个参数
    1)复写onSizeChanged() 这是draw第二个方法执行结束后layout布局完后执行布局调用,可以拿到View的x,y参数
    2)歌词是绘制的,通过画笔拿到x,y Rect矩形类
     val bounds= Rect()//新建bounds矩形对象,确定长/宽先整形状paint.getTextBounds(text,0,text.length,bounds)//传递指针,参数指向boundsval textW=bounds .width()val textH=bounds.height()val x=viewW/2-textW/2val y=viewH/2-textH/2
  1. 增加字体大小与颜色,变量声明,init中进行赋值,ps用到的字体定义到R.dimen包下,定义的颜色到R.color下
    在inDraw中,设置paint的textSize和color就可以看到效果代码中用到颜色都要先定义出来,要不然可能会认为是未定义的变量
init {bigSize=resources.getDimension(R.dimen.bigSize)smallSize=resources.getDimension(R.dimen.smallSize)white=resources.getColor(R.color.white)green=resources.getColor(R.color.green)}
  1. 进一步优化,在init中设置画笔paint.textAlign=Paint.Align.CENTER//在x方向确定位置是以中间确定位置,再来到drawText方法设置为view的一半即可

3.绘制多行居中文本【静态】

  1. 定义歌词的bean类,先看这张图,时间+内容 建立bean类(网易云的歌词就比较复杂)
  2. 具体步骤
    1)显示多行,行高思路
    2)根据图示,我们首先要拿到居中行
    a. 在ondraw方法中抽取drawMutieLine(canvas)用于多行的显示
    b. init中的定义for循环存入list类,20行,
    c. 定义centerLine居中行初始为10,拿到对应的数据val centerText=list.get(centerLine).content//拿到居中行文本
    d. 确定位置,和单行的操作类似
    3)紧接着for循环通过index遍历绘制内容, 拿到当前行的x和y(通过公式)并取出对应的内容,按公式里乘上行高,我们定义一下,再init中lineHeight = resources.getDimensionPixelOffset(R.dimen.lineHeight)和颜色一样,类css定义dimen一下
    4)最后和以前一样,通过canvas绘制出来就好了
  3. 优化
    1)index取出判断,中心行变为大绿,其他几行也是有绘制的,超出了高度就不显示了

    2)优化,如果超出位置就不再绘制了curY这个当前位置就很管用了。
    a. 代码
    b. 分离,如果大于底部就直接跳出,因为目前后面的不会再显示
  if(curY<0 || curY>viewH+lineHeight)continue

十三:[fourteenth Commit]静态多行歌词【绘制画面与切换】

1. 播放到哪行让哪行居中[播放进度相关]

  1. 写个方法updateProgress(),psAATY中也有这个同名方法,对应绘制,和之前一样,重点找居中行

    1)首先得获取居中行行号[通过list的starttime和progress]
    a. 进度大于最后一行的开始时间,则最后一行居中
    b. 其他行居中的话 循环遍历集合 找到居中行
    2)找到居中行后,绘制多行歌词 重新调用ondraw方法,其中有三种无参方法
    a. invalidate()执行ondraw,宽度变化不管 只会改变内容,条目控件的绘
    b. postInvalidate() 执行ondraw方法,可在子线程刷新
    c. requestLayout 执行ondraw,view布局(宽度和高度)参数个改变时。刷新,view宽高重新绘制
  2. 来到AATY的同名方法将其传过来即可
  3. 小结:设置进度,找到居中行,重新执行ondraw刷新

2.首先歌词滚动过渡

  1. 首先得知道走的距离及时间,求居中行的偏移距离y,其他行按这个一起偏移
    1)偏移时间指:居中行开始时间到真正开始走的时间。
    a. progress-开始时间,即指正在偏移的时间
    2)行可用时间指当前行开始居中到下一行居中的时间
    a. 最后一行居中,行可用即为doration总时长-最后一行开始
    b. 其他行即为下一行开始时间(nexttime)-居中开始时间
  2. 来到多行移动方法drawMutieLine()内,走流程,储存总时长和进度,然后走流程,最后回到APATY,拿到总进度的地方就设置过来
  3. 发现好像有点卡,不够丝滑?没事,调整通过handlr定时获取进度(startUpadateProgress)的频率,或者直接去掉delayed,收到消息就更新,丝滑。
  4. 查看歌词文件,我的虚拟机里暂时看不了emmm
  5. 网易云本地的歌词到是有,不过他是通过id来对应歌的,并且他是bean类,没个还不一样==领会精神就好了

十四:歌词解析

1.创建解析Util类LyricUtil,解析歌词文件获取歌词集合

  1. 创建方法parseLyric(),传递一个文件,返回bean类list
    1)创建集合保存bean类
    2)判断是否为空,做放误
    3)存在就通过IO流读取
    a. java的BufferedReader方式
    b. kotlin式可以直接拿到file
        var bfr=BufferedReader(InputStreamReader(FileInputStream(file),"gbk"))//返回集合var line=bfr.readLine()while (line!=null){//解析line=bfr.readLine()}return list
 var linesList=file.readLines(Charset.forName("gbk"))//读取歌曲文件,返回每行撒数据的集合for (line in linesList){//解析一行 通过集合表示val lineList:List<LyricBean> =parseLine(line)//添加到集合中list.addAll(lineList)}
  1. 其中的parseLine是一个解析方法【原因是那种歌词文本会有两个bean类,即重复语合并如:[01:33.67 [02:46.87 伤心的泪儿谁来擦 有些会这样,重】
    1)使用split进行分割成三个集合,如果是time,time,content格式,根据timefor循环添加,最后全拿到后通过sortBy 方法合并升序拍了
    2)ps:时间我们定义的是int,这边拿到数据转换再add进list通过substring(1)去掉第一个括号,小时,分钟,秒数和之前一样,按:切割,切割就没了,list存起来,看size

  2. 上个方法中要传一个歌词文件,这里得拿到文件,定义Util静态类专门拿到文件,参数是名称,返回位置,获取外部存储,指定路径

 var dir=File(Environment.getExternalStorageDirectory(),"Doload/Lyric")fun loadLyricFile(display_name:String):File{return File(dir,display_name+".lrc")}
  1. 回到LyricView,获取当前播放名称,将歌词文件添加进集合
    1)写个方法setSomeName,传个名字过来,同那个获取总进度的
    2)内部依次通过刚刚写的LyricLoader和LyricUtil传递歌名和文件,就可以拿到歌词集合,addall
  2. 来到AC的onEvent方法中设置歌名即可,这边可能会乱码,指定下编码格式,在Util中Readline设置gbk编码格式
  3. 软件崩溃说越界了,自定义View显示,进入界面,歌词list为设置,未播放时就会对界面歌词View初始化ondraw方法,之前我们直接写死初始化的
    1)将centerline变为0,因为有些拿不到歌词
    2)onDraw(canvas)加个判断list.size==0,则调用之前的加载单行,其他的加载多行
    a. 未准备好
    b. 拿不到歌词
  4. 后续优化,View中解析是IO操作,可以放在异步线程,java是用thread,Kotlin调用doAsync,由于换线程,this加上注释@LyricView指定一下就行了。

2. 手势调节歌词【无法加载歌词,学习精神】

  1. 在LyricView中复写onTouchEvent方法,返回一个true

1)手指按下时,停止通过进度更新歌词
a. MotionEvent.ACTION_Down:(鼠标按下),停止通过进度更新歌词,不执行updateprogress(传递progress)代码即可,加个flag变量,updateByPro=true,按下变为false
b. ACTION_UP手指松开,flag=true

2)手指拖动歌词,所有歌词偏移,得求手指的move,手指按下a中记录y值
c. ACTION_MOVE,记录当前的y值,偏移y等于当前-记录,将歌词重新绘制出来,一说到这个,肯定用到onDraw(),还记得drawMutieLine(),这边如果标志变量为false,这边的偏移量也不用了(加个if),说明是手指控制,偏移y值加到外面,如果手势更新设置算的偏移y给咱的偏移对象,重新绘制执行invidate
3)新问题,拖动差值,加上的话会有偏差,因为任然在放歌,把原本进度的y记录,最后手势更新一起加上绘制就可以了。目前只是绘制出来

十五:拖动歌词更新

  1. 处理居中行的变化,每个都有行高,移动最终的偏移量大于原播放行高就要变化,偏移行数整除就知道了,然后一加搞定。
  2. 发现歌词移动速度比我们手移得快,偏移位置的问题,重新确定偏移y值,去掉整行高 。还有一个原因是居中行的变化,第几个居中,偏移几个像素,这里不细说了,暂时没法实践
  3. 拖动同步更新,居中行改变时,更新播放进度
    1)java用接口回调,kotlin函数一等公民写个进度回调函数和设置进度回调函数
 //进度回调函数private var listener:((progress:Int)->Unit)? = null//设置进度回调函数函数fun setProgressListener(listener:(progress:Int)->Unit){this.listener  = listener}

2)LyricView中调用更新播放进度,传递当前行的时间,之前做的移动行得到当前时间
3)APATY中设置一个拖动监听,lyricView.setProgresslistener,it接收并更新播放进度及显示,用之前的方法就可以了(seekto和updatePregress)

十六:优化更新

1.歌曲信息

  1. 歌曲名的显示问题,如:园游会显示为周杰伦-园游会,数据库查询问题,以前LocalPlayer的方法查询数据库,将DISPLAY_NAME改为Title
    1)VBangFragment在查询的部分修改
    2)在AudioBean设置参数的地方修改,变为Title后就不用截取.flac了

2.通知栏

  1. 设置对应的歌曲封面,原本是通过找到的id来进行对应封面库匹配,结果存入到bean类中的Bitmap对象,基本属性列举
  2. 补充一下,访问本地媒体库的播放时长,mediaplayer是有自己的方法的,不用在查询时做。 而且可以加一个favourite是否喜欢的属性
  3. Drawable和bitmap转换,关于通知栏,效果是这样的,以后封装成专辑封面

【Kotlin】FarawayPlayer相关推荐

  1. Kotlin第5篇项目实战2:开发【Kotlin】版QQ2006聊天工具-关东升-专题视频课程

    Kotlin第5篇项目实战2:开发[Kotlin]版QQ2006聊天工具-420人已学习 课程介绍         本视频是智捷课堂推出的一套"Kotlin语言学习立体教程"的视频 ...

  2. Kotlin第4篇 【Kotlin】进阶视频课程-关东升-专题视频课程

    Kotlin第4篇 [Kotlin]进阶视频课程-376人已学习 课程介绍         本视频是智捷课堂推出的一套"Kotlin语言学习立体教程"的视频第四部分,主要内容包括: ...

  3. Kotlin从小白到大牛第1篇 【Kotlin】基础视频课程-关东升-专题视频课程

    Kotlin从小白到大牛第1篇 [Kotlin]基础视频课程-7239人已学习 课程介绍         本视频是智捷课堂推出的一套"Kotlin语言学习立体教程"的视频第一部分, ...

  4. 【Kotlin】Kotlin 自定义组件 ( 自定义 View | 自定义 SurfaceView )

    文章目录 一.自定义 View 组件 ( Kotlin ) 二.自定义 SurfaceView 组件 ( Kotlin ) 自定义组件构造函数统一在 constructor(context: Cont ...

  5. 【Kotlin】循环控制流 ( for 循环 | Iterator 遍历形式 | Iterator 遍历要求 | IntArray 源码解析 )

    文章目录 一.For 循环 二.For 循环遍历 Iterator 对象 三.Iterator 遍历要求 四.IntArray 源码解析 一.For 循环 For 循环有两种遍历形式 : 一种是 遍历 ...

  6. 【Kotlin】函数类型 ( 函数类型 | 带参数名称的参数列表 | 可空函数类型 | 复杂函数类型 | 带接收者函数类型 | 函数类型别名 | 函数类型实例化 | 函数调用 )

    文章目录 I . 函数类型 II . 带参数名的参数列表 III . 可空函数类型 IV . 复杂函数类型解读 V . 函数类型别名 VI . 带 接收者类型 的函数类型 VII . 函数类型实例化 ...

  7. 【Kotlin】Lambda 表达式 ( 简介 | 表达式语法 | 表达式类型 | 表达式返回值 | 调用方式 | 完整示例 )

    文章目录 I . Lambda 表达式 简介 II . Lambda 表达式语法 III . Lambda 表达式类型 IV . Lambda 表达式返回值 V . Lambda 表达式调用 VI . ...

  8. 【Kotlin】变量简介 ( 可空类型 | lateinit | 初始化判定 | 非空类型 | !! 与 ? 修饰符 | ?= ?. ?: 运算符 | 抽象属性变量)

    文章目录 I . Kotlin 变量总结 II . Kotlin 非空变量 III . 非空变量不能赋空 IV . lateinit 关键字 V . lateinit 初始化判定 VI . Kotli ...

  9. 【Kotlin】Kotlin 中使用 ButterKnife ( 仅用于适配 Kotlin 语言 | 不推荐新项目使用 )

    文章目录 I . 特别注意 : ButterKnife 已停止维护 ( 新项目禁止使用该框架 ) II . Android Studio 中配置 Kotlin 和 ButterKnife 步骤 III ...

  10. 【Kotlin】扩展接收者 与 分发接收者 ( 类内部扩展用法 | 注意事项 | open 修饰扩展 )

    文章目录 I . 类内部扩展其它类 II . 扩展接收者 与 分发接收者 注意事项 III . open 修饰 分发接收者 类型中的扩展 I . 类内部扩展其它类 1 . 扩展函数 / 属性声明的位置 ...

最新文章

  1. 剑指offer:把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
  2. 很好的阻止了事件的发生_请定好您的闹钟,八月,夜空中将发生这13件超酷的天文事件...
  3. 技嘉主板万能网卡驱动_技嘉Z490系列主板来袭:16相供电/钽电容,堆料更进一步...
  4. POJ - 2352 Stars(线段树/树状数组)
  5. 牛客 216 C 小K的疑惑
  6. 扩容是元素还是数组_Java中对数组的操作
  7. Flask+uwsgi+Nginx环境搭建
  8. [转载] Java8新特新--Stream语法应用在ArrayList的元素移除和排序
  9. SQL SERVER LEFT JOIN, INNER JOIN, RIGHT JOIN
  10. 【c++leetcode】翻转链表
  11. [渝粤教育] 中国地质大学 中外美术史 复习题
  12. 24_多易教育之《yiee数据运营系统》OLAP平台-运营分析篇
  13. mysql字符串查询_mysql字符串查询常用命令
  14. 利用MapGis6.7 对 jpg图像文件进行图形校准
  15. 结构专业规范大全_监理签字用语规范大全,就是这么专业!
  16. 工具网站:10个国外免费、无版权、高清图片素材站
  17. java读取pdf文档
  18. 【五校联考5day2】光棍
  19. 【WebService】Caused by: org.apache.cxf.transport.http.HTTPException: HTTP respon ...
  20. 抖音、快手、B站的广告投放原理

热门文章

  1. 新股高中签率的技巧|提高新股中签率技巧
  2. 2021年12月22日 腾讯会议Ipad录屏无法录制声音问题解决
  3. pkg-config
  4. Oracle EBS AutoConfig详解
  5. 工业制造中的UWB定位技术
  6. GD32 定时器+一个IO实现SIF读取
  7. 荣耀8X0安卓系统切换鸿蒙系统,华为大改安卓Q,EMUI 10将启用全新UI,花粉沸腾:静候鸿蒙...
  8. Qt 实现画线笔锋效果详细原理
  9. 路由器、交换机设备管理
  10. System.Data.SqlClient.SqlError: Exclusive access could not be obtained because the database is in us