当前位置:首页 » 《随便一记》 » 正文

安卓开发: Jetpack compose + kotlin 实现 俄罗斯方块游戏_zyw2002的博客

20 人参与  2021年11月08日 17:43  分类 : 《随便一记》  评论

点击全文阅读


文章目录

  • 前言
  • 俄罗斯方块开发文档
    • 1.摘要
    • 2.开发工具选取
      • 2.1.Compose 的自身优点
      • 2.2.数据驱动界面
    • 3.设计需求
      • 3.1.功能需求
        • 3.1.1.基本游戏功能
        • 3.1.2.拓展功能
      • 3.2.界面需求
        • 3.2.1.整体界面设计
        • 3.2.2.特色界面设计
    • 4.项目文件及其功能
      • 4.1.游戏架构基于 MVI 设计
      • 4.2.源文件功能简述表
      • 4.3 功能索引
    • 5.遇到的困难和解决方案
      • 5.1.零基础学习新的语言和框架
      • 5.2.使用 Gitee 托管仓库
      • 5.3.编辑 build.gradle 环境配置文件
      • 5.4.用 Kotlin 编写游戏逻辑实现 Model 层次
      • 5.6.基于 Compose 实现 View 层次
        • 俄罗斯方块游戏屏幕
        • 俄罗斯方块游戏按钮布局
        • 俄罗斯方块游戏机背景
        • 游戏设置页面
      • 5.7.实现界面切换和导航功能
      • 5.8.设置显示界面主题
      • 5.9.动画设计
    • 附录:源代码 Git 仓库链接

前言

项目地址:github 开源地址,码云开源地址
项目背景:刚刚结束的暑假小学期中,和我的两个神仙队友WYX,XPF(我的队友超级棒!)共同完成的第一个安卓开发项目——俄罗斯方块游戏。
框架和语言:kotlin+ jetpack compose (compose 是谷歌最新推出的开发框架,AndroidStudio刚出了支持compose的稳定版本)
参考项目:参考了https://github.com/leavesC/compose_tetris
在此基础上增加了许多丰富的功能(如切换游戏难度,切换背景音乐,记录最高分…)

俄罗斯方块开发文档

应用名称:鹅螺蛳方块
应用类型:休闲游戏
小组成员:WYX、ZYW、XPF
开发时间:2021年7月26日 - 2021年8月14日

1.摘要

本组使用 Android studio 作为集成式开发环境,完全自主学习 Kotlin 编程语言和 Jetpack Compose 框架,编写了一个功能齐全、具有动态界面的俄罗斯方块安卓游戏。
主要工作包括:搭建 Compose Activity 项目环境,全栈开发实现俄罗斯方块的后端游戏逻辑、界面交互、前端界面设计、音效播放、动画效果等功能(详见“设计需求”一节)。
学习使用全新的 Jetpack Compose 框架是本项目最大的挑战。Jetpack Compose 是谷歌推出的新式声明性界面工具包 ,包括一整套全新的渲染、布局、事件、刷新机制,需要一段时间才能入门,但有效提高了本小组的开发效率。
本文档用于描述《计算机科学与技术专业实践与训练》课程所编写程序的设计方案,文档阅读对象为本课程授课教师及本课堂同学。

2.开发工具选取

Jetpack Compose 是用于构建原生界面的最新的 Android 工具包,结合了反应式编程模型和 Kotlin 编程语言的简洁性和易用性。

2.1.Compose 的自身优点

  • 采用声明式 UI 设计
  • 提供丰富的 Material 组件,加速开发
  • 具有直观的 Kotlin API
  • 拥有更简单的、自定义的实时交互预览功能

2.2.数据驱动界面

游戏的执行程序可以概括为不断等待用户的输入信息,进行状态查询,渲染界面的过程。而这种游戏模型的逻辑非常符合前端开发的思想:数据驱动界面。
在这里插入图片描述

当下的小游戏多以前端为主,客户端开发成本较大,而使用 Compose 可以降低开发成本。Compose 实现数据驱动是依赖类似 Flutter 的 Provide 一样的更新机制,但并没有采用采用了传统 UI 的多层继承结构,而是多个 View 组合成一个 View ,更新数据只要在使用的实体上面加注解 @Model ,在更新实体的同时,会通知 UI 进行修改。

3.设计需求

3.1.功能需求

3.1.1.基本游戏功能

  • 实现俄罗斯方块的游戏的基本逻辑:控制方块的运动和旋转
  • 实现游戏整体功能控制:开始游戏、暂停游戏、重新开始、音乐开启关闭;

3.1.2.拓展功能

  • 实现游戏高级设置:调整游戏难度,游戏界面主题切换,更改游戏背景音乐;
  • 增加计分系统,在游戏界面实时显示目前得分,统计历史最高得分;
  • 增加玩家档案信息,使玩家可以输入保存自己的姓名;
  • 增加游戏说明的信息,使得玩家了解游戏规则和技巧;
  • 实现关于我们页面:展示应用版本信息、开发团队的留言、产品设计理念;

3.2.界面需求

3.2.1.整体界面设计

  • 设计导航栏使得界面的跳转更加简明快捷。
  • 设计垂直滚动式页面增加单个页面展示的空间。
  • 设计白天与黑夜两种模式的界面配色,启动应用时会自动根据手机夜间模式切换,界面内包括太阳和月亮的动画。
  • 设计多种模式的渐变色按钮,增加按钮的质感以提升玩家的体验。
  • 设计多种风格的字体样式以区分不同的内容和突出重点。
  • 设计多种矢量图标,使得信息的展现更加形象。

3.2.2.特色界面设计

  • 游戏屏幕可由用户自己调节大小,并当调节范围超出最大和最小的界限时,设计弹窗提醒用户。
  • 游戏屏幕边框实现色彩的渐变,增添游戏的炫酷感。
  • 设计手势控制的旋转圆形头像,可变带宽和模糊度的圆环边框,可变模糊度和曲线形状的顶部背景图片。
  • 设计 3D 翻折动画,隐藏和展开游戏说明,使得界面更加简洁灵动。
  • 设计水墨渲染动画,通过手势将灰度图片点亮,增添界面的高级感。
  • 设计上下两个图层,通过下图层的阴影使得可360°翻转的图片有肉眼3D的效果
  • 设计星空背景和火箭发射的动画,更形象的展示我们的理念和深层寓意。
  • 设计色彩渐变的字体,是界面更加有趣美观。
  • 仿照胶片样式,设计横向滚动的图像卡片,展示开发者留言。
  • 设计关键信息可复制的文本,展示项目地址,开发者联系方式。

4.项目文件及其功能

4.1.游戏架构基于 MVI 设计

MVI 即 Model-View-Intent ,提倡一种单向数据流的设计思想,非常适合在 Compose 项目中实现逻辑部分,可以彻底贯彻“数据驱动 UI”的核心思想。
在这里插入图片描述

  • View 层:基于 Compose 打造,所有 UI 元素都由代码实现
  • Model 层:ViewModel 维护 State 的变化,游戏逻辑交由 reduce 处理
  • V-M 通信:通过 StateFlow 驱动 Compose 刷新,用户事件由 Action 分发至 ViewModel

4.2.源文件功能简述表

如下表所示,所有源代码部分都在 com.android_tetris 包内:
在这里插入图片描述
在这里插入图片描述

4.3 功能索引

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5.遇到的困难和解决方案

5.1.零基础学习新的语言和框架

本程序完全使用 Kotlin 语言开发,并选用 Jetpack Compose 框架作为前端框架。
我们完全自主学习了 Jetpack Compose (以下简称 Compose )这个还未推出正式版的声明式 UI 框架。由于谷歌暂未推出 Compose 的正式版本,现在网上相关资料和教程都还非常稀少,API文档还是全英文的,我们常常在意想不到的问题上被卡住还没有什么办法。
虽然入门过程极其艰难,但是在学习使用 Compose 的全新框架开发的过程中,我们的英语水平、信息检索能力、解决问题的能力,都得到了极大的锻炼,相信这段艰苦的自主学习经历对我们之后的编程之路一定有所帮助。

5.2.使用 Gitee 托管仓库

由于这是一个工程量比较大的项目,我们采用 Gitee 进行版本管理。但在使用过程中,我们上传经常遇到“文件超过 100M ”的错误提示。
为解决这一问题,我们学习了 .gitignore 文件的使用方法:在该文件按优先级从高到低,写明让 Git 仓库上传时忽略掉的文件目录/后缀名, Git 就会主动忽略这些文件。
我们可以用这一办法处理项目编译产生的文件和庞大的开发环境文件。

5.3.编辑 build.gradle 环境配置文件

开发初期,由于 Kotlin 和 Compose 还在频繁更新 API ,版本迭代很快,因此我们遇到了很多次 build 不成功的情况,报错位置都出现在 build.gradle 文件。
查阅资料发现, Project 层级和 Module 层级各有相对应的两个 build.gradle 文件,在 Android studio 中分别显示为 build.gradle (Project: 项目名) 和 build.gradle (Module: 项目名.app) 。
在 build.gradle (Project: 项目名) 中, buildscript { } 代码块是该项目的 gradle 配置,只存放用到的代码托管库和项目构建级别的依赖:

buildscript {
    ext {
        compose_version = '1.0.0-beta08'
    }
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:7.1.0-alpha02'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.10"
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
        // (此处的注释提示:不要把应用程序的依赖库在此引用)
    }
}

在 build.gradle (Module: 项目名.app) 中, build.gradle 主要用于配置模块级别的配置信息和依赖:

plugins {
    id 'com.android.application'
    id 'kotlin-android'
}

android {
    compileSdk 30
    buildToolsVersion "30.0.3"
    defaultConfig {
        applicationId "com.android_tetris"
        minSdk 21
        targetSdk 30
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        vectorDrawables {
            useSupportLibrary true
        }
    }
    
    // 输出 APK 用到的应用签名信息
    signingConfigs {
        releaseConfig {
            storeFile file("..\\key.jks")
            storePassword "123456"
            keyAlias "key0"
            keyPassword "123456"
            v1SigningEnabled true
            v2SigningEnabled true
        }
    }

    buildTypes {
        debug {
            signingConfig signingConfigs.releaseConfig
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
        release {
            signingConfig signingConfigs.releaseConfig
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
        useIR = true
    }
    buildFeatures {
        compose true
    }
    composeOptions {
        kotlinCompilerExtensionVersion compose_version
        kotlinCompilerVersion '1.5.10'
    }
}

dependencies {
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.3.0'
    ...
}

5.4.用 Kotlin 编写游戏逻辑实现 Model 层次

为遵守 MVI 设计逻辑,对于游戏界面,所有会由游戏内行为发出再导致前端显示刷新的变化,都集中存放在 TetrisState.kt 文件中。界面的所有变化都依赖后端 State 的变化而刷新。
俄罗斯方块共有 7 种形状,每种形状有随着旋转还会有几种不同形态,本项目在 MockData.kt 文件中选用 44 的二维 IntArray 数组储存方块形状,在 TetrisState.kt 中选用 1024 的二维 IntArray 数组表示存放方块的屏幕。
以下是 TetrisState.kt 文件中一部分重要函数和类的代码:

// class Tetris:对于单个俄罗斯方块的 State 状态类
data class Tetris constructor(
    val shapes: List<List<Location>>, // 此方块所有可能的旋转结果
    val type: Int,                 // 用于标记当前处于哪种旋转状态
    val offset: Location,          // 方块相对屏幕左上角的偏移量
) { ... }

// class TetrisState:游戏的 State 状态类
data class TetrisState(
    val brickArray: Array<IntArray>,    //  屏幕坐标系
    val tetris: Tetris,                 //  下落的方块
    val gameStatus: GameStatus = GameStatus.Welcome, //  游戏状态
    val soundEnable: Boolean = true,    //  是否开启音效
    val nextTetris: Tetris = Tetris(),  //  下一个方块
    var currentScore: Int = infoStorage.currentScore //  当前分数
) { ... }

// class Action:接受用户 View 层次传入信号的 Action 类
sealed class Action {            
    object Welcome : Action()
    object Start : Action()
    object Pause : Action()
    object Reset : Action()
    object Sound : Action()
    object Settings : Action()
    object Background : Action()
    object Resume : Action()
    data class Transformation(val transformationType: TransformationType) : Action()
}

// fun combinedPlayListener() 函数:Action 行为的监听器,监听器接收到信号后,分别再发送给 private fun TetrisState.onStart() 等 TetrisState 的私有函数,进行处理
fun combinedPlayListener(        
    onStart: () -> Unit = {},
    onPause: () -> Unit = {},
    onReset: () -> Unit = {},
    onSound: () -> Unit = {},
    onSettings: () -> Unit = {},
    onTransformation: (TransformationType) -> Unit = {}
) = PlayListener(
    onStart = onStart,
    onPause = onPause,
    onReset = onReset,
    onSound = onSound,
    onSettings = onSettings,
    onTransformation = onTransformation
)

5.5.View 层和 Model 层的交互机制
以下是 TetrisViewModel 类声明部分代码 :

class TetrisViewModel : ViewModel() {
    //功能:接收 Action 信号,由用户的动作改变 ViewModel
	fun dispatch(action: Action) {...}  
    //功能:改变下落速度
    fun changeDownSpeed(newSpeed :Long){...}  
    //功能:初始进入软件时的欢迎效果
    private fun onWelcome() {...}
    //功能:开始游戏
    private fun onStartGame(){...}
    //功能:暂停游戏
    private fun onPauseGame(){...}
    //功能:结束游戏
    private fun onGameOver(){...}
    //功能:开始清空界面
    private fun startClearScreenJob(invokeOnCompletion: () -> Unit){...}
    //功能:取消清空界面
    private fun cancelClearScreenJob(){...}
    //功能:方块下降
    private fun startDownJob() {...}
    //功能:暂停方块下降
    private fun cancelDownJob() {...}
    //功能:将当前界面状态传递给用户
    private fun dispatchState(tetrisState: TetrisState) {..}
    //功能:游戏背景音乐播放
    private fun playSound(action: Action) {...}
    //功能:播放不同类型的背景音乐
    private fun playSound(soundType: SoundType){...}
}

5.6.基于 Compose 实现 View 层次

由于文档篇幅有限,详细的媒体内容展示可参考源代码、 PPT、和应用展示视频。

俄罗斯方块游戏屏幕

主要用到了 Compose 的自定义图像核心可组合项 Canvas ,预览效果如下图:
在这里插入图片描述

主要逻辑功能包括:绘制游戏屏幕背景、绘制以难度指定速度不断下落的方块、为方块提供按键移动功能、判断是否进行消行、如果方块超出当前屏幕则结束游戏。
代码如下:

// 源文件:TetrisScreen.kt 
package com.android_tetris.ui
import...
@Composable
fun TetrisScreen(tetrisState: TetrisState) {...  
    Canvas(modifier=Modifier.(...)){...
        kotlin.run { //绘制方块矩阵
            screenMatrix.forEachIndexed { y, ints ->
                ints.forEachIndexed { x, isFill ->
                    translate(...) {drawBrick(...)}
                }
            }
        } 
        kotlin.run { ...drawPath(...)}//绘制下落方块        
        kotlin.run { drawRightPanel(...)}//绘制右侧得分栏   
		kotlin.run { drawHint(...)} //绘制提示文字
    }
}
//绘制单个方块
fun DrawScope.drawBrick(brickSize: Float,//每一个方块的size
                        color: Color,//砖块颜色
                        background: Color //背景颜色
) {...
    translate(...) {drawRect(...)}//绘制外部矩形边框
   	translate(...) {drawRect(...)}//绘制内部矩形边框
}
private fun DrawScope.drawRightPanel(...) {...}//绘制右侧得分栏
private fun DrawScope.drawHint(...) {...}//绘制提示文字

俄罗斯方块游戏按钮布局

预览效果如下图:
在这里插入图片描述

ConstraintLayout 是一种根据可组合项的相对位置关系显示的布局类型,代码如下:

//TetrisButton.kt
fun TetrisButton(
    playListener: PlayListener = combinedPlayListener()
) {
	Column(...){
		Row(...){
			ControlButton(...){ playListener.onStart()}//开始游戏
			ControlButton(...){ playListener.onPause()}//暂停游戏
			ControlButton(...){ playListener.onReset()}//重新开始
			ControlButton(...){ playListener.onSound()}//音乐开关
		}
		ConstraintLayout(...){
			PlayButton(...){playListener.onTransformation(Rotate)}//旋转方块
			PlayButton(...){playListener.onTransformation(Fall)}//方块加速下落
			PlayButton(...){ playListener.onTransformation(Left)}//方块左移
			PlayButton(...){playListener.onTransformation(Right)}//方块右移
			PlayButton(...){playListener.onTransformation(FastDown)}//方块直接降落到最底部
		}	
	}
}

俄罗斯方块游戏机背景

预览效果如下图:
在这里插入图片描述

代码如下:
// TetrisBody.kt
package com.android_tetris.ui
fun TetrisBody(
    tetrisScreen: @Composable (() -> Unit), 
    tetrisButton: @Composable (() -> Unit),
){...
  	val size by animateDpAsState(...)//改变大小状态
  	val color by infiniteTransition.animateColor(..,)//改变颜色状态
	Column(...){
		TopBar(...){
			Row(...){
				Icon(...); Text(...)//转到setting界面
				Icon(...); Text(...)//转到More界面
				Icon(...); Text(...)//增大屏幕
				Icon(...); Text(...)//减小屏幕
			}
		}
		Box(...){
			Column(...){ tetrisScreen()}//游戏屏幕区
		}
	}
	tetrisButton()//游戏按钮区
}

最终主界面的整体组合效果:
在这里插入图片描述

游戏设置页面

预览效果如下图:
在这里插入图片描述

代码如下:

//TetrisSettingScreen.kt
fun SettingsScreen(){
	Box(modifier = Modifier
            .fillMaxSize()
            .verticalScroll(rememberScrollState())//垂直滚动
	){
		Box(...){
			Box{
				Column(...){
					Box(...){
						 LoginPageTopBlurImage(...)//顶部模糊背景
						 LoginPageTopRotaAndScaleImage(...)//顶部旋转头像
					}
					Column(...){
						Row{
							Text(...)//game setting 图标
							if(...){... PlanetMoon()}//黑夜模式展示月亮动画
							else AnimateSun(Modifier.size(50.dp))//白天模式展示太阳
						}
						ConstraintLayout(...){...
							Row(...){...//难度选择
								Column(...){
									 RadioSelectionButton(label = "Easy",...)//简单
									 RadioSelectionButton(label = "Normal",...)//普通
									 RadioSelectionButton(label = "Hard",...)//困难
								}
							}
							Row(...){...//主题选择
								Column(...){
									RectangularButton(...)
									{viewModel.Theme = ComposeTetrisTheme.Theme.Light} //白天模式
									RectangularButton(...)
									{viewModel.Theme = ComposeTetrisTheme.Theme.Dark} //黑夜模式
								}
							}
							Row(...){...//音乐切换
								 RectangularButton(...){when(infoStorage.bgm){...}}
							}							
						}
						Text( text = "Player Profile",...)//个人主页标题
						ConstraintLayout(...){
							Row(...){...//用户名
								TextField(...)
							}
							Row(...){...//最高分
								Text(text = "$highest",...)                          
							}
							Row(...){...//帮助文档
								DropDownAnimate(...){Text(...)}
							}
						}
					}
				}
			}
		}
	}
}

关于我们页面
预览效果如图所示:
在这里插入图片描述

代码如下:

//AboutUsScreen.kt
package com.android_tetris
...
fun AboutUsScreen(){
	    Box(modifier = Modifier.verticalScroll(rememberScrollState())//垂直滚动界面
        .background(color = Color.Black)
    ){
    	Column(...){
    		 topBarView_More()  //顶部导航栏
    		 Text( buildAnnotatedString {...})//彩虹式标题
    		 BlinkTag {...}//提示文字,点击图片
    		 Row(...){InkColorCanvas()} // 水墨渲染图片
    		 Row(...){Parallax()}//3D安卓小人,展示开发信息
    		 Row(...){
    		 	Text(text = "Our Faith",...)//标题
    		 	Spacer(...)//间距
    		 	Image(...)//月亮图片
    		 }
    		 Row(...){...
    		 	BoxWithConstraints(...){
    		 		Rocket(...)//发射动画
    		 		LaunchButton(...)//发射按钮
    		 	}
    		 }
    		 Text(text = "Message Board",...)//标题
    		 Image(...)//胶片上边框
    		 Box( modifier = Modifier.horizontalScroll(rememberScrollState())...   		 		
    		 ){//横向滚动
    		 	Row{
    		 		Box(...){ ImageCard(...)}...//留言卡片一
    		 		Box(...){ ImageCard(...)}...//留言卡片二
    		 		Box(...){ ImageCard(...)}...//留言卡片三
    		 	} 		 	
    		 }
    		 Text(text = "Contact us",...)//标题
    		 Text(...)//提示信息
    		 Box( modifier = Modifier.horizontalScroll(rememberScrollState())...   		 		
    		 ){//横向滚动
    		 	Row{
    		 		Box(...){Card(...){...}}//项目地址信息
    		 		Box(...){Card(...){...}}//开发者邮箱
    		 		Box(...){Card(...){...}}//开发者码云账户
    		 	} 		 	
    		 }
    	}
    }
}

5.7.实现界面切换和导航功能

最开始,我们想借助 Jetpack Navigation 框架的接口实现界面跳转,但进行了很多天的尝试也未能成功。
随着我们更加深入理解 Compose 的特性,我们认识到 Compose 作为“声明式界面”本身就能够自动更新其中的数据。因此,可以定义一个顶级声明变量,使该变量指代当前应该显示的屏幕,从而采用选择性显示或隐藏某界面的方式,间接实现界面切换功能。

// MainActivity.kt 中选择性显示界面的代码
ComposeTetrisTheme(viewModel.Theme) {
    when (infoStorage.currentScreen) {
        0 -> {
            TetrisGameScreen()
        }
        1 -> {
            SettingsScreen()
        }
        2 -> {
            AboutUsScreen()
        }
    }
}

//Topbar.kt
package com.android_tetris.ui.theme

//设置界面导航栏
@Composable
fun topBarView_Set(){...}

//关于我们界面导航栏
@Preview
@Composable
fun topBarView_More(){...}

5.8.设置显示界面主题

定义主题颜色:分白天和黑夜两个主题,对游戏界面背景颜色、设置界面背景颜色、字体和按钮等颜色进行设置。根据用户所选择的主题,确定颜色组合,赋值给 MaterialTheme 的 colors 成员。

// 黑色主题
private val DarkColorPalette = darkColors( ... )
// 白天主题
private val LightColorPalette = lightColors( ... )

构建 ComposeTetrisTheme 对象,用枚举类存放白天黑夜两个主题供用户选择。

object ComposeTetrisTheme {
    enum class Theme {
        Light, Dark
    }
}

编写主题设置函数:定义 fun ComposeTetrisTheme () 函数,该函数第一个参数接收一个主题对象参数,用于参数判断当前的主题类型;第二个参数接收一个 content 参数确定主题函数作用范围。将变量 colors 传给 MaterialTheme。

fun ComposeTetrisTheme(
    theme: ComposeTetrisTheme.Theme = ComposeTetrisTheme.Theme.Light,
    content: @Composable () -> Unit
) {
    val colors = if (theme == ComposeTetrisTheme.Theme.Dark)
        DarkColorPalette
    else
        LightColorPalette

    MaterialTheme(
        colors = colors,
        typography = Typography,
        shapes = Shapes,
        content = content
    )
}

设置按钮事件实现主题切换:绘制两个按钮分别代表白天模式和黑夜模式,效果是点击按钮 day mode,将 viewModel.Theme 置为 Light,点击按钮 Night mode,将 viewModel.Theme 置为 Dark。主题 Theme 的控制变量放在 ThemeViewModel 类中。 ThemeViewModel 继承自 ViewModel , ViewModel 旨在以生命周期意识的方式,存储和管理用户界面相关的数据,目的是存放应用程序页面所需的数据。这样页面只需要处理用户交互,以及负责展示数据的工作。
ThemeViewModel 只包含一个成员 var Theme ,用 by mutableStateOf(ComposeTetrisTheme.Theme.Light) 初始化。其中 mutableStateOf 函数的作用是显式标明这个 Theme 是有状态的,如果 Theme 的状态发生了改变,所有引用这个状态的控件都需要重新绘制。

var viewModel : ThemeViewModel = viewModel()
    Column(modifier = Modifier.fillMaxWidth()) {
        RectangularButton(
            label = "Daytime mode") {
            viewModel.Theme = ComposeTetrisTheme.Theme.Light
        }

        RectangularButton(
            label = "Night mode",
        ) {
            viewModel.Theme = ComposeTetrisTheme.Theme.Dark
        }
    }

class ThemeViewModel : ViewModel() {
    // 选择夜间模式还是普通配色
    var Theme by mutableStateOf(ComposeTetrisTheme.Theme.Light)
}

5.9.动画设计

Jetpack Compose 为各种动画效果提供实验性 API,可能会在之后的版本中逐步完善:

// Animation.kt
package com.android_tetris.ui.theme

// 绘制动态月亮
@Composable
fun PlanetMoon() {...}// 插入月亮图片
@Composable
private fun buildEarthFloatAnimation(): State<Float>{...}//实现月亮垂直方向浮动效果
@Composable
private fun buildEarthRotationAnimation(): State<Float> {...}//实现月亮沿着竖直轴旋转效果

// 水墨渲染动画
@Composable
fun InkColorCanvas() {...}

// 旋转太阳的动画
@Composable
fun AnimateSun(modifier: Modifier = Modifier){...}
@Composable
fun Sun(modifier: Modifier = Modifier) {...}

// 3D页面下翻动画
@Composable
fun DropDownAnimate(
    text: String,
    modifier: Modifier = Modifier,
    initiallyOpened: Boolean = false,
    content: @Composable () -> Unit
) {...}

//3D 安卓小人动画
fun Parallax() {...}
fun getRotationAngles(
    start: Pair<Float, Float>,
    end: Pair<Float, Float>,
    size: Size
): Pair<Float, Float> {...}
fun getDistances(p1: Pair<Float, Float>, p2: Pair<Float, Float>): Pair<Float, Float> {...}
fun getTranslation(angle: Float, maxDistance: Float): Float {...}

// 火箭发射动画
@Composable
fun Rocket(
    isRocketEnabled: Boolean,
    maxWidth: Dp,
    maxHeight: Dp
) {...}
//火箭发射按钮
@ExperimentalAnimationApi
@Composable
fun LaunchButton( 
    animationState: Boolean,
    onToggleAnimationState: () -> Unit,
){...}

//ImageCard
@Composable
fun ImageCard(
    painter: Painter,
    contentDescription: String,
    title: String,
    modifier:Modifier=Modifier
){...}

// Login.kt
package com.android_tetris.ui.theme
import...

//登陆背景模糊头缩放部图片
@Composable
fun LoginPageTopBlurImage(
    animatedBitmap: Animatable<Float, AnimationVector1D>,
    animatedOffset: Animatable<Float, AnimationVector1D>,
    animatedScales: Animatable<Float, AnimationVector1D>
) {...}

//登陆页面头部旋转缩放的图片
@Composable
fun LoginPageTopRotaAndScaleImage(
    animatedColor: Animatable<androidx.compose.ui.graphics.Color, AnimationVector4D>,
    animatedScales: Animatable<Float, AnimationVector1D>,
    animatedOffset: Animatable<Float, AnimationVector1D>
) {...}

//圆形图片
@Stable
class CicleImageShape(val circle: Float = 0f) : Shape {
    override fun createOutline(...): Outline {...}
}

//形状裁剪
@Stable
class QureytoImageShapes(var hudu: Float = 100f, var controller:Float=0f) : Shape {
    override fun createOutline(...): Outline {...}
}

// 模糊效果
object BitmapBlur {
    fun doBlur(sentBitmap: Bitmap, radiu: Int = 1, canReuseInBitmap: Boolean): Bitmap {}
}

//RainbowSpark.kt
package com.android_tetris.ui.theme
import...

// 彩虹渐变字体
@ExperimentalAnimationApi
@Composable
fun MultiColorSmoothText(
    modifier: Modifier = Modifier,
    text: String,
    style: TextStyle = LocalTextStyle.current,
    rainbow: List<Color> = PastelRainbow,
    startIndex: Int = 0,
    duration: Int
) {...}

//闪烁字体
@ExperimentalAnimationApi
@Composable
fun BlinkTag(
    modifier: Modifier = Modifier,
    duration: Int = 500000,
    content: @Composable (modifier: Modifier) -> Unit
) {...}

附录:源代码 Git 仓库链接

Gitee:https://gitee.com/yxwang2023/android_tetris
GitHub:https://github.com/zyw-stu/Andriod_Tetris


点击全文阅读


本文链接:http://m.zhangshiyu.com/post/30667.html

界面  游戏  功能  
<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1