玖叶教程网

前端编程开发入门

Jetpack Compose Paging使用(jetpack compose教程)

1.请求接口地址:https://api.github.com/search/repositories

2.创建网络请求GithubService及ViewMode

import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.GET
import retrofit2.http.Query

interface GithubService {
    /**
     * 仓库搜索
     */
    @GET("search/repositories")
    suspend fun searchRepositors(
        @Query("q") words: String,
        @Query("page") page: Int = 1,
        @Query("per_page") pageSize: Int = 30,
        @Query("sort") sort:String = "stars",
        @Query("order") order: String = "desc",
    ): RepositorResult
}

private val service: GithubService by lazy {
    val okHttpClient = OkHttpClient.Builder()
        .addInterceptor(HttpLoggingInterceptor().apply { setLevel(HttpLoggingInterceptor.Level.BODY) })
        .build()

    val retrofit = Retrofit.Builder()
        .baseUrl("https://api.github.com")
        .client(okHttpClient)
        .addConverterFactory(GsonConverterFactory.create())
        .build()

    retrofit.create(GithubService::class.java)
}

fun getGithubService() = service

GithubViewModel

class GithubViewModel: ViewModel() {
    val githubService: GithubService = getGithubService()
    val repositorPager = Pager(config = PagingConfig(pageSize = 6)){
        MyPagingSource(getGithubService(),"compose")
    }.flow.cachedIn(viewModelScope)
    val dataLive = MutableLiveData<MutableList<RepositorItem>>()
    fun loadData(offset:Int,pageSize:Int){
        viewModelScope.launch {
            val repositorRst = githubService.searchRepositors("android", offset, pageSize)
            dataLive.postValue(repositorRst.items.toMutableList())
        }
    }
}

3.实体Bean

data class RepositorResult(
    @SerializedName("incomplete_results")
    var incompleteResults: Boolean,
    @SerializedName("items")
    var items: List<RepositorItem>,
    @SerializedName("total_count")
    var totalCount: Int
)


data class Rsp(
    @SerializedName("incomplete_results")
    var incompleteResults: Boolean,
    @SerializedName("items")
    var items: List<RepositorItem>,
    @SerializedName("total_count")
    var totalCount: Int
)
data class RepositorItem(
    @SerializedName("allow_forking")
    var allowForking: Boolean,
    @SerializedName("archive_url")
    var archiveUrl: String,
    @SerializedName("archived")
    var archived: Boolean,
    @SerializedName("assignees_url")
    var assigneesUrl: String,
    @SerializedName("blobs_url")
    var blobsUrl: String,
    @SerializedName("branches_url")
    var branchesUrl: String,
    @SerializedName("clone_url")
    var cloneUrl: String,
    @SerializedName("collaborators_url")
    var collaboratorsUrl: String,
    @SerializedName("comments_url")
    var commentsUrl: String,
    @SerializedName("commits_url")
    var commitsUrl: String,
    @SerializedName("compare_url")
    var compareUrl: String,
    @SerializedName("contents_url")
    var contentsUrl: String,
    @SerializedName("contributors_url")
    var contributorsUrl: String,
    @SerializedName("created_at")
    var createdAt: String,
    @SerializedName("default_branch")
    var defaultBranch: String,
    @SerializedName("deployments_url")
    var deploymentsUrl: String,
    @SerializedName("description")
    var description: String,
    @SerializedName("disabled")
    var disabled: Boolean,
    @SerializedName("downloads_url")
    var downloadsUrl: String,
    @SerializedName("events_url")
    var eventsUrl: String,
    @SerializedName("fork")
    var fork: Boolean,
    @SerializedName("forks")
    var forks: Int,
    @SerializedName("forks_count")
    var forksCount: Int,
    @SerializedName("forks_url")
    var forksUrl: String,
    @SerializedName("full_name")
    var fullName: String,
    @SerializedName("git_commits_url")
    var gitCommitsUrl: String,
    @SerializedName("git_refs_url")
    var gitRefsUrl: String,
    @SerializedName("git_tags_url")
    var gitTagsUrl: String,
    @SerializedName("git_url")
    var gitUrl: String,
    @SerializedName("has_discussions")
    var hasDiscussions: Boolean,
    @SerializedName("has_downloads")
    var hasDownloads: Boolean,
    @SerializedName("has_issues")
    var hasIssues: Boolean,
    @SerializedName("has_pages")
    var hasPages: Boolean,
    @SerializedName("has_projects")
    var hasProjects: Boolean,
    @SerializedName("has_wiki")
    var hasWiki: Boolean,
    @SerializedName("homepage")
    var homepage: String,
    @SerializedName("hooks_url")
    var hooksUrl: String,
    @SerializedName("html_url")
    var htmlUrl: String,
    @SerializedName("id")
    var id: Int,
    @SerializedName("is_template")
    var isTemplate: Boolean,
    @SerializedName("issue_comment_url")
    var issueCommentUrl: String,
    @SerializedName("issue_events_url")
    var issueEventsUrl: String,
    @SerializedName("issues_url")
    var issuesUrl: String,
    @SerializedName("keys_url")
    var keysUrl: String,
    @SerializedName("labels_url")
    var labelsUrl: String,
    @SerializedName("language")
    var language: String,
    @SerializedName("languages_url")
    var languagesUrl: String,
    @SerializedName("license")
    var license: License,
    @SerializedName("merges_url")
    var mergesUrl: String,
    @SerializedName("milestones_url")
    var milestonesUrl: String,
    @SerializedName("mirror_url")
    var mirrorUrl: Any,
    @SerializedName("name")
    var name: String,
    @SerializedName("node_id")
    var nodeId: String,
    @SerializedName("notifications_url")
    var notificationsUrl: String,
    @SerializedName("open_issues")
    var openIssues: Int,
    @SerializedName("open_issues_count")
    var openIssuesCount: Int,
    @SerializedName("owner")
    var owner: Owner,
    @SerializedName("private")
    var `private`: Boolean,
    @SerializedName("pulls_url")
    var pullsUrl: String,
    @SerializedName("pushed_at")
    var pushedAt: String,
    @SerializedName("releases_url")
    var releasesUrl: String,
    @SerializedName("score")
    var score: Double,
    @SerializedName("size")
    var size: Int,
    @SerializedName("ssh_url")
    var sshUrl: String,
    @SerializedName("stargazers_count")
    var stargazersCount: Int,
    @SerializedName("stargazers_url")
    var stargazersUrl: String,
    @SerializedName("statuses_url")
    var statusesUrl: String,
    @SerializedName("subscribers_url")
    var subscribersUrl: String,
    @SerializedName("subscription_url")
    var subscriptionUrl: String,
    @SerializedName("svn_url")
    var svnUrl: String,
    @SerializedName("tags_url")
    var tagsUrl: String,
    @SerializedName("teams_url")
    var teamsUrl: String,
    @SerializedName("topics")
    var topics: List<String>,
    @SerializedName("trees_url")
    var treesUrl: String,
    @SerializedName("updated_at")
    var updatedAt: String,
    @SerializedName("url")
    var url: String,
    @SerializedName("visibility")
    var visibility: String,
    @SerializedName("watchers")
    var watchers: Int,
    @SerializedName("watchers_count")
    var watchersCount: Int,
    @SerializedName("web_commit_signoff_required")
    var webCommitSignoffRequired: Boolean
) {
    data class License(
        @SerializedName("key")
        var key: String,
        @SerializedName("name")
        var name: String,
        @SerializedName("node_id")
        var nodeId: String,
        @SerializedName("spdx_id")
        var spdxId: String,
        @SerializedName("url")
        var url: String
    )

    data class Owner(
        @SerializedName("avatar_url")
        var avatarUrl: String,
        @SerializedName("events_url")
        var eventsUrl: String,
        @SerializedName("followers_url")
        var followersUrl: String,
        @SerializedName("following_url")
        var followingUrl: String,
        @SerializedName("gists_url")
        var gistsUrl: String,
        @SerializedName("gravatar_id")
        var gravatarId: String,
        @SerializedName("html_url")
        var htmlUrl: String,
        @SerializedName("id")
        var id: Int,
        @SerializedName("login")
        var login: String,
        @SerializedName("node_id")
        var nodeId: String,
        @SerializedName("organizations_url")
        var organizationsUrl: String,
        @SerializedName("received_events_url")
        var receivedEventsUrl: String,
        @SerializedName("repos_url")
        var reposUrl: String,
        @SerializedName("site_admin")
        var siteAdmin: Boolean,
        @SerializedName("starred_url")
        var starredUrl: String,
        @SerializedName("subscriptions_url")
        var subscriptionsUrl: String,
        @SerializedName("type")
        var type: String,
        @SerializedName("url")
        var url: String
    )
}

4.MyPagingSource

import androidx.paging.PagingSource
import androidx.paging.PagingState

class MyPagingSource(
    val githubService: GithubService = getGithubService(),
    val words: String,
) : PagingSource<Int, RepositorItem>() {

    override fun getRefreshKey(state: PagingState<Int, RepositorItem>): Int? {
        return state.anchorPosition?.let {
            val anchorPage = state.closestPageToPosition(it)
            anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
        }
    }

    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, RepositorItem> {
        try {
            val nextPage: Int = params.key ?: 1
            val repositorRst = githubService.searchRepositors(words, nextPage, 20)
            return LoadResult.Page(
                data = repositorRst.items,
                prevKey = if (nextPage == 1) null else nextPage - 1,
                nextKey = if (repositorRst.items.isEmpty()) null else nextPage + 1
            )
        }catch (e:Exception){
            return LoadResult.Error(e)
        }
    }
}

5.上拉加载及下拉刷新组建SwipeRefreshList

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ButtonDefaults.elevation
import androidx.compose.material.ButtonDefaults.textButtonColors
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.paging.LoadState
import androidx.paging.compose.LazyPagingItems
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
import com.zj.refreshlayout.SwipeRefreshLayout
import kotlinx.coroutines.delay

/**
 * 下拉加载封装
 *
 * implementation 'io.github.shenzhen2017:compose-refreshlayout:1.0.0'
 * */
@Composable
fun <T : Any> SwipeRefreshList(
    collectAsLazyPagingItems: LazyPagingItems<T>,
    listContent: LazyListScope.() -> Unit,
) {
    var refreshing by remember { mutableStateOf(false) }
    LaunchedEffect(refreshing) {
        if (refreshing) {
            delay(2000)
            collectAsLazyPagingItems.refresh()
            refreshing = false
        }
    }
    val rememberSwipeRefreshState = rememberSwipeRefreshState(isRefreshing = false)
    SwipeRefreshLayout(isRefreshing = refreshing, onRefresh = {
            refreshing  = true
        }){
        rememberSwipeRefreshState.isRefreshing = collectAsLazyPagingItems.loadState.refresh is LoadState.Loading

        LazyColumn(
            modifier = Modifier
                .fillMaxWidth()
                .fillMaxHeight(),

            ) {
            listContent()
            collectAsLazyPagingItems.apply {
                when {
                    loadState.append is LoadState.Loading -> {
                        //加载更多,底部loading
                        item { LoadingItem() }
                    }
                    loadState.append is LoadState.Error -> {
                        //加载更多异常
                        item {
                            ErrorMoreRetryItem() {
                                collectAsLazyPagingItems.retry()
                            }
                        }
                    }
                    loadState.refresh is LoadState.Error -> {
                        if (collectAsLazyPagingItems.itemCount <= 0) {
                            //刷新的时候,如果itemCount小于0,第一次加载异常
                            item {
                                ErrorContent() {
                                    collectAsLazyPagingItems.retry()
                                }
                            }
                        } else {
                            item {
                                ErrorMoreRetryItem() {
                                    collectAsLazyPagingItems.retry()
                                }
                            }
                        }
                    }
                    loadState.refresh is LoadState.Loading -> {
                        // 第一次加载且正在加载中
                        if (collectAsLazyPagingItems.itemCount == 0) {
                        }
                    }
                }
            }

        }
    }
}

/**
 * 底部加载更多失败处理
 * */
@Composable
fun ErrorMoreRetryItem(retry: () -> Unit) {
    Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
        TextButton(
            onClick = { retry() },
            modifier = Modifier
                .padding(20.dp)
                .width(80.dp)
                .height(30.dp),
            shape = RoundedCornerShape(6.dp),
            contentPadding = PaddingValues(3.dp),
            colors = textButtonColors(backgroundColor = Color.White),
            elevation = elevation(
                defaultElevation = 2.dp,
                pressedElevation = 4.dp,
            ),
        ) {
            Text(text = "加载失败,请重试", color = Color.Gray)
        }
    }
}

/**
 * 页面加载失败处理
 * */
@Composable
fun ErrorContent(retry: () -> Unit) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(top = 100.dp),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Image(
            modifier = Modifier.padding(top = 80.dp),
            painter = painterResource(id = R.drawable.loading_big_13),
            contentDescription = null
        )
        Text(text = "请求失败,请检查网络", modifier = Modifier.padding(8.dp))
        TextButton(
            onClick = { retry() },
            modifier = Modifier
                .padding(20.dp)
                .width(80.dp)
                .height(30.dp),
            shape = RoundedCornerShape(10.dp),
            contentPadding = PaddingValues(5.dp),
            colors = textButtonColors(backgroundColor = Color.White),
            elevation = elevation(
                defaultElevation = 2.dp,
                pressedElevation = 4.dp,
            )
            //colors = ButtonDefaults
        ) { Text(text = "重试", color = Color.Gray) }
    }
}

/**
 * 底部加载更多正在加载中...
 * */
@Composable
fun LoadingItem() {
    Row(
        modifier = Modifier
            .height(34.dp)
            .fillMaxWidth()
            .padding(5.dp),
        horizontalArrangement = Arrangement.Center
    ) {
        CircularProgressIndicator(
            modifier = Modifier
                .size(24.dp),
            color = Color.Gray,
            strokeWidth = 2.dp
        )
        Text(
            text = "加载中...",
            color = Color.Gray,
            modifier = Modifier
                .fillMaxHeight()
                .padding(start = 20.dp),
            fontSize = 18.sp,
        )
    }
}

6.显示到Activity中

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.compose.ui.platform.ComposeView
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.items

class LoadActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_load)
       val composeView = findViewById<ComposeView>(R.id.compose)
        composeView.setContent {
            val viewModel: GithubViewModel = viewModel()
            val lazyPagingItems = viewModel.repositorPager.collectAsLazyPagingItems()
            SwipeRefreshList(lazyPagingItems){
                items(items = lazyPagingItems) { item ->
                    item?.let {
                        RepositorCard(item)
                    }
                }
        }
    }}
}

7.UI组件RepositorCard


import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Card
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage

@Composable
fun RepositorCard(repositorItem: RepositorItem) {
    Card(modifier = Modifier
        .fillMaxWidth()
        .padding(8.dp)) {
        Row(modifier = Modifier
            .fillMaxWidth()
            .height(88.dp)) {
            Spacer(modifier = Modifier.width(10.dp))
            Surface(shape = CircleShape, modifier = Modifier
                .size(66.dp)
                .align(Alignment.CenterVertically)) {
                AsyncImage(model = repositorItem.owner.avatarUrl,
                    contentDescription = "",
                    contentScale = ContentScale.Crop)
            }

            Spacer(modifier = Modifier.width(15.dp))
            Column(modifier = Modifier.fillMaxWidth()) {
                Spacer(modifier = Modifier.height(8.dp))
                Text(text = repositorItem.name,
                    color = MaterialTheme.colors.primary,
                    style = MaterialTheme.typography.h6)
                Text(text = repositorItem.fullName, style = MaterialTheme.typography.subtitle1)
            }
        }
    }
}

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言