I made an expand-collapse button. sharing it here in case it might be useful for anyone. (It's easy ...
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