开发目的
想打造个性化的私人闹钟APP,放到桌面上提示时间,但是感觉应用商店中的相关软件不好用,有些有广告,就难受。而且没有办法DIY自己想要的时钟样式。
所以,开搞!(初学者入门,慢慢摸索呗)
开发环境
1、windows操作系统
2、Android Studio 2024
3、JDK 1.8(已配置的jdk环境,因为Android Studio基于IntelliJ IDEA,需要jdk环境启动)
详细步骤
1、安装并搭建开发环境
Android Studio工具下载官网
按步骤安装即可,记得点上AVD,方便后续测试
下载SDK
2、创建工程
创建一个虚拟的手机视图(AVD)模拟器来方便我们开发
由于我使用的手机是MIUI13的版本,这里就用了Android 12来构建
项目下面可以有多个模块,编译运行app一般指的是运行某个模块
项目目录:
mainfests:下面有一个XML文件,是App的运行配置文件
kotlin+java:包含源代码、测试代码
res:资源文件,主要包括:drawable、layout、mipmap、values
Gradle Script主要包括工程编译配置文件,build-gradle是编译规则文件,分为全局配置和模块配置
3、创建XML文件(布局文件)
可以通过拖拽的方式添加组件了,右侧工具栏设置样式
点击右上角可以切换视图
先试着创建一个视图
要求显示一个居中显示的时钟、一个切换背景颜色的按钮,背景颜色默认为黑色
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rootLayoutId"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
android:gravity="center"
android:orientation="vertical">
<TextClock
android:id="@+id/textClock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textAppearance="@style/TextAppearance.AppCompat.Display1"
android:textColor="#258729"
android:textSize="200sp"
android:textStyle="bold"
android:typeface="normal" />
<Button
android:id="@+id/button2"
android:layout_width="100dp"
android:layout_height="50dp"
android:onClick="onChangeBackgroundClick"
android:text="@string/change" />
</LinearLayout>
在MainActivity中编写逻辑
package com.example.clock
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.View
import android.widget.LinearLayout
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
fun onChangeBackgroundClick(view: View) {
// 找到布局
val rootLayout = findViewById<LinearLayout>(R.id.rootLayoutId)
// 设置背景颜色
var backgroundDrawable = rootLayout.background
val currentColor = (backgroundDrawable as ColorDrawable).color
if (currentColor != -1) {
rootLayout.setBackgroundColor(resources.getColor(R.color.white))
} else {
rootLayout.setBackgroundColor(resources.getColor(R.color.black))
}
}
}
效果:
记得调整时区为”Asia/Shanghai”
var clock = findViewById<TextClock>(R.id.textClock)
clock.timeZone = "Asia/Shanghai"
后续加入倒计时功能:
需要添加一个用户输入框用于输入倒计时长、和一个开始倒计时的确认框
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<EditText
android:id="@+id/inputEditText"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="48dp"
android:autofillHints="5"
android:ems="10"
android:gravity="center"
android:hint="Countdown (minute)"
android:textColor="#258729"
android:textColorHint="#258729"
android:inputType="text" />
<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onStartCounting"
android:text="@string/start" />
</LinearLayout>
用<LinearLayout>标签将两元素框起,保证在同一水平行中
编写业务逻辑:
1、当用户输入内容在[1,999]之间,跳转到倒计时页面,传一个用户输入的时间参数过去,开始倒计时
2、若输入内容不符合规范,提示内容要在[1,999]之间,不跳转
这是一个函数,判断用户的“Start”行为,进行处理
private fun getUserInput() {
// 获取 EditText 的引用
val inputEditText = findViewById<EditText>(R.id.inputEditText)
// 获取用户输入的数据
val userInput = inputEditText.text.toString() // 转换为 String
if (userInput.matches("^[1-9][0-9]{0,2}\$".toRegex())) {
// 匹配成功,准备跳转页面
// 创建一个新的Intent,用于从当前Activity跳转到CountdownActivity
val intent = Intent(this, CountdownActivity::class.java)
// 可选:将用户输入作为额外信息传递给CountdownActivity
// CountdownActivity有一个EXTRA_TIME的额外字符串字段来接收时间
intent.putExtra("EXTRA_TIME", userInput)
// 启动CountdownActivity
startActivity(intent)
} else {
Toast.makeText(this, "Please enter a number between 0 and 999.", Toast.LENGTH_SHORT).show()
}
}
给按钮编写事件监听器
val button = findViewById<Button>(R.id.buttonStart)
button.setOnClickListener {
getUserInput() // 当按钮被点击时调用此函数
}
编写一个CountdownActivity 来实现倒计时模块
class CountdownActivity : AppCompatActivity() {
private lateinit var timeTextView: TextView
private var timeSeconds: Long = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_countdown)
// 获取从Intent中传递的额外数据
val timeInput = intent.getStringExtra("EXTRA_TIME")?.toIntOrNull()
timeSeconds = (timeInput?.times(60) ?: 0).toLong()
timeTextView = findViewById(R.id.time)
timeTextView.text = timeSeconds.toString()
if (timeSeconds > 0) {
// 使用协程来处理倒计时
lifecycleScope.launch(Dispatchers.Main) {
countdown()
}
}
}
private suspend fun countdown() {
while (timeSeconds > 0) {
delay(1000) // 暂停1秒钟
timeSeconds--
withContext(Dispatchers.Main) {
// 更新UI必须在主线程
timeTextView.text = timeSeconds.toString()
}
}
// 倒计时结束后,启动MainActivity
withContext(Dispatchers.Main) {
val intent = Intent(this@CountdownActivity, MainActivity::class.java)
startActivity(intent)
finish() // 结束当前的CountdownActivity
}
}
}
显示页面包含倒计时时间以及豌豆射手贴图:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/countLayoutId"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/textview"
android:textColor="#258729"
android:textSize="150sp"
android:textStyle="bold"/>
<!-- 如果需要ImageView,可以添加以下属性 -->
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:contentDescription="@string/todo"
app:srcCompat="@drawable/pp" />
</LinearLayout>
添加点击主页空白处隐藏(显示)按钮和输入框的功能,让页面简洁,方便显示
点击空白处的处理函数 以及 判断是否点击空白处的函数
private fun onEmptySpaceClicked() {
if (findViewById<Button>(R.id.buttonStart).isVisible) {
findViewById<Button>(R.id.buttonStart).visibility = View.INVISIBLE;
findViewById<Button>(R.id.buttonChange).visibility = View.INVISIBLE;
findViewById<EditText>(R.id.inputEditText).visibility = View.INVISIBLE;
} else {
findViewById<Button>(R.id.buttonStart).visibility = View.VISIBLE;
findViewById<Button>(R.id.buttonChange).visibility = View.VISIBLE;
findViewById<EditText>(R.id.inputEditText).visibility = View.VISIBLE;
}
}
private fun isClickOnView(rootView: View, targetView: View): Boolean {
val location = IntArray(2)
targetView.getLocationOnScreen(location)
val x = rootView.x.toInt()
val y = rootView.y.toInt()
return location[0] in x until x + targetView.width && location[1] in y until y + targetView.height
}
至此,V1.0版本已经完成! 期待后续添加更多自定义功能和样式!
错误日志
问题:
在应用试图使用与AppCompatActivity不兼容的样式或主题时发生
java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity.
指出你的Activity需要使用AppCompat主题或其派生主题。
解决:
设置主题,在AndroidManifest.xml设置主题为AppCompat的派生主题
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
问题:
跳转失败,无法跳转到目标Activity,直接闪退。
解决:
在AndroidManifest.xml中添加目标Activity的声明。
<activity android:name=".CountdownActivity"
android:exported="true"
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
</activity>
问题:
apk包无法安装至手机上
解决:
首先要在gradle.properties中设置
android.injected.testOnly=false
其次,由于release版本的apk包才能在手机上运行,我们需要设置一个签名
选择打包为apk
create new
选择Release
成功安装!
问题:
项目没有横屏显示
解决:
在<application>
标签中设置android:screenOrientation
属性:
<application
android:screenOrientation="landscape"
问题:
想让倒计时,每一秒刷新一次时间,利用Thread.sleep实现,但是应用直接被系统杀掉了
解决:
原因是在UI线程(主线程)进行了sleep的操作,这会导致应用程序的UI冻结
需要使用kotlin中提供的“协助线程”来解决
修改后代码如下
package com.example.clock
import android.content.Intent
import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.*
class CountdownActivity : AppCompatActivity() {
private lateinit var timeTextView: TextView
private var timeSeconds: Long = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_countdown)
// 获取从Intent中传递的额外数据
val timeInput = intent.getStringExtra("EXTRA_TIME")?.toIntOrNull()
timeSeconds = (timeInput?.times(60) ?: 0).toLong()
timeTextView = findViewById(R.id.time)
timeTextView.text = timeSeconds.toString()
if (timeSeconds > 0) {
// 使用协程来处理倒计时
lifecycleScope.launch(Dispatchers.Main) {
countdown()
}
}
}
private suspend fun countdown() {
while (timeSeconds > 0) {
delay(1000) // 暂停1秒钟
timeSeconds--
withContext(Dispatchers.Main) {
// 更新UI必须在主线程
timeTextView.text = timeSeconds.toString()
}
}
// 倒计时结束后,启动MainActivity
withContext(Dispatchers.Main) {
val intent = Intent(this@CountdownActivity, MainActivity::class.java)
startActivity(intent)
finish() // 结束当前的CountdownActivity
}
}
}
标签:findViewById,val,import,APP,开发,CountdownActivity,timeSeconds,Android,id
From: https://blog.csdn.net/ddfhfjd/article/details/139848827