先上实现效果图
横屏界面
点右上角菜单一键切换主题 , 黑色主题颜色没调(随便弄的颜色) , 以后可能回调
效果图放完了就开讲
就在网上随便弄道题目
val ques = "000078962763429185928561374284196753396745218157832496831257649672984531549613827"
val answ = "415378962763429185928561374284196753396745218157832496831257649672984531549613827"
初始化游戏题目成二维数组格式
我们先弄99 格式的数独 , 每个Cell代表数组的每一格 , 99数独有81个Cell ,
然后把每个Cell对应的 行列和宫数据都初始化好
fun getQuesArray2d(ques: String = Constant.ques): Array<Array<Cell>> {
val ans = Constant.answ
var index = 0
val sectors =
arrayOfNulls<CellGroup>(9)
val rows =
arrayOfNulls<CellGroup>(9)
val cols =
arrayOfNulls<CellGroup>(9)
for (i in 0 until 9) {
sectors[i] = CellGroup()
rows[i] = CellGroup()
cols[i] = CellGroup()
}
val array2d = Array(9) { rowIndex ->
Array(9) { colIndex ->
val cellVal = ques[index].toString().toInt()
val ansVal = ans[index++].toString().toInt()
Cell(
rowIndex = rowIndex,
colIndex = colIndex,
value = cellVal,
ansValue = ansVal,
isEditable = cellVal == 0
).apply {
row = rows[rowIndex]!!
col = cols[colIndex]!!
sector = sectors[((colIndex / 3) * 3) + (rowIndex / 3)]!!
row?.addCell(this)
col?.addCell(this)
sector?.addCell(this)
}
}
}
return array2d
}
数据准备并初始化好就开始draw了 , 要绘制的有高亮的行列宫格 , 相似Cell , Cell , 网格线
贴一下draw网格线的代码吧 , 用到的主要是drawLine 方法
drawline很简单 , 两点确定一点直线 , 计算得到开始点和结束点就行了
private fun DrawScope.drawLine(
width: Float
) {
//画9x9垂直线
for (c in 0..9) {
val start = Offset(c * cellWidth, 0f)
val end = Offset(c * cellWidth, width)
drawLine(Color.Black, start, end, strokeWidth = 2.dp.value)
}
//画9x9水平线
for (c in 0..9) {
val start = Offset(0f, c * cellWidth)
val end = Offset(width, c * cellWidth)
drawLine(Color.Black, start, end, strokeWidth = 2.dp.value)
}
//画3x3 垂直粗线
for (c in 0..3) {
val start = Offset(c * cellWidth * 3, 0f)
val end = Offset(c * cellWidth * 3, width)
drawLine(Color.Black, start, end, strokeWidth = 8.dp.value, cap = StrokeCap.Square)
}
//画3x3 水平粗线
for (c in 0..3) {
val start = Offset(0f, c * cellWidth * 3)
val end = Offset(width, c * cellWidth * 3)
drawLine(Color.Black, start, end, strokeWidth = 8.dp.value, cap = StrokeCap.Square)
}
}
Modifier 有个pointerInteropFilter , 触摸事件都可以在这个方法监听到
Canvas(
modifier = Modifier
.padding(start = sudoku_view_padding, end = sudoku_view_padding)
.fillMaxSize()
.background(AppTheme.colors.sudoku_view_bg)
.pointerInteropFilter {
return@pointerInteropFilter handleMotionEvent(it) { row, col ->
viewModel.dispatch(SudokuOperateAction.MOVE(row, col))
}
}
)
通过 x 和 y值得到当前触摸点的 rowIndex 和 colIndex
fun handleMotionEvent(it: MotionEvent, onTouch: (Int, Int) -> Unit): Boolean {
val row = (it.y / cellWidth).toInt()
val col = (it.x / cellWidth).toInt()
onTouch(row, col)
return true
}
compose触摸事件和原生的view事件传递机制不一样 , compose触摸区域是整个屏幕区域而不是当前draw区域 , 所以就直接try一下吧 就不做边界判断了
fun dispatch(action: SudokuOperateAction) {
when (action) {
is SudokuOperateAction.MOVE -> {
try {
viewStates = viewStates.copy(selectCell = viewStates.cells!![action.row][action.col])
} catch (e: Exception) {
}
}
....
}
viewStates = viewStates.copy 会重新 刷新界面 调用draw方法 , 但是点击按钮 就不用给viewStates 重新赋值了 , 因为点击按钮 界面也会刷新 , 没必要在刷新一次
is SudokuOperateAction.CHANGE_VALUE -> {
//text click 会触发 SudokuView Canvas Draw
// 所以不用 viewStates = viewStates.copy(selectCell = copyCell) 再去触发一次了
if (!viewStates.selectCell.isEditable) return
if (viewStates.selectCell.value == action.value){
action.value = 0
}
val selectCell =viewStates.selectCell
selectCell.value =action.value
viewStates.cells!![selectCell.rowIndex][selectCell.colIndex] .value= action.value
if (selectCell.value!=0){
viewModelScope.launch {
_viewEvents.send(SudokuOperateAction.DO_ANIMATION)
}
}
checkSuccess()
}
先把判断横竖屏提取出一个方法来 , 很多地方都会用到
@Composable
fun isPortrait():Boolean{
val configuration = LocalConfiguration.current
return configuration.orientation == Configuration.ORIENTATION_PORTRAIT
}
竖屏的时候就以宽作为游戏的宽高 , 横屏的时候就以高的0.9倍作为游戏宽高
val screenWidth= if (isPortrait()){
configuration.screenWidthDp.dp
}else {
configuration.screenHeightDp.dp.times(0.9f)
}
Box(
modifier = modifier
.width(screenWidth)
.height(screenWidth)
) {
Canvas(){
......}
}
用最基础的Animatable 来实现一个数字从0 放到sudoku_text_size 动画
val textAnimate = remember { Animatable(sudoku_text_size.toPx()) }
LaunchedEffect(Unit) {
viewModel.viewEvents.collect {
if (it is SudokuOperateAction.DO_ANIMATION){
if (!textAnimate.isRunning){
textAnimate.stop()
textAnimate.snapTo(0f)
textAnimate.animateTo(sudoku_text_size.toPx(),
animationSpec = tween(
durationMillis = 500
)
)
}
}
}
}
- 游戏设置界面
- 数据持久化
- 行列宫格 , 开局动画