Android Compose 新聞App(七)網絡圖檔加載、Tab、HorizontalPager
- 前言
- 正文
- 一、申請API
- ① 增加服務接口
- ② HomeRepository
- ③ HomeViewModel
- 二、網絡圖檔加載
- 三、BottomBar遮擋
- 四、Tab + HorizontalPager
- 五、修改頁面
- 六、源碼
前言
在上一篇文章中,新增加了一個首頁面,那麼這個首頁面用來做什麼呢?首頁面的底部我分為兩個部分,目前是首頁和收藏,首頁需要顯示好幾個類型的新聞資料,那麼我們先來做這一步,本文效果圖如下:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIiZpdmLxUTM1QmYzIDZyEmZ1kTMzYzX2ATMyETM4AzLclDMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.gif)
正文
首先我們需要申請API,在天行API中申請如下圖所示的API接口.
鑒于五個不同的資料類型,我們就需要五個接口。
一、申請API
首先從社會新聞這個接口開始,我們通過測試請求,然後就能拿到此接口的傳回值,通過這個傳回值我們生成一個資料類,在bean包下建立一個News類,代碼如下:
data class News(val msg: String = "",
val code: Int = 0,
val newslist: List<Newslist>)
data class Newslist(val picUrl: String = "",
val ctime: String = "",
val description: String = "",
val id: String = "",
val source: String = "",
val title: String = "",
val url: String = "")
現在資料有了,下面我們就現在HomeItem.kt中顯示資料。顯示資料也得一步一步來,首先。
① 增加服務接口
首先在ApiService中添加getSocialNews()函數,代碼如下:
/**
* 擷取社會新聞
*/
@GET("/social/index?key=$API_KEY")
fun getSocialNews(): Call<News>
然後在NetworkRequest中增加getSocialNews()函數,代碼如下:
//擷取社會新聞
suspend fun getSocialNews() = service.getSocialNews().await()
② HomeRepository
現在我們還沒有HomeRepository的,在repository中新增HomeRepository,代碼如下:
@ViewModelScoped
class HomeRepository @Inject constructor() : BaseRepository() {
/**
* 擷取社會新聞
*/
fun getSocialNews() = fire(Dispatchers.IO) {
val news = NetworkRequest.getSocialNews()
if (news.code == CODE) Result.success(news)
else Result.failure(RuntimeException("getNews response code is ${news.code} msg is ${news.msg}"))
}
}
這個方法就是調用NetworkRequest中的getSocialNews()函數,這在前面的文章中你可能見到過。那麼下一個就是建立ViewModel,與HomeItem相對應的就是HomeViewModel。
③ HomeViewModel
在viewmodel包下中新增一個HomeViewModel,裡面的代碼如下:
@HiltViewModel
class HomeViewModel @Inject constructor(repository: HomeRepository) :ViewModel () {
val result = repository.getSocialNews()
}
然後在頁面上我們需要一層一層的傳遞。通常我們Activity和ViewModel是綁定,之前我們在HomeActivity中建立了一個MainViewModel,然後我們在HomeActivity中再加一個HomeViewModel,代碼如下:
val homeViewModel: HomeViewModel = viewModel()
同樣我們需要在導航到HomePage中時增加導航控制器和homeViewModel,如下圖所示:
下面我們更改HomePage()函數中的參數,如下圖所示:
這裡又把參數傳遞到HomeItem中,下面我們再修改一下HomeItem中的代碼,如下所示:
@Composable
fun HomeItem(mNavController: NavHostController, viewModel: HomeViewModel) {
val dataState = viewModel.result.observeAsState()
dataState.value?.let {
ShowNewsList(mNavController,it.getOrNull()!!.newslist)
}
}
@Composable
fun ShowNewsList(mNavController: NavHostController, newslist: List<Newslist>) {
LazyColumn(
state = rememberLazyListState(),
modifier = Modifier.padding(8.dp)
) {
items(newslist) { new ->
Log.d("TAG", "ShowNewsList: ${Gson().toJson(new)}")
Column(modifier = Modifier
.clickable {
val encodedUrl = URLEncoder.encode(new.url, StandardCharsets.UTF_8.toString())
mNavController.navigate("${PageConstant.WEB_VIEW_PAGE}/${new.title}/$encodedUrl")
}
.padding(8.dp)) {
Text(
text = new.title,
fontWeight = FontWeight.ExtraBold,
fontSize = 16.sp,
modifier = Modifier.padding(0.dp, 10.dp)
)
Text(text = new.description, fontSize = 12.sp)
Text(text = new.ctime, fontSize = 12.sp)
}
Divider(
modifier = Modifier.padding(horizontal = 8.dp),
color = colorResource(id = R.color.black).copy(alpha = 0.08f)
)
}
}
}
下面我們運作一下:
這裡的資料就顯示出來了,通過日志列印我看到有一個圖檔Url。
然後如果我們要通過圖檔Url顯示圖檔要怎麼做呢?
二、網絡圖檔加載
之前在Android的開發你肯定是了解過Glide架構的,那麼現在在Compose中使用Coli庫,這個庫有什麼優點呢?
Coil 是一個 Android 圖檔加載庫,通過 Kotlin 協程的方式加載圖檔。使用它需要添加依賴,在app的build.gradle的dependencies{}閉包,代碼如下:
//Coil庫
implementation 'io.coil-kt:coil-compose:2.0.0-rc03'
然後我們需要修改一下之前的item,因為要添加一個圖檔,是以在Column的外部再添加一個Row,代碼如下:
Row(modifier = Modifier
.clickable {
val encodedUrl = URLEncoder.encode(new.url, StandardCharsets.UTF_8.toString())
mNavController.navigate("${PageConstant.WEB_VIEW_PAGE}/${new.title}/$encodedUrl")
}
.padding(8.dp)
) {
AsyncImage(
model = new.picUrl,
contentDescription = null,
modifier = Modifier
.width(120.dp)
.height(80.dp),
contentScale = ContentScale.FillBounds
)
Column(modifier = Modifier.padding(8.dp,0.dp,0.dp,0.dp)) {
Text(
text = new.title,
fontWeight = FontWeight.ExtraBold,
fontSize = 16.sp
)
Row(modifier = Modifier.padding(0.dp, 10.dp)) {
Text(text = new.source, fontSize = 12.sp)
Text(
text = new.ctime,
fontSize = 12.sp,
modifier = Modifier.padding(8.dp, 0.dp)
)
}
}
}
這裡的圖檔使用AsyncImage,而不是Image,在這個控件裡面增加圖檔的加載位址,然後修改一下圖檔的寬高和占滿邊界,注意一下上面這段代碼添加的位置,如下圖所示:
下面我們運作一下:
三、BottomBar遮擋
我們嘗試一下把這個清單滑動到頁面底部看看。
可以看到這裡的BottomBar遮擋住客了這個清單的最後一項,那麼怎麼解決這個問題呢?其實很簡單,一行代碼解決問題。
navigationBarsPadding()
在HomeItem中增加,如下圖所示:
這樣就可以解決了。
四、Tab + HorizontalPager
這裡的Tab是已經有了,但是要使用HorizontalPager還需要添加依賴,在app的build.gradle的dependencies{}閉包中添加如下依賴:
//viewpage
implementation "com.google.accompanist:accompanist-pager:$accompanist_version"
//viewpage訓示器
implementation "com.google.accompanist:accompanist-pager-indicators:$accompanist_version"
使用的話我們用一個函數來表示,在HomeItem中新增一個TabViewPager函數,代碼如下:
@SuppressLint("UnrememberedMutableState")
@OptIn(ExperimentalPagerApi::class)
@Composable
private fun TabViewPager() {
Column(modifier = Modifier.fillMaxSize()) {
val pages by mutableStateOf(
listOf("社會", "軍事", "科技", "财經", "娛樂")
)
val pagerState = rememberPagerState(initialPage = 0)//初始化頁面,0就表示第一個頁面
TabRow(
selectedTabIndex = pagerState.currentPage,
// 使用提供的 pagerTabIndicatorOffset 修飾符自定義訓示器
indicator = { tabPositions ->
TabRowDefaults.Indicator(
Modifier.pagerTabIndicatorOffset(pagerState, tabPositions)
)
},
backgroundColor = colorResource(id = R.color.white),
contentColor = colorResource(id = R.color.black)
) {
//給全部頁面添加标簽欄
pages.forEachIndexed { index, title ->
Tab(
text = { Text(title) },
selected = pagerState.currentPage == index,//是否選中
onClick = {
CoroutineScope(Dispatchers.Main).launch {
pagerState.scrollToPage(index)
}
},
modifier = Modifier.alpha(0.9f),//透明度
enabled = true,//是否啟用
selectedContentColor = colorResource(id = R.color.black),//選中的顔色
unselectedContentColor = colorResource(id = R.color.gray),//未選中的顔色
)
}
}
HorizontalPager(
count = pages.size,
state = pagerState,//用于控制或觀察viewpage狀态的狀态對象。
modifier = Modifier.padding(top = 4.dp),
itemSpacing = 2.dp
) { page ->
Column(modifier = Modifier.fillMaxSize()) {
Text(
text = "Page: $page",
modifier = Modifier.fillMaxWidth()
)
}
}
}
}
你不熟悉的隻是控件使用而已,裡面的參數用幾次就都會了,下面在HomeItem中調用此函數。
運作一下,看看效果:
五、修改頁面
現在五個頁面的内容就隻有一個Text,下面我們設定第一個頁面為之前寫的社會新聞資料,這裡首先我們要确定一個事情,那就參數要傳遞進入TabViewPager函數,如下圖所示修改:
然後就是修改頁面的顯示内容,代碼如下:
val dataState = viewModel.result.observeAsState()
when(page) {
0 -> dataState.value?.let {
ShowNewsList(mNavController, it.getOrNull()!!.newslist)
}
else -> {
Column(modifier = Modifier.fillMaxSize()) {
Text(
text = "Page: $page",
modifier = Modifier.fillMaxWidth()
)
}
}
}
這裡當頁面為第一個頁面時,我們現實社會新聞資料,其他頁面就和之前一樣顯示頁面下标,代碼添加位置如下圖所示:
還有一個地方要注意,那就是之前我們在ShowNewsList函數中設定的navigationBarsPadding(),挪到TabViewPager函數中,如下圖所示:
上面的代碼在電腦虛拟機和真機上運作效果不一樣,是以我将naviationBarsPadding改成了.padding(0.dp, 0.dp, 0.dp, 50.dp),這個我就不截圖了,下面運作一下: