aN情景
系列ol2.x
集成开发框架使用
livery
1.2.x开始使用了androidx,需要替换掉了所有的support-v4,v7包.aN情景
系列ol2.x
集成开发框架使用ol
是一款针对Android平台下的便捷集成开发框架,帮助开发者架构企业级应用.
基于Livery(版本1.3.12)
演化而来,针对此做了很多优化,当前优化后最新体积仅有868.4KB.
⚠️注意
2023年1🈷️15日
当前ol
最新版本为:,建议使用最新版本。查看
旧版本日志
也可以了解到更多的信息.
我的唯一知乎地址. (感谢关注🙏)
专注于物联网领域,世界的通信标准从今开始改变,手机也可以是路由器,成功于视频直播,标准并不一定是Http/s,也可以是Bluetooth.
ol
一路走来经历了很多版本,现在是一个非常成熟的稳定版本
;它包含一些很实用的能力和技巧, 用简洁友好的方式,助力便捷开发;以下列举当前支持的能力fuction.
vm.download()
).PictureSelector
⚠️livery旧版图片选择已移除).漫天飞羽
). ~~~集成方式有三种,集成之前,先在你项目的根目录setting.gradle
文件→repositories
属性下加入远程库地址.
pluginManagement {
repositories {
gradlePluginPortal()
google()
mavenCentral()
maven { url 'https://plugins.gradle.org/m2/' }
maven { url 'https://jitpack.io' }
maven { url 'https://maven.aliyun.com/nexus/content/repository/google' }
maven { url 'https://maven.aliyun.com/nexus/content/groups/public' }
maven { url 'https://maven.aliyun.com/nexus/content/repository/jcenter' }
maven { url 'https://maven.aliyun.com/repository/releases' }
maven { url "https://maven.aliyun.com/repository/google" }
maven { url "https://maven.aliyun.com/repository/public" }
maven {url 'https://developer.huawei.com/repo/'}
// maven { url 'https://google.bintray.com/flexbox-layout' }
}
}
⚠️注意
由于
Jcenter服务关闭
,请按照以上远程库的顺序配置.
(module)
的**build.gradle
**中添加:dependencies {
implementation'com.github.qydq:ol:2.0.0'
}
致谢sonatype.
AAR
拷贝到libs目录中添加引用关系:dependencies {
implementation(name:'ba', ext:'aar')
implementation(name:'ol', ext:'aar')
}
使用网络请求
,在你的XXXApplication
中继承KApplication
,然后在onCreate()
方法中调用如下代码
//推荐使用方式1
OLibrary.initApi(ApiService.api)
.addHeader("Client-Version", "APP-" + PackageUtils.versionName)
.addHeader("UUID", PackageUtils.uid ?: "").setBaseUrl(BuildConfig.BASE_URL)
.setToken("your token")
.addTokenExpiredListener { }.addErrorHandler().initBugly("buglyId")
//使用方式2
OLibrary.initApi(ApiService.api)
OLibrary.addHeader("Client-Version", "APP-" + PackageUtils.versionName)
OLibrary.addHeader("UUID", PackageUtils.uid ?: "")
OLibrary.setBaseUrl(BuildConfig.BASE_URL)
OLibrary.setToken("your token")
OLibrary.addTokenExpiredListener { }
OLibrary.addErrorHandler()
OLibrary.initBugly("buglyId")
主要是initApi(ApiService.api)
,ApiService为解耦的服务接口,BASE_URL
是符合RESTful的网络地址,如:https://qydq.github.io/
,需要以/
结尾,如果请求网络需要验证登录则需要调用setToken(token)
设置token.
⚠️注意
ol
还有其它配置,如日志崩溃记录bugly,数据库setDbName,渠道setChannel,最后需要把XXXApplication
添加到AndroidManifest.xml
中.
在集成ol
时,可能需要合并AndroidManifest,否则可能造成冲突.
<application
android:name="com.sunst0069.demo.XXXApplication"
android:allowBackup="true"
android:icon="@drawable/hong"
android:label="@string/app_name"
android:requestLegacyExternalStorage="true"
android:roundIcon="@drawable/hong"
android:supportsRtl="true"
android:theme="@style/ParallaxAppTheme"
tools:replace="android:theme,android:name">
⚠️注意
建议继承扩展
ol
中的类和主题,否则如夜间模式主题不可用.
这里介绍部分情景能力(核心,可选)
的使用方法,更多ol
使用可以查看中文API帮助文档2.0.x,或在an情景专栏
中获取.
⚠️注意
hong.apk
为提供的安装包(可以扫描前面的二维码下载),如果实际项目用到上传图片的功能,建议使用PictureSelector
第三方图片框架,并建议尽量少的依赖其它库来完成your project.
一个简单启动页面为例:kotlin Code
@SuppressLint("CustomSplashScreen")
@ActivityNoTitle
class SplashActivity : BindingActivity<ActivitySplashBinding>() {
override fun initView(instanceIntent: Intent?) {
binding.ivSplash.animate().alpha(1f)?.setDuration(2000)
?.setListener(object : Animator.AnimatorListener {
override fun onAnimationStart(animation: Animator) {
}
override fun onAnimationEnd(animation: Animator) {
if (OLibrary.isLogin()) {
startActivity(Intent(mThis, MainActivity::class.java))
} else {
LoginActivity.startAction(mThis)
finish()
}
}
override fun onAnimationCancel(animation: Animator) {}
override fun onAnimationRepeat(animation: Animator) {}
})?.start()
binding.tvProduct.animate()?.alpha(0.5f)?.setDuration(500)?.start()
// todo : ur implemented
}
}
布局文件:activity_splash.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/ivSplash"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/bgwan" />
<TextView
android:id="@+id/tvProduct"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="80dp"
android:letterSpacing="0.2"
android:text="@string/app_name"
android:textColor="@color/white"
android:textSize="@dimen/an_font_large" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center"
android:layout_marginBottom="80dp"
android:letterSpacing="0.2"
android:text="@string/copyRight"
android:textColor="@color/white"
android:textSize="@dimen/an_font_level3" />
</FrameLayout>
</layout>
一个简单的例子:kotlin Code
class TestFragment : BindingFragment<FragmentSecondBinding, BaseViewModel>() {
companion object {
fun instance(data: String): TestFragment {
val fragment = TestFragment()
val bundle = Bundle()
bundle.putString("deptCode", data)
fragment.arguments = bundle
return fragment
}
}
override fun initView(instanceIntent: Intent?) {
TODO("UR implemented")
}
}
布局文件:fragment_second.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data />
<LinearLayout
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="wrap_content"
android:orientation="vertical"
android:overScrollMode="never"
android:scrollbars="none"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</LinearLayout>
</layout>
⚠️说明
BindingActivity
与BindingFragment
两者都能使用ActivityNoTitle
表示此窗口无标题;通过binding对象的使用,可以不写findViewById
;当然你也可以不使用dataBinding,就按照正常的写法将Binding改为Base,如:BindingActivity改为BaseActivity也是一样的,但是这样你就需要手动findViewById
.- 特别注意
ol
提供了两个BindingActivity
与BindingFragment
,一个包在com.sunst.ol.
下,一个包在com.sunst.ol.md.
下,它们的区别是前者不需要提供mvvm的vm具体实现,后者需要提供继承于BaseViewModel
的vm具体实现.BindingFragment
默认启动了懒加载。类似的还有BaseDialog
,这里不再介绍.
一个简单的适配器DemoAdapter1
列表,同样支持dataBinding操作.
class ExampleActivity : BindingActivity<ActivityRoomBinding>() {
private val adapter by lazy { DemoAdapter1() }
override fun initView(instanceIntent: Intent?) {
binding.recyclerView.adapter = adapter
val list = arrayListOf<AppData>()
for (i in 1..30) {
val appData = AppData()
appData.ext = "成都欢迎你$i"
list.add(appData)
}
adapter.setList(list)
adapter.addChildClickViewIds(R.id.tvName, R.id.tvContent)
adapter.setOnItemChildClickListener { _, view, position ->
when (view.id) {
R.id.tvName -> {
toast(adapter.data[position].ext ?: "空数据")
}
R.id.tvContent -> {
toast("Bgwan")
}
}
}
adapter.listener = { v, item, data ->
} }
//com.sunst.ol.BaseAdapter
inner class ListAdapter : BaseAdapter<LevelMode>(R.layout.page_layout) {
override fun bind(holder: BaseViewHolder, item: LevelMode) {
holder.getView<TextView>(R.id.description).text = item.title
}
}
//com.sunst.ol.md.BindingAdapter
inner class DemoAdapter2(data: MutableList<LevelMode>? = null) :
BindingAdapter<ItemMallBinding, LevelMode>(R.layout.item_mall, data) {
override fun bind(holder: BaseViewHolder, item: LevelMode) {
binding.tvName.text = item.title
}
}
}
以Github Api请求qydq示例:https://api.github.com/users/qydq?page=1&per_page=50
返回的json数据为:
{
"login": "qydq",
"id": 20716264,
"node_id": "MDQ6VXNlcjIwNzE2MjY0",
"avatar_url": "https://avatars.githubusercontent.com/u/20716264?v=4",
"url": "https://api.github.com/users/qydq",
"html_url": "https://github.com/qydq",
"name": "晴雨荡气",
"company": "qydq",
"blog": "https://zhihu.com/people/qydq",
"location": "SiChuan Of ChengDu from China",
"bio": "an情景系列ol Android作者,软件开发工程师,致力于物联网,人工智能研究"
}
~第1步:定义一个实体类GithubQydqRes.java
class GithubQydqRes(val id: Int, val node_id: Int) : Serializable {
val serialVersionUID = "GithubQydqRes".hashCode().toLong()
val login: String? = null
val avatar_url: String? = null
val url: String? = null
val html_url: String? = null
val name: String? = null
val company: String? = null
val blog: String? = null
val location: String? = null
val bio: String? = null
}
~第2步:创建ApiService服务接口(具体格式点击参考
)实现
interface ApiService {
@GET(REQUEST_URL) //保留-直接请求1:带参数Query
fun getGithub_AbsQuery(@Query("page") page: Int, @Query("per_page") per_page: Int): Observable<GithubQydqRes>
@GET("users/qydq") //保留-直接请求2:带参数QueryMap的hashMap
fun getGithub_AbsQueryMap(@QueryMap params: Map<String, Object>):Observable<QydqGithubUserRes>
@GET("users/qydq") //保留-间接请求3:定义的Body:这里假设返回数据为List
fun getGithub_BodyReq(@Body req: GithubQydqReq):Observable<List<GithubQydqRes>>
@GET(REQUEST_URL) //推荐使用方法:这里假设返回数据为List
suspend fun getGithub_kotlinSupspend(@Body req: GithubQydqReq): ApiResult<BaseResponse<List<GithubQydqRes>>>
@POST("/users/qydq/post/samples")
fun getHomePage(@Body req: SomethingReq): Observable<BaseResponse<List<SomethingList>>>
//多图片上传
@Multipart
@POST("/api/test/sunsta/upload")
fun uploadFiles(@PartMap map: Map<String, RequestBody>): Observable<ResponseBody>
// 视频上传
@Multipart
@POST("/api/test/sunsta/uploadMovieFiles")
fun uploadMovieFiles(@Part file: MultipartBody.Part, @Part params: MultipartBody.Part): Observable<ResponseBody?> //@PartMap Map<String, RequestBody> params
companion object {
val api = OLibrary.creator(ApiService::class.java)
fun initParams() {
//场景1
var orderParams = BuildOf().page(1).pageSize(10)
//场景2
var orderParams1 = BuildOf().page(1).pageSize(10).park("sunst0069")
//场景3
var ooo = BuildOf().park("").page(1)
BuildOf().park("")
}
const val REQUEST_URL = "https://api.github.com/users/qydq"
}
}
⚠️注意
- 需要在XXXApplication初始化
OLibrary.initApi(ApiService.api)
,并配置BASE_URL,如果在ApiService中没有指定全路径,则会自动拼接请求地址.- SomethingReq为请求参数,SomethingRes为请求响应的参数,GithubQydqReq为响应参数,以上以GET和POST作为演示,其它如上传文件等方法为参考使用.
~第3步:请求网络
请求网络有两种大类,第一类是livery
旧版retrofit+rxJava
的Observable观察者模式(ol
做了兼容保留);另一种是新版ol
扩展,使用ApiResult<BaseResponse<List<SuperRes>>>
协程来接受数据,如声明的getGithub_kotlinSupspend()
方法.
这里以GET请求示例,在基础窗口类 BindingFragment或BindingActivity,下面直接调用getGithub_XXX
方法(调用POST
请求类似,直接将@GET
注解修改为@POST
即可).
(1)第一类兼容性使用方法
//直接请求1:带参数Query
ApiService.api.getGithub_AbsQuery(1,2)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
if (it != null) {
toast(it.toString())
} else {
toast("成功,但数据为空")
}
}
//直接请求2:带参数QueryMap的hashMap
val params = HashMap<String, Int>()
params.put("page", 1)
params.put("per_page", 50)
ApiService.api.getGithub_AbsQueryMap(params)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
if (it != null) {
toast(it.toString())
} else {
toast("成功,但数据为空")
}
}
//间接请求3:定义的Body:这里假设返回数据为List
data class GithubQydqReq(val page: Int?) : BaseRequest() {
var per_page: Int? = 0
}
vm.of(List::class.java).subscribe {
val list = it as List<GithubQydqRes>
adapter.data.clear()
adapter.setList(list)
binding.recyclerView.adapter = adapter
adapter.setEmptyView(R.layout.base_item_error)
val data = list[2]
binding.textStr = data.organName
binding.data = data
val textView = findViewById<TextView>(R.id.tvOne)
textView.leftMargin = 100//扩展函数
}
vm.request<Any>("getGithub_BodyReq", GithubQydqReq(1, 50))
vm.request<Any>(GithubQydqReq(1, 50))//或者
vm.request<List<GithubQydqRes>>(GithubQydqReq(1, 50)){
toast(it?.size.toString())
}//或者
使用vm.request(){}
发起网络请求,可以直接接收返回的数据,如果不直接接收也可以使用vm.of(List::class.java).subscribe
订阅接收消息.
⚠️注意
livery
上个版本使用mHttp.post
发起网络请求,mHttp.of
订阅请求结果,在ol
中进行了重命名.
(2)推荐第二类-基于MVVM
协程的使用方法
class HttpVm : BaseViewModel() {
val listEvent = UnPeekLiveData<Pair<Boolean, List<GithubQydqRes>?>>()
fun getGithubData(loading: Boolean? = false) {
val request = GithubQydqReq(1, 50)
request<List<GithubQydqRes>>(request, loading = loading!!) {
listEvent.postValue(Pair(it?.isNotEmpty()!!, it))
}
}
}
先定义一个MVVM的HttpVm
,在getGithubData
方法中调用request()
将请求的结果用LiveEventBus
(即listEvent)post到Activity中,如HttpNetActivity
class HttpNetActivity : BindingActivity<ActivityNetHttpBinding, HttpVm>() {
private val adapter by lazy { DemoAdapter() }
override fun initView(instanceIntent: Intent?) {
title = "简单网络请求2"
vm.getGithubData(true)
}
override fun dataEvent() {
super.dataEvent()
vm.listEvent.observe(this) {
adapter.data.clear()
adapter.setList(it.second)
binding.recyclerView.adapter = adapter
adapter.setEmptyView(R.layout.base_item_error)
val data = it.second!![2]
binding.textStr = data.organName
binding.data = data
val textView = findViewById<TextView>(R.id.tvOne)
textView.leftMargin = 100//扩展函数
}
adapter.listener = { v, _item, _cmInfo ->
binding.data = _item
}
}
inner class DemoAdapter(data: MutableList<GithubQydqRes>? = null) : BindingAdapter<GithubQydqRes>(R.layout.an_item_barrage, data) {
var listener: ((View, GithubQydqRes, GithubQydqRes) -> Unit)? = null
override fun bind(holder: BaseViewHolder, item: GithubQydqRes) {
holder.setText(R.id.tvName, item.name)
holder.itemView.setOnClickListener {
listener?.invoke(it, item, item)
}
}
}
}
⚠️说明
- 同样也可以直接vm.request(“getGithub_kotlinSupspend”),或者vm.request(GithubQydqReq) 完成网络请求.
- 默认request方法主动toast一些异常信息,如果要启动一个loadding框,需要传一个loadding=true的参数;另外如果不想自动解析网络请求的数据,也可以传递一个origin=true的参数,既原始original方法,后台返回什么就是什么.
- 除此之外,如果我们是在Activity窗口中主动拉起loading(),则在网络请求的时候则不会再显示Loadding,同样关闭Loading则需要我们主动关闭.
~第4步:异常处理
如果确定需要捕获异常信息(如连接超时
,500
,400
,JSON解析异常
),在基础窗口类 BindingFragment或BindingActivity的error(error: BaseError)
直接监听请求失败的场景.
override fun error(error: BaseError) {
//TODO("UR implemented")
}
相比于基于核心DownloaderAsyncTask
的livery
,ol
下载文件改为DownloadManager
,只需调用vm.download()
即可;同样支持断点续传(默认开启),单个\多个文件下载,暂停\取消,数据库状态持久化,下载完成通知提醒(可选),支持apk文件自动安装.
这里以同时下载youwo.apk更新版本作为参考
class DownloadActivity : BindingActivity<ActivityNetDownloadBinding, HttpVm>(), EasyPermission.PermissionCallback {
private val url = "https://bgwan.oss-cn-shanghai.aliyuncs.com/sun/youwo/youwo-last.apk"
override fun initView(instanceIntent: Intent?) {
title = "下载文件"
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermission()
}
binding.startDownload.setOnClickListener { view ->
val downloadRops = DownloadReq(url)
downloadRops.autoOpen = true
downloadRops.canCancel = true
downloadRops.timeOfName = false
downloadRops.openNotification = true
// getMHttp().downloadSerice(getMThis(), rops, downloadReceiver)
val rops = DialogMode(1)
rops.title = "正在下载..."
rops.cancelable = false
rops.titleGravity = Gravity.LEFT
rops.downloadRops = downloadRops
vm.download(downloadRops) {
HttpLogger.d("hong11---" + it.progress)
//下载中。。。
binding.progress.progress = it.progress
val dataService = DataService.instance
val currentDownloadSize: String = dataService.getDataSize(it.finishedLength)
val totalDownloadFileSize: String = dataService.getDataSize(it.length)
val shouldTipsProgress = "$currentDownloadSize/$totalDownloadFileSize"
binding.tv.text = shouldTipsProgress
if (it.finished == OlCode.download_success) {
binding.tv.text = "下载完成" + it.downloadPath
} else if (it.finished == OlCode.download_failure) {
binding.tv.text = it.message//下载失败
}
}
}
binding.tv.text = PictureMimeType.getFileMimeType("sunsta0069.pdf")
// binding.tv.setText(PictureMimeType.getFileMimeType("sunsta0069.pdf"))
}
private fun requestPermission() {
EasyPermission.with(this)
.rationale(StringUtils.getString(R.string.open_my_permission))
.addRequestCode(2)
.permissions(EasyPermission.PERMISSION_INSTALL_PACKAGES)
.request()
}
override fun onPermissionGranted(requestCode: Int, perms: List<String>?) {
HttpLogger.d("hong--onPermissionGranted--$perms")
}
override fun onPermissionDenied(requestCode: Int, perms: List<String>?) {
HttpLogger.d("hong--onPermissionDenied--$perms")
}
}
⚠️注意
ol
目前也保留了livery
下载文件的方式.- 下载是一个耗时的过程,
ol
默认异步任务执行下载过程,这里不会阻塞UI.- 下载成功下载的过程中会创建一个下载通知(默认是关闭的),只需要在设置
openNotification=true
中开启即可启动一个通知功能.- 如果要做多个文件下载,建议在下载的时候给一个下载文件的ID,不给id也可下载文件.
- 文件下载成功默认保存的路径为:sdcard/Aliff/相应的目录文件中,如果没有读写文件权限则会自动保存到包名下面.
- 需要在BindingActivity或BindingFragment中调用
vm.download()
即可完成下载,下载完成以后会自动处理,也可以你主动监听,而不用管内部的实现.
<com.sunst.ba.layout.gif.GifImageView
android:id="@+id/gifImageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@mipmap/gif"/>
循环播放GifImageView
默认播放一次就停止了,我们可以通过GifImageView
获取GifDrawable
,然后再通过GifDrawable
设置循环播放的次数,或者设置无限循环播放
GifImageView gifImageView = findViewById(R.id.gifImageView);
GifDrawable gifDrawable = (GifDrawable) gifImageView.getDrawable();
gifDrawable.setLoopCount(5); //设置具体播放次数
gifDrawable.setLoopCount(0); //设置无限循环播放
网络图片
String url = "https://p6-tt-ipv6.byteimg.com/origin/pgc-image/596f8546221046c2a394ced23120e3d8";
gifImageView.setImageUrl(url);
<com.sunst.ba.layout.INABarrageView
android:id="@+id/barrageView"
android:layout_width="match_parent"
android:layout_height="300dp"
android:layout_marginTop="100dp"
android:background="#FF4081"
app:anBarrageGravity="center"
app:anBarrageFloatTime="4000"
app:anBarrageItemGap="15dp"
app:anBarrageRowNum="1"
app:anBarrageSpeed="500"
app:asBarrageFly="true"
app:asBarrageeFloat="true" />
支持的属性(也可通过代码动态配置)
XML中属性 | 含义 |
---|---|
anBarrageRowNum | //设置弹幕有多少行,默认1行弹幕 |
anBarrageRowHeight | //设置弹幕的高度,默认39dp(当覆盖布局文件中的高度时,会使用xml中的布局高度) |
anBarrageSpeed | //设置弹幕的滚动的速度 ,默认8000秒 |
anBarrageItemGap | //设置弹幕与弹幕之间的间距,默认10dp |
anBarrageRowGap | //设置弹幕与弹幕之间的行距,默认2dp |
anBarrageMode | //设置弹幕的布局方式,默认正常(normal)平均(average) |
anBarrageGravity | //设置弹幕内容,相对于布局INABarrageView的方向, 默认default,则有可能从上到下显示top优先上半部分区域,center中间区域,bottom底部区域 |
anBarrageDuration | // 保留字段,不可用,或无效 |
anBarrageRepeatCount | //设置动画重复的次数,默认0,设置为:ValueAnimator.INFINITE 则该弹幕会无限循环播放 |
anBarrageFloatSpeed | //弹幕悬浮边缘的向左废除的速度 |
anBarrageFloatTime | //弹幕悬浮边缘的停留的时间 |
anBarrageIdleTime | //配置弹幕的空闲时间,注意空闲时间要比一条弹幕的动画时间长 |
asBarrageeFloat | //当弹幕悬浮边缘,开启向弹性向左飞出效果 |
asBarrageAuto | //设置弹幕自动播放,默认false |
asBarrageFly | //设置弹幕是否可以漫天飞羽,默认true |
asKeepSequence | //当漫天飞羽时,设置是否保持先后顺序,默认false表示弹幕在布局INABarrageView内随机没有顺序的,不分先后出现,true表示弹幕每次显示一行,当第一行显示完再显示第二行(当设置弹幕行数大于1(超过默认行数),则漫天飞羽关闭) |
asBarrageInterpolator | //启用设置差值器,(保留字段)动画持续的时间,不同宽度的物体,划过同一个窗口,规定了总时间,以此获取对应的速度 |
漫天飞羽弹幕特效
弹幕显示的核心来自ol
的BarrageDataAdapter
提供,显示一个满天飞羽弹幕
首先需要获取BarrageDataAdapter
实例,如下参考:
BarrageDataAdapter mBarrageAdapter = inaBarrageView.obtainBarrageAdapter(this);
一个简单的列子
private void showDanMu() {
Barrage barrage = new Barrage(BarrageDataAdapter.BarrageType.IMAGE_TEXT);
barrage.setPrimaryIvId(R.drawable.hong);
barrage.setContent("欢迎进入Bgwan的直播间,请多多关注哟~");
mBarrageAdapter.addBarrage(barrage);
}
Barrage会要求传入一个type参数(IMAGE_TEXT图片文字组合弹幕,TEXT纯文字弹幕)
一个复杂的效果
漫天飞羽弹幕使支持许多自定义属性,比如:弹幕的layout,自适应文字长度,弹幕背景颜色,头像,甚至是动画,包括XML中的所有属性,这样可以加一些逻辑就可以做到动态配置每个弹幕行为,下面是一个复杂的弹幕.
private void showDanMu2() {
Barrage barrage = new Barrage(BarrageDataAdapter.BarrageType.IMAGE_TEXT);
barrage.setGifIvId(R.mipmap.gif1);
barrage.setGifIvId2(R.mipmap.gif2);
barrage.setGifIvId3(R.mipmap.gif3);
barrage.setUserName("sunst0069");
barrage.setBarrageLayout(R.layout.an_item_barrage);//弹幕的layout,可以自定义
barrage.setBackground(R.mipmap.image_select);
barrage.setContent("亲爱的热爱的进入了你的房间");
barrage.setBarrageFloat(true);
barrage.setBarrageFloatSpeed(1800L);
// mBarrageAdapter.addBarrage(barrage);//显示弹幕方法1(默认)
mBarrageAdapter.addBarrage(barrage, new OnBarrageLayout() {
@Override
public View barrageLayout(View layoutView, Barrage barrage) {
return mBarrageAdapter.loadBarrageData(layoutView, barrage);
}
});//显示弹幕方法2(该效果和方法1等同效果) }
为了增加开发者控制每一条弹幕(不管Barrage属性里面有值的情况),提供以下操作
mBarrageAdapter.addBarrage(barrage, new OnBarrageLayout() {
@Override
public View barrageLayout(View layoutView, Barrage barrage) {
BarrageItemView barrageItem = mBarrageAdapter.getBarrageItemView(layoutView, barrage);
//barrageItem.getXXX().setXXX()
return barrageItem.getConverView();//显示弹幕方法3(每条弹幕单独控制)
}
});
在遵循an_item_barrage.xml
控件命名的情况下,通过barrageItem对象能够获取每一个弹幕布局的控件.
漫天飞羽弹幕事件
弹幕点击事件:
mBarrageAdapter.setBarrageClickListener(new OnBarrageClickListener() {
@Override
public void onClick(Barrage barrage) {
ToastUtils.s(BarrageActivity.this, "点击:" + barrage.getType());
}
});
弹幕空闲(弹幕空闲Nms后回调,在此函数设置监听)
inaBarrageView.setIdleListener(new OnBarrageIdleListener() {
@Override
public void onIdle(long idleTimeMs, INABarrageView view) {
LaLog.d("hong---弹幕空闲时间,idleTimeMs = " + idleTimeMs + "view=" + view.toString());
}
});
⚠️特别注意
- 弹幕的属性通过xml,java,Barrage设置优先级:Barrage > java > xml,即Barrage中优先级最高.
- 漫天飞宇弹幕使在
livery1.2.0
之后开始支持设置3张Gif,配合自定义layout可以实现更多更炫的特效.- 自定义弹幕布局时控件的命名应与livery中
an_item_barrage.xml
一致,否则在调用mBarrageAdapter.addBarrage()
中需要你自己去findViewById()(除非livery提供的特效不能满足需求).
阿里云sunst/an
从此刻起,拥抱阿里云oss做资源整合管理,github上私人相关尽早做优化
⚠️备份一个命令
doctoc README.md
混淆规则一定要看:Android App代码混淆解决方案click
#---------------------------4.(反射实体)个人指令区-qy晴雨(请关注知乎Bgwan)---------------------
# 混淆一区:ol框架
-keep class com.sunst.ba.**{*;}
-dontwarn com.sunst.ba.**
-keep class com.sunst.ba.layout.**{*;}
-keep class com.sunst.ba.layout.gif.**{*;}
-keep public class com.sunst.ba.layout.gif.GifIOException{<init>(int);}
-keep class com.sunst.ba.layout.gif.GifInfoHandle{<init>(long,int,int,int);}
-keep class com.sunst.ba.layout.inner.**{*;}
-keep class com.sunst.ba.layout.pick.**{*;}
-keep class com.sunst.ba.layout.swip.**{*;}
-keep class com.sunst.ba.ee.ViewModelLifecycle
-keep class com.sunst.ba.md.** { *; }
-keepclasseswithmembers class com.sunst.ba.KConstants$LOG {
<methods>;}
-keepclasseswithmembers class com.sunst.ba.KConstants$DEFAULT {
<methods>;}
-keep class com.sunst.ol.**{*;}
-dontwarn com.sunst.ol.**
# navigationContrller
-keep class com.sunst.ol.layout.**{*;}
-keep class com.sunst.ol.layout.inner.**{*;}
-keep class com.sunst.ol.md.** { *; }
-keep class com.sunst.ba.net.ApiResult {*;}
-keep class com.sunst.ba.net.ApiResult$Success {*;}
-keep class com.sunst.ba.net.ApiResult$Failure {*;}
-keep class com.sunst.ba.md.BaseResponse {*;}
-keep class com.sunst0069.demo.api.ApiService {*;}
-keep class com.sunst0069.demo.BR{*;}
-keep class com.sunst.ba.BR{*;}
-keep class com.sunst.ol.BR{*;}
-keep class * extends com.sunst.ba.md.BaseViewModel
./gradlew processDebugManifest --stacktrace
livery
1.2.x开始使用了androidx,需要替换掉了所有的support-v4,v7包.由于谷歌android版本开发库
更新升级,livery
在版本1.1.x
以后用androidx
替换了所有的support-v4,v7
等,如果你的项目已经包含了v4,v7,
建议删除跟v4,v7
的依赖,如不能删除,请降低使用livery1.1.0
之前的版本,或参考如下配置.
在项目的根目录的build.gradle中添加,这样就可以忽略support相关的包引用问题
configurations {
all*.exclude group :'com.android.support',module:'support-compat'
all*.exclude group :'com.android.support',module:'support-v4'
all*.exclude group :'com.android.support',module:'support-annotations'
all*.exclude group :'com.android.support',module:'support-fragment'
all*.exclude group :'com.android.support',module:'support-core-utils'
all*.exclude group :'com.android.support',module:'support-core-ui'
}
或参考:
implementation('me.imid.swipebacklayout.lib:library:1.1.0') {
exclude group: 'com.android.support' }
这是FileProvider
冲突,修改AndroidManifest.xml文件,(参考:
).
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/gdt_file_path" /> </provider>
<provider
android:name=".utils.BuglyFileProvider"
android:authorities="${applicationId}.fileProvider"
android:exported="false"
android:grantUriPermissions="true"
tools:replace="name,authorities,exported,grantUriPermissions">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/bugly_file_paths"
tools:replace="name,resource" />
</provider>
在你项目的gradle.properties配置文件中加入
android.useAndroidX=true
参考配置:
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
android.injected.testOnly=false
android.disableAutomaticComponentCreation=true
android.useDeprecatedNdk=true
# R. file task
android.nonTransitiveRClass=false
org.gradle.java.home=/Applications/Android Studio.app/Contents/jre/Contents/Home
即`AndroidManifest``资源冲突,在你的AndroidManifest.xml application标签中添加(根据需要添加).
tools:replace="android:icon,android:theme,android:label,android:allowBackup,android:name"
⚠️提示
也可以删除
application
标签中重复的资源属性.
这是rxJava冲突,在app目录的build.gradle下添加
packagingOptions {
exclude 'META-INF/rxjava.properties'
}
ol
框架AAR与
初始APP或DEMO版本日志记录
初始/最新版本 | 项目名称及(时间线) |
AARrelease 大小 |
备注 |
---|---|---|---|
2016/12/09 v0.3.3 2018/06/09 |
an-aw-base |
# | 18年 初始版本为小团子芳儿开发的一款应用级APP |
~2019/12/31 v1.3.12 2022/02/15 |
livery |
6.4M - 867.4KB | 推广引入livery 首发-INATabLayout使用案例aN情景 系列-Livery1.3.0使用 |
v2.0.02023/01/15 v2.0.0 规划2026/06/09 |
ol |
197KB | 基于MVVM的kotlin版本实现 [ aN情景 系列-ol2.0.0使用] |
⚠️说明
- 初始
APP0.0.69
为小团子开发的hong项目,项目的灵感来源.- 表格中版本仅记录aN情景系列-重要内容,
删除线表示此版本不再维护.
⚠️注意
代码提交严格跟随日志内容,方便日后查阅回溯,为控制字数;这里只记录
aN情景
系列-各版本重要日志总述(可以点击以上旧版本log了解详细).
本着追本溯源原则,以下会记录aN情景
系列-各发行版本演变的日志简介.
aN情景
系列-Livery
的第一个发行版本,是从基础的an-base仓库(#原an框架)重构而来,支持的android最低版本为minSdkVersion=19
,总共发行了20个实际版本
,依赖:
implementation 'com.sunsta:livery:1.0.x'
1.0.x包含包含了support系列的库
appcompat : 'com.android.support:appcompat-v7:27.0.2',
constraint: 'com.android.support.constraint:constraint-layout:1.0.2',
design : 'com.android.support:design:27.0.2',
recyclerview : 'com.android.support:recyclerview-v7:27.0.2',
retrofit : 'com.squareup.retrofit2:retrofit:2.3.0',
gson: 'com.squareup.retrofit2:converter-gson:2.1.0',
rxjava2: 'com.squareup.retrofit2:adapter-rxjava2:2.3.0',
okhttp : 'com.squareup.okhttp3:okhttp:3.8.1',
glide : 'com.github.bumptech.glide:glide:4.3.1',
xutils : 'org.xutils:xutils:3.5.0',
glidecompiler: 'com.github.bumptech.glide:compiler:4.3.1',
multipleimageselect: 'com.darsh.multipleimageselect:multipleimageselect:1.0.4',
crop: 'com.soundcloud.android.crop:lib_crop:1.0.0',
advancedluban: 'me.shaohui.advancedluban:library:1.3.2',
nineoldandroids : 'com.nineoldandroids:library:2.4.0',
相比上个系列的版本,不支持support包
,为了控制性能,最低版本minSdkVersion=21
,也就是说,livery1.1.x
不支持android5.0
以下的系统集成 ,依赖:
implementation 'com.sunsta:livery:1.1.x'
⚠️特别注意(记录内容)
api "androidx.concurrent:concurrent-futures:1.0.0-rc01"
api "androidx.camera:camera-lifecycle:1.0.0-alpha01"
api "androidx.camera:camera-core:1.0.0-alpha08"
api "androidx.camera:camera-camera2:1.0.0-alpha05"
相比上个系列的版本,更新了许多最新的库文件,比如glide版本;另外由于jcenter服务关闭,项目的迁移到maven,依赖的规则也发生了变化. maven依赖:
implementation'com.github.qydq:livery:1.2.0'
在aN情景
系列-Livery1.2.x
的基础上,新增和优化了许多实用的工具,如高效加载GIF,烈焰弹幕使;并对项目进行了重构,如基础类库,网络请求更友好,依赖:
implementation'com.github.qydq:livery:1.3.12'
本篇即为ol
的介绍内容,在aN情景
系列-Livery1.3.x
的基础上,完全引入了kotlin版本的jitpack 的MVVM使用方式,并且重构项目,项目采用了kotlin语言开发,这是与livery
区别最大的地方,依赖:
implementation'com.github.qydq:ol:2.0.0'
#---------------------------3.(自定义apk)个人其它说明区-sunst(请关注知乎Bgwan)---------------------
// 便利所有的Variants,all是迭代遍历操作符,相当于for applicationVariants.all { variant ->
// 遍历得出所有的variant
variant.outputs.all {
// 遍历所有的输出类型,一般是debug和replease
// 定义apk的名字,拼接variant的版本号
def apkName = "app_${variant.versionName}"
// 判断是否为空
if (!variant.flavorName.isEmpty()) {
apkName += "_${variant.flavorName}"
}
// 赋值属性
String time = new Date().format("_YYYYMMddHH")
if (variant.buildType.name.equals("release")){
outputFileName = apkName + "_Replease" + time + ".apk"
}else {
outputFileName = apkName + "_Debug" +time + ".apk"
}
}
}
#---------------------------4.(应用内apk安装)个人其它说明区-sunst(请关注知乎Bgwan)---------------------
private Intent getInstallIntent() {
String fileName = savePath + appName + ".apk";
Uri uri = null;
Intent intent = new Intent(Intent.ACTION_VIEW);
try {
if (Build.VERSION.SDK_INT >= 24) {//7.0 Android N //com.xxx.xxx.fileprovider为上述manifest中provider所配置相同 uri = FileProvider.getUriForFile(mContext, "你自己的包名.fileprovider", new File(fileName));
intent.setAction(Intent.ACTION_INSTALL_PACKAGE);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);//7.0以后,系统要求授予临时uri读取权限,安装完毕以后,系统会自动收回权限,该过程没有用户交互 } else {//7.0以下 uri = Uri.fromFile(new File(fileName));
intent.setAction(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
intent.setDataAndType(uri, "application/vnd.android.package-archive");
startActivity(intent);
return intent;
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (ActivityNotFoundException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return intent;
}
非常感谢以下前辈(or开源组织机构)的开源精神,当代互联网的发展离不开前辈们的分享,ol
发布也是.
再次感谢🙏。最后感谢优秀的Github代码管理平台(排名不分先后) .