【问题标题】:How to create timed Instagram story loading bar using Jetpack Compose animation?如何使用 Jetpack Compose 动画创建定时 Instagram 故事加载栏?
【发布时间】:2021-08-28 12:22:59
【问题描述】:

我想创建一个非常类似于 Instagram 故事加载栏的可组合组件,持续时间为 10 秒。

我有一个想法,但我不知道如何执行。我正在考虑使用 BOX 添加一个静态条(灰色),然后添加另一个条(白色),它会在 10 秒内从 0 动画到可组合宽度的最终。

你知道如何实现这个组件吗?

【问题讨论】:

    标签: android android-layout android-animation android-jetpack-compose


    【解决方案1】:

    您可以使用此 Composable 创建分段进度条

    private const val BackgroundOpacity = 0.25f
    private const val NumberOfSegments = 8
    private val StrokeWidth = 4.dp
    private val SegmentGap = 8.dp
    
    @Composable
    fun SegmentedProgressIndicator(
        /*@FloatRange(from = 0.0, to = 1.0)*/
        progress: Float,
        modifier: Modifier = Modifier,
        color: Color = MaterialTheme.colors.primary,
        backgroundColor: Color = color.copy(alpha = BackgroundOpacity),
        strokeWidth: Dp = StrokeWidth,
        numberOfSegments: Int = NumberOfSegments,
        segmentGap: Dp = SegmentGap
    ) {
        val gap: Float
        val stroke: Float
        with(LocalDensity.current) {
            gap = segmentGap.toPx()
            stroke = strokeWidth.toPx()
        }
        Canvas(
            modifier
                .progressSemantics(progress)
                .fillMaxWidth()
                .height(strokeWidth)
                .focusable()
        ) {
            drawSegments(1f, backgroundColor, stroke, numberOfSegments, gap)
            drawSegments(progress, color, stroke, numberOfSegments, gap)
        }
    }
    
    private fun DrawScope.drawSegments(
        progress: Float,
        color: Color,
        strokeWidth: Float,
        segments: Int,
        segmentGap: Float,
    ) {
        val width = size.width
        val start = 0f
        val gaps = (segments - 1) * segmentGap
        val segmentWidth = (width - gaps) / segments
        val barsWidth = segmentWidth * segments
        val end = barsWidth * progress + (progress * segments).toInt()* segmentGap
    
        repeat(segments) { index ->
            val offset = index * (segmentWidth + segmentGap)
            if (offset < end) {
                val barEnd = (offset + segmentWidth).coerceAtMost(end)
                drawLine(
                    color,
                    Offset(start + offset, 0f),
                    Offset(barEnd, 0f),
                    strokeWidth
                )
            }
        }
    }
    

    你是这样用的

    var running by remember { mutableStateOf(false) }
    val progress: Float by animateFloatAsState(
        if (running) 1f else 0f,
        animationSpec = tween(
            durationMillis = 10_000,
            easing = LinearEasing
        )
    )
    Surface(color = MaterialTheme.colors.background) {
        Column(
            modifier = Modifier.fillMaxSize(),
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            SegmentedProgressIndicator(
                progress = progress,
                modifier = Modifier
                    .padding(top = 64.dp, start = 32.dp, end = 32.dp)
                    .fillMaxWidth(),
            )
    
            Button(
                onClick = { running = !running },
                modifier = Modifier.padding(top = 32.dp)
            ) {
                Text(
                    text = if (running) "Reverse Animation" else "Start Animation"
                )
            }
        }
    }
    

    这是结果

    【讨论】:

      【解决方案2】:

      您可以为LinearProgressIndicator 的进度设置动画。 比如:

      var enabled by remember { mutableStateOf(false) }
      val progress: Float by animateFloatAsState(
          if (enabled) 1f else 0.0f,
          animationSpec = tween(
              durationMillis = 10000,
              delayMillis = 40,
              easing = LinearOutSlowInEasing
          )
      )
      
      LinearProgressIndicator(
          color = White,
          backgroundColor = LightGray,
          progress = progress,
          modifier = Modifier.width(100.dp)
      )
      

      只需设置enabled=true 即可开始动画(这只是一个示例)。

      【讨论】:

      • 嗨 Gabriele...此代码没有动画...:/
      • @nglauber 只需设置enabled=true 即可开始动画。这只是一个示例,其想法是使用LinearProgressIndicator 而不是为Boxwidth 设置动画。
      • 我复制并粘贴了您的代码(将 enable 设置为 true),但它没有动画...:/
      • 我在我的可组合函数中添加了这个... val progress: Float by animateFloatAsState( 1f, animationSpec = tween( durationMillis = 10000, delayMillis = 40, easing = LinearOutSlowInEasing ) ) LinearProgressIndicator( color =红色,背景颜色 = 蓝色,进度 = 进度,修饰符 = Modifier.width(100.dp))
      • 好的...这非常奇怪...当我使用 LaunchedEffect 更改启用标志时,动画工作...:/
      【解决方案3】:

      试试这个解决方案,它应该可以工作:

       var progress by remember {mutableStateOf(0.0f)}
      var enabled by remember { mutableStateOf(true) }
      LaunchedEffect(key1 = progress, key2 = enabled) {
          if(progress<1 && enabled) {
              delay(100L)
              progress += 0.01F
          }
      }
      LinearProgressIndicator(
          color = White,
          backgroundColor = LightGray,
          progress = progress,
          modifier = Modifier.width(100.dp)
      )
      

      【讨论】:

      【解决方案4】:

      这是一个完整的示例解决方案,您可以查看并尝试扩展和重构。该代码是不言自明的,您将看到它是如何进行的。

      一个可组合的故事:

      @OptIn(ExperimentalAnimationApi::class)
      @Composable
      fun Story(story: Stories) {
          AnimatedContent(story) {
              when (story) {
                  Stories.ONE -> {
                      StoryContent("1")
                  }
                  Stories.TWO -> {
                      StoryContent("2")
                  }
                  Stories.THREE -> {
                      StoryContent("3")
                  }
              }
          }
      }
      
      @Composable
      private fun StoryContent(content: String) {
          Text("Story $content")
      }
      

      故事进度指示器:

      @Composable
      fun StoryProgressIndicator(running: Boolean, modifier: Modifier = Modifier, onTenSecondsOnThisStory: () -> Unit) {
          val progress: Float by animateFloatAsState(
              if (running) 1f else 0f,
              animationSpec = tween(
                  durationMillis = if (running) 10_000 else 0,
                  easing = LinearEasing
              )
          )
          if (progress == 1f) {
              onTenSecondsOnThisStory()
          }
          LinearProgressIndicator(
              progress, modifier
          )
      }
      

      还有一个屏幕,其中包含故事之间的导航和播放动画。

      @OptIn(ExperimentalAnimationApi::class)
      @Composable
      fun InstagramLikeStories() {
          var screenWidth by remember { mutableStateOf(1) }
          var currentStory by remember { mutableStateOf(Stories.ONE) }
          var currentStoryPointer by remember { mutableStateOf(0) }
          var runningStoryOne by remember { mutableStateOf(false) }
          var runningStoryTwo by remember { mutableStateOf(false) }
          var runningStoryThree by remember { mutableStateOf(false) }
      
          val runStoryOne = { runningStoryOne = true; runningStoryTwo = false; runningStoryThree = false }
          val runStoryTwo = { runningStoryOne = false; runningStoryTwo = true; runningStoryThree = false }
          val runStoryThree = { runningStoryOne = false; runningStoryTwo = false; runningStoryThree = true }
      
          val stories = Stories.values()
      
          LaunchedEffect(Unit) { runStoryOne() }
      
          Column(
              Modifier.fillMaxSize().onGloballyPositioned {
                  screenWidth = it.size.width
              }.pointerInput(Unit) {
                  detectTapGestures(onTap = {
                      if ((it.x / screenWidth) * 100 > 50) {
                          if (currentStoryPointer == stories.size - 1) {
                              currentStoryPointer = 0
                          } else {
                              currentStoryPointer++
                          }
                          currentStory = stories[currentStoryPointer]
                      } else {
                          if (currentStoryPointer != 0) {
                              currentStoryPointer--
                              currentStory = stories[currentStoryPointer]
                          }
                      }
                      runStoryIndicator(currentStory, runStoryOne, runStoryTwo, runStoryThree)
                  })
              }
          ) {
      
              Row(Modifier.fillMaxWidth().padding(horizontal = 16.dp)) {
                  StoryProgressIndicator(runningStoryOne, Modifier.weight(1f), onTenSecondsOnThisStory = {
                      runStoryIndicator(currentStory, runStoryOne, runStoryTwo, runStoryThree)
                      currentStoryPointer = 1
                      currentStory = stories[currentStoryPointer]
                  })
                  Spacer(Modifier.weight(0.1f))
                  StoryProgressIndicator(runningStoryTwo, Modifier.weight(1f), onTenSecondsOnThisStory = {
                      runStoryIndicator(currentStory, runStoryOne, runStoryTwo, runStoryThree)
                      currentStoryPointer = 2
                      currentStory = stories[currentStoryPointer]
                  })
                  Spacer(Modifier.weight(0.1f))
                  StoryProgressIndicator(runningStoryThree, Modifier.weight(1f), onTenSecondsOnThisStory = {
                      runStoryIndicator(currentStory, runStoryOne, runStoryTwo, runStoryThree)
                      // go to first one
                      currentStoryPointer = 0
                      currentStory = stories[currentStoryPointer]
                  })
              }
          }
          Story(currentStory)
          Row {
              Button(onClick = {
              }) {
                 Text("button")
              }
          }
      }
      
      private fun runStoryIndicator(
          currentStory: Stories,
          runStoryOne: () -> Unit,
          runStoryTwo: () -> Unit,
          runStoryThree: () -> Unit
      ) {
          when (currentStory) {
              Stories.ONE -> runStoryOne()
              Stories.TWO -> runStoryTwo()
              Stories.THREE -> runStoryThree()
          }
      }
      
      enum class Stories {
          ONE, TWO, THREE
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-02-24
        • 1970-01-01
        • 1970-01-01
        • 2022-10-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多