桌面时钟APP的简单开发(Android开发)

开发目的

想打造个性化的私人闹钟APP,放到桌面上提示时间,但是感觉应用商店中的相关软件不好用,有些有广告,就难受。而且没有办法DIY自己想要的时钟样式。

所以,开搞!(初学者入门,慢慢摸索呗)

开发环境

1、windows操作系统

2、Android  Studio 2024

3、JDK 1.8(已配置的jdk环境,因为Android  Studio基于IntelliJ IDEA,需要jdk环境启动)

详细步骤

1、安装并搭建开发环境

Android  Studio工具下载官网

下载 Android Studio 和应用工具 - Android 开发者  |  Android Developers (google.cn)icon-default.png?t=N7T8https://developer.android.google.cn/studio?hl=zh-cn

按步骤安装即可,记得点上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
        }
    }
}

  • 28
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值