当前位置:首页 » 《随便一记》 » 正文

Android compose wandroid app之项目页面的实现_theyangchoi的博客

10 人参与  2022年06月05日 10:52  分类 : 《随便一记》  评论

点击全文阅读


项目页面的实现

  • 前言
    • 获取数据
    • ScrollableTabRow实现顶部滑动菜单
      • ScrollableTabRow属性解析
        • 将ScrollableTabRow和HorizontalPager进行绑定
        • 根据各种属性设置样式
        • 循环添加tabs子项元素
    • HorizontalPager 实现页面数据列表
        • 列表样式
        • 使用HorizontalPager加载页面
    • Compose中Webview的使用
      • AndroidView的属性
        • 使用Webview
    • 源码地址

前言

之前已经实现首页和分类页面,今天来实现项目页面,主要是一个顶部的滑动菜单,和下面滑动的pager,以及点击item跳转webview打开链接;先来看一下效果图,因为手机崩掉了,所以用一张小程序的效果图代替,大致一样。

在这里插入图片描述

获取数据

在viewmodel中获取数据,顶部的tab菜单栏直接获取,底部数据栏在选中的tab发生变化时要重新获取。

class ProjectViewModel : ViewModel() {
    private var _treeList = MutableLiveData(listOf<TreeEntity>())
    val treeList = _treeList

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

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

        })
    }

    private var _treeChild = MutableLiveData<TreeChildEntity>()
    val treeChild:MutableLiveData<TreeChildEntity> = _treeChild

    fun getTreeChildList(cid:Int){
        NetWork.service.getProjectTreeChildList(cid).enqueue(object : Callback<BaseResult<TreeChildEntity>>{
            override fun onResponse(
                call: Call<BaseResult<TreeChildEntity>>,
                response: Response<BaseResult<TreeChildEntity>>) {
                response.body()?.let {
                    _treeChild.value = it.data
                }
            }

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

        })
    }

    val _isToWebview: MutableLiveData<Boolean> = MutableLiveData(false)
    val webviewUrl: MutableLiveData<String> = MutableLiveData("")
    init {
        getProjectTreeList()
    }
}

点击tab重新获取列表数据

val dataList by pVM.treeChild.observeAsState()

LaunchedEffect(key1 = pageIndex, block = {
    pVM.getTreeChildList(cid = cid)
})

数据已经请求到了,接下来就是页面的绘制。

ScrollableTabRow实现顶部滑动菜单

使用TabRow可以实现一个菜单栏,但是不能滑动,这里官方推出了ScrollableTabRow组件来实现可滑动的菜单栏,在菜单栏有很多的情况下可以使用此组件来实现。

ScrollableTabRow属性解析

先来了解一下ScrollableTabRow的属性,方便在项目中使用:

@Composable
fun ScrollableTabRow(
    selectedTabIndex: Int,
    modifier: Modifier = Modifier,
    backgroundColor: Color = MaterialTheme.colors.primarySurface,
    contentColor: Color = contentColorFor(backgroundColor),
    edgePadding: Dp = TabRowDefaults.ScrollableTabRowPadding,
    indicator: @Composable (tabPositions: List<TabPosition>) -> Unit = @Composable { tabPositions ->
        TabRowDefaults.Indicator(
            Modifier.tabIndicatorOffset(tabPositions[selectedTabIndex])
        )
    },
    divider: @Composable () -> Unit = @Composable {
        TabRowDefaults.Divider()
    },
    tabs: @Composable () -> Unit
) 
  1. selectedTabIndex 当前选中的tabitem下标
  2. modifier .
  3. backgroundColor 背景颜色
  4. contentColor 内容颜色
  5. edgePadding 左边边距,默认是有52.dp的边距,所以左边和右边看起来就会离屏幕两侧很远
  6. indicator 设置滑动条的属性,默认是白色
  7. divider 底部分割线
  8. tabs tab元素集合

将ScrollableTabRow和HorizontalPager进行绑定

在代码中使用ScrollableTabRow,在使用之前我们需要获取HorizontalPager的页面状态,毕竟菜单栏和页面是要进行绑定的,所以用HorizontalPager的页面状态来进行绑定:

val pagerState =  rememberPagerState()//记录页面状态
selectedTabIndex = pagerState.currentPage,//将页面和tab进行绑定
pagerState.scrollToPage(index)//点击tab滑动到指定pager

根据各种属性设置样式

ScrollableTabRow(
     selectedTabIndex = pagerState.currentPage,
     modifier = Modifier
           .fillMaxWidth()
           .height(50.dp),
     backgroundColor = Color.White,
     indicator = { positions ->//设置滑动条的属性,默认是白色的
           TabRowDefaults.Indicator(
                  color = Color(114,160,240),
                  modifier = Modifier
                        .tabIndicatorOffset(positions[pagerState.currentPage])
                        .width(10.dp)
           )
     },
     divider = {//设置底部的分割线
           Box(Modifier
                   .fillMaxWidth()
                   .height(2.dp)
                   .background(Color.White)) {

           }
     },edgePadding = 0.dp) {
     //tasb
}

循环添加tabs子项元素

if (treeList !== null && treeList?.size !== 0){
        treeList!!.forEachIndexed { index, treeEntity ->
               Tab(
                     text = {
                          Text(
                                    text = treeEntity.name,
                                    color = if (index == pagerState.currentPage) Color(114,160,240) else Color.Black)
                     },                          //标签名
                     selected = pagerState.currentPage == index,      //是否选中
                     onClick = {                                      //点击事件
                                CoroutineScope(Dispatchers.Main).launch {
                                    pagerState.scrollToPage(index)
                                }
                      },
                      modifier=Modifier.alpha(0.9f),            //透明度
                      enabled=true,                                   //是否启用
                      selectedContentColor= Color(114,160,240),                    //选中的颜色
                      unselectedContentColor= Color.Black,                  //未选中的颜色
               )
     }

}

HorizontalPager 实现页面数据列表

HorizontalPager和ConstraintLayout已经在前面介绍过了,这里就不再介绍了,直接贴列表代码了。

列表样式

列表样式没什么特殊的,就用到了compose 的ConstraintLayout布局,在使用的时候多注意控件id就可以了。

@ExperimentalMaterialApi
@Composable
private fun TreeChildList(viewModel: BottomTabBarViewModel,pageIndex:Int,cid:Int,pVM: ProjectViewModel){

    val dataList by pVM.treeChild.observeAsState()

    LaunchedEffect(key1 = pageIndex, block = {
        pVM.getTreeChildList(cid = cid)
    })

    if (dataList !== null && dataList?.size !== 0){
        initTreeChild(viewModel,dataList!!.datas!!,pVM)
    }
}

@ExperimentalMaterialApi
@Composable
private fun initTreeChild(viewModel: BottomTabBarViewModel,dataList:List<TreeChildDetailEntity>,pVM: ProjectViewModel){
    LazyColumn(modifier = Modifier.fillMaxSize()){
        itemsIndexed(dataList){ index: Int, item: TreeChildDetailEntity ->
            TreeChildItem(viewModel,entity = item,pVM)
        }
    }
}

@SuppressLint("UnrememberedMutableState")
@ExperimentalMaterialApi
@Composable
private fun TreeChildItem(viewModel: BottomTabBarViewModel,entity: TreeChildDetailEntity,pVM: ProjectViewModel){
    val openWebView by pVM._isToWebview.observeAsState()
    val url by pVM.webviewUrl.observeAsState()
    if (openWebView == true && !url.isNullOrBlank()){
        pVM._isToWebview.value = false
        viewModel.openWebViewPage = url
    }
    Card(
        modifier = Modifier
            .padding(10.dp, 10.dp, 10.dp, 0.dp)
            .clickable {
                pVM._isToWebview.value = true
                pVM.webviewUrl.value = entity.link
            },
        elevation = 2.dp,
        shape = RoundedCornerShape(10.dp)) {
        ConstraintLayout(modifier = Modifier.fillMaxWidth()) {
            val (img,title,desc,time,author) = createRefs()
            Image(
                painter = rememberImagePainter(entity.envelopePic),
                modifier = Modifier
                    .width(120.dp)
                    .height(150.dp)
                    .padding(10.dp, 10.dp, 10.dp, 10.dp)
                    .constrainAs(img) {},
                contentScale = ContentScale.Crop,
                contentDescription = null
            )

            Text(modifier = Modifier
                .padding(0.dp, 10.dp, 32.dp, 0.dp)
                .constrainAs(title) {
                    top.linkTo(img.top)
                    start.linkTo(img.end)
                },
                text = entity.title,
                fontSize = 14.sp,
                color = Color.Black,
                maxLines = 2,
                overflow = TextOverflow.Ellipsis)

            Text(modifier = Modifier
                .padding(0.dp, 10.dp, 32.dp, 0.dp)
                .constrainAs(desc) {
                    top.linkTo(title.bottom)
                    start.linkTo(img.end)
                },
                text = entity.desc,
                fontSize = 12.sp,
                color = Color.Gray,
                maxLines = 2,
                overflow = TextOverflow.Ellipsis)


            Text(text = entity.niceShareDate,
                fontSize = 12.sp,
                color = Color.Gray,modifier = Modifier
                    .constrainAs(time) {
                        bottom.linkTo(img.bottom)
                        start.linkTo(img.end)
                    }
                    .padding(vertical = 10.dp))

            Text(text = "${entity.author}",
                modifier = Modifier
                    .padding(8.dp)
                    .border(
                        1.dp, color = Color(R.color.b_666),
                        RoundedCornerShape(8.dp)
                    )
                    .width(100.dp)
                    .padding(horizontal = 8.dp, vertical = 2.dp)
                    .constrainAs(author) {
                        bottom.linkTo(img.bottom)
                        end.linkTo(parent.end)
                    },
                color = Color.Gray,
                fontSize = 12.sp,
                maxLines = 1,
                textAlign = TextAlign.Center,
                overflow = TextOverflow.Ellipsis
            )
        }
    }
}

使用HorizontalPager加载页面

在使用HorizontalPager的时候要注意count和state参数就可以了。

HorizontalPager(
         count = treeList!!.size,
         state = pagerState,                                     //用于控制或观察viewpage状态的状态对象。
         modifier=Modifier.padding(top = 4.dp),                  //修饰符
         reverseLayout=false,                                    //反转滚动和布局的方向,为true时第一个页面在最后
         itemSpacing=2.dp,                                       //水平间距
         verticalAlignment= Alignment.CenterVertically           //垂直对齐
         ) { page ->                                                 //页面内容描述
           TreeChildList(viewModel,pageIndex = pagerState.currentPage,cid = treeList!![pagerState.currentPage].id,pVM = pVM)
}

Compose中Webview的使用

在android中我们要记在一个网页链接都是使用webview进行加载,但是在compose里面没有对应的组件,那么还是要使用Webview进行加载;这里就需要使用到AndroidView这个组件了,让我们在compose中可以使用原生android的控件,这里就来使用一下Webview。

AndroidView的属性

先来看一下AndroidView的属性

@Composable
fun <T : View> AndroidView(
    factory: (Context) -> T,
    modifier: Modifier = Modifier,
    update: (T) -> Unit = NoOpUpdate
) 
  1. modifier
  2. factory 主要就是这个参数,实现我们使用android原生view的代码块

使用Webview

既然是在compose里面使用,所以方法还是要被@Composable标注起来,再传入一个String类型的url就可以了;然后webview的配置可以和在android中使用时的配置一样。

fun WebViewPage(modifier: Modifier = Modifier,linkUrl:String){
    Column(modifier = Modifier.fillMaxSize()) {
        AndroidView(modifier = Modifier.fillMaxSize(),
            factory = {
                val webView = WebView(it)
                webView.settings.javaScriptEnabled = true
                webView.settings.javaScriptCanOpenWindowsAutomatically = true
                webView.settings.domStorageEnabled = true
                webView.settings.loadsImagesAutomatically = true
                webView.settings.mediaPlaybackRequiresUserGesture = false
                webView.webViewClient = WebViewClient()
                webView.loadUrl(linkUrl)
                webView
            })
    }
}

配置完成了再点击item的时候打开当前pager就行了;但是有一个问题,知道compose是在当前页面打开,所以如果在点击item的时候加载这个webview,那么他就会直接在item那里进行加载,而不是跳转到新页面,这里就需要将Webview的pager放置到首页,然后通过viewmodel记录状态进行打开。

在viewmodel中记录打开状态:

var openModule: String? by mutableStateOf(null)
var openWebViewPage: String? by mutableStateOf(null)

如果符合条件viewModel.openWebViewPage !== null,则加载webview的页面:

val openOffset by animateFloatAsState(
       if (viewModel.openModule == null) {
             1f
       } else {
             0f
       }
)
fun Modifier.percentOffsetX(percent: Float) = this.layout { measurable, constraints ->
       val placeable = measurable.measure(constraints)
       layout(placeable.width, placeable.height) {
             placeable.placeRelative(IntOffset((placeable.width * percent).roundToInt(), 0))
       }
}
if (viewModel.openWebViewPage !== null){
        WebViewPage(Modifier.percentOffsetX(openOffset),linkUrl = viewModel.openWebViewPage!!)
}

在点击item的时候设置viewmodel中打开状态的值:

viewModel.openWebViewPage = url//这里是设置为要打开的链接,具体可以按照自己需求设置

以上就是在compose中使用android view的方法了。

源码地址

到这里今天的代码就结束了,源码戳~


点击全文阅读


本文链接:http://zhangshiyu.com/post/41419.html

页面  滑动  属性  
<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1