主頁 > 移動端開發 > Android Compose wanandroid app 之底部按鈕以及首頁內容

Android Compose wanandroid app 之底部按鈕以及首頁內容

2021-11-12 12:59:43 移動端開發

使用Compose實作底部按鈕和首頁banner以及資料串列

  • 前言
    • Column、Row、ConstraintLayout布局先知
      • Column縱向排列布局
      • Row橫向排列布局
      • ConstraintLayout 約束布局
    • Modifier的簡單使用
    • 底部導航欄的實作
    • 首頁內容的實作
      • Banner的實作
      • 首頁ViewModel
      • 實作文章串列
    • SwipeRefresh下拉重繪
    • LaunchedEffect簡介
    • HorizontalPager簡介
    • 原始碼地址

前言

compose作為Android現在主推的UI框架,各種文章鋪天蓋地的席卷而來,作為一名Android開發人員也是很有必要的學習一下了,這里就使用wanandroid的開放api來撰寫一個compose版本的玩安卓客戶端,全當是學習了,各位大佬輕噴~

先來看一下首頁的效果圖:
在這里插入圖片描述
從圖片中可以看到首頁的內容主要分為三部分,頭部標題欄,banner,資料串列,底部導航欄;今天就實作這幾個功能,

Column、Row、ConstraintLayout布局先知

在Compose布局中主要常用的就是這三個布局,分別代表縱向排列布局,橫向排列布局,以及約束布局;先大概了解一下用法,以及布局包裹內部元素的排列方便在專案中更好的使用,

Column縱向排列布局

Column主要是將布局包裹內的元素由上至下垂直排列顯示,類似于Recyclerview的item,簡單來看一段代碼:

@Preview
@Composable
fun ColumnItems(){
    Column {
        Text(text = "我是第一個Column元素",Modifier.background(Color.Gray))
        Text(text = "我是第二個Column元素",Modifier.background(Color.Green))
        Text(text = "我是第三個Column元素",Modifier.background(Color.LightGray))
    }
}

可以看到在一個Column里面包裹了三個Text,那么來看一下效果:
在這里插入圖片描述
可以看到所有元素是由上至下進行排列的,

Row橫向排列布局

簡而言之就是將布局里面的元素一個一個的由左到右橫向排列,

再來看一段簡短的代碼:

@Preview
@Composable
fun RowItems(){
    Row {
        Text(text = "我是第一個Row元素",Modifier.background(Color.Gray).height(100.dp))
        Text(text = "我是第二個Row元素",Modifier.background(Color.Green).height(100.dp))
        Text(text = "我是第三個Row元素",Modifier.background(Color.LightGray).height(100.dp))
    }
}

在Row里面同樣包裹了三個Text文本,再來看一下效果:
在這里插入圖片描述
可以看到Row里面的元素是由左到右橫向進行排列的,

ConstraintLayout 約束布局

在compose里面同樣可以使用約束布局,主要主用于一些Column或者Row或者Box布局無法直接實作的布局,在實作更大的布局以及有許多復雜對齊要求以及布局嵌套過深的場景下,ConstraintLayout 用起來更加順手,在使用ConstraintLayout 之前需要先匯入相關依賴包:

implementation "androidx.constraintlayout:constraintlayout-compose:1.0.0-rc01"

這里額外提一句,在你創建專案的時候所有compose的相關依賴包都要和你專案當前的compose版本一致,或者都更新到最新版,如果compose的版本大于你現在匯入的其他依賴庫的版本,那么就會報錯,

在使用ConstraintLayout需要注意以下幾點:

  1. 宣告元素 通過 createRefs() 或 createRef() 方法初始化宣告的,并且每個子元素都會關聯一個ConstraintLayout 中的 Composable 組件;
  2. 關聯組件 Modifier.constrainAs(text)通過constrainAs關聯組件
  3. 約束關系可以使用 linkTo 或其他約束方法實作;
  4. parent 是一個默認存在的參考,代表 ConstraintLayout 父布局本身,也是用于子元素的約束關聯,

來看一段代碼:

@Preview
@Composable
fun ConstraintLayoutDemo(){
    ConstraintLayout {
        //宣告元素
        val (text,text2,text3) = createRefs()

        Text(text = "我是第一個元素",Modifier.height(50.dp).constrainAs(text){
            //將第一個元素固定到父布局的右邊
            end.linkTo(parent.end)
        })
        Text(text = "老二",modifier = Modifier.background(Color.Green).constrainAs(text2){
            //將第二個元素定位到第一個元素的底部
            top.linkTo(text.bottom)
            //,然后于第一個元素居中
            centerTo(text)
        })
        Text(text = "老三",modifier = Modifier.constrainAs(text3){
            //將第三個元素定位到第二個元素的底部
            top.linkTo(text2.bottom)
            //將第三個元素定位在第二個元素的右邊
            start.linkTo(text2.end)
        })
    }
}

看一下效果:
在這里插入圖片描述
約束布局只要習慣linkTo的使用就能很好的使用該布局,

Modifier的簡單使用

Modifier在compose里面可以設定元素的寬高,大小,背景色,邊框,邊距等屬性;這里只介紹一些簡單的用法,

先看一段代碼:

modifier = Modifier
//            .fillMaxSize()//橫向  縱向 都鋪滿,設定了fillMaxSize就不需要設定fillMaxHeight和fillMaxWidth了
//            .fillMaxHeight()//fillMaxHeight縱向鋪滿
            .fillMaxWidth()//fillMaxWidth()橫向鋪滿  match
            .padding(8.dp)//外邊距 vertical = 8.dp 上下有8dp的邊距;  horizontal = 8.dp 水平有8dp的邊距
            .padding(8.dp)//內邊距  padding(8.dp)=.padding(8.dp,8.dp,8.dp,8.dp)左上右下都有8dp的邊距
//            .width(100.dp)//寬100dp
//            .height(100.dp)//高100dp
            .size(100.dp)//寬高 100dp
//            .widthIn(min: Dp = Dp.Unspecified, max: Dp = Dp.Unspecified)//設定自身的最小和最大寬度(當子級元素超過自身時,子級元素超出部分依舊可見);
            .background(Color.Green)//背景顏色
            .border(1.dp, Color.Gray,shape = RoundedCornerShape(20.dp))//邊框
  1. fillMaxSize 設定布局縱向橫向都鋪滿
  2. fillMaxHeight 設定布局鋪滿縱向
  3. fillMaxWidth 設定布局鋪滿橫向,這三個屬性再使用了fillMaxSize 就沒必要在設定下面兩個了
  4. padding 設定邊距,方向由左上右下設定,添加了vertical就是設定垂直的上下邊距,horizontal設定了水平的左右邊距,這里注意寫了兩個padding,第一個是外邊距,第二個是內邊距,外邊距最好是放在Modifier的第一個元素,
  5. width 設定元素的寬
  6. height 設定元素的高
  7. size 設定元素大小,只有一個值時寬高都是一個值,.size(100.dp,200.dp)兩個值前者是寬,后者是高
  8. widthIn 設定自身的最小和最大寬度(當子級元素超過自身時,子級元素超出部分依舊可見)
  9. background 設置元素的背景顏色
  10. border 設定邊框,引數值:邊框大小,邊框顏色,shape

更多Modifier的設定可以查看原始碼或者官方檔案,

底部導航欄的實作

從圖中可以可以出,底部導航欄主要包含四個tab,分別是首頁、專案、分類以及我的,而每個tab又分別包含一張圖片和一個文字,
在這里插入圖片描述
具體實作步驟:

1.撰寫每個tab的樣式,這里要使用到Column進行布局,Column列的意思,就是Column里面的元素會一個順著一個往下排的意思,所以我們需要在里面放一個圖片Icon和一個文本Text,

Column(
   modifier.padding(vertical = 8.dp),//垂直(上下邊距)8dp
   horizontalAlignment = Alignment.CenterHorizontally) {//對齊方式水平居中
   Icon(painter = painterResource(id = iconId),//圖片資源
        contentDescription = tabName,//描述
        //圖片大小						//顏色
        modifier = Modifier.size(24.dp),tint = tint)
        //      文本			字體大小			字體顏色
   Text(text = tabName,fontSize = 11.sp,color = tint)
}

因為是四個按鈕,并且有著選中和未選中的狀態,所以我們需要封裝成一個方法進行使用:

/**
 * 引數決議
 * @DrawableRes iconId: Int
 *
 * iconId  引數名稱
 * Int     引數型別
 * @DrawableRes 只能填入符合當前屬性的值
 * */
@Composable
private fun TabItem(@DrawableRes iconId: Int, //tab 圖示資源
                    tabName: String,//tab 名稱
                    tint: Color,//tab 顏色(選中或者未選中狀態)
                    modifier: Modifier = Modifier
){
    Column(
        modifier.padding(vertical = 8.dp),
        horizontalAlignment = Alignment.CenterHorizontally) {
        Icon(painter = painterResource(id = iconId),
            contentDescription = tabName,
            modifier = Modifier.size(24.dp),tint = tint)
        Text(text = tabName,fontSize = 11.sp,color = tint)
    }
}

2.使用Row放置四個TabItem,Row水平排列的意思,

@Composable
fun BottomBar(modifier: Modifier = Modifier, content: @Composable RowScope.() -> Unit) {
    Row(
        modifier
            .fillMaxWidth()
            .background(ComposeUIDemoTheme.colors.bottomBar)
            .padding(4.dp, 0.dp)
            .navigationBarsPadding(),
        content = content
    )
}
@Composable
fun BottomTabBar(selectedPosition: Int, currentChanged: (Int) -> Unit){
	//使用Row將四個TabItem包裹起來,讓它們水平排列
    BottomBar() {
        TabItem(
            iconId = if (selectedPosition == 0) R.drawable.home_selected else R.drawable.home_unselected,
            tabName = "首頁",
            tint = if (selectedPosition == 0) ComposeUIDemoTheme.colors.iconCurrent else ComposeUIDemoTheme.colors.icon,
            Modifier
                .clickable {
                    currentChanged(0)
                }
                .weight(1f))
        TabItem(
            iconId = if (selectedPosition == 1) R.drawable.project_selected else R.drawable.project_unselected,
            tabName = "專案",
            tint = if (selectedPosition == 1) ComposeUIDemoTheme.colors.iconCurrent else ComposeUIDemoTheme.colors.icon,
            Modifier
                .clickable {
                    currentChanged(1)
                }
                .weight(1f))
        TabItem(
            iconId = if (selectedPosition == 2) R.drawable.classic_selected else R.drawable.classic_unselected,
            tabName = "分類",
            tint = if (selectedPosition == 2) ComposeUIDemoTheme.colors.iconCurrent else ComposeUIDemoTheme.colors.icon,
            Modifier
                .clickable {
                    currentChanged(2)
                }
                .weight(1f))
        TabItem(iconId = if (selectedPosition == 3) R.drawable.mine_selected else R.drawable.mine_unselected,
            tabName = "我的",
            tint = if (selectedPosition == 3) ComposeUIDemoTheme.colors.iconCurrent else ComposeUIDemoTheme.colors.icon,
            Modifier
                .clickable {
                    currentChanged(3)
                }
                .weight(1f))
    }
}

TabItem填充決議:

  1. iconId tab圖示資源,當選中的下標等于當前tab的下標時顯示選中的資源,否則顯示非選中資源
  2. tabName tab文本
  3. tint tab 顏色,同樣分為選中和未選中
  4. Modifier 使用Modifier設定點擊事件,以及權重
  5. currentChanged(0) tabitem的點擊事件,回傳當前item的下標
TabItem(
   iconId = if (selectedPosition == 0) R.drawable.home_selected elseR.drawable.home_unselected,
   tabName = "首頁",
   tint = if (selectedPosition == 0) ComposeUIDemoTheme.colors.iconCurrent else ComposeUIDemoTheme.colors.icon,
   Modifier
        .clickable {
             currentChanged(0)
        }
        .weight(1f))

3.分別創建HomePage、ProjectPage、ClassicPage和MinePage四個頁面,頁面撰寫一些簡單的代碼鋪滿頁面即可,

@Composable
fun ClassicPage(viewModel: BottomTabBarViewModel = viewModel()){
    Column(Modifier.fillMaxWidth()) {
        DemoTopBar(title = "分類")
        Box(
            Modifier
                .background(ComposeUIDemoTheme.colors.background)
                //使用Modifier將頁面鋪滿
                .fillMaxSize()
        ) {
            Text(text = "分類")
        }
    }
}

4.使用HorizontalPager進行頁面滑動,并且與tabitem的點擊事件進行系結,達到頁面滑動切換以及點擊tabitem進行切換的效果,

HorizontalPager主要引數決議:

  1. count 總頁面數
  2. state 當前選中的頁面狀態

使用HorizontalPager需要匯入以下資源:

implementation "com.google.accompanist:accompanist-pager:$accompanist_pager"//0.20.2

具體實作步驟如下:
先通過remember記錄住當前選中的下標,這個主要作用與tabItem的切換

//記錄頁面狀態
val indexState = remember { mutableStateOf(0) }

然后通過rememberPagerState記錄HorizontalPager的currentPager也就是當前頁面下標

val pagerState = rememberPagerState()

使用HorizontalPager填充頁面

HorizontalPager(count = 4,
   state = pagerState,
   modifier = Modifier.fillMaxSize().weight(1f))
    { page: Int ->
         when(page){
             0 ->{
             	HomePage()
             }
             1 ->{
                ProjectPage()
             }
             2 ->{
                ClassicPage()
             }
             3 ->{
                 MinePage()
             }
     }
}

使用LaunchedEffect進行頁面切換

//頁面切換
LaunchedEffect(key1 = indexState.value, block = {
      pagerState.scrollToPage(indexState.value)
})

最后系結底部導航欄并系結點擊事件

//滑動系結底部選單欄
/**
selectedPosition = pagerState.currentPage
將當前的currentPager賦值給tabitem的selectPosition對底部導航欄進行系結

indexState.value = it
將底部導航欄的點擊回呼下標賦值給indexState對pager進行系結
*/
BottomTabBar(selectedPosition = pagerState.currentPage){
       indexState.value = it
}

到這里就能實作一個底部導航欄以及四個頁面的切換了,

首頁內容的實作

Banner的實作

因為獲取Banner資料要進行網路請求,至于網路封裝就不貼代碼了,這里直接從ViewModel開始展示,具體的網路代碼可以移步到專案進行觀看,

首頁ViewModel

主要用于Banner和首頁文章串列的網路請求:

class HomeViewModel : ViewModel() {
    private var _bannerList = MutableLiveData(listOf<BannerEntity>())
    val bannerList:MutableLiveData<List<BannerEntity>>  = _bannerList

    fun getBannerList(){
        NetWork.service.getHomeBanner().enqueue(object : Callback<BaseResult<List<BannerEntity>>>{
            override fun onResponse(call: Call<BaseResult<List<BannerEntity>>>,response: Response<BaseResult<List<BannerEntity>>>) {
                response.body()?.let {
                    _bannerList.value = it.data
                }
            }

            override fun onFailure(call: Call<BaseResult<List<BannerEntity>>>, t: Throwable) {
            }
        })
    }

    private var _articleData = MutableLiveData<ArticleEntityPage>()
    val articleData:MutableLiveData<ArticleEntityPage> = _articleData

    fun getArticleData(){
        NetWork.service.getArticleList().enqueue(object : Callback<BaseResult<ArticleEntityPage>>{
            override fun onResponse(call: Call<BaseResult<ArticleEntityPage>>,response: Response<BaseResult<ArticleEntityPage>>) {
                response.body()?.let {
                    articleData.value = it.data
                }
            }

            override fun onFailure(call: Call<BaseResult<ArticleEntityPage>>, t: Throwable) {
            }
        })
    }
}

在呼叫HomePage的時候將HomeViewModel傳入進去,不推薦直接在compose里面直接呼叫,會重復呼叫:

val bVM = HomeViewModel()
HomePage(bVM = bVM)

HomePage的創建:

fun HomePage(viewModel: BottomTabBarViewModel = viewModel(), bVM:HomeViewModel){
}

資料呼叫進行請求,首先要創建變數通過observeAsState進行資料接收重繪

val bannerList by bVM.bannerList.observeAsState()

Compose的網路請求要放到LaunchedEffect去執行,才不會重復請求資料

val requestState = remember { mutableStateOf("") }
LaunchedEffect(key1 = requestState.value, block = {
   bVM.getBannerList()
})

繪制Banner的View,這里同樣使用到HorizontalPager,并且還使用了coil進行網路加載,需要匯入相關依賴包

implementation 'io.coil-kt:coil-compose:1.3.0'

BannerView的代碼,實作大致和tabitem差不多,只是添加了一個輪播,就不做過多的極細,直接貼代碼了

@ExperimentalCoilApi
@ExperimentalPagerApi
@Composable
fun BannerView(bannerList: List<BannerEntity>,timeMillis:Long){
    Box(
        Modifier
            .fillMaxWidth()
            .height(160.dp)) {

        val pagerState = rememberPagerState()
        var executeChangePage by remember { mutableStateOf(false) }
        var currentPageIndex = 0

        HorizontalPager(count = bannerList.size,
            state = pagerState,
            modifier = Modifier
                .pointerInput(pagerState.currentPage) {
                    awaitPointerEventScope {
                        while (true) {
                            val event = awaitPointerEvent(PointerEventPass.Initial)
                            val dragEvent = event.changes.firstOrNull()
                            when {
                                dragEvent!!.positionChangeConsumed() -> {
                                    return@awaitPointerEventScope
                                }
                                dragEvent.changedToDownIgnoreConsumed() -> {
                                    //記錄下當前的頁面索引值
                                    currentPageIndex = pagerState.currentPage
                                }
                                dragEvent.changedToUpIgnoreConsumed() -> {
                                    if (pagerState.targetPage == null) return@awaitPointerEventScope
                                    if (currentPageIndex == pagerState.currentPage && pagerState.pageCount > 1) {
                                        executeChangePage = !executeChangePage
                                    }
                                }
                            }
                        }
                    }
                }
                .clickable {
                    Log.e(
                        "bannerTAG",
                        "點擊的banner item:${pagerState.currentPage}  itemUrl:${bannerList[pagerState.currentPage].imagePath}"
                    )
                }
                .fillMaxSize()) { page ->
            Image(
                painter = rememberImagePainter(bannerList[page].imagePath),
                modifier = Modifier.fillMaxSize(),
                contentScale = ContentScale.Crop,
                contentDescription = null
            )
        }

        //自動輪播
        LaunchedEffect(key1 = pagerState.currentPage, block = {
            Log.e("LaunchedEffect","${pagerState.currentPage}")
            if (pagerState.currentPage >=0 && pagerState.currentPage < bannerList.size -1){
                delay(timeMillis = timeMillis)
                pagerState.animateScrollToPage(pagerState.currentPage +1)
            }else{
                delay(timeMillis = timeMillis)
                pagerState.animateScrollToPage(0)
            }
        })

    }
}

最后就行呼叫

InitBanner(bannerList= bannerList!!,2000L)

實作文章串列

資料請求就不做過多贅述了,和banner的資料請求一樣,這里主要決議一下Compose的約束布局ConstraintLayout,

Compose約束布局需要匯入以下相關依賴:

implementation "androidx.constraintlayout:constraintlayout-compose:1.0.0-rc01"
  1. val img = createRef() 創建一個依賴點,就像xml約束布局里面的id意義昂
  2. Modifier 使用Modifier的constrainAs進行約束調整
ConstraintLayout(
        Modifier
            .fillMaxWidth()
            .padding(horizontal = 8.dp)) {
             val img = createRef()
             Text(text = "作者:${entity.audit}")

             Image(painter = painterResource(id = R.drawable.icon_un_select),
                   contentDescription = "收藏",
                   Modifier
                       .width(30.dp)
                       .height(30.dp)
                       .constrainAs(img) {
                           end.linkTo(parent.end)
                        },
            		alignment = Alignment.CenterEnd)
}

Item代碼如下

@Composable
private fun ArticleListItem(entity: ArticleEntity,modifier: Modifier = Modifier){
    Card(
        shape = RoundedCornerShape(10.dp),
        backgroundColor = ComposeUIDemoTheme.colors.listItem,
        elevation = 2.dp,modifier =
        Modifier.padding(0.dp,10.dp,0.dp,0.dp)
    ) {
        Row(
            Modifier
                .fillMaxWidth()
                .clickable {
                    Log.e("articleTAG", "文章點擊")
                }) {
            Column(Modifier.fillMaxWidth()) {
                Text(text = "${entity.title}",
                    Modifier.padding(8.dp,8.dp,8.dp,8.dp),
                    fontSize = 16.sp,
                    color = ComposeUIDemoTheme.colors.textPrimary,
                    maxLines = 1,
                    overflow = TextOverflow.Ellipsis)

                ConstraintLayout(
                    Modifier
                        .fillMaxWidth()
                        .padding(horizontal = 8.dp)) {
                    val img = createRef()
                    Text(text = "作者:${entity.audit}")

                    Image(painter = painterResource(id = R.drawable.icon_un_select),
                        contentDescription = "收藏",
                        Modifier
                            .width(30.dp)
                            .height(30.dp)
                            .constrainAs(img) {
                                end.linkTo(parent.end)
                            },
                        alignment = Alignment.CenterEnd)
                }

                ConstraintLayout(
                    Modifier
                        .fillMaxWidth()
                        .padding(8.dp)) {
                    val parentView = createRef()

                    Text(text = "${entity.superChapterName}",
                        modifier = Modifier
                            .padding(8.dp)
                            .border(
                                1.dp, color = Color(R.color.b_666),
                                RoundedCornerShape(8.dp)
                            )
                            .padding(horizontal = 8.dp, vertical = 2.dp),
                        color = Color.Gray,
                        fontSize = 12.sp
                    )
                    Text(text = "${entity.niceShareDate}",modifier = Modifier.constrainAs(parentView){
                        end.linkTo(parent.end)
                        centerVerticallyTo(parent)
                    },color = Color.Gray,fontSize = 12.sp,textAlign = TextAlign.Center)
                }
            }
        }
    }
}

SwipeRefresh下拉重繪

操作串列的時候下拉重繪和上拉加載更多的操作肯定是少不了的,這里先說一下Compose SwipeRefresh下拉重繪組件的使用,

首先匯入依賴包:

implementation "com.google.accompanist:accompanist-swiperefresh:$accompanist_pager"//0.20.2

在ViewModel里面保存一個記錄重繪狀態的元素,在重繪請求資料程序中這個值要變成true進行重繪影片的加載,重繪完成之后要變成false關閉加載影片,

val _isRefreshing: MutableLiveData<Boolean> = MutableLiveData(false)
val isRefreshing by viewModel._isRefreshing.observeAsState(false)
SwipeRefresh(state = rememberSwipeRefreshState(isRefreshing = isRefreshing),
    onRefresh = {
          //將重繪狀態的值改為true  顯示加載影片
          viewModel._isRefreshing.value = true
          //將請求資料的值做改變  讓它能重新請求資料
          requestState.value = "refresh"+System.currentTimeMillis()
    }) {
    	//填充資料
        Box(
           Modifier
                .background(ComposeUIDemoTheme.colors.background)
                .fillMaxSize()
          ) {
              Column(Modifier.fillMaxWidth()) {
                    if (bannerList !== null){
                        InitBanner(bannerList= bannerList!!,2000L)
                    }
                    if (articleEntityPage !== null){
                        InitHomeArticleList(articleData = articleEntityPage!!)
                    }
            }
     }
}

LaunchedEffect(key1 = requestState.value, block = {
    bVM.getBannerList()
    bVM.getArticleData({
        viewModel._isRefreshing.value = false//將重繪狀態的值改成false 關閉加載狀態
    })
})

首頁的內容一共就這么多了,到這里就能實作一個 app的地步導航欄以及首頁了,

LaunchedEffect簡介

LaunchedEffect存在的意義是允許我們在被Composable標注的方法中使用協程,

@Composable
@NonRestartableComposable
@OptIn(InternalComposeApi::class)
fun LaunchedEffect(
    key1: Any?,
    block: suspend CoroutineScope.() -> Unit
) {
    val applyContext = currentComposer.applyCoroutineContext
    remember(key1) { LaunchedEffectImpl(applyContext, block) }
}

主要引數是key1,block;key1是觸發條件,當key1發生改變的時候就會執行block里面的方法,注意是發生改變的時候,

我們一開始在請求資料的時候創建了一個requestState來記錄請求狀態,如果這個狀態不發生改變,那么就只會觸發一次LaunchedEffect對資料進行請求,

//記錄請求狀態
val requestState = remember { mutableStateOf("") }
LaunchedEffect(key1 = requestState.value, block = {
   bVM.getBannerList()
   bVM.getArticleData({
        viewModel._isRefreshing.value = false//將重繪狀態的值改成false 關閉加載狀態
   })
})

而當我們在重繪的時候在onRefres里面改變了這個引數的值,那么它就會觸發LaunchedEffect重新進行資料請求,

onRefresh = {
      //將重繪狀態的值改為true  顯示加載影片
      viewModel._isRefreshing.value = true
      //將請求資料的值做改變  讓它能重新請求資料
      requestState.value = "refresh"+System.currentTimeMillis()
}

HorizontalPager簡介

HorizontalPager類似于AndroidView的ViewPager,用于頁面承載和滑動切換,主要引數是count頁面資料,以及state 當前頁面狀態,

// Display 10 items
HorizontalPager(count = 10) { page ->
    // Our page content
    Text(
        text = "Page: $page",
        modifier = Modifier.fillMaxWidth()
    )
}

通過scrollToPage可以進行頁面切換,但是只能在協程里面被執行,所以同樣要使用LaunchedEffect進行頁面切換,

//頁面切換
LaunchedEffect(key1 = indexState.value, block = {
     pagerState.scrollToPage(indexState.value)
})

更多用法以及demo移步以下鏈接,

https://google.github.io/accompanist/pager/

原始碼地址

碼云地址,記得切換main分支
完結~

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/356134.html

標籤:其他

上一篇:Android應用生命周期實作簡單的秒表App

下一篇:Android 12 滅屏流程(一) Power鍵滅屏

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more