Repository: sunnyxibei/HenCoderPractice
Branch: master
Commit: da5c22cd5748
Files: 27
Total size: 34.1 KB
Directory structure:
gitextract_mu2re4hw/
├── .gitignore
├── README.md
├── app/
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src/
│ ├── androidTest/
│ │ └── java/
│ │ └── com/
│ │ └── sunnyxibei/
│ │ └── hencoderpractice/
│ │ └── ExampleInstrumentedTest.kt
│ ├── main/
│ │ ├── AndroidManifest.xml
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── sunnyxibei/
│ │ │ └── hencoderpractice/
│ │ │ ├── MainActivity.kt
│ │ │ └── MapView.kt
│ │ └── res/
│ │ ├── drawable/
│ │ │ ├── ic_launcher_background.xml
│ │ │ └── ic_launcher_foreground.xml
│ │ ├── layout/
│ │ │ └── activity_main.xml
│ │ ├── menu/
│ │ │ └── menu_item.xml
│ │ ├── mipmap-anydpi-v26/
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ └── values/
│ │ ├── attrs.xml
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test/
│ └── java/
│ └── com/
│ └── sunnyxibei/
│ └── hencoderpractice/
│ └── ExampleUnitTest.kt
├── build.gradle.kts
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle.kts
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
*.iml
.gradle
.idea
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.externalNativeBuild
================================================
FILE: README.md
================================================
# HenCoderPractice
HenCoder 三维旋转动效的实现

>这是凯哥在 HenCoder 讲解几何变换章节提到的一个动画栗子,很炫酷,极大地激发了偶的好奇心。正好这个周末手头没啥事儿,研究了一下实现。
* 首先,拆解动效,把gif图片放到手机上,咔咔咔不停地截屏,截出一帧一帧的图片
* 然后,分析这些图片,可以发现,动效分为三部分:
* 开始动画,一个Y轴旋转45度动效,看过hencoder教程的童鞋应该很轻松实现
* 中间动画,比较复杂,后面着重分解
* 结束动画,一个绕X轴旋转45度动效,看过hencoder教程的童鞋应该很轻松实现
重点分析中间动画,一帧一帧观察,基本判断是,先旋转canvas坐标系,再裁切图形,然后使用camera3D旋转,保存camera效果 `camera.applyToCanvas(canvas);`,然后再把canvas坐标系旋转回来。本来想画个图解释一下,奈何时间有限,你先仔细体会一下吧......
实现中间动画,原理上面已经说了
```
canvas.save();
camera.save();
canvas.translate(centerX, centerY);
canvas.rotate(-degreeZ);
camera.rotateY(degreeY);
camera.applyToCanvas(canvas);
//计算裁切参数时清注意,此时的canvas的坐标系已经移动
canvas.clipRect(0, -centerY, centerX, centerY);
canvas.rotate(degreeZ);
canvas.translate(-centerX, -centerY);
camera.restore();
canvas.drawBitmap(bitmap, x, y, paint);
canvas.restore();
```
划重点,需要注意:
1、Canvas的几何变换,顺序是倒序
2、clipRect执行时,canvas的坐标系已经被我们移动了,计算参数时要以移动后的坐标系计算
3、循环播放动效时,在执行动效前,要重置几个动效相关的参数值
4、第三个动效执行时,我们的canvas已经旋转了270度,所以此时的旋转
实际上
是绕Y轴旋转,而且旋转角度的正负也是反着的,你看下代码,再仔细体会一下
================================================
FILE: app/.gitignore
================================================
/build
================================================
FILE: app/build.gradle.kts
================================================
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("org.jetbrains.kotlin.plugin.compose")
}
android {
namespace = "com.sunnyxibei.hencoderpractice"
compileSdk = 35
defaultConfig {
applicationId = "com.sunnyxibei.hencoderpractice"
minSdk = 26
targetSdk = 35
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" // Updated
vectorDrawables {
useSupportLibrary = true
}
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
}
}
buildFeatures {
compose = true
buildConfig = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.14"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_17.toString()
}
}
dependencies {
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
implementation("androidx.appcompat:appcompat:1.6.1")
// Jetpack Compose BOM
val composeBom = platform("androidx.compose:compose-bom:2024.05.00")
implementation(composeBom)
androidTestImplementation(composeBom)
// Compose specific dependencies
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.ui:ui-tooling-preview")
debugImplementation("androidx.compose.ui:ui-tooling")
implementation("androidx.activity:activity-compose:1.9.0")
// Test dependencies
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") {
exclude(group = "com.android.support", module = "support-annotations")
}
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
testImplementation("junit:junit:4.12")
}
================================================
FILE: app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in C:\Develop\Android\sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
================================================
FILE: app/src/androidTest/java/com/sunnyxibei/hencoderpractice/ExampleInstrumentedTest.kt
================================================
package com.sunnyxibei.hencoderpractice
import android.support.test.runner.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.*
import org.junit.Test
import org.junit.runner.RunWith
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.sunnyxibei.hencoderpractice", appContext.packageName)
}
}
================================================
FILE: app/src/main/AndroidManifest.xml
================================================
================================================
FILE: app/src/main/java/com/sunnyxibei/hencoderpractice/MainActivity.kt
================================================
package com.sunnyxibei.hencoderpractice
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.graphics.BitmapFactory
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import androidx.activity.ComponentActivity // Changed from AppCompatActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.* // Using Material 3
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.viewinterop.AndroidView
// It's good practice to define a theme for Compose
// For now, a basic MaterialTheme will be used.
// Usually, this would be in a separate Theme.kt file.
@Composable
fun AppTheme(content: @Composable () -> Unit) {
MaterialTheme(
colorScheme = lightColorScheme(), // Or darkColorScheme()
content = content
)
}
class MainActivity : ComponentActivity() { // Changed from AppCompatActivity
private val handler = Handler(Looper.getMainLooper())
private var mapViewInstance: MapView? = null // To hold the MapView instance for animation control
private var animatorSet: AnimatorSet? = null // To control the animator set
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AppTheme {
MainScreen()
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainScreen() {
val context = LocalContext.current
var showMenu by remember { mutableStateOf(false) }
Scaffold(
topBar = {
TopAppBar(
title = { Text(stringResource(id = R.string.app_name)) },
actions = {
IconButton(onClick = { showMenu = !showMenu }) {
Icon(Icons.Filled.MoreVert, contentDescription = "More options")
}
DropdownMenu(
expanded = showMenu,
onDismissRequest = { showMenu = false }
) {
DropdownMenuItem(
text = { Text("Google Map") },
onClick = {
mapViewInstance?.setBitmap(
BitmapFactory.decodeResource(
context.resources,
R.drawable.google_map
)
)
showMenu = false
}
)
DropdownMenuItem(
text = { Text("FlipBoard") },
onClick = {
mapViewInstance?.setBitmap(
BitmapFactory.decodeResource(
context.resources,
R.drawable.flip_board
)
)
showMenu = false
}
)
}
}
)
}
) { paddingValues ->
Box(modifier = Modifier.padding(paddingValues).fillMaxSize()) {
MapViewComposable()
}
}
}
@Composable
fun MapViewComposable() {
AndroidView(
modifier = Modifier.fillMaxSize(),
factory = { context ->
MapView(context).apply {
mapViewInstance = this
val animator1 = ObjectAnimator.ofFloat(this, "degreeY", 0f, -45f).apply {
duration = 1000
startDelay = 500
}
val animator2 = ObjectAnimator.ofFloat(this, "degreeZ", 0f, 270f).apply {
duration = 800
startDelay = 500
}
val animator3 = ObjectAnimator.ofFloat(this, "fixDegreeY", 0f, 30f).apply {
duration = 500
startDelay = 500
}
animatorSet = AnimatorSet().apply {
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
super.onAnimationEnd(animation)
handler.postDelayed({
mapViewInstance?.let {
if (!isDestroyed && !isFinishing) {
it.reset()
start()
}
}
}, 500)
}
})
playSequentially(animator1, animator2, animator3)
start()
}
}
}
)
}
override fun onDestroy() {
super.onDestroy()
handler.removeCallbacksAndMessages(null)
animatorSet?.cancel()
}
}
================================================
FILE: app/src/main/java/com/sunnyxibei/hencoderpractice/MapView.kt
================================================
package com.sunnyxibei.hencoderpractice
import android.content.Context
import android.graphics.*
import android.graphics.drawable.BitmapDrawable
import android.util.AttributeSet
import android.view.View
import androidx.annotation.Keep
/**
* 整个动画拆分成了三部分
*
* Created by jiayuanbin on 2017-9-23.
*/
class MapView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
//Y轴方向旋转角度
@set:Keep
var degreeY: Float = 0f
set(value) {
field = value
invalidate()
}
//不变的那一半,Y轴方向旋转角度
@set:Keep
var fixDegreeY: Float = 0f
set(value) {
field = value
invalidate()
}
//Z轴方向(平面内)旋转的角度
@set:Keep
var degreeZ: Float = 0f
set(value) {
field = value
invalidate()
}
private val paint: Paint
private var bitmap: Bitmap
private val camera: Camera
init {
val a = context.obtainStyledAttributes(attrs, R.styleable.MapView)
val drawable = a.getDrawable(R.styleable.MapView_mv_background) as? BitmapDrawable
a.recycle()
bitmap = drawable?.bitmap ?: BitmapFactory.decodeResource(resources, R.drawable.flip_board)
paint = Paint(Paint.ANTI_ALIAS_FLAG)
camera = Camera()
val displayMetrics = resources.displayMetrics
val newZ = -displayMetrics.density * 6
camera.setLocation(0f, 0f, newZ) // Use float values for camera
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val bitmapWidth = bitmap.width
val bitmapHeight = bitmap.height
val centerX = width / 2f // Use float for center calculations
val centerY = height / 2f // Use float for center calculations
val x = centerX - bitmapWidth / 2f
val y = centerY - bitmapHeight / 2f
//画变换的一半
//先旋转,再裁切,再使用camera执行3D动效,**然后保存camera效果**,最后再旋转回来
canvas.save()
camera.save()
canvas.translate(centerX, centerY)
canvas.rotate(-degreeZ)
camera.rotateY(degreeY)
camera.applyToCanvas(canvas)
//计算裁切参数时清注意,此时的canvas的坐标系已经移动
canvas.clipRect(0f, -centerY, centerX, centerY) // Use float values
canvas.rotate(degreeZ)
canvas.translate(-centerX, -centerY)
camera.restore()
canvas.drawBitmap(bitmap, x, y, paint)
canvas.restore()
//画不变换的另一半
canvas.save()
camera.save()
canvas.translate(centerX, centerY)
canvas.rotate(-degreeZ)
//计算裁切参数时清注意,此时的canvas的坐标系已经移动
canvas.clipRect(-centerX, -centerY, 0f, centerY) // Use float values
//此时的canvas的坐标系已经旋转,所以这里是rotateY
camera.rotateY(fixDegreeY)
camera.applyToCanvas(canvas)
canvas.rotate(degreeZ)
canvas.translate(-centerX, -centerY)
camera.restore()
canvas.drawBitmap(bitmap, x, y, paint)
canvas.restore()
}
/**
* 启动动画之前调用,把参数reset到初始状态
*/
fun reset() {
degreeY = 0f
fixDegreeY = 0f
degreeZ = 0f
invalidate() // Invalidate after resetting properties
}
fun setBitmap(bitmap: Bitmap) {
this.bitmap = bitmap
invalidate()
}
}
================================================
FILE: app/src/main/res/drawable/ic_launcher_background.xml
================================================
================================================
FILE: app/src/main/res/drawable/ic_launcher_foreground.xml
================================================
================================================
FILE: app/src/main/res/layout/activity_main.xml
================================================
================================================
FILE: app/src/main/res/menu/menu_item.xml
================================================
================================================
FILE: app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
================================================
================================================
FILE: app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
================================================
================================================
FILE: app/src/main/res/values/attrs.xml
================================================
================================================
FILE: app/src/main/res/values/colors.xml
================================================
#3F51B5
#303F9F
#FF4081
================================================
FILE: app/src/main/res/values/strings.xml
================================================
HenCoderPractice
================================================
FILE: app/src/main/res/values/styles.xml
================================================
================================================
FILE: app/src/test/java/com/sunnyxibei/hencoderpractice/ExampleUnitTest.kt
================================================
package com.sunnyxibei.hencoderpractice
import org.junit.Assert.*
import org.junit.Test
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}
================================================
FILE: build.gradle.kts
================================================
allprojects {
repositories {
maven { url = uri("https://maven.aliyun.com/repository/public") }
maven { url = uri("https://maven.aliyun.com/repository/central") }
maven { url = uri("https://maven.aliyun.com/repository/google") }
maven { url = uri("https://maven.aliyun.com/repository/gradle-plugin") }
google()
mavenCentral()
maven { url = uri("https://jitpack.io") }
}
}
val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register("clean") {
delete(rootProject.layout.buildDirectory)
}
================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Sat Sep 23 22:49:00 CST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
================================================
FILE: gradle.properties
================================================
org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true
android.nonTransitiveRClass=true
#set gradle jdk 17
org.gradle.java.home=/Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home
================================================
FILE: gradlew
================================================
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
================================================
FILE: gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: settings.gradle.kts
================================================
pluginManagement {
buildscript {
repositories {
maven { url = uri("https://maven.aliyun.com/repository/public") }
maven { url = uri("https://maven.aliyun.com/repository/central") }
maven { url = uri("https://maven.aliyun.com/repository/google") }
maven { url = uri("https://maven.aliyun.com/repository/gradle-plugin") }
mavenCentral()
}
}
repositories {
maven { url = uri("https://maven.aliyun.com/repository/public") }
maven { url = uri("https://maven.aliyun.com/repository/central") }
maven { url = uri("https://maven.aliyun.com/repository/google") }
maven { url = uri("https://maven.aliyun.com/repository/gradle-plugin") }
google()
mavenCentral()
maven { url = uri("https://jitpack.io") }
gradlePluginPortal()
}
}
plugins {
id("com.android.application") version "8.7.3" apply false
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
id("org.jetbrains.kotlin.plugin.compose") version "2.1.0" apply false
}
rootProject.name = "HenCoderPractice"
include(":app")