这篇文章主要围绕Android:开火xmpp展开,旨在为您提供一份详细的参考资料。我们将全面介绍Android:开火xmpp,同时也会为您带来AndroidComposewanandroidapp之底
这篇文章主要围绕Android:开火xmpp展开,旨在为您提供一份详细的参考资料。我们将全面介绍Android:开火xmpp,同时也会为您带来Android Compose wanandroid app 之底部按钮以及首页内容、Android GCM:了解XMPP、Android XML文件中android:id,android:name和name标签之间的区别、android xmpp 如何实现心跳的实用方法。
本文目录一览:- Android:开火xmpp
- Android Compose wanandroid app 之底部按钮以及首页内容
- Android GCM:了解XMPP
- Android XML文件中android:id,android:name和name标签之间的区别
- android xmpp 如何实现心跳
Android:开火xmpp
我想在Android中制作一个小型聊天应用程序.为此我完成了以下页面中提到的步骤
http://davanum.wordpress.com/2007/12/31/android-just-use-smack-api-for-xmpp/
如果我们输入如下的gmail凭据,它的工作正常: –
private final static String SERVER_HOST = "talk.google.com";
private final static int SERVER_PORT = 5222;
private final static String SERVICE_NAME = "gmail.com";
private final static String LOGIN = "myemail@gmail.com";
private final static String PASSWORD = "mypassword";
但我想用我自己的服务器代替gmail.我在我的系统中安装了openfire,并且我在openfire中非常新鲜,如果我使用自己的服务器,请建议我应该为上述凭据做什么条目.
如果有任何疑问,请随时询问,我总是在这里..
先感谢您.
server_host是您连接的服务器,登录名是< something> @service_name,它应该与您服务器的声明服务名称匹配(可能匹配server_host或server_host的最后一部分)
因此,如果您的服务器是xmpp.example.com,则最有可能是您的条目更改
SERVER_HOST = "xmpp.example.com"
SERVICE_NAME = "example.com"
LOGIN = "johndoe@example.com"
要么
SERVER_HOST = "xmpp.example.com"
SERVICE_NAME = "xmpp.example.com"
LOGIN = "johndoe@xmpp.example.com"
显然,让端口匹配你运行openfire的端口(5222是默认值)
Android Compose wanandroid app 之底部按钮以及首页内容

转:
Android Compose wanandroid app 之底部按钮以及首页内容
使用 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 需要注意以下几点:
- 声明元素 通过 createRefs () 或 createRef () 方法初始化声明的,并且每个子元素都会关联一个 ConstraintLayout 中的 Composable 组件;
- 关联组件 Modifier.constrainAs (text) 通过 constrainAs 关联组件
- 约束关系可以使用 linkTo 或其他约束方法实现;
- 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))//边框
- fillMaxSize 设置布局纵向横向都铺满
- fillMaxHeight 设置布局铺满纵向
- fillMaxWidth 设置布局铺满横向,这三个属性再使用了 fillMaxSize 就没必要在设置下面两个了
- padding 设置边距,方向由左上右下设置,添加了 vertical 就是设置垂直的上下边距,horizontal 设置了水平的左右边距。这里注意写了两个 padding,第一个是外边距,第二个是内边距,外边距最好是放在 Modifier 的第一个元素。
- width 设置元素的宽
- height 设置元素的高
- size 设置元素大小,只有一个值时宽高都是一个值,.size (100.dp,200.dp) 两个值前者是宽,后者是高
- widthIn 设置自身的最小和最大宽度(当子级元素超过自身时,子级元素超出部分依旧可见)
- background 设置元素的背景颜色
- 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 填充解析:
- iconId tab 图标资源,当选中的下标等于当前 tab 的下标时显示选中的资源,否则显示非选中资源
- tabName tab 文本
- tint tab 颜色,同样分为选中和未选中
- Modifier 使用 Modifier 设置点击事件,以及权重
- 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 主要参数解析:
- count 总页面数
- 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"
- val img = createRef () 创建一个依赖点,就像 xml 约束布局里面的 id 意义昂
- 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 分支
完结~
转:
Android Compose wanandroid app 之底部按钮以及首页内容
--Posted from Rpc
Android GCM:了解XMPP
我正在尝试使用app在我的GCM中实现XMPP协议,但即使经过广泛搜索,我也不了解它背后的概念.
另外,也许我真的不需要XMPP用于我想用我的应用程序做什么,但我喜欢学习东西.
让我们举一个关于我可以用HTTP做什么的例子:
>我的应用程序发送“hello word”和regId到我的小人物服务器:url.openConnection(“”),然后OutputStream用于发送POST数据和InputStream用于获取响应
>服务器,在这个url,将“hello word”消息放入带有regId的数据库中,然后使用PHP的curl库将数据作为json字符串发送到GCM服务器,如{“myResponse”:“我是不是世界我是丹“}(在模拟器中使用测试目的地ID)
> GCM服务器做他的事
>我的应用程序(可能在另一部手机上)在WakefulbroadcastReceiver中使用IntentService,它将消息作为intent.getExtras().getString(“myResponse”)
这很好用,我可以使用我的应用程序从一部手机向另一部手机发送消息,并通过我的服务器收集数据.
很少问题
这种处理HTTP的方式是理论上的吗? (我看过很多帖子和教程,特别是谷歌的帖子和教程,但仍然不确定)
大的真实问题
使用XMPP执行相同操作的步骤是什么?
我不想要教程或代码片段,我想了解信息通过这个协议的方式我不太了解(我设法在我的服务器上安装ejabberd并在我的电脑上使用pidgin和在我的手机上使用Xabber ).
解决方法:
Official定义:
The Google Cloud Messaging (GCM) Cloud Connection Server (CCS) is an
XMPP endpoint that provides a persistent, asynchronous, bidirectional
connection to Google servers.
建立与CCS的连接是这里第一步也是最重要的一步.完成此操作并保持长期连接后,其他部分就不那么棘手了.
两者之间存在一些差异:
1)与HTTP不同,对于XMPP消息,您不需要在每个有效负载中包含Authentication头,因为服务器在连接时已经过身份验证,我们正在维护相同的连接.
2)CCS使用XMPP作为传输层,因此在成功建立连接后,您可以交换节.
3)您可以继续使用HTTP作为下游,如果您愿意,只能将XMPP用于上游.
4)而不是registration_ids param用于:在XMPP中,我们只能通过一个节发送到一个RegID.
因此,如果我要解释您的示例如何与XMPP一起使用:
– 与CCS建立连接
– 从客户端“Hello,World!”向服务器发送upstream message.
– 一旦您的服务器通过向GCM发送ACK接收此消息,请确认
– 对于downstream message,您可以选择使用HTTP或XMPP
– 但是如果XMPP:接收,保存在数据库中并且发送响应({“myResponse”:“我不是世界我是Dan”})回到客户端(相同或不同的RegID)发送下游节到CCS; CCS将发送ACK / NACK以确认它已收到该消息
– 一旦客户端应用程序收到消息,您还将收到delivery_receipt(如果请求).
除此之外,您可以通过阅读我在整个帖子中链接的官方文档来更深入地了解.
希望这可以帮助!
Android XML文件中android:id,android:name和name标签之间的区别
例如,当我在res / values / array.xml文件中有一个字符串数组时,我使用定义的数组中的name字段进行访问,而Javadoc则将其称为“ID”.
android:id似乎只是在Views中使用?
我错过了一些东西,还是不会有简单的标签?
解决方法
要进一步深入,我相信android:id属性只分配给视图和扩展视图的类.这样做可以使用findViewById从代码中以编程方式访问该视图:
Button myButton =(Button)findViewById(R.id.whatever_id_assigned_to_view)
这与诸如strings.xml或array.xml之类的资源不同,这些资源仅通过名称来标识,例如可以在strings.xml中找到的以下示例:
< string name =“string_name”>文本资源此处< / string>
并使用…访问
getResources().的getText(R.string.string_name)
我想像这是因组织原因而分开的.这样生成的Android资源文件(R.java)包含为R.id中的视图指定的ID,R.string中包含的字符串ID,R.array等中的数组ID.
android xmpp 如何实现心跳
要实现推送功能,客户端需要定时发起心跳到xmpp服务端,请问,心跳代码如何实现哦?能给一个案例吗?关于Android:开火xmpp的问题就给大家分享到这里,感谢你花时间阅读本站内容,更多关于Android Compose wanandroid app 之底部按钮以及首页内容、Android GCM:了解XMPP、Android XML文件中android:id,android:name和name标签之间的区别、android xmpp 如何实现心跳等相关知识的信息别忘了在本站进行查找喔。
本文标签: