aN情景系列ol2.x集成开发框架使用

@sunstApache-2.0 Maven Central JCenter MinSdk Release Version

ol是一款针对Android平台下的便捷集成开发框架,帮助开发者架构企业级应用.

基于Livery(版本1.3.12)演化而来,针对此做了很多优化,当前优化后最新体积仅有868.4KB.

⚠️注意

2023年1🈷️15日当前ol最新版本为:Release Version,建议使用最新版本。查看旧版本日志也可以了解到更多的信息.

我的唯一知乎地址.       (感谢关注🙏)

专注于物联网领域,世界的通信标准从今开始改变,手机也可以是路由器,成功于视频直播,标准并不一定是Http/s,也可以是Bluetooth.

情景能力# Ability

ol一路走来经历了很多版本,现在是一个非常成熟的稳定版本;它包含一些很实用的能力和技巧, 用简洁友好的方式,助力便捷开发;以下列举当前支持的能力fuction.

集成方式# Binaries

集成方式有三种,集成之前,先在你项目的根目录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服务关闭,请按照以上远程库的顺序配置.

1.(建议)通过maven集成,在你项目app或其它(module)的**build.gradle**中添加:

dependencies {
  implementation'com.github.qydq:ol:2.0.0'
}    

致谢sonatype.

2.(可选)手动集成 下载最新版本ol-aar,然后将AAR拷贝到libs目录中添加引用关系:

dependencies {    
  implementation(name:'ba', ext:'aar')  
  implementation(name:'ol', ext:'aar')    
}    

使用步骤# Use Step

第1步:配置主Application

使用网络请求,在你的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中.

第2步:配置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中的类和主题,否则如夜间模式主题不可用.

模块介绍# Details Module

这里介绍部分情景能力(核心,可选)的使用方法,更多ol使用可以查看中文API帮助文档2.0.x,或在an情景专栏中获取.

帮助文档

⚠️注意

一:核心情景能力演示

1. 基础窗口类 BindingActivity

一个简单启动页面为例: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>

2. 基础窗口类 BindingFragment

一个简单的例子: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> 

⚠️说明

3. 基础适配器类 BindingAdapter

一个简单的适配器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  
    }  
}
}

4. 基础网络请求 vm

以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"  
    }  
}

⚠️注意

~第3步:请求网络

请求网络有两种大类,第一类是livery旧版retrofit+rxJava的Observable观察者模式(ol做了兼容保留);另一种是新版ol扩展,使用ApiResult<BaseResponse<List<SuperRes>>>协程来接受数据,如声明的getGithub_kotlinSupspend()方法.

这里以GET请求示例,在基础窗口类 BindingFragmentBindingActivity,下面直接调用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订阅接收消息.

⚠️注意

(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)  
            }  
        }  
    }  
}

⚠️说明

~第4步:异常处理

如果确定需要捕获异常信息(如连接超时500400JSON解析异常),在基础窗口类 BindingFragmentBindingActivityerror(error: BaseError)直接监听请求失败的场景.

override fun error(error: BaseError) {  
    //TODO("UR implemented")  
}

5. 基础文件下载 vm

相比于基于核心DownloaderAsyncTaskliveryol下载文件改为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")  
    }  
}

⚠️注意

二:可选情景能力演示

1. 高效GIF加载 像GifImageView

<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);

2.漫天飞羽弹幕使 动画子弹射击

<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 //启用设置差值器,(保留字段)动画持续的时间,不同宽度的物体,划过同一个窗口,规定了总时间,以此获取对应的速度

漫天飞羽弹幕特效

弹幕显示的核心来自olBarrageDataAdapter提供,显示一个满天飞羽弹幕首先需要获取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());    
    }    
});

⚠️特别注意

下载# Download

1.  API帮助文档阿里云sunst/an

从此刻起,拥抱阿里云oss做资源整合管理,github上私人相关尽早做优化

2.  体验demo扫描二维码

  

⚠️备份一个命令

doctoc README.md

混淆配置# proguard-rules

混淆规则一定要看: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 

常见错误# Easy Mistake

./gradlew processDebugManifest --stacktrace  

重要1:livery1.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'  }    

重要2: Manifest merger failed : Attribute meta-data#android.support.FILE_PROVIDER_PATHS.

这是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>  

重要3:This project uses AndroidX dependencies, but the ‘android.useAndroidX’ property is not enabled. Set this property to true in the gradle.properties file and retry.

在你项目的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

注意1:Attribute application@theme value=(@style/AppTheme) from AndroidManifest.xml:11:9-40 is also present at [com.sunsta.livery:livery:1.2.x] AndroidManifest.xml

即`AndroidManifest``资源冲突,在你的AndroidManifest.xml application标签中添加(根据需要添加).

tools:replace="android:icon,android:theme,android:label,android:allowBackup,android:name"

⚠️提示

也可以删除application标签中重复的资源属性.

注意2: More than one file was found with OS independent path ‘META-INF/rxjava.properties’

这是rxJava冲突,在app目录的build.gradle下添加

 packagingOptions {    
   exclude 'META-INF/rxjava.properties'    
  }

更多:其它android中常见错误解决方法点击这里查看。

版本日志# Version LOG

ol框架AAR初始APP或DEMO版本日志记录

初始/最新版本 项目名称及(时间线) AARrelease大小 备注
v0.1.02016/12/09
v0.3.32018/06/09
an-aw-base
# 18年初始版本为小团子芳儿开发的一款应用级APP
~v1.0.1~2019/12/31
v1.3.122022/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使用]

⚠️说明

1.各个历史版本日志记录

⚠️注意

代码提交严格跟随日志内容,方便日后查阅回溯,为控制字数;这里只记录aN情景系列-各版本重要日志总述(可以点击以上旧版本log了解详细).

2.重要版本记录总述

本着追本溯源原则,以下会记录aN情景系列-各发行版本演变的日志简介.

~~livery1.0.x版本总述 ~~

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',

~~livery1.1.x版本总述 ~~

相比上个系列的版本,不支持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"

~~livery1.2.x版本总述 ~~

相比上个系列的版本,更新了许多最新的库文件,比如glide版本;另外由于jcenter服务关闭,项目的迁移到maven,依赖的规则也发生了变化. maven依赖:

implementation'com.github.qydq:livery:1.2.0'

~~livery1.3.x版本总述 ~~

aN情景系列-Livery1.2.x的基础上,新增和优化了许多实用的工具,如高效加载GIF,烈焰弹幕使;并对项目进行了重构,如基础类库,网络请求更友好,依赖:

implementation'com.github.qydq:livery:1.3.12'

~~ol2.0.0版本总述 ~~

本篇即为ol的介绍内容,在aN情景系列-Livery1.3.x的基础上,完全引入了kotlin版本的jitpack 的MVVM使用方式,并且重构项目,项目采用了kotlin语言开发,这是与livery区别最大的地方,依赖:

implementation'com.github.qydq:ol:2.0.0'

其它说明# More

关于自定义apk名说明

#---------------------------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"    
  }
 }    
}

关于应用内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代码管理平台(排名不分先后) .

LICENSE

版权声明©️