一、Flow的基本概念

Kotlin 的 Flow 相信大家都或多或少使用过,毕竟目前比较火,目前我把Flow的使用整理了一下。希望和大家所学对照一下,能有所启发。

Kotlin 的 Flow 用于流式编程,作用于 Kotlin 的协程内。与协程的生命周期绑定,当协程取消的时候,Flow 也会取消。

Flow 的操作符和 RxJava 类似,如果之前会 RxJava 那么可以轻松上手,相比 RxJava ,Flow 更加的简单与场景化。

按照 Flow 的数据流顺序发送的过程,我们对数据流的三个角色交提供方(创建),中介(转换),使用方(接收)。

按照 Flow 流 是否由接收者开始接收触发整个流的启动,我们分为冷流(由接收方启动)与热流(不由接收方启动)。

基本的使用:

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
   runBlocking {
        flow {
            for (i in 1..5) {
                delay(100)
                emit(i)
            }
        }.collect {
            println(it)
        }
    }

打印结果:

基本的使用默认是冷流,从 collect 方法开始发送数据,这里简单的定义一个创建者和一个接收者。

二、Flow的生命周期与异常处理

和 RxJava 一样 ,我们同样的可以监听任务流的生命周期,开始,结束,与接收

2.1 开始与结束

      runBlocking {
            flow {
                for (i in 1..5) {
                    delay(100)
                    emit(i)
                }
            }.onStart {
                YYLogUtils.w("onStart")
            }.onCompletion { exception ->
                YYLogUtils.w("onCompletion: $exception")
            }.collect { v ->
                YYLogUtils.w(v.toString())
            }
        }

打印结果:

2.2 异常的处理

假如如果我们手动的抛一个异常,看看日志的打印顺序

        val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
            YYLogUtils.e(throwable.message ?: "Unkown Error")
        }
        lifecycleScope.launch(exceptionHandler) {
            flow {
                for (i in 1..5) {
                    delay(100)
                    emit(i)
                    if (i == 3) throw RuntimeException("自定义错误")
                }
            }.onStart {
                YYLogUtils.w("onStart")
            }.onCompletion { exception ->
                YYLogUtils.w("onCompletion: $exception")
            }.collect { v ->
                YYLogUtils.w(v.toString())
            }
        }

看到打印结果:

我们可以在 onCompletion 中打印出错误的对象。但是大家看到我这里使用的协程的上下文来捕获的异常,因为如果不捕获这个异常,程序会崩溃,那可不可以不使用协程的异常捕获?可以,我们可以使用 Flow 的异常捕获。

        runBlocking {
            flow {
                for (i in 1..5) {
                    delay(100)
                    emit(i)
                    if (i == 3) throw RuntimeException("自定义错误")
                }
            }.onStart {
                YYLogUtils.w("onStart")
            }.onCompletion { exception ->
                YYLogUtils.w("onCompletion: $exception")
            }.catch { exception ->
                YYLogUtils.e("catch: $exception")
            }.collect { v ->
                YYLogUtils.w(v.toString())
            }
        }

可以看到打印的顺序是和上面的一样的,只是换成了Flow来捕获异常了

2.3 retry的处理

retry 方法可以让我们程序在错误或异常的时候尝试重新执行创建者的操作。

       runBlocking {
            flow {
                for (i in 1..5) {
                    delay(100)
                    emit(i)
                    if (i == 3) throw RuntimeException("自定义错误")
                }
            }.retry(2).onStart {
                YYLogUtils.w("onStart")
            }.onCompletion { exception ->
                YYLogUtils.w("onCompletion: $exception")
            }.catch { exception ->
                YYLogUtils.e("catch: $exception")
            }.collect { v ->
                YYLogUtils.w(v.toString())
            }
        }

可以看到打印的结果:

只会走一次开始的回调和结束的回调。

2.4 超时的处理

measureTimeMillis { }withTimeout(xx) { } 这些都是协程内部的快捷处理方法,处理时间相关。

withTimeout 超时的处理使用的时候协程的快捷函数,其实跟Flow没什么关系,并不是Flow包下面的,所以我们不能使用Flow的异常来处理,因为它是Flow的父亲协程里面的错误,我们可以使用try-catch。也可以使用协程上下文来处理异常。

       val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
            YYLogUtils.e(throwable.message ?: "Unkown Error")
        }
        lifecycleScope.launch(exceptionHandler) {
            withTimeout(200) {
                flow {
                    for (i in 1..5) {
                        delay(100)
                        emit(i)
                        if (i == 3) throw RuntimeException("自定义错误")
                    }
                }.retry(2).onStart {
                    YYLogUtils.w("onStart")
                }.onCompletion { exception ->
                    YYLogUtils.w("onCompletion: $exception")
                }.catch { exception ->
                    YYLogUtils.e("catch: $exception")
                }.collect { v ->
                    YYLogUtils.w(v.toString())
                }
            }
        }

打印结果如下:

如果我想计算Flow的耗时,其实就是计算协程内的一个任务的耗时,跟是不是Flow没关系。

       lifecycleScope.launch(exceptionHandler) {
            val time = measureTimeMillis {
                flow {
                    for (i in 1..5) {
                        delay(100)
                        emit(i)
                    }
                }.onStart {
                    YYLogUtils.w("onStart")
                }.onCompletion { exception ->
                    YYLogUtils.w("onCompletion: $exception")
                }.catch { exception ->
                    YYLogUtils.e("catch: $exception")
                }.collect { v ->
                    YYLogUtils.w(v.toString())
                }
            }
            YYLogUtils.w("总耗时time:"   time)
        }

打印的结果:

2.5 Flow的取消

同样的 Flow 并没有提供取消的方法,因为Flow是运行在协程中的我们可以依赖协程的运行与取消来实现 Flow 的取消。

我们知道默认的flow是冷流 ,flow.collect 才是触发点,它标记为 suspend 函数,需要执行在协程中。我们就能把Flow的创建和接收分开,并且取消 flow.collect 的作用域协程。

       runBlocking {
            val flow = flow {
                for (i in 1..5) {
                    delay(100)
                    emit(i)
                }
            }.onStart {
                YYLogUtils.w("onStart")
            }.onCompletion { exception ->
                YYLogUtils.w("onCompletion: $exception")
            }.catch { exception ->
                YYLogUtils.e("catch: $exception")
            }
            //Flow的取消依赖协程的取消
            withTimeoutOrNull(210) {
                flow.collect {
                    YYLogUtils.w("collect:$it")
                }
            }
        }

效果如下:

甚至我们Flow的创建一般都不需要在协程中,比如我还能这么改

     val flow = (1..5).asFlow().onStart {
            YYLogUtils.w("onStart")
        }.onCompletion { exception ->
            YYLogUtils.w("onCompletion: $exception")
        }.catch { exception ->
            YYLogUtils.e("catch: $exception")
        }
        runBlocking {
            //Flow的取消依赖协程的取消
            withTimeoutOrNull(210) {
                flow.collect {
                    YYLogUtils.w("collect:$it")
                }
            }
        }

这里先讲到Flow的基本生命周期与异常处理,Flow的超时,计时,取消等概念,下面我们看看Flow的创建的方式

三、Flow的创建方式

上面的代码中我们可以看到Flow的创建有几种方式,这里总结一下

flow:创建Flow的操作符。 flowof:构造一组数据的Flow进行发送。 asFlow:将其他数据转换成Flow,一般是其他数据格式向Flow的转换

flow构建器 是经常被使用的流构建器,emit 是 suspend 的需要在协程中执行

flow {
    for (i in 1..5) {
        delay(100)
        emit(i)
    }
}

flowOf构建器 可以用于定义能够发射固定数量值的流

flowOf(1,2,3,4,5)

asFlow构建器可以将各种集合转为Flow 例如 LongRange IntRange IntArray Array Sequence

(1..10).asFlow()

LiveData同样可以使用 asFlow 扩展方法转换为flow

  val liveData = MutableLiveData<Int>(1)
        val flow = liveData.asFlow()
            .onStart {
                YYLogUtils.w("onStart")
            }.onCompletion { exception ->
                YYLogUtils.w("onCompletion: $exception")
            }.catch { exception ->
                YYLogUtils.e("catch: $exception")
            }
        runBlocking {
            //Flow的取消依赖协程的取消
            withTimeoutOrNull(210) {
                flow.collect {
                    YYLogUtils.w("collect:$it")
                }
            }
        }

四、Flow的接收方式

Flow的接收函数操作符常见的有

  • collect、数据收集操作符,默认的flow是冷流,即当执行collect时,上游才会被触发执行。

  • collectIndexed、带下标的收集操作,如collectIndexed{ index, value -> }
  • collectLatest、与collect的区别:当新值从上游发出时,如果上个收集还未完成,会取消上个值得收集操作
  • toList、toSet等 将flow{}结果转化为集合。
  • single 确保流发射单个值
  • first 仅仅取第一个值
  • reduce 如果发射的是 Int,最终会得到一个 Int,可做累加操作
  • fold reduce 的升级版,多了一个初始值

其他的终端操作符,大家看名字都知道怎么使用了,我就不一一演示了,这里看看后面2个 reduce 怎么使用的。

       runBlocking {
            val reduce = flowOf(1, 2, 3).reduce { accumulator, value ->
                YYLogUtils.w("accumulator:"   accumulator   " value:"   value)
                accumulator   value
            }
            YYLogUtils.w("reduce:"   reduce)
        }

看Log可以看到 accumulator 是已经加过的数值 value是当前数值

那么fold和 reduce类似,只是多了一个初始值

        runBlocking {
            val fold = flowOf(1, 2, 3).fold(4) { accumulator, value ->
                YYLogUtils.w("accumulator: $accumulator value: $value")
                accumulator   value
            }
            println(fold)
            YYLogUtils.w("fold:"   fold)
        }

比较难理解的就是这2个了。

五、Flow的转换操作符

我们除了Flow创建于接收之外的一些操作符,另外都是一些中间操作符,要说起flow操作符可太多了,这里只说下常用的几个操作符吧

5.1 基本操作符

transform transform 它会调用 Flow 的 collect() 函数,然后构建一个新的 Flow. 如果想要继续发射值,需要重新调用 emit() 函数。

runBlocking {
    flowOf(1, 2, 3).transform {
        emit("transformed $it")
    }.collect {
        println("Collect: $it")
    }
}

map实际上就是 transform emit ,自动封装了

runBlocking {
    flowOf(1, 2, 3).map {
        "mapped $it"
    }.collect {
        println("Collect: $it")
    }
}

drop 根据条件不要表达式内的数据

runBlocking {
    flowOf(1, 2, 3).dropWhile {
        it < 2
    }.collect {
        println("Collect $it")
    }
}

打印值为: 2 3

filter 根据条件只要表达式内的数据

runBlocking {
    flowOf(1, 2, 3).filter {
        it < 2
    }.collect {
        println("Collect $it")
    }
}

打印值为: 1

debounce 防抖动函数,当用户在很短的时间内输入 “d”,“dh”,“dhl”,但是用户可能只对 “dhl” 的搜索结果感兴趣,因此我们必须舍弃 “d”,“dh” 过滤掉不需要的请求,针对于这个情况,我们可以使用 debounce 函数,在指定时间内出现多个字符串,debounce 始终只会发出最后一个字符串

runBlocking {
   val result = flow {
    emit("h")
    emit("i")
    emit("d")
    delay(90)
    emit("dh")
    emit("dhl")
}.debounce(200).toList()
println(result)
}

打印值为:dhl

distinctUntilChanged 用来过滤掉重复的请求,只有当前值与最后一个值不同时才将其发出

runBlocking {
   val result = flow {
    emit("d")
    emit("d")
    emit("d")
    emit("d")
    emit("dhl")
    emit("dhl")
    emit("dhl")
    emit("dhl")
}.distinctUntilChanged().toList()
println(result)
}

打印值为:d, dhl

flatMapLatest当有新值发送时,会取消掉之前还未转换完成的值

runBlocking {
flow {
    emit("dh")
    emit("dhl")
}.flatMapLatest { value ->
    flow<String> {
        delay(100)
        println("collected $value") // 最后输出 collected dhl
    }
}.collect()
}

场景如下,正在查询 “dh”,然后用户输入 “dhl”

打印值为:dhl

5.2 特殊操作符

这里涉及到上下游数据耗时的问题,类似RxJava的背压的概念,比如发出的数据的速度比较快,但是接受的数据的速度比较慢。场景如下

        runBlocking {
            val time = measureTimeMillis {
                flow {
                    (1..5).forEach {
                        delay(200)
                        println("emit: $it, ${System.currentTimeMillis()}, ${Thread.currentThread().name}")
                        emit(it)
                    }
                }.collect {
                    delay(500)
                    println("Collect $it, ${System.currentTimeMillis()}, ${Thread.currentThread().name}")
                }
            }
            println("time: $time")
        }

那么就会阻塞掉,5*700 = 3500

可以看到上面的结果是串行等待执行的,那么我们可以通过下面的操作符让代码并发执行优化效率

buffer 该运算符会在执行期间为流创建一个单独的协程。从而实现并发效果。

        runBlocking {
            val time = measureTimeMillis {
                flow {
                    (1..5).forEach {
                        delay(200)
                        println("emit: $it, ${System.currentTimeMillis()}, ${Thread.currentThread().name}")
                        emit(it)
                    }
                }.buffer().collect {
                    delay(500)
                    println("Collect $it, ${System.currentTimeMillis()}, ${Thread.currentThread().name}")
                }
            }
            println("time: $time")
        }

可以看到是并发执行的。时间是200 5*500 = 2700

conflate 发射数据太快,只处理最新发射的

        runBlocking {
            val time = measureTimeMillis {
                flow {
                    (1..5).forEach {
                        delay(200)
                        println("emit: $it, ${System.currentTimeMillis()}, ${Thread.currentThread().name}")
                        emit(it)
                    }
                }.conflate().collect {
                    delay(500)
                    println("Collect $it, ${System.currentTimeMillis()}, ${Thread.currentThread().name}")
                }
            }
            println("time: $time")
        }

注意会丢失数据的,2和4没有接收到

collectLatest 接收处理太慢,只处理最新接收的,但是注意一下这个不是中间操作符,这个是接收操作符,这里作为对比在这里演示

        runBlocking {
            val time = measureTimeMillis {
                flow {
                    (1..5).forEach {
                        delay(200)
                        println("emit: $it, ${System.currentTimeMillis()}, ${Thread.currentThread().name}")
                        emit(it)
                    }
                }.collectLatest {
                    // 消费效率较低
                    delay(500)
                    println("Collect $it, ${System.currentTimeMillis()}, ${Thread.currentThread().name}")
                }
            }
            println("time: $time")
        }

这样就只会接收到最后一个 5

5.3 组合与展平操作符

zip 组合两个流,将2个Flow合并为1个Flow

runBlocking {
    flowOf("a", "b", "c").zip(flowOf(1, 2, 3)) { a, b -&gt;
        a   b  //自己定义规则
    }.collect {
        println(it)
    }
}

打印 a1 b2 c3

combine 可以合并多个不同的 Flow 数据流,生成一个新的流。只要其中某个子 Flow 数据流有产生新数据的时候,就会触发 combine 操作,进行重新计算,生成一个新的数据。

      val bannerFlow = MutableStateFlow<String?>(null)
        val listFlow = MutableStateFlow<String?>(null)
        lifecycleScope.launch {
            combine(bannerFlow, listFlow) { banner, list ->
                val resultList = mutableListOf<String?>()
                if (banner != null) {
                    resultList.add(banner)
                }
                if (list != null) {
                    resultList.add(list)
                }
                return@combine resultList
            }.collect { list ->
                YYLogUtils.w("list:"   list)
            }
            withContext(Dispatchers.Default) {
                delay(1000)
                bannerFlow.emit("Banner")
            }
            withContext(Dispatchers.Default) {
                delay(3000)
                listFlow.emit("list")
            }
        }

可以看到只要其中一个Flow更新了数据都会刷新

对比zip与combine 同样的代码我们对比zip与combine的区别,注意看Log的打印

        val bannerFlow = MutableStateFlow<String?>(null)
        val listFlow = MutableStateFlow<String?>(null)
        lifecycleScope.launch {
            bannerFlow.zip(listFlow){ banner, list ->
                val resultList = mutableListOf<String?>()
                if (banner != null) {
                    resultList.add(banner)
                }
                if (list != null) {
                    resultList.add(list)
                }
                return@zip resultList
            }.collect { list ->
                YYLogUtils.w("list:"   list)
            }
        }
        lifecycleScope.launch {
            withContext(Dispatchers.Default) {
                delay(1000)
                bannerFlow.emit("Banner")
            }
            withContext(Dispatchers.Default) {
                delay(3000)
                listFlow.emit("list")
            }
        }

zip只有在都更新了才会触发,这也是最重要的他们的不同点

merge

merge 操作符想说 活都被你们干完了,我还活不活了!😂😂

其实我们可以理解 同一类型我们可以用 merge ,不同的类型我们用zip、combine 。 zip、combine需要我们自定义拼接方式,而 merge 则是需要两者类型一样,直接合并为一个对象。

       val bannerFlow = MutableStateFlow<String?>(null)
        val listFlow = MutableStateFlow<String?>(null)
        lifecycleScope.launch {
            listOf(bannerFlow, listFlow).merge().collect {
                YYLogUtils.w("value:$it")
            }
        }
        lifecycleScope.launch {
            withContext(Dispatchers.Default) {
                delay(1000)
                bannerFlow.emit("Banner")
            }
            withContext(Dispatchers.Default) {
                delay(3000)
                listFlow.emit("list")
            }
        }

打印结果:

展平操作符 flattenConcat 以顺序方式将给定的流展开为单个流

    runBlocking {
        flow {
            emit(flowOf(1, 2))
            emit(flowOf(3,4))
        } .flattenConcat().collect { value->
            print(value)
        }
    }

执行结果:1 2 3 4

flattenMerge 作用和 flattenConcat 一样,但是可以设置并发收集流的数量

    runBlocking {
        flow {
            emit(flowOf(1, 2))
            emit(flowOf(3,4))
        } .flattenMerge(2).collect { value->
            print(value)
        }
    }

执行结果:1 2 3 4

flatMapConcat通过展平操作,用这两个元素各自构建出一个新的流 it "一年级", it "二年级", it "三年级"。

runBlocking {
    flowOf("初中", "高中").flatMapConcat {
        flowOf(it   "一年级", it   "二年级", it   "三年级")
    }.collect {
        YYLogUtils.w(it)
    }
}

flatMapMerge

runBlocking {
    flowOf("初中", "高中").flatMapMerge {
        flowOf(it   "一年级", it   "二年级", it   "三年级")
    }.collect {
        YYLogUtils.w(it)
    }
}

实现的效果是一样的,和上面的 flatten 效果类似,后缀为 Concat 是串行,后缀为 Merge 的是并行,效率更高。

5.4 切换线程

Flow的切换线程相比协程的更加简单,至于使用的方式大家可能都不陌生了,这里我简单的举例。

切换线程的操作分一个中间操作符和一个接收操作符,代码如下:

        flow {
            YYLogUtils.w( "start: ${Thread.currentThread().name}")
            repeat(3) {
                delay(1000)
                this.emit(it)
            }
            YYLogUtils.w( "end: ${Thread.currentThread().name}")
        }
            .flowOn(Dispatchers.Main)
            .onEach {
                YYLogUtils.w( "collect: $it, ${Thread.currentThread().name}")
            }
            .launchIn(CoroutineScope(Dispatchers.IO))

打印结果如下:

launchIn为接收操作符,指明此Flow运行的协程作用域对象,一般我们可以指定作用域,指定运行线程,例如CoroutineScope(Dispatchers.IO)、 MainScope() 、lifecycleScope等。

flowOn则是切换线程,需要指明协程的上下文对象 ,一般我们用于切换线程。

总结

不知不觉文章又超长了,内容有点太多了,下面总结一下。

总的来说以上应该都是 Flow 使用的基础了,看下来感觉和RxJava还是很像的吧,我们使用 Kotlin 构建项目的过程中,我们真心是不需要 RxJava 了,基本上 Flow 能替代 RxJava 完成我们想要的效果了。毕竟导入一个 RxJava 的库也不小。

Flow 的使用还是很常见的,同时还有它的一些封装类 SharedFlow 与 StateFlow 也比较常用。

以上就是Kotlin Flow操作符及基本使用详解的详细内容,更多关于Kotlin Flow操作符的资料请关注Devmax其它相关文章!

Kotlin Flow操作符及基本使用详解的更多相关文章

  1. ios – 如何旋转与照片应用程序类似的UICollectionView并保持当前视图居中?

    我有一个照片库视图使用UICollectionView与UICollectionViewFlowLayout,它有pagingEnabled和水平滚动显示一次只有一个视图.工作很好,直到我尝试旋转它当我旋转设备时,willRotatetoInterfaceOrientation:duration:我更新collectionView.contentOffset,使其保持在正确的项目上,并调整curr

  2. Kotlin难点解析:extension和this指针

    扩展是Kotlin语言中使用非常简单的一个特性。关于这个问题,其实我之前的一篇文章[[Kotlin]LambdaandExtension](https://www.jianshu.com/p/d7a...中有提到过。为了解决这个问题,官方提出了两个新的概念:dispatchreceiver和extensionreceiver。extensionreceiver:中文翻译为扩展接收者。为了简化,这里我们将dispatchreceiver简称为DR,将extensionreceiver简称为ER。如果你习惯了

  3. android – 为Flow和Mortar应用程序编写测试

    我想知道是否有任何为Flow和MortarAndroid应用编写单元测试的例子.MVP模式的部分优势在于它提供了表示和视图逻辑的分离,而表示逻辑是您要编写测试的.我希望我能看到人们如何使用Flow&迫击炮,以及他们依赖的图书馆.建立一些最佳实践会很棒.编辑:值得注意的是谷歌刚刚发布了junitsupportforAndroid.解决方法有一个u2020-mortar项目使用Mortar和F

  4. android – Kotlin类NoClassDefFoundError崩溃

    我有一个使用以下库的现有Android项目:>Autovalue>Dagger2>RxJava>Retrolambda我正在尝试添加Kotlin支持,以便我可以将项目慢慢迁移到Kotlin.这就是我所做的.>添加了Kotlin依赖.>将其中一个类转换为Kt类并转移到src/main/kotlin/..包中.>在源集中添加了kotlin.sourceSets{main.java.srcDirs=’s

  5. android – Kotlin和Dagger2

    我正在尝试将Kotlin添加到我的项目中,但在启用Kotlin之后我无法构建,因为Dagger2类不再生成.我尝试了第二个项目,我有同样的问题.这些是我为启用Kotlin所做的改变:项目build.gradle:Appbuild.gradle:错误发生在这里:其中不再定义DaggerObjectGraph.任何帮助将不胜感激.解决方法只需删除

  6. android – 在Kotlin中不能使用argb color int值吗?

    当我想在Kotlin中为TextView的textColor设置动画时:发生此错误:似乎在Kotlin中不能将值0xFF8363FF和0xFFC953BE强制转换为Int,但是,它在Java中是正常的:有任何想法吗?提前致谢.解决方法0xFF8363FF是Long,而不是Int.你必须明确地将它们转换为Int:关键是0xFFC953BE的数值是4291384254,因此它应该存储在Long变量中.但这里的高位是符号位,表示负数:-3583042,可以存储在Int中.这就是两种语言之间的区别.在Kotlin

  7. 什么是我可以使用Kotlin的最早的Android API级别?

    我认为这个问题很清楚但是我能在Kotlin上定位的最早API级别是什么?解决方法实际上,任何API级别.这是因为Kotlin被编译为JVM6平台的字节码,所有AndroidAPI级别都支持该字节码.因此,除非您在Kotlin代码中使用任何较新的AndroidAPI,否则它不需要任何特定的API级别.

  8. android – Kotlin数据类和可空类型

    我是Kotlin的新手,我不知道为什么编译器会抱怨这段代码:编译器抱怨测试?.data.length,它说我应该这样做:test?.length.但是数据变量是String,而不是String?,所以我不明白为什么我要把它?当我想检查长度.解决方法表达式test?.data部分可以为空:它是test.data或null.因此,获取其长度并不是零安全的,而是应该再次使用safecalloperator:test?.length.可空性通过整个调用链传播:你必须将这些链写成?.)).e),因为,如果其中一个左

  9. android – Kotlin自定义获取执行方法调用

    像这样的东西:仍在使用Kotlin并且不确定get()方法是否会引用编辑器而不是创建新的编辑器.解决方法第二个属性声明适合您的需要:它有一个customgetter,因此获取属性值将始终执行getter,并且不存储该值.你可能会被等号get()=…

  10. android – Kotlin合成扩展和几个包含相同的布局

    我找了一些这样的:我在Studio中看到我可以访问dayName但是dayNameTextView引用了哪一个?正常,如果我只有一个包含的布局,它工作正常.但现在我有多次包含相同的布局.我当然可以这样做:但我正在寻找好的解决方案.版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请发送邮件至dio@foxmail.com举报,一经查实,本站将立刻删除。

随机推荐

  1. Flutter 网络请求框架封装详解

    这篇文章主要介绍了Flutter 网络请求框架封装详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Android单选按钮RadioButton的使用详解

    今天小编就为大家分享一篇关于Android单选按钮RadioButton的使用详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧

  3. 解决android studio 打包发现generate signed apk 消失不见问题

    这篇文章主要介绍了解决android studio 打包发现generate signed apk 消失不见问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

  4. Android 实现自定义圆形listview功能的实例代码

    这篇文章主要介绍了Android 实现自定义圆形listview功能的实例代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  5. 详解Android studio 动态fragment的用法

    这篇文章主要介绍了Android studio 动态fragment的用法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  6. Android用RecyclerView实现图标拖拽排序以及增删管理

    这篇文章主要介绍了Android用RecyclerView实现图标拖拽排序以及增删管理的方法,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下

  7. Android notifyDataSetChanged() 动态更新ListView案例详解

    这篇文章主要介绍了Android notifyDataSetChanged() 动态更新ListView案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下

  8. Android自定义View实现弹幕效果

    这篇文章主要为大家详细介绍了Android自定义View实现弹幕效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  9. Android自定义View实现跟随手指移动

    这篇文章主要为大家详细介绍了Android自定义View实现跟随手指移动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. Android实现多点触摸操作

    这篇文章主要介绍了Android实现多点触摸操作,实现图片的放大、缩小和旋转等处理,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部