I made an expand-collapse button. sharing it here ...
# compose
p
I made an expand-collapse button. sharing it here in case it might be useful for anyone. (It's easy & fun to make with Compose!) https://gist.github.com/pt2121/3fa9e0e3e2659171102d752b33d1b1c7
👍 14
z
under 150 lines for the whole thing, nice!
👍 1
s
Is the rest of the demo (fullscreen one in gist) done in compose as well?
p
Yes, the rest of the demo is also done in compose. The code is still very hacky tho - especially, the transition animation.
s
Can you share it? Despite being hacky, it looks incredible :)
c
that's so cool
p
just made the repo public https://github.com/pt2121/kramer any feedback is welcome
l
just curious - did you find that the
Box
here was required? It seems like it wouldn’t be since Canvas is basically just a child-less box with a draw call
very nice though. this code made me smile 🙂
p
Thanks, Leland. The
Box
is a workaround to increase there tap target size. not sure if there is a proper way to do this?
l
i think there is a
TouchSlop
notion, but i’m not familiar with it. though if you’re just using a
Box
right now i think just adding a
LayoutPadding
modifier to the canvas would be sufficient
👍 1
cc @Shep Shapard
p
ah, I didn't think about adding
LayoutPadding
. will try that
l
there is a simple animate API that will be coming out (or is out already? I can never remember). I tweaked your code a bit to show you how I might do this a bit simpler:
Copy code
@Composable
fun ExpandCollapseButton(
  up: Boolean,
  onStateChange: (Boolean) -> Unit,
  modifier: Modifier = Modifier.None,
  animationDuration: Int = 200,
  color: Color = MaterialTheme.colors().secondary
) {
  Box(modifier) {
    Ripple(bounded = false) {
      Caret(
        LayoutPadding(14.dp) + 
        ContentGravity.Center + 
        LayoutSize(20.dp) + 
        OnClick {
          onStateChange(!up)
        }
        up, 
        color, 
        animationDuration
      )
    }
  }
}

@Composable
private fun Caret(
  modifier: Modifier,
  up: Boolean,
  activeColor: Color,
  animationDuration: Int
) {
  val strokeWidthDp = 3.dp
  val paint = remember {
    Paint().apply {
      isAntiAlias = true
      strokeCap = StrokeCap.round
      color = activeColor
      style = PaintingStyle.stroke
    }
  }

  // simpler animation api for simple transitions
  // return value will be animated between 1f and 0f. It's just a Float.
  val t = animate(if (up) 1f else 0f, animationDuration)
  val animatedYOffset = lerp(0f, 1f, t) // equivalent to just `t`, but you get the idea
  val fraction = lerp(-1f, 1f, t)
  
  Canvas(modifier) {
    val strokeWidth = strokeWidthDp.toPx().value
    paint.strokeWidth = strokeWidth
    val height = size.height.value / 4
    val x = size.height.value / 4
    val yOffset = (size.height.value - height) / 2
    val path = Path()

    path.moveTo(x, yOffset + height * animatedYOffset)
    path.relativeLineTo(x, -height * fraction)
    path.relativeLineTo(x, height * fraction)

    drawPath(path, paint)
  }
}
z
This animation API is so nice to reason about, it’s so easy to just animate everything. It actually makes me excited about writing custom animations 😂
😂 1
😁 1
1
🙏 1
p
Yeah, Zach. I really like the API too. Thanks for the tweaks Leland.
t
Nice transitions. The app looks nice too. Congrats
🙏 1
l
cc @Nader Jawad is there a recommended way that we can have the code above avoid the
Paint
allocation?
t
@Leland Richardson [G] on your code above, the
OnClick
is a modifier?
d
BTW, the
animate
API that @Leland Richardson [G] mentioned above is available to use, since a couple of releases ago. Here is the API doc.
👍 1
🙏 1
l
yeah, that is coming. not sure what is released yet or not
it’s easier for me to just keep track of where things are headed than keeping track of where everything currently is 🙂 sorry, that means this code sample might not work verbatim
p
Doris, I'll def check it out. Thanks, all