本文由玉剛說寫作平臺提供寫作贊助颤练,版權(quán)歸玉剛說微信公眾號所有
原作者:Mr.s(豬_隊友)
版權(quán)聲明:未經(jīng)玉剛說許可,不得以任何形式轉(zhuǎn)載
今年谷歌I/O大會驱负,谷歌發(fā)布了 Android Jetpack.這是新一代組件嗦玖、工具和架構(gòu)指導,用谷歌官方的話就是旨在加快開發(fā)者的 Android 應用開發(fā)速度跃脊。Android Jetpack 組件將現(xiàn)有的支持庫與架構(gòu)組件聯(lián)系起來宇挫,并將它們分成四個類別:
Android Jetpack 組件以“未捆綁的”庫形式提供,這些庫不是基礎 Android 平臺的一部分酪术。這就意味著器瘪,我們可以根據(jù)自己的需求采用每一個組件。在新的 Android Jetpack 功能發(fā)布后绘雁,我們可以將其添加到自己的應用中橡疼,將我們的應用部署到應用商店并向用戶提供新功能,如果我們的行動足夠快庐舟,所有這些可以在一天內(nèi)完成欣除!
那么谷歌發(fā)布JetPack的目的是什么呢?
- 加速開發(fā)
組件可單獨采用挪略,但可以一起使用历帚,同時利用Kotlin語言功能,提高工作效率杠娱。(ps:Kotlin可以大大減少代碼量挽牢,據(jù)說可以減少到1/3,請一定要學習kotlin) - 減少并消除樣板代碼
Android Jetpack管理諸如后臺任務摊求,導航和生命周期管理等繁瑣的活動禽拔,因此我們可以專注提高應用品質(zhì)等其他方面。 - 構(gòu)建高品質(zhì)睹簇,強大的應用
以現(xiàn)代設計實踐為基礎奏赘,Android Jetpack組件可降低崩潰次數(shù)并減少內(nèi)存泄漏寥闪,并向后兼容太惠。
除了這三點外,谷歌想給開發(fā)者定制一套標準疲憋,比如框架的標準,我們平時MVC,MVP昧碉,MVVM等等,現(xiàn)在谷歌自己搞了一套MVP-CLEAN搪锣。
我們從JetPack的四大部分也可以看出,谷歌想要結(jié)束混亂的局面彩掐,給開發(fā)者一個規(guī)范构舟,這個對我們開發(fā)者也是一件好事,跟著官方走總不會差的堵幽。更多參見App體系結(jié)構(gòu)指南狗超。
那么我在這一篇給大家介紹一下Paging、WorkManager朴下、Slices和他們的使用方式努咐,篇幅較長,請大家酌情找尿點殴胧。
Paging(分頁)
背景:
很多應用程序從包含大量項目的數(shù)據(jù)源中獲取數(shù)據(jù)渗稍,但一次只顯示一小部分數(shù)據(jù)。加載應用程序中顯示的數(shù)據(jù)可能很大并且代價高昂团滥,因此要避免一次下載竿屹,創(chuàng)建或呈現(xiàn)太多數(shù)據(jù)。為了可以更輕松地在我們的應用程序中逐漸加載數(shù)據(jù)谷歌方法提供了這個組件灸姊,可以很容易地加載和現(xiàn)在的大數(shù)據(jù)集與我們的RecyclerView快速羔沙,無限滾動。它可以從本地存儲厨钻,網(wǎng)絡或兩者加載分頁數(shù)據(jù)扼雏,并且可以讓我們自定義如何加載內(nèi)容。它可以與Room夯膀,LiveData和RxJava一起使用诗充。
Paging Libray分為三部分:DataSource, PagedList, PagedAdapter
DataSource:
它就像是一個抽水泵,而不是真正的水源诱建,它負責從數(shù)據(jù)源加載數(shù)據(jù)蝴蜓,可以看成是Paging Library與數(shù)據(jù)源之間的接口。
Datasource<Key, Value>是數(shù)據(jù)源相關的類俺猿,Key是加載數(shù)據(jù)的條件信息茎匠,Value是返回結(jié)果, 針對不同場景我們需要用不同的Datasource押袍,Paging提供了三個子類來供我們選擇诵冒。
- PageKeyedDataSource<Key, Value> :適用于目標數(shù)據(jù)根據(jù)頁信息請求數(shù)據(jù)的場景,即Key 字段是頁相關的信息谊惭。比如請求的數(shù)據(jù)的參數(shù)中包含類似next/previous的信息汽馋。
- ItemKeyedDataSource<Key, Value> :適用于目標數(shù)據(jù)的加載依賴特定item的信息侮东, 即Key字段包含的是Item中的信息,比如需要根據(jù)第N項的信息加載第N+1項的數(shù)據(jù)豹芯,傳參中需要傳入第N項的ID時悄雅,該場景多出現(xiàn)于論壇類應用評論信息的請求。
- PositionalDataSource<T>:適用于目標數(shù)據(jù)總數(shù)固定铁蹈,通過特定的位置加載數(shù)據(jù)宽闲,這里Key是Integer類型的位置信息,T即Value握牧。 比如從數(shù)據(jù)庫中的1200條開始加在20條數(shù)據(jù)便锨。
PagedList:
它就像是一個蓄水池,DataSource抽的水放到PagedList中我碟。它是List的子類放案,它包含著我們的數(shù)據(jù)并告訴數(shù)據(jù)源何時加載數(shù)據(jù)。我們也可以配置一次加載多少數(shù)據(jù)矫俺,以及應該預取多少數(shù)據(jù)吱殉。它提供適配器的更新作為頁面中加載的數(shù)據(jù)。
PagedList有五個重要的參數(shù):
mMainThreadExecutor: 主線程的Excutor, 用于將結(jié)果post到主線程厘托。
mBackgroundThreadExecutor: 后臺線程的Excutor.
BoundaryCallback:加載Datasource中的數(shù)據(jù)加載到邊界時的回調(diào).
-
Config: 配置PagedList從Datasource加載數(shù)據(jù)的方式友雳, 其中包含以下屬性:
- pageSize:設置每頁加載的數(shù)量
- prefetchDistance:預加載的數(shù)量
- initialLoadSizeHint:初始化數(shù)據(jù)時加載的數(shù)量
- enablePlaceholders:當item為null是否使用PlaceHolder展示
- PagedStorage<T>: 用于存儲加載到的數(shù)據(jù),它是真正的蓄水池所在铅匹,它包 含一個ArrayList<List<T>> 對象mPages押赊,按頁存儲數(shù)據(jù)。
PagedListAdapter:
這個類是RecyclerView.adapter的實現(xiàn)包斑,它提供來自PagedList的數(shù)據(jù)并以DiffUtil作為參數(shù)來計算數(shù)據(jù)的差異并為你做所有的更新工作流礁。
看十遍不如敲一遍,搞起罗丰,搞起~~(本篇全部用Kotlin語言,涉及到LiveData神帅,ViewModel,Room萌抵,請大家系好安全帶)
功能:本地增加和刪除Item的列表
1找御、添加依賴
def paging_version = "1.0.0"
def lifecycle_version = "1.1.1"
def room_version = "1.1.0"
//這是 Paging的依賴
implementation "android.arch.paging:runtime:$paging_version"
// alternatively - without Android dependencies for testing
testImplementation "android.arch.paging:common:$paging_version"
// optional - RxJava support, currently in release candidate
implementation 'android.arch.paging:rxjava2:1.0.0-rc1'
//這是 ViewModel and LiveData 的依賴
implementation "android.arch.lifecycle:extensions:$lifecycle_version"
implementation "android.arch.persistence.room:runtime:$room_version"
//這是room的依賴
implementation "android.arch.persistence.room:rxjava2:$room_version"
// optional - Guava support for Room, including Optional and ListenableFuture
implementation "android.arch.persistence.room:guava:$room_version"
// Test helpers
testImplementation "android.arch.persistence.room:testing:$room_version"
網(wǎng)上有很多文章都是如此添加依賴,但是我們用到了ROOM這個組件绍填,對于Kotlin是有問題的霎桅。因為Kotlin需要Kotlin-kapt插件,用來引入注解處理庫讨永,java的話可以用 annotationProcessor滔驶,我們需要apply plugin: 'kotlin-kapt',然后在上面的依賴中添加
//java 用這個
// annotationProcessor "android.arch.persistence.room:compiler:$room_version"
//kotlin 用這個
kapt 'android.arch.persistence.room:compiler:1.0.0'
不然就會有xx_Impl does not exist
at android.arch.persistence.room.Room.getGeneratedImplementation的錯誤住闯,這個坑讓我爬了一上午瓜浸,很是狼狽澳淑。而且谷歌demo的項目代碼和我們創(chuàng)建的有區(qū)別比原,所以會有很多注意不到的坑插佛。
2、加載單一數(shù)據(jù)源的數(shù)據(jù)
Student.kt
@Entity
data class Student(@PrimaryKey(autoGenerate = true) val id: Int, val name: String)
StudentDao.kt
對數(shù)據(jù)庫數(shù)據(jù)的操作接口(增刪改查等方法類)
@Dao
interface StudentDao {
/**
* Room knows how to return a LivePagedListProvider, from which we can get a LiveData and serve
* it back to UI via ViewModel.
*/
@Query("SELECT * FROM Student ORDER BY name COLLATE NOCASE ASC")
fun allStudentByName(): DataSource.Factory<Int, Student>
@Insert
fun insert(student: List<Student>)
@Insert
fun insert(student: Student)
@Delete
fun delete(student: Student)
}
Executors.kt
實用工具方法量窘,用于在專用后臺線程上運行塊雇寇,用于io/數(shù)據(jù)庫工作,下面的類里會用到
private val IO_EXECUTOR = Executors.newSingleThreadExecutor()
/**
* Utility method to run blocks on a dedicated background thread, used for io/database work.
*/
fun ioThread(f : () -> Unit) {
IO_EXECUTOR.execute(f)
}
StudentDb.kt
數(shù)據(jù)庫的創(chuàng)建類
@Database(entities = arrayOf(Student::class), version = 1)
abstract class StudentDb : RoomDatabase() {
abstract fun studentDao(): StudentDao
override fun clearAllTables() {
}
companion object {
private var instance: StudentDb? = null
@Synchronized
fun get(context: Context): StudentDb {
if (instance == null) {
instance = Room.databaseBuilder(context.applicationContext,
StudentDb::class.java, "student.db")
.addCallback(object : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
fillInDb(context.applicationContext)
}
}).build()
}
return instance!!
}
/**
* fill database with list of cheeses
*/
private fun fillInDb(context: Context) {
// 在Room中的插入是在當前線程上執(zhí)行的蚌铜,因此我們將插入到后臺線程中
ioThread {
get(context).studentDao().insert(
CHEESE_DATA.map { Student(id = 0, name = it) })
}
}
}
}
private val CHEESE_DATA = arrayListOf(
"Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi",
"Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", "Airag", "Airedale",
"Aisy Cendre", "Allgauer Emmentaler", "Alverca", "Ambert", "American Student",
"Ami du Chambertin", "Anejo Enchilado", "Anneau du Vic-Bilh", "Anthoriro", "Appenzell",
"Aragon", "Ardi Gasna", "Ardrahan", "Armenian String", "Aromes au Gene de Marc",
"Asadero", "Asiago", "Aubisque Pyrenees", "Autun", "Avaxtskyr", "Baby Swiss",
"Babybel", "Baguette Laonnaise", "Bakers", "Baladi", "Balaton", "Bandal", "Banon",
"Barry's Bay Cheddar", "Basing", "Basket Student", "Bath Student", "Bavarian Bergkase",
"Baylough", "Beaufort", "Beauvoorde", "Beenleigh Blue", "Beer Student", "Bel Paese",
"Bergader", "Bergere Bleue", "Berkswell", "Beyaz Peynir", "Bierkase", "Bishop Kennedy",
"Blarney", "Bleu d'Auvergne", "Bleu de Gex", "Bleu de Laqueuille",
"Bleu de Septmoncel", "Bleu Des Causses", "Blue", "Blue Castello", "Blue Rathgore",
"Blue Vein (Australian)", "Blue Vein Cheeses", "Bocconcini", "Bocconcini (Australian)",
"Boeren Leidenkaas", "Bonchester", "Bosworth", "Bougon", "Boule Du Roves",
"Boulette d'Avesnes", "Boursault", "Boursin", "Bouyssou", "Bra", "Braudostur",
"Breakfast Student", "Brebis du Lavort", "Brebis du Lochois", "Brebis du Puyfaucon",
"Bresse Bleu", "Brick", "Brie", "Brie de Meaux", "Brie de Melun", "Brillat-Savarin",
"Brin", "Brin d' Amour", "Brin d'Amour", "Brinza (Burduf Brinza)",
"Briquette de Brebis", "Briquette du Forez", "Broccio", "Broccio Demi-Affine",
"Brousse du Rove", "Bruder Basil", "Brusselae Kaas (Fromage de Bruxelles)", "Bryndza",
"Buchette d'Anjou", "Buffalo", "Burgos", "Butte", "Butterkase", "Button (Innes)",
"Buxton Blue", "Cabecou", "Caboc", "Cabrales", "Cachaille", "Caciocavallo", "Caciotta",
"Caerphilly", "Cairnsmore", "Calenzana", "Cambazola", "Camembert de Normandie",
"Canadian Cheddar", "Canestrato", "Cantal", "Caprice des Dieux", "Capricorn Goat",
"Capriole Banon", "Carre de l'Est", "Casciotta di Urbino", "Cashel Blue", "Castellano",
"Castelleno", "Castelmagno", "Castelo Branco", "Castigliano", "Cathelain",
"Celtic Promise", "Cendre d'Olivet", "Cerney", "Chabichou", "Chabichou du Poitou",
"Chabis de Gatine", "Chaource", "Charolais", "Chaumes", "Cheddar",
"Cheddar Clothbound", "Cheshire", "Chevres", "Chevrotin des Aravis", "Chontaleno",
"Civray", "Coeur de Camembert au Calvados", "Coeur de Chevre", "Colby", "Cold Pack",
"Comte", "Coolea", "Cooleney", "Coquetdale", "Corleggy", "Cornish Pepper",
"Cotherstone", "Cotija", "Cottage Student", "Cottage Student (Australian)",
"Cougar Gold", "Coulommiers", "Coverdale", "Crayeux de Roncq", "Cream Student",
"Cream Havarti", "Crema Agria", "Crema Mexicana", "Creme Fraiche", "Crescenza",
"Croghan", "Crottin de Chavignol", "Crottin du Chavignol", "Crowdie", "Crowley",
"Cuajada", "Curd", "Cure Nantais", "Curworthy", "Cwmtawe Pecorino",
"Cypress Grove Chevre", "Danablu (Danish Blue)", "Danbo", "Danish Fontina",
"Daralagjazsky", "Dauphin", "Delice des Fiouves", "Denhany Dorset Drum", "Derby",
"Dessertnyj Belyj", "Devon Blue", "Devon Garland", "Dolcelatte", "Doolin",
"Doppelrhamstufel", "Dorset Blue Vinney", "Double Gloucester", "Double Worcester",
"Dreux a la Feuille", "Dry Jack", "Duddleswell", "Dunbarra", "Dunlop", "Dunsyre Blue",
"Duroblando", "Durrus", "Dutch Mimolette (Commissiekaas)", "Edam", "Edelpilz",
"Emental Grand Cru", "Emlett", "Emmental", "Epoisses de Bourgogne", "Esbareich",
"Esrom", "Etorki", "Evansdale Farmhouse Brie", "Evora De L'Alentejo", "Exmoor Blue",
"Explorateur", "Feta", "Feta (Australian)", "Figue", "Filetta", "Fin-de-Siecle",
"Finlandia Swiss", "Finn", "Fiore Sardo", "Fleur du Maquis", "Flor de Guia",
"Flower Marie", "Folded", "Folded cheese with mint", "Fondant de Brebis",
"Fontainebleau", "Fontal", "Fontina Val d'Aosta", "Formaggio di capra", "Fougerus",
"Four Herb Gouda", "Fourme d' Ambert", "Fourme de Haute Loire", "Fourme de Montbrison",
"Fresh Jack", "Fresh Mozzarella", "Fresh Ricotta", "Fresh Truffles", "Fribourgeois",
"Friesekaas", "Friesian", "Friesla", "Frinault", "Fromage a Raclette", "Fromage Corse",
"Fromage de Montagne de Savoie", "Fromage Frais", "Fruit Cream Student",
"Frying Student", "Fynbo", "Gabriel", "Galette du Paludier", "Galette Lyonnaise",
"Galloway Goat's Milk Gems", "Gammelost", "Gaperon a l'Ail", "Garrotxa", "Gastanberra",
"Geitost", "Gippsland Blue", "Gjetost", "Gloucester", "Golden Cross", "Gorgonzola",
"Gornyaltajski", "Gospel Green", "Gouda", "Goutu", "Gowrie", "Grabetto", "Graddost",
"Grafton Village Cheddar", "Grana", "Grana Padano", "Grand Vatel",
"Grataron d' Areches", "Gratte-Paille", "Graviera", "Greuilh", "Greve",
"Gris de Lille", "Gruyere", "Gubbeen", "Guerbigny", "Halloumi",
"Halloumy (Australian)", "Haloumi-Style Student", "Harbourne Blue", "Havarti",
"Picodon de Chevre", "Picos de Europa", "Piora", "Pithtviers au Foin",
"Plateau de Herve", "Plymouth Student", "Podhalanski", "Poivre d'Ane", "Polkolbin",
"Pont l'Eveque", "Port Nicholson", "Port-Salut", "Postel", "Pouligny-Saint-Pierre",
"Pourly", "Prastost", "Pressato", "Prince-Jean", "Processed Cheddar", "Provolone",
"Stilton", "Stinking Bishop", "String", "Sussex Slipcote", "Sveciaost", "Swaledale",
"Sweet Style Swiss", "Swiss", "Syrian (Armenian String)", "Tala", "Taleggio", "Tamie",
"Tasmania Highland Chevre Log", "Taupiniere", "Teifi", "Telemea", "Testouri",
"Tete de Moine", "Tetilla", "Texas Goat Student", "Tibet", "Tillamook Cheddar",
"Zamorano", "Zanetti Grana Padano", "Zanetti Parmigiano Reggiano");
StudentAdapter.kt
繼承PagedListAdapter锨侯,構(gòu)造屬于我們的Adapter
class StudentAdapter : PagedListAdapter<Student, StudentViewHolder>(diffCallback) {
override fun onBindViewHolder(holder: StudentViewHolder, position: Int) {
holder.bindTo(getItem(position))
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StudentViewHolder =
StudentViewHolder(parent)
companion object {
/**
* 這個diff回調(diào)通知PagedListAdapter在新列表到來的時候如何計算列表差異
*
* 當您使用“add”按鈕添加一個Student的時候,PagedListAdapter使用diffCallback t去檢測到與以前的Item的不同冬殃,所以它只需要重畫和重新綁定一個視圖囚痴。
*
* @see android.support.v7.util.DiffUtil
*/
private val diffCallback = object : DiffUtil.ItemCallback<Student>() {
override fun areItemsTheSame(oldItem: Student, newItem: Student): Boolean =
oldItem.id == newItem.id
/**
* 注意 kotlin的== 等價于java的equas()方法
*/
override fun areContentsTheSame(oldItem: Student, newItem: Student): Boolean =
oldItem == newItem
}
}
}
StudentViewHolder.kt
class StudentViewHolder(parent :ViewGroup) : RecyclerView.ViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.student_item, parent, false)) {
private val nameView = itemView.findViewById<TextView>(R.id.name)
var student : Student? = null
/**
* Items might be null if they are not paged in yet. PagedListAdapter will re-bind the
* ViewHolder when Item is loaded.
*/
fun bindTo(student : Student?) {
this.student = student
nameView.text = student?.name
}
}
StudentiewModel.kt
繼承AndroidViewModel類,這也是JetPack推薦的視圖數(shù)據(jù)關聯(lián)方式审葬,避免了Acticity和Fragment的任務繁重深滚。Pagelist數(shù)據(jù)用LiveData包裝,這樣可以避免生命周期對數(shù)據(jù)的影響涣觉,減少內(nèi)存泄漏痴荐。
class StudentiewModel(app: Application) : AndroidViewModel(app) {
val dao = StudentDb.get(app).studentDao()
companion object {
private const val PAGE_SIZE = 30
/**如果啟用了占位符,PagedList將報告完整的大小,但是有的Item在onBind方法中可能會為空(PagedListAdapter在加載數(shù)據(jù)時觸發(fā)重新綁定)
如果禁用了占位符官册,onBind將永遠不會收到null生兆。如果你禁用占位符那么你應該禁用滾動條,不然隨著頁面已加載的增多膝宁,滾動條將隨著新頁面的加載而抖動
*/
private const val ENABLE_PLACEHOLDERS = true
}
#Config可以設置頁面顯示的數(shù)量鸦难,是否啟動占位符等等
val students = LivePagedListBuilder(dao.allStudentByName(), PagedList.Config.Builder()
.setPageSize(PAGE_SIZE)
.setEnablePlaceholders(ENABLE_PLACEHOLDERS)
.build()).build()
//直接插入到數(shù)據(jù)庫
fun insert(text: CharSequence) = ioThread {
dao.insert(Student(id = 0, name = text.toString()))
}
//從數(shù)據(jù)庫刪除
fun remove(cheese: Student) = ioThread {
dao.delete(cheese)
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
private val viewModel by lazy(LazyThreadSafetyMode.NONE) {
ViewModelProviders.of(this@MainActivity).get(StudentiewModel::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val adapter = StudentAdapter()
cheeseList.adapter = adapter
// 將adapter添加訂閱到ViewModel,當列表改變時员淫,Adapter中的item會被刷新
viewModel.students.observe(this, Observer(adapter::submitList))
initAddButtonListener()
initSwipeToDelete()
}
private fun initSwipeToDelete() {
ItemTouchHelper(object : ItemTouchHelper.Callback() {
// //使Item能向左或向右滑動
override fun getMovementFlags(recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder): Int =
makeMovementFlags(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT)
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder): Boolean = false
//當項被滑動時明刷,通過ViewModel刪除該項。列表項將會自動刪除满粗,因為adapter正在觀察這個Live List辈末。
override fun onSwiped(viewHolder: RecyclerView.ViewHolder?, direction: Int) {
(viewHolder as? StudentViewHolder)?.student?.let {
viewModel.remove(it)
}
}
}).attachToRecyclerView(cheeseList)
}
private fun addStudnet() {
val newCheese = inputText.text.trim()
if (newCheese.isNotEmpty()) {
viewModel.insert(newCheese)
inputText.setText("")
}
}
private fun initAddButtonListener() {
addButton.setOnClickListener {
addStudnet()
}
// 當用戶點擊屏幕鍵盤上的“完成”按鈕時,保存item.
inputText.setOnEditorActionListener({ _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
addStudnet()
return@setOnEditorActionListener true
}
false // action that isn't DONE occurred - ignore
})
// 當用戶單擊按鈕或按enter時映皆,保存該 item.
inputText.setOnKeyListener({ _, keyCode, event ->
if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER) {
addStudnet()
return@setOnKeyListener true
}
false // event that isn't DOWN or ENTER occurred - ignore
})
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_height="match_parent"
android:orientation="vertical"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText
android:id="@+id/inputText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="@string/add_cheese"
android:imeOptions="actionDone"
android:inputType="text"
android:maxLines="1"/>
<Button
android:id="@+id/addButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:text="@string/add"/>
</LinearLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/cheeseList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
app:layoutManager="android.support.v7.widget.LinearLayoutManager"/>
</LinearLayout>
student_item.xml
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:cardUseCompatPadding="true">
<TextView android:id="@+id/name"
android:layout_width="match_parent" android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/card_vertical_margin"
android:layout_marginTop="@dimen/card_vertical_margin"/>
</android.support.v7.widget.CardView>
ok,到此demo完工挤聘,大家可以試試效果,不過從代碼量來看kotlin比java簡潔太多了捅彻,不過這demo里面基本把JetPack里的LiveData,ViewModel,Paging,Room都用到了组去,很多細節(jié)和用法,需要大家連貫起來學習步淹。從MainActivity的代碼來看很簡潔从隆,意思也很明確诚撵,比java的閱讀性要高很多,不過如果沒有學JetPack的同學看到這些代碼我想內(nèi)心是MMP的键闺。
WorkManager
WorkManager 可以輕松指定可延遲的異步任務以及何時運行寿烟。這些API可讓我們創(chuàng)建任務并將其交給WorkManager,以便立即或在適當?shù)臅r間運行辛燥。例如筛武,應用程序可能需要不時從網(wǎng)絡下載新資源。使用這些類挎塌,可以設置一個任務徘六,選擇適合它運行的環(huán)境(例如“僅在設備充電和聯(lián)網(wǎng)時”),并在符合條件時將其交給WorkManager運行榴都。即使您的應用程序強制退出或設備重新啟動待锈,該任務仍可保證運行。
注意:WorkManager適用于需要保證即使應用退出也能運行系統(tǒng)的任務嘴高,例如將應用數(shù)據(jù)上傳到服務器竿音。如果應用程序進程消失,它不適用于可以安全終止的進程內(nèi)后臺工作; 對于這樣的情況阳惹,推薦使用ThreadPools谍失。
以上是官方的介紹,那么我們就來白話一下
谷歌出這個到底是干嘛坝ㄌ馈快鱼? 不是有JobScheduler, AlarmManger,AsyncTask, ThreadPool, RxJava等等了嗎纲岭?怎么又來一套抹竹?
其實不是的,這回谷歌真的替我們做了很多我們平時比較頭疼的東西止潮,什么呢窃判?WorkManager的作用是在應用退出或者某些原因終止了之后,任務還可以進行喇闸,至于采取什么方法袄琳,這個我們不需要去管,WorkManager都替我們處理了燃乍。WorkManage會根據(jù)系統(tǒng)版本來選擇用JobScheduler, Firebase的JobDispatcher, 或是AlarmManager唆樊。
至于AsyncTask, ThreadPool, RxJava這三個和WorkManager是沒有沖突的,人家WorkManager是為了保證任務的可靠運行刻蟹,但是AsyncTask, ThreadPool, RxJava逗旁,這三兄弟app退出人家就不干活了,和WorkManager的職責有著本事區(qū)別舆瘪。一個是風雨無阻完成任務片效,一個有點事就撂挑子不干活了红伦。
我們表揚下WorkManager的好處
1、 易于調(diào)度
- 后臺工作程序只能在特定條件下調(diào)度任務(例如只有設備處于充電狀態(tài)淀衣,該任務才會運行)
- 一旦你調(diào)度了任務昙读,就可以忘記任務的存在,調(diào)度程序應該提供在所需條件匹配的情況下保證任務運行舌缤。
- 每個任務可以與另外一個任務并行鏈接箕戳,以并行或順序運行多個任務某残。
2国撵、易于取消
- 你必須擁有對任務的控制權(quán),調(diào)度程序應該提供API以輕松取消計劃任務玻墅。
3介牙、易于查詢
- 你的應用程序可能會需要顯示任務的狀態(tài)。
- 假設你要上傳照片并且需要在界面上顯示上傳的百分比澳厢。
- 調(diào)度程序必須提供API以輕松獲取任務的當前狀態(tài)环础,如果任務完成之后可以傳遞一些結(jié)果數(shù)據(jù),那就更棒了剩拢!
4线得、支持所有的Android版本
- 調(diào)度程序API應該在所有的Android版本中都一樣。
WorkManager由以下幾個部分組成
Worker:指定您需要執(zhí)行的任務徐伐。WorkManager API包含一個抽象Worker類贯钩。你擴展這個類并且在這里執(zhí)行這個工作。
-
WorkRequest:代表一個單獨的任務办素。至少角雷,WorkRequest對象指定應該執(zhí)行任務的Worker類。但是性穿,您也可以向WorkRequest對象添加細節(jié)勺三,指定任務應該運行的環(huán)境。每個工作請求都有一個自動生成的唯一ID;您可以使用ID來執(zhí)行諸如取消排隊任務或獲取任務的狀態(tài)等操作需曾。WorkRequest是一個抽象類;在您的代碼中吗坚,您將使用一個直接子類,一個timeworkrequest(一次性)或PeriodicWorkRequest(周期性)呆万。
- WorkRequest.Builder:創(chuàng)建工作請求對象的構(gòu)造類商源。同樣,您將使用一個子類桑嘶,OneTimeWorkRequest炊汹。建筑商或PeriodicWorkRequest.Builder。
- Constraints:指定任務運行時間的限制(例如逃顶,“僅在連接到網(wǎng)絡時”)讨便。
WorkManager:排隊和管理工作請求充甚。你傳遞你的WorkRequest 對象WorkManager來排隊的任務。WorkManager調(diào)度任務的方式是分散系統(tǒng)資源的負載霸褒,同時遵守您指定的約束條件伴找。
WorkStatus:包含有關特定任務的信息。包含關于特定任務的信息废菱。WorkManager為每個WorkRequest對象提供一個LiveData技矮。LiveData保存一個WorkStatus對象;通過觀察這個LiveData,您可以確定任務的當前狀態(tài)殊轴,并在任務完成后獲得任何返回值衰倦。
了解完WorkManager,該擼代碼了旁理,前方高能依然是Kotlin樊零。請抓好安全帶。
這個小demo的功能是執(zhí)行延時任務孽文,獲取廣告信息驻襟,然后通知UI顯示廣告,看看能不能做一些無賴的事情芋哭,比如一有廣告直接調(diào)起app顯示沉衣。
1、添加依賴
implementation "android.arch.work:work-runtime-ktx:1.0.0-alpha01"
AdWorker.kt
做了一個任務的開關减牺,inputData是輸入信息outputData是對外輸出信息豌习,也就是根據(jù)inputData的信息去做不同的事情,然后把結(jié)果通過outputData送出來烹植。
WorkerResult的狀態(tài) FAILURE,RETRY斑鸦,SUCCESS;
RETRY:WorkManager可以再次重試該工作
FAILURE:發(fā)生了一個或多個錯誤
SUCCESS:任務成功完成
class AdWorker : Worker() {
override fun doWork(): WorkerResult {
//輸入data
val is_open = this.inputData.getBoolean("is_open_ad", false)
if (is_open) {
//模擬延時操作
Thread.sleep(10000)
val ad = getAd()
outputData = Data.Builder().putString("key_ad", ad).build()
Log.e("ad", "SUCCESS")
return WorkerResult.SUCCESS
} else {
Log.e("ad", "FAILURE:")
return WorkerResult.FAILURE
}
}
private fun getAd(): String {
return "我是廣告君草雕,沒進剛哥知識星球的趕緊加入了啊~~" + System.currentTimeMillis()
}
}
AdEngine.kt
任務的調(diào)度類 我們在此用的是OneTimeWorkRequestBuilder一次性調(diào)用巷屿。
如果我們需要重復執(zhí)行一項任務的話使用PeriodicWorkRequest.Builder
不過這個有個坑在等著大家。使用PeriodicWorkRequest的時候outputdata的里面的值是空的墩虹,上網(wǎng)查了很多資料都沒有寫出這個問題嘱巾,但是OneTimeWorkRequestBuilder確實沒有問題的,希望各位大神可以給解答一下這個問題诫钓。
這里可以往AdWorker進行setInputData旬昭,數(shù)據(jù)輸入。然后加入任務隊列菌湃。我們在此保存好任務ID问拘,根據(jù)這個ID才可以找到這個任務。
那么只有這一個方法才能找到任務嗎?答案是否定的骤坐。
我們可以標記任務 .addTag("tag_ad")
這個方法來獲取任務 WorkManager.getInstance().getStatusesByTag("tag_id")
約束:
定義約束條件以告訴WorkManager合適安排任務執(zhí)行绪杏,如果沒有提供任何約束條件,那么該任務將立即運行纽绍。
以下是僅在設備充電和設備是否為空閑才運行任務的約束
val myConstraints = Constraints.Builder()
.setRequiresDeviceIdle(true)
.setRequiresCharging(true)
.build()
class AdEngine {
fun schedulAd() {
val adReauest = OneTimeWorkRequestBuilder<AdWorker>()
.setInputData(
Data.Builder().putBoolean("is_open_ad", true)
.build()
)
.setConstraints(myConstraints)
.addTag("tag_ad")
.build()
WorkManager.getInstance().enqueue(adReauest)
//保存任務ID
val adRequestId = adReauest.id
var arid by Preference("adRequestId", "")
arid = adRequestId.toString()
}
//這是約束條件
@RequiresApi(Build.VERSION_CODES.M)
val myConstraints = Constraints.Builder()
.setRequiresDeviceIdle(true)
.setRequiresCharging(true)
.build()
}
Preference.kt
SharedPreferences在kotlin的工具類
class Preference<T>(val name: String, private val default: T) {
private val prefs: SharedPreferences by lazy { App.instance.applicationContext.getSharedPreferences(name, Context.MODE_PRIVATE) }
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
Log.i("info", "調(diào)用$this 的getValue()")
return getSharePreferences(name, default)
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
Log.i("info", "調(diào)用$this 的setValue() value參數(shù)值為:$value")
putSharePreferences(name, value)
}
@SuppressLint("CommitPrefEdits")
private fun putSharePreferences(name: String, value: T) = with(prefs.edit()) {
when (value) {
is Long -> putLong(name, value)
is String -> putString(name, value)
is Int -> putInt(name, value)
is Boolean -> putBoolean(name, value)
is Float -> putFloat(name, value)
else -> throw IllegalArgumentException("This type of data cannot be saved!")
}.apply()
}
@Suppress("UNCHECKED_CAST")
private fun getSharePreferences(name: String, default: T): T = with(prefs) {
val res: Any = when (default) {
is Long -> getLong(name, default)
is String -> getString(name, default)
is Int -> getInt(name, default)
is Boolean -> getBoolean(name, default)
is Float -> getFloat(name, default)
else -> throw IllegalArgumentException("This type of data cannot be saved!")
}
return res as T
}
}
app.kt
class App :Application(){
companion object {// 伴生對象 java里的靜態(tài)屬性
lateinit var instance: App
private set
}
override fun onCreate() {
super.onCreate()
instance = this
}
}
Main2Activity.kt
調(diào)用adEngine.schedulAd()方法蕾久,開啟任務,然后通過 getStatusById返回LiveData<WorkStatus>拌夏,添加到LifecycleOwner(AppCompatActivity本身就是一個LifecycleOwner)僧著,我們就可以根據(jù)state的改變,改變TextView的內(nèi)容障簿。顯示我們的廣告語盹愚。結(jié)果我們會發(fā)現(xiàn)并不會直接調(diào)起來APP,這是為什么呢卷谈?
因為我們用的是LiveData杯拐,當Activity銷毀后霞篡,LiveData處于未激活的狀態(tài)世蔗,不回去接受數(shù)據(jù)的改變,只有Activity從新獲得生命周期后朗兵,LiveData才會接受數(shù)據(jù)的變化涡尘,從而受到LiveData的通知坪创,所以想要做壞事的同學,這條路行不通的,谷歌只是為了保證一些任務的可靠性稽物,而不是保證你的App的生命。
當然任務可以執(zhí)行就可以取消
WorkManager.getInstance().cancelByWorkId(uuid);
鏈式調(diào)用
WorkManager.getInstance().
beginWith(workA1泊柬,workA2波附,workA3)
.then
(workB)
.then(workC1,workC2).enqueue();
這樣就完了嗎冗美?還有更復雜的鏈式調(diào)用WorkContinuation大家可以自行學習下魔种。
class Main2Activity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
button.setOnClickListener(View.OnClickListener {
val adEngine: AdEngine = AdEngine()
adEngine.schedulAd()
showAd(this, textad)
})
showAd(this, textad)
}
}
fun showAd(lifeowner: LifecycleOwner, textad: TextView) {
var arid by Preference("adRequestId", "")
if (!arid.equals("")) {
val uuid = UUID.fromString(arid)
// public abstract LiveData<WorkStatus> getStatusById(@NonNull UUID id);
WorkManager.getInstance().getStatusById(uuid)
.observe(lifeowner, android.arch.lifecycle.Observer<WorkStatus> { state ->
if (state != null && state.state.isFinished) {
val adResult = state.outputData.getString("key_ad", "無")
textad.text = adResult
}
})
}
}
Slices
Slices 在國內(nèi)應用的范圍不廣,重要是因為Slices是 Google Assistant 的延伸粉洼,谷歌希望使用者能過快速到達App里面的某個特點功能节预,舉一例子就是,你對Google Assistant說你要回家属韧,那么以前可能只會出現(xiàn)滴滴安拟,Uber的選項,但是引進Slices之后會顯示更加詳細的數(shù)據(jù)列表宵喂,比如滴滴item下會出現(xiàn)到家多少距離糠赦,多少錢,是否立即打車等等。Google Assistant 在國內(nèi)不好用拙泽,但是谷歌有這個功能開源我們自己其實也可以去實現(xiàn)唆铐,可能小米會把這個功能給小艾同學吧。
開始搭建我們的Slices吧奔滑。
注意注意:開發(fā)環(huán)境必須是Android Studio 3.2 以及以上艾岂,最低版本Android 4.4 (API level 19) ,我們可以從官網(wǎng)下載Android Studio 3.2 朋其,圖標是黃色的王浴,可以和我們之前的Android Studio 共存,相互沒有干擾梅猿,講實話Android Studio 3.2 真的處處是坑氓辣,特別和Kotlin配合,那真的是一言難盡袱蚓,苦不堪言钞啸。
no.1
如果沒有這個選項的話
<provider
android:name=".MySliceProvider"
android:authorities="com.simple.slicesapplication"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.app.slice.category.SLICE" />
<data
android:host="simple.com"
android:pathPrefix="/ssy"
android:scheme="http" />
</intent-filter>
</provider>
值得一說的是 依賴的版本真的很坑,注意是否依賴了正確的版本
implementation "androidx.slice:slice-core:1.0.0-alpha1"
implementation "androidx.slice:slice-builders:1.0.0-alpha1"
no.2
class MySliceProvider : SliceProvider() {
/**
* Instantiate any required objects. Return true if the provider was successfully created,
* false otherwise.
*/
override fun onCreateSliceProvider(): Boolean {
return true
}
override fun onMapIntentToUri(intent: Intent?): Uri {
// Note: implementing this is only required if you plan on catching URL requests.
// This is an example solution.
var uriBuilder: Uri.Builder = Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
if (intent == null) return uriBuilder.build()
val data = intent.data
if (data != null && data.path != null) {
val path = data.path.replace("/", "")
uriBuilder = uriBuilder.path(path)
}
val context = context
if (context != null) {
uriBuilder = uriBuilder.authority(context.getPackageName())
}
return uriBuilder.build()
}
override fun onBindSlice(sliceUri: Uri): Slice? {
val context = getContext() ?: return null
return if (sliceUri.path == "/") {
// Path recognized. Customize the Slice using the androidx.slice.builders API.
// Note: ANR and StrictMode are enforced here so don't do any heavy operations.
// Only bind data that is currently available in memory.
ListBuilder(context, sliceUri)
.addRow { it.setTitle("URI found.") }
.build()
} else {
// Error: Path not found.
ListBuilder(context, sliceUri)
.addRow { it.setTitle("URI not found.") }
.build()
}
}
override fun onSlicePinned(sliceUri: Uri?) {
}
override fun onSliceUnpinned(sliceUri: Uri?) {
// Remove any observers if necessary to avoid memory leaks.
}
}
綁定Slice
override fun onBindSlice(sliceUri: Uri): Slice? {
val activityAction = createActivityAction()
return if (sliceUri.path == "/ssy") {
ListBuilder(context, sliceUri, ListBuilder.INFINITY)
.addRow { it.setTitle("URI found. 我是標題")
it.setSubtitle("我是子標題")
//設置Action
it.setPrimaryAction(activityAction)}
}
.build()
} else {
ListBuilder(context, sliceUri, ListBuilder.INFINITY)
.addRow { it.setTitle("URI not found.") }
.build()
}
}
//創(chuàng)建Action
fun createActivityAction(): SliceAction {
val intent = Intent(context, MainActivity::class.java)
return SliceAction(PendingIntent.getActivity(context, 0, intent, 0),
IconCompat.createWithResource(context, R.drawable.ic_launcher_background),
"Open MainActivity."
)
}
將URL轉(zhuǎn)變成content URI
override fun onMapIntentToUri(intent: Intent?): Uri {
var uriBuilder: Uri.Builder = Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
if (intent == null) return uriBuilder.build()
val data = intent.data
if (data != null && data.path != null) {
val path = data.path.replace("/ssy", "")
uriBuilder = uriBuilder.path(path)
}
val context = context
if (context != null) {
uriBuilder = uriBuilder.authority(context.getPackageName())
}
return uriBuilder.build()
}
這樣我們我們就完成了一個簡單的Slice喇潘,什么体斩?怎么用?下載這個slice-viewer.apk充當Google Assistant吧颖低,然后我們需要做的是
adb shell am start -a android.intent.action.VIEW -d slice-content://com.simple.slicesapplication/ssy
藍色的是我們自己的Content Uri絮吵,這樣就會在slice-viewer.apk打開我們的slice了。
點擊會跳轉(zhuǎn)到我們的app忱屑。
什么蹬敲?覺得不夠豐富?來來來莺戒,阿秀同志請坐下伴嗡,有好東西給你
創(chuàng)建一個帶有Togglebutton和進度條 的Slice和action,只需要替換我們上面的Slice和action即可
fun createBrightnessSlice(sliceUri: Uri): Slice {
val toggleAction =
SliceAction(createToggleIntent(), "Toggle adaptive brightness", true)
return ListBuilder(context, sliceUri, ListBuilder.INFINITY)
.addRow {
it.apply {
setTitle("Adaptive brightness")
setSubtitle("Optimizes brightness for available light")
//這是togglebutton
setPrimaryAction(toggleAction)
}
}.addInputRange {
it.apply {
//這個是進度條
setInputAction(brightnessPendingIntent)
setMax(100)
setValue(45)
}
}.build()
}
fun createToggleIntent(): PendingIntent {
val intent = Intent(context, MyBroadcastReceiver::class.java)
return PendingIntent.getBroadcast(context, 0, intent, 0)
}
class MyBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.hasExtra(Slice.EXTRA_TOGGLE_STATE)) {
Toast.makeText(context, "Toggled: " + intent.getBooleanExtra(
Slice.EXTRA_TOGGLE_STATE, false),
Toast.LENGTH_LONG).show()
}
}
companion object {
const val EXTRA_MESSAGE = "message"
}
啥从铲?就這些瘪校?你想點擊Slice后,在Slice上面動態(tài)修改一些信息食店,這是你的應用嗎渣淤?咋想這么多呢?好吧吉嫩,怕你了价认,來來來(谷歌官網(wǎng)的代碼有坑,真的有坑)
fun createDynamicSlice(sliceUri: Uri): Slice {
return when (sliceUri.path) {
"/ssy" -> {
val toastAndIncrementAction = SliceAction(createToastAndIncrementIntent("Item clicked"),
IconCompat.createWithResource(context, R.drawable.no1), "Increment.")
ListBuilder(context, sliceUri, ListBuilder.INFINITY)
.addRow {
it.apply {
setPrimaryAction(toastAndIncrementAction)
setTitle("Count: ${MyBroadcastReceiver.receivedCount}")
setSubtitle("Click me")
}
}
.build()
}
else -> {
ListBuilder(context, sliceUri, ListBuilder.INFINITY)
.addRow { it.setTitle("URI not found.") }
.build()
}
}
}
fun createToastAndIncrementIntent(s: String): PendingIntent {
return PendingIntent.getBroadcast(context, 0,
Intent(context, MyBroadcastReceiver::class.java)
.putExtra(android.app.slice.Slice.EXTRA_TOGGLE_STATE, s), 0)
}
class MyBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.hasExtra(Slice.EXTRA_TOGGLE_STATE)) {
Toast.makeText(context, "Toggled: " + intent.getBooleanExtra(
Slice.EXTRA_TOGGLE_STATE, false),
Toast.LENGTH_LONG).show()
receivedCount++;
context.contentResolver.notifyChange(sliceUri, null)
}
}
companion object {
var receivedCount = 0
val sliceUri = Uri.parse("content://com.simple.slicesapplication/ssy")
const val EXTRA_MESSAGE = "message"
}
}
這回行了吧自娩,什么布局能不能改用踩?啥你想加廣告渠退?真是,好吧好吧
Slice templates
這個大哥叫切片模板
定義你的切片模板
切片是通過使用ListBuilder構(gòu)造的 脐彩。ListBuilder允許您添加列表中顯示的不同類型的行碎乃。本節(jié)介紹每種行類型及其構(gòu)造方式。
SliceAction
Slice模板的最基本元素是 SliceAction惠奸。SliceAction包含一個標簽以及一個PendingIntent梅誓, 并且是以下之一:
- 圖標按鈕
- 默認切換
- 自定義切換(具有開/關狀態(tài)的可繪圖)
切片模板
SliceAction由本節(jié)其余部分描述的模板構(gòu)建器使用。SliceAction可以定義一個圖像模式佛南,用于確定如何為該動作呈現(xiàn)圖像:
- ICON_IMAGE:尺寸小梗掰,可上色
- SMALL_IMAGE:體積小,不可著色
- LARGE_IMAGE:最大的尺寸和不可著色
HeaderBuilder
在大多數(shù)情況下嗅回,您應該使用HeaderBuilder為您的模板設置標題 及穗。標頭可以支持以下內(nèi)容:
- 標題
- 字幕
- 摘要字幕
- 主要行動
下面顯示了一些示例頭部配置。請注意绵载,灰色框顯示潛在的圖標和填充位置:
標題在不同的表面上呈現(xiàn)
當需要切片時埂陆,顯示表面決定如何渲染切片。請注意娃豹,托管表面之間的渲染可能有所不同焚虱。
在較小的格式中,通常只顯示標題(如果存在)培愁。如果您為標題指定了摘要著摔,則會顯示摘要文本而不是字幕文本。
如果您沒有在模板中指定標題定续,則通常會顯示添加到ListBuilder的第一行
那我們就試一下
fun createSliceWithHeader(sliceUri: Uri): Slice =
ListBuilder(context, sliceUri, ListBuilder.INFINITY)
.setAccentColor(0xff0F9D) // Specify color for tinting icons
.setHeader {
it.apply {
setTitle("Get a ride")
setSubtitle("Ride in 4 min")
setSummary("Work in 1 hour 45 min | Home in 12 min")
}
}.addRow {
it.apply {
setTitle("Home")
setSubtitle("12 miles | 12 min | $9.00")
addEndItem(IconCompat.createWithResource(context, R.drawable.ic_launcher_background), SliceHints.ICON_IMAGE)
}
}
.build()
}
感覺有點意思了,不過還是沒有想象中該有的樣子禾锤。上下標題差不多有了私股,右邊的icon可以多幾個嗎?
fun createSliceWithActionInHeader(sliceUri: Uri): Slice {
// Construct our slice actions.
val noteAction = SliceAction(takeNoteIntent,
IconCompat.createWithResource(context, R.drawable.a),
ICON_IMAGE, "Take note")
val voiceNoteAction = SliceAction(voiceNoteIntent,
IconCompat.createWithResource(context, R.drawable.b),
ICON_IMAGE,
"Take voice note")
val cameraNoteAction = SliceAction(cameraNoteIntent,
IconCompat.createWithResource(context, R.drawable.c),
ICON_IMAGE,
"Create photo note")
// Construct the list.
return ListBuilder(context, sliceUri, ListBuilder.INFINITY)
.setAccentColor(0xfff4b4) // Specify color for tinting icons
.setHeader {
it.apply {
setTitle("Create new note")
setSubtitle("Easily done with this note taking app")
}
}
.addAction(noteAction)
.addAction(voiceNoteAction)
.addAction(cameraNoteAction)
.build()
}
最后來個全家桶
fun createSliceWithGridRow(sliceUri: Uri): Slice {
// Create the parent builder.
val icon_a = IconCompat.createWithResource(context, R.mipmap.a)
val icon_b = IconCompat.createWithResource(context, R.mipmap.b)
val icon_c = IconCompat.createWithResource(context, R.mipmap.c)
val icon_d = IconCompat.createWithResource(context, R.mipmap.d)
val intent = Intent(context, MainActivity::class.java)
val brightnessPendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
return ListBuilder(context, sliceUri, ListBuilder.INFINITY)
.setHeader {
it.apply {
setTitle("玩具")
setPrimaryAction(SliceAction(brightnessPendingIntent, icon_c, "Famous restaurants"))
}
}
.addRow {
it.apply {
setSubtitle("12 miles | 12 min | $9.00")
addEndItem(IconCompat.createWithResource(context, R.mipmap.b), SliceHints.LARGE_IMAGE)
}
}
.addGridRow {
it.apply {
addCell {
it.apply {
addImage(icon_a, LARGE_IMAGE)
addTitleText("積木")
addText("¥100")
setContentIntent(brightnessPendingIntent)
}
}
addCell {
it.apply {
addImage(icon_b, LARGE_IMAGE)
addTitleText("搖桿")
addText("¥200")
setContentIntent(brightnessPendingIntent)
}
}
addCell {
it.apply {
addImage(icon_c, LARGE_IMAGE)
addTitleText("十合一卡")
addText("¥300")
setContentIntent(brightnessPendingIntent)
}
}
addCell {
it.apply {
addImage(icon_d, LARGE_IMAGE)
addTitleText("手柄t")
addText("¥200")
setContentIntent(brightnessPendingIntent)
}
}
}
}
.build()
}
本篇把Paging恩掷、WorkManager倡鲸、Slices的概念和簡單的應用梳理了一遍,其中也發(fā)現(xiàn)了很多坑黄娘,網(wǎng)上好多文章峭状,只是講原理不寫實例,或者有的人寫了實例但是自己沒有驗證過逼争,就是谷歌文檔也有很多不清楚的地方优床,特別是Kotlin的依賴的配置,所幸把大部分的問題解決 了誓焦,不過還有一些問題依然不清楚胆敞,查看了官方文檔,谷歌了眾多文章可是資料很少,希望有小伙伴可以給解惑移层。一起學習仍翰。
大家可以點個關注,告訴我大家想要深入探究哪些問題观话,希望看到哪方面的文章予借,我可以免費給你寫專題文章。频蛔。哈哈蕾羊。。帽驯。
希望大家多多支持龟再。。你的一個關注尼变,是我堅持的最大動力利凑。。