Android性能優(yōu)化之RecyclerView分頁(yè)加載組件功能詳解
引言
在Android應(yīng)用中,列表有著舉足輕重的地位,幾乎所有的應(yīng)用都有列表的身影,但是對(duì)于列表的交互體驗(yàn)一直是一個(gè)大問(wèn)題。在性能比較好的設(shè)備上,列表滑動(dòng)幾乎看不出任何卡頓,但是放在低端機(jī)上,卡頓會(huì)比較明顯,而且列表中經(jīng)常會(huì)伴隨圖片的加載,卡頓會(huì)更加嚴(yán)重,因此本章從手寫(xiě)分頁(yè)加載組件入手,并對(duì)列表卡頓做出對(duì)應(yīng)的優(yōu)化
1 分頁(yè)加載組件
為什么要分頁(yè)加載,通常列表數(shù)據(jù)存儲(chǔ)在服務(wù)端會(huì)超過(guò)100條,甚至上千條,如果服務(wù)端一次性返回,我們一次性接受直接加載,如果其中有圖片加載,肯定直接報(bào)OOM,應(yīng)用崩潰,因此我們通常會(huì)跟服務(wù)端約定分頁(yè)的規(guī)則,服務(wù)端會(huì)按照頁(yè)碼從0開(kāi)始給數(shù)據(jù),或者在數(shù)據(jù)中返回下一頁(yè)對(duì)應(yīng)的索引,當(dāng)出發(fā)分頁(yè)加載時(shí),就會(huì)拿到下一頁(yè)的頁(yè)碼請(qǐng)求新一頁(yè)的數(shù)據(jù)。
目前在JetPack組件中,Paging是使用比較多的一個(gè)分頁(yè)加載組件,但是Paging使用的場(chǎng)景有限,因?yàn)榱鞯南拗?,?dǎo)致只能是單一數(shù)據(jù)源,而且數(shù)據(jù)不能斷,只能全部加載進(jìn)來(lái),因此決定手寫(xiě)一個(gè)分頁(yè)加載組件,適用多種場(chǎng)景。
1.1 功能定制
如果想要自己寫(xiě)一個(gè)分頁(yè)加載庫(kù),首先需要明白,分頁(yè)加載組件需要做什么事?
對(duì)于RecyclerView來(lái)說(shuō),它的主要功能就是創(chuàng)建視圖并綁定數(shù)據(jù),因此我們先定義分頁(yè)列表的基礎(chǔ)能力,綁定視圖和數(shù)據(jù)
interface IPagingList<T> {
fun bindView(context: Context,lifecycleOwner: LifecycleOwner, recyclerView: RecyclerView,adapter: PagingAdapter<T>,mode: ListMode) {}
fun bindData(model: List<BasePagingModel<T>>) {}
}
bindData:
bindData就不多說(shuō)了,就是綁定數(shù)據(jù),首先我們拿到的數(shù)據(jù)一定是一個(gè)列表數(shù)據(jù),因?yàn)椴⒉恢罉I(yè)務(wù)方需要展示的數(shù)據(jù)類(lèi)型是啥樣的,因此需要泛型修飾,那么BasePagingModel是干什么的呢?
open class BasePagingModel<T>(
var pageCount: String = "", //頁(yè)碼
var type: Int = 1, //分頁(yè)類(lèi)型 1 帶日期 2 普通列表
var time: String = "", //如果是帶日期的model,那么需要傳入此值
var itemData: T? = null
)
首先BasePagingModel是分頁(yè)列表中數(shù)據(jù)的基類(lèi),其中存儲(chǔ)的元素包括pageCount,代表傳進(jìn)來(lái)的數(shù)據(jù)列表是哪一頁(yè),type用來(lái)區(qū)分列表數(shù)據(jù)類(lèi)型,time可以代表當(dāng)前數(shù)據(jù)在服務(wù)端的時(shí)間(主要場(chǎng)景就是列表中數(shù)據(jù)展示需要帶時(shí)間,并根據(jù)某一天進(jìn)行數(shù)據(jù)聚合),itemData代表業(yè)務(wù)層需要處理的數(shù)據(jù)。
bindView:
對(duì)于RecyclerView來(lái)說(shuō),創(chuàng)建視圖、展示數(shù)據(jù)需要適配器,因此這里傳入了RecyclerView還有通用的適配器PagingAdapter
abstract class PagingAdapter<T> : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var datas: List<BasePagingModel<T>>? = null
private var maps: MutableMap<String, MutableList<BasePagingModel<T>>>? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return buildBusinessHolder(parent, viewType)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (datas != null) {
bindBusinessData(holder, position, datas)
} else if (maps != null) {
bindBusinessMapData(holder, position, maps)
}
}
abstract fun getHolderWidth(context: Context):Int
override fun getItemCount(): Int {
return if (datas != null) datas!!.size else 0
}
open fun bindBusinessMapData(
holder: RecyclerView.ViewHolder,
position: Int,
maps: MutableMap<String, MutableList<BasePagingModel<T>>>?
) {
}
open fun bindBusinessData(
holder: RecyclerView.ViewHolder,
position: Int,
datas: List<BasePagingModel<T>>?
) {
}
abstract fun buildBusinessHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder
fun setPagingData(datas: List<BasePagingModel<T>>) {
this.datas = datas
notifyDataSetChanged()
}
fun setPagingMapData(maps: MutableMap<String, MutableList<BasePagingModel<T>>>) {
this.maps = maps
notifyDataSetChanged()
}
}
這一章,我們先介紹使用場(chǎng)景比較多的單數(shù)據(jù)列表
PagingAdapter是一個(gè)抽象類(lèi),攜帶的數(shù)據(jù)同樣是業(yè)務(wù)方需要處理的數(shù)據(jù),是一個(gè)泛型,創(chuàng)建視圖方法buildBusinessHolder交給業(yè)務(wù)方實(shí)現(xiàn),這里我們關(guān)注兩個(gè)數(shù)據(jù)相關(guān)的方法 bindBusinessData和setPagingData,當(dāng)調(diào)用setPagingData方法時(shí),將處理好的數(shù)據(jù)列表發(fā)進(jìn)來(lái),然后調(diào)用notifyDataSetChanged方法刷新列表,這個(gè)時(shí)候會(huì)調(diào)用bindBusinessData將列表中的數(shù)據(jù)綁定并展示出來(lái)。
這里我們還需要關(guān)注一個(gè)方法,這個(gè)方法業(yè)務(wù)方必須要實(shí)現(xiàn),這個(gè)方法有什么作用呢?
abstract fun getHolderWidth(context: Context):Int
這個(gè)方法用于返回列表中每個(gè)ItemView的尺寸寬度,因?yàn)樵诜猪?yè)組件中會(huì)判斷當(dāng)前列表可見(jiàn)的ItemView有多少個(gè)。這里大家可能會(huì)有疑問(wèn),RecyclerView的LayoutManager不是有對(duì)應(yīng)的api嗎,像
findFirstVisibleItemPosition() findLastVisibleItemPosition() findFirstCompletelyVisibleItemPosition() findLastCompletelyVisibleItemPosition()
為什么不用呢?因?yàn)槲覀兊姆猪?yè)組件是要兼容多種視圖形式的,雖然我們今天講到的普通列表用這個(gè)是沒(méi)有問(wèn)題的,但是有些視圖類(lèi)型是不能兼容這個(gè)api的,后續(xù)會(huì)介紹。
1.2 手寫(xiě)分頁(yè)列表
先把第一版的代碼貼出來(lái),有個(gè)完整的體系
class PagingList<T> : IPagingList<T>, IModelProcess<T>, LifecycleEventObserver {
private var mTotalScroll = 0
private var mCallback: IPagingCallback? = null
private var currentPageIndex = ""
//模式
private var mode: ListMode = ListMode.DATE
private var adapter: PagingAdapter<T>? = null
//支持的類(lèi)型 普通列表
private val dateMap: MutableMap<String, MutableList<BasePagingModel<T>>> by lazy {
mutableMapOf()
}
private val simpleList: MutableList<BasePagingModel<T>> by lazy {
mutableListOf()
}
override fun bindView(
context: Context,
lifecycleOwner: LifecycleOwner,
recyclerView: RecyclerView,
adapter: PagingAdapter<T>,
mode: ListMode
) {
this.mode = mode
this.adapter = adapter
recyclerView.adapter = adapter
recyclerView.layoutManager =
LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
addRecyclerListener(recyclerView)
lifecycleOwner.lifecycle.addObserver(this)
}
private fun addRecyclerListener(recyclerView: RecyclerView) {
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
if (!recyclerView.canScrollHorizontally(1) && currentPageIndex == "-1" && mTotalScroll > 0) {
//滑動(dòng)到底部
mCallback?.scrollEnd()
}
//獲取可見(jiàn)item的個(gè)數(shù)
val visibleCount = getVisibleItemCount(recyclerView.context, recyclerView)
if (recyclerView.childCount > 0 && visibleCount >= (getListCount(mode) ?: 0)) {
if (currentPageIndex != "-1") {
//請(qǐng)求下一頁(yè)數(shù)據(jù)
mCallback?.scrollRefresh()
}
}
} else {
//暫停刷新
mCallback?.scrolling()
}
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (!recyclerView.canScrollHorizontally(1) && currentPageIndex == "-1" && mTotalScroll > 0) {
//滑動(dòng)到底部
mCallback?.scrollEnd()
}
mTotalScroll += dx
//滑動(dòng)超出2屏
// binding.ivBackFirst.visibility =
// if (mTotalScroll > ScreenUtils.getScreenWidth(requireContext()) * 2) View.VISIBLE else View.GONE
}
})
}
override fun bindData(model: List<BasePagingModel<T>>) {
//處理數(shù)據(jù)
dealPagingModel(model)
//adapter刷新數(shù)據(jù)
if (mode == ListMode.DATE) {
adapter?.setPagingMapData(dateMap)
} else {
adapter?.setPagingData(simpleList)
}
}
fun setScrollListener(callback: IPagingCallback) {
this.mCallback = callback
}
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == Lifecycle.Event.ON_RESUME) {
//TODO 加載圖片
// Glide.with(requireContext()).resumeRequests()
} else if (event == Lifecycle.Event.ON_PAUSE) {
//TODO 停止加載圖片
} else if (event == Lifecycle.Event.ON_DESTROY) {
//TODO 頁(yè)面銷(xiāo)毀不會(huì)加載圖片
}
}
/**
* 獲取可見(jiàn)的item個(gè)數(shù)
*/
private fun getVisibleItemCount(context: Context, recyclerView: RecyclerView): Int {
var totalCount = 0
//首屏假設(shè)全部占滿(mǎn)
totalCount +=
ScreenUtils.getScreenWidth(recyclerView.context) / adapter?.getHolderWidth(context)!!
totalCount += mTotalScroll / adapter?.getHolderWidth(context)!!
return (totalCount + 1)
}
override fun getTotalCount(): Int? {
return getListCount(mode)
}
override fun dealPagingModel(data: List<BasePagingModel<T>>) {
this.currentPageIndex = updateCurrentPageIndex(data)
if (mode == ListMode.DATE) {
data.forEach { model ->
val time = DateFormatterUtils.check(model.time)
if (dateMap.containsKey(time)) {
model.itemData?.let {
dateMap[time]?.add(model)
}
} else {
val list = mutableListOf<BasePagingModel<T>>()
list.add(model)
dateMap[time] = list
}
}
} else {
simpleList.addAll(data)
}
}
private fun updateCurrentPageIndex(data: List<BasePagingModel<T>>): String {
if (data.isNotEmpty()) {
return data[0].pageCount
}
return "-1"
}
private fun getListCount(mode: ListMode): Int? {
var count = 0
if (mode == ListMode.DATE) {
dateMap.keys.forEach { key ->
//獲取key下的元素個(gè)數(shù)
count += dateMap[key]?.size ?: 0
}
} else {
count = simpleList.size
}
return count
}
}
首先,PagingList實(shí)現(xiàn)了IPagingList接口,我們先看實(shí)現(xiàn),在bindView方法中,其實(shí)就是給RecyclerView設(shè)置了適配器,然后注冊(cè)了RecyclerView的滑動(dòng)監(jiān)聽(tīng),我們看下監(jiān)聽(tīng)器中的主要實(shí)現(xiàn)。
onScrollStateChanged方法主要用于監(jiān)聽(tīng)列表是否在滑動(dòng),當(dāng)列表的狀態(tài)為SCROLL_STATE_IDLE時(shí),代表列表停止了滑動(dòng),這里做了兩件事:
(1)首先判斷列表是否滑動(dòng)到了底部
if (!recyclerView.canScrollHorizontally(1) && currentPageIndex == "-1" && mTotalScroll > 0) {
//滑動(dòng)到底部
mCallback?.scrollEnd()
}
這里需要滿(mǎn)足三個(gè)條件:recyclerView.canScrollHorizontally(1)如果返回了false,那么代表列表不能繼續(xù)滑動(dòng);還有就是會(huì)判斷currentPageIndex是否是最后一頁(yè),如果等于-1那么就是最后一頁(yè),同樣需要判斷滑動(dòng)的距離,綜合來(lái)說(shuō)就是【如果列表滑動(dòng)到了最后一頁(yè)而且不能再繼續(xù)滑動(dòng)了,那么就是到底了】,這里可以展示尾部的到底UI。
(2)判斷是否能夠觸發(fā)分頁(yè)加載
/**
* 獲取可見(jiàn)的item個(gè)數(shù)
*/
private fun getVisibleItemCount(context: Context, recyclerView: RecyclerView): Int {
var totalCount = 0
//首屏假設(shè)全部占滿(mǎn)
totalCount +=
ScreenUtils.getScreenWidth(recyclerView.context) / adapter?.getHolderWidth(context)!!
totalCount += mTotalScroll / adapter?.getHolderWidth(context)!!
return (totalCount + 1)
}
首先這里會(huì)判斷展示了多少I(mǎi)temView,之前提到的適配器中的getHolderWidth這里就用到了,首先我們會(huì)假設(shè)首屏全部占滿(mǎn)了ItemView,然后根據(jù)列表滑動(dòng)的距離,判斷后續(xù)有多少I(mǎi)temView展示出來(lái),最終返回結(jié)果。
我們先不看下面的邏輯,因?yàn)榉猪?yè)加載涉及到了數(shù)據(jù)的處理,因此我們先看下bindData的實(shí)現(xiàn)
override fun bindData(model: List<BasePagingModel<T>>) {
//處理數(shù)據(jù)
dealPagingModel(model)
//adapter刷新數(shù)據(jù)
if (mode == ListMode.DATE) {
adapter?.setPagingMapData(dateMap)
} else {
adapter?.setPagingData(simpleList)
}
}
在調(diào)用bindData時(shí)會(huì)傳入一頁(yè)的數(shù)據(jù),dealPagingModel方法用于處理數(shù)據(jù),首先獲取當(dāng)前數(shù)據(jù)的頁(yè)碼,用于判斷是否需要繼續(xù)分頁(yè)加載。
override fun dealPagingModel(data: List<BasePagingModel<T>>) {
this.currentPageIndex = updateCurrentPageIndex(data)
if (mode == ListMode.DATE) {
data.forEach { model ->
val time = DateFormatterUtils.check(model.time)
if (dateMap.containsKey(time)) {
model.itemData?.let {
dateMap[time]?.add(model)
}
} else {
val list = mutableListOf<BasePagingModel<T>>()
list.add(model)
dateMap[time] = list
}
}
} else {
simpleList.addAll(data)
}
}
剩下的工作用于組裝數(shù)據(jù),simpleList用于存儲(chǔ)全部的列表數(shù)據(jù),每次傳入一頁(yè)數(shù)據(jù),都會(huì)存在這個(gè)集合中。處理完數(shù)據(jù)之后,將數(shù)據(jù)塞進(jìn)adapter,用于刷新數(shù)據(jù)。
然后我們回到前面,我們?cè)谀玫搅丝梢?jiàn)的ItemView的個(gè)數(shù)之后,首先會(huì)判斷recyclerView展示的ItemView個(gè)數(shù),如果等于0,那么就說(shuō)明沒(méi)有數(shù)據(jù),就不需要觸發(fā)分頁(yè)加載。
if (recyclerView.childCount > 0 && visibleCount >= (getListCount(mode) ?: 0)) {
if (currentPageIndex != "-1") {
//請(qǐng)求下一頁(yè)數(shù)據(jù)
mCallback?.scrollRefresh()
}
}
假設(shè)每頁(yè)展示10條數(shù)據(jù),這個(gè)時(shí)候getListCount方法返回的就是總的數(shù)據(jù)個(gè)數(shù)(10),如果visibleCount超過(guò)了List的總個(gè)數(shù),那么就需要觸發(fā)分頁(yè)加載,因?yàn)橹拔覀兲岬?,最后一?yè)的index就是-1,所以這里判斷如果是最后一頁(yè),就不需要分頁(yè)加載了。
1.3 生命周期管理
在PagingList中,我們實(shí)現(xiàn)了LifecycleEventObserver接口,這里的作用是什么呢?
就是我們知道,在列表中經(jīng)常會(huì)有圖片的加載,那么在圖片加載時(shí)如果滑動(dòng)列表,那么勢(shì)必會(huì)產(chǎn)生卡頓,因此我們?cè)诨瑒?dòng)的過(guò)程中不會(huì)去加載圖片,而是在滑動(dòng)停止時(shí),重新加載,這個(gè)優(yōu)化體驗(yàn)是沒(méi)有問(wèn)題,用戶(hù)不會(huì)關(guān)注滑動(dòng)時(shí)的狀態(tài)。
那么這里會(huì)存在一個(gè)問(wèn)題,例如我們?cè)诨瑒?dòng)的過(guò)程中退出到后臺(tái),這個(gè)時(shí)候列表滑動(dòng)停止時(shí)加載圖片,可能存在上下文找不到的場(chǎng)景導(dǎo)致應(yīng)用崩潰,因此我們傳入生命周期的目的在于:讓列表具備感知生命周期的能力,當(dāng)列表處在不可見(jiàn)的狀態(tài)時(shí),不能進(jìn)行多余的網(wǎng)絡(luò)請(qǐng)求。
2022-09-04 15:41:43.541 2763-2763/com.lay.paginglist E/MainActivity: scrolling--
2022-09-04 15:41:43.651 2763-2763/com.lay.paginglist E/MainActivity: scrolling--
2022-09-04 15:41:43.661 2763-2763/com.lay.paginglist E/MainActivity: scrollRefresh--
2022-09-04 15:41:43.668 2763-2763/com.lay.paginglist E/MyAdapter: bindBusinessData ---
2022-09-04 15:41:43.674 2763-2763/com.lay.paginglist E/MyAdapter: bindBusinessData ---
2022-09-04 15:41:43.877 2763-2763/com.lay.paginglist E/MainActivity: scrolling--
2022-09-04 15:41:43.885 2763-2763/com.lay.paginglist E/MyAdapter: bindBusinessData ---
2022-09-04 15:41:43.950 2763-2763/com.lay.paginglist E/MainActivity: scrolling--
2022-09-04 15:41:44.101 2763-2763/com.lay.paginglist E/MyAdapter: bindBusinessData ---
2022-09-04 15:41:44.175 2763-2763/com.lay.paginglist E/MainActivity: scrolling--
2022-09-04 15:41:44.318 2763-2763/com.lay.paginglist E/MainActivity: scrolling--
2022-09-04 15:41:44.467 2763-2763/com.lay.paginglist E/MyAdapter: bindBusinessData ---
2022-09-04 15:41:44.475 2763-2763/com.lay.paginglist E/MainActivity: scrolling--
2022-09-04 15:41:45.188 2763-2777/com.lay.paginglist I/.lay.paginglis: WaitForGcToComplete blocked RunEmptyCheckpoint on ProfileSaver for 12.247ms
2022-09-04 15:41:47.008 2763-2763/com.lay.paginglist E/MainActivity: scrolling--
2022-09-04 15:41:47.099 2763-2763/com.lay.paginglist E/MainActivity: scrolling--
2022-09-04 15:41:47.186 2763-2763/com.lay.paginglist E/MyAdapter: bindBusinessData ---
2022-09-04 15:41:47.322 2763-2763/com.lay.paginglist E/MainActivity: scrolling--
2022-09-04 15:41:47.403 2763-2763/com.lay.paginglist E/MainActivity: scrolling--
2022-09-04 15:41:47.404 2763-2763/com.lay.paginglist E/MyAdapter: bindBusinessData ---
2022-09-04 15:41:47.514 2763-2763/com.lay.paginglist E/MyAdapter: bindBusinessData ---
2022-09-04 15:41:47.606 2763-2763/com.lay.paginglist E/MainActivity: scrolling--
2022-09-04 15:41:47.650 2763-2763/com.lay.paginglist E/MyAdapter: bindBusinessData ---
2022-09-04 15:41:47.683 2763-2763/com.lay.paginglist E/MainActivity: scrolling--
2022-09-04 15:41:47.781 2763-2763/com.lay.paginglist E/MyAdapter: bindBusinessData ---
2022-09-04 15:41:47.889 2763-2763/com.lay.paginglist E/MainActivity: scrolling--
2022-09-04 15:41:47.950 2763-2763/com.lay.paginglist E/MainActivity: scrolling--
2022-09-04 15:41:47.963 2763-2763/com.lay.paginglist E/MyAdapter: bindBusinessData ---
2022-09-04 15:41:48.156 2763-2763/com.lay.paginglist E/MainActivity: scrolling--
2022-09-04 15:41:48.182 2763-2763/com.lay.paginglist E/MyAdapter: bindBusinessData ---
2022-09-04 15:41:48.231 2763-2763/com.lay.paginglist E/MainActivity: scrolling--
2022-09-04 15:41:48.489 2763-2763/com.lay.paginglist E/MainActivity: scrolling--
2022-09-04 15:41:48.533 2763-2763/com.lay.paginglist E/MainActivity: scrolling--
2022-09-04 15:41:48.593 2763-2763/com.lay.paginglist E/MainActivity: scrollEnd--
我們可以看下具體的實(shí)現(xiàn)效果就是,當(dāng)觸發(fā)分頁(yè)加載時(shí),scrollRefresh會(huì)被回調(diào),這里可以進(jìn)行網(wǎng)絡(luò)請(qǐng)求,拿到數(shù)據(jù)之后再次調(diào)用bindData方法,然后繼續(xù)往下滑動(dòng),當(dāng)滑動(dòng)到最后一頁(yè)時(shí),scrollEnd被回調(diào),具體的使用,可以在demo中查看。
2 github
之前有小伙伴提到這個(gè)事情,希望在github上放出源碼,所以就做了 github.com/LLLLLaaayyy…
大家可以在v1.0分支查看源碼,在app模塊中有一個(gè)demo大家可以看具體的使用方式,分頁(yè)列表的代碼在paging模塊中

以上就是Android性能優(yōu)化之RecyclerView分頁(yè)加載組件功能詳解的詳細(xì)內(nèi)容,更多關(guān)于Android RecyclerView分頁(yè)加載的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android抓取CSDN首頁(yè)極客頭條內(nèi)容完整實(shí)例
這篇文章主要介紹了Android抓取CSDN首頁(yè)極客頭條內(nèi)容完整實(shí)例,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-01-01
詳解Android中的ActivityThread和APP啟動(dòng)過(guò)程
ActivityThread就是我們常說(shuō)的主線程或UI線程,ActivityThread的main方法是整個(gè)APP的入口,本篇深入學(xué)習(xí)下ActivityThread,順便了解下APP和Activity的啟動(dòng)過(guò)程。2021-06-06
Android開(kāi)發(fā)之MediaPlayer多媒體(音頻,視頻)播放工具類(lèi)
這篇文章主要介紹了Android開(kāi)發(fā)之MediaPlayer多媒體(音頻,視頻)播放工具類(lèi),涉及Android針對(duì)音頻文件的讀取、播放、暫停、繼續(xù)等操作實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-12-12
Android NavigationBar問(wèn)題處理的方法
本篇文章主要介紹了Android NavigationBar問(wèn)題處理的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-10-10
Android不規(guī)則封閉區(qū)域填充色彩的實(shí)例代碼
這篇文章主要介紹了Android不規(guī)則封閉區(qū)域填充色彩的實(shí)例代碼, 具有很好的參考價(jià)值,希望對(duì)大家有所幫助,一起跟隨小編過(guò)來(lái)看看吧2018-05-05
Android編程設(shè)計(jì)模式之工廠方法模式實(shí)例詳解
這篇文章主要介紹了Android編程設(shè)計(jì)模式之工廠方法模式,結(jié)合實(shí)例形式詳細(xì)分析了Android工廠方法模式的概念、原理、使用方法及相關(guān)注意事項(xiàng),需要的朋友可以參考下2017-12-12
Android 游戲引擎libgdx 資源加載進(jìn)度百分比顯示案例分析
因?yàn)榘咐容^簡(jiǎn)單,所以簡(jiǎn)單用AndroidApplication -> Game -> Stage 搭建框架感興趣的朋友可以參考下2013-01-01
Android實(shí)現(xiàn)登錄界面的注冊(cè)功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)登錄界面的注冊(cè)功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04
Android車(chē)載空調(diào)系統(tǒng)(HVAC)開(kāi)發(fā)方法分析
HVAC?全稱(chēng):供暖通風(fēng)與空氣調(diào)節(jié)(Heating?Ventilation?and?Air?Conditioning),用戶(hù)可以通過(guò)他來(lái)控制整個(gè)汽車(chē)的空調(diào)系統(tǒng),是汽車(chē)中非常重要的一個(gè)功能,汽車(chē)的空調(diào)HMI雖然并不復(fù)雜,但是大多都是用符號(hào)來(lái)表示功能,必須理解空調(diào)的各個(gè)符號(hào)表示的含義2023-12-12

