Skip to content

Instantly share code, notes, and snippets.

@decodeandroid
Created August 18, 2024 17:18
Show Gist options
  • Select an option

  • Save decodeandroid/da5cd2b90735e4d72b3e0f2d5b58104a to your computer and use it in GitHub Desktop.

Select an option

Save decodeandroid/da5cd2b90735e4d72b3e0f2d5b58104a to your computer and use it in GitHub Desktop.
Pattern View Jetpack Compose
import android.view.MotionEvent
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.input.pointer.pointerInteropFilter
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlin.math.sqrt
@Preview(showBackground = true, showSystemUi = true)
@Composable
fun PreviewPattern() {
PatternView()
}
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun PatternView() {
val rowCount = 3
val columnCount = 3
val selectedCellsList = remember { mutableStateListOf<CellModel>() }
var selectedCellIndex by remember { mutableIntStateOf(0) }
val selectedCellsIndexList = remember {
mutableStateListOf<Int?>()
}
val selectedCellCenterList = remember { mutableStateListOf(Offset.Zero) }
var text by remember {
mutableStateOf("Draw Your Pattern")
}
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.SpaceEvenly,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = text, color = Color.Black, fontSize = 20.sp)
Canvas(
modifier = Modifier
.fillMaxWidth()
.height(400.dp)
.background(Color.White)
.pointerInteropFilter {
when (it.action) {
MotionEvent.ACTION_DOWN -> {
val cell = CellModel(
offset = Offset(it.x, it.y)
)
//add new cell to list
selectedCellsList.add(cell)
//clear previous cells centers
selectedCellCenterList.clear()
}
MotionEvent.ACTION_MOVE -> {
text = "Release Finger When Done"
val cell = CellModel(
offset = Offset(it.x, it.y)
)
selectedCellsList.add(cell)
}
MotionEvent.ACTION_UP -> {
text = "Selected Index : ${selectedCellsIndexList.toList()}"
}
}
true
}
) {
val width = size.width
val height = size.height
val boxSizeInX = width / 3
val boxCenterInX = boxSizeInX / 2
val boxSizeInY = height / 3
val boxCenterInY = boxSizeInY / 2
val circleRadius = width / 8
//to draw all dots
for (row in 0..<rowCount) {
for (column in 0..<columnCount) {
drawCircle(
color = Color.Black,
radius = 25f,
center = Offset((boxCenterInX + boxSizeInX * column), (boxCenterInY + boxSizeInY * row))
)
}
}
selectedCellsList.forEachIndexed { index, offset ->
//first find the index of each cell we have selected
//index will vary b/w 0,1,2
val columnIndex = (offset.offset.x / width * columnCount).toInt()
val rowIndex = (offset.offset.y / height * rowCount).toInt()
val pathOffset = Offset(offset.offset.x, offset.offset.y)
val currentCellCenter =
Offset((boxCenterInX + boxSizeInX * columnIndex), (boxCenterInY + boxSizeInY * rowIndex))
//use pythagoras theorem -> r*r = X*X + Y*Y
val distanceFromCenter = sqrt(
(pathOffset.x - currentCellCenter.x) * (pathOffset.x - currentCellCenter.x) +
((pathOffset.y - currentCellCenter.y) * (pathOffset.y - currentCellCenter.y))
)
//we will only include a cell if we move fingers only through a specific radius around the cell
if (distanceFromCenter < circleRadius) {
selectedCellIndex = columnIndex + 1 + rowIndex * columnCount
if (!selectedCellsIndexList.contains(selectedCellIndex) && selectedCellIndex > 0) {
selectedCellsIndexList.add(selectedCellIndex)
}
if (!selectedCellCenterList.contains(currentCellCenter)) {
selectedCellCenterList.add(currentCellCenter)
}
drawCircle(
offset.color,
center = currentCellCenter,
radius = 50f,
style = Stroke(offset.strokeWidth)
)
}
}
//to draw green line behind
if (selectedCellCenterList.size > 1) {
//First create path for line
val path = Path().apply {
//start from first circle and draw line till the end one
selectedCellCenterList.forEachIndexed { index, offset ->
if (index == 0) {
moveTo(offset.x, offset.y)
} else {
lineTo(
(offset.x),
(offset.y)
)
}
}
}
//finally draw the path
drawPath(path, Color.Blue.copy(0.5f), style = Stroke(15f))
}
}
Text(modifier = Modifier.clickable {
selectedCellsList.clear()
selectedCellsIndexList.clear()
selectedCellCenterList.clear()
text = "Draw Your Pattern"
}, text = "Clear", color = Color.Blue.copy(0.5f), fontSize = 20.sp)
}
}
data class CellModel(
val offset: Offset,
val color: Color = Color.Blue.copy(0.5f),
val strokeWidth: Float = 5f
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment