升级 GeoQuiz 应用,展示更多的地理知识测试题目。
创建新类
New → Kotlin Class/File (Data Class),kt数据类很方便,比起 java,省去了很多代码。
代码语言:javascript复制data class Question(@StringRes val textResId: Int, val answer: Boolean)
- Kotlin 数据类: 只保存数据的类,关键字为 data,编译器会自动的从主构造函数中根据所有声明的属性提取以下函数:
- equals() / hashCode()
- toString() 格式如 "User(name=John, age=42)"
- componentN() functions 对应于属性,按声明顺序排列
- copy() 函数
- 数据类的要求:
- 主构造函数至少包含一个参数
- 主构造函数的参数必须标识为val 或者 var
- 数据类不可以声明为 abstract, open, sealed 或者 inner
- 在1.1版本之前,数据类只实现接口。1.1版本之后,数据类可以扩展其他类
Android 与 MVC 设计模式
❝
MVC图解❞
- M —— 模型对象存储着应用的数据和业务逻辑。比如说一些JavaBean属于这层,不关心用户界面,目的是存储和管理应用数据。
- V —— 视图对象知道如何在屏幕上绘制自己以及如何响应用户的输入。比如说layout中的xml文件,也可以自定义控件,反正是看得见的对象,就是视图对象。
- C —— 控制对象含有应用的逻辑单元,是视图与模型对象的联系纽带。通常是Activity、Fragment 或 Service 的一个子类。
❝
MVC数据控制流与用户交互❞
注意,模型对象与视图对象不直接交互。控制器作为它们之间的联系纽带,接收对象发送的消息,然后向其他对象发送操作指令。
更新视图层
代码语言:javascript复制activity_main.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:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@ id/question_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="24dp"
tools:text="@string/question_australia" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@ id/true_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/true_button" />
<Button
android:id="@ id/false_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="@string/false_button" />
</LinearLayout>
<Button
android:id="@ id/next_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/next_button" />
</LinearLayout>
代码语言:javascript复制string.xml
<resources>
<string name="app_name">GeoQuiz</string>
<string name="question_australia">Canberra is the capital of Australia.</string>
<string name="true_button">True</string>
<string name="false_button">False</string>
<string name="next_button">Next</string>
<string name="correct_toast">Correct!</string>
<string name="incorrect_toast">Incorrect!</string>
<string name="question_oceans">The Pacific Ocean is larger than the Atlantic Ocean.</string>
<string name="question_mideast">The Suez Canal connects the Red Sea and the Indian Ocean.</string>
<string name="question_africa">The source of the Nile River is in Egypt.</string>
<string name="question_americas">The Amazon River is the longest river in the Americas.</string>
<string name="question_asia">Lake Baikal is the world's oldest and deepest freshwater lake.</string>
</resources>
更新控制器层
代码语言:javascript复制MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var trueButton: Button
private lateinit var falseButton: Button
private lateinit var nextButton: Button
private lateinit var questionTextView: TextView
private val questionBank = listOf(
Question(R.string.question_australia, true),
Question(R.string.question_oceans, true),
Question(R.string.question_mideast, false),
Question(R.string.question_africa, false),
Question(R.string.question_americas, true),
Question(R.string.question_asia, true)
)
private var currentIndex = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
trueButton = findViewById(R.id.true_button)
falseButton = findViewById(R.id.false_button)
nextButton = findViewById(R.id.next_button)
questionTextView = findViewById(R.id.question_text_view)
trueButton.setOnClickListener { checkAnswer(true) }
falseButton.setOnClickListener { checkAnswer(false) }
nextButton.setOnClickListener {
currentIndex = (currentIndex 1) % questionBank.size
updateQuestion()
}
updateQuestion()
}
/**
* 更新问题
*/
private fun updateQuestion() {
val questionTextResId = questionBank[currentIndex].textResId
questionTextView.setText(questionTextResId)
}
private fun checkAnswer(userAnswer: Boolean) {
val correctAnswer = questionBank[currentIndex].answer
val messageResId = if (userAnswer == correctAnswer) {
R.string.correct_toast
} else {
R.string.incorrect_toast
}
Toast.makeText(this, messageResId, Toast.LENGTH_SHORT).show()
}
}
添加图标资源
把图片资源放入 drawable 目录或者 mipmap 目录中,注意,文件名必须是小写字母且不能由任何空格符号。
- ldpi:适用于低密度 (ldpi) 屏幕 (~ 120dpi) 的资源 | 36x36 (0.75x)
- mdpi:中等像素密度屏幕(约160dpi)| 48x48(1.0x 基准)
- hdpi:高像素密度屏幕(约240dpi)| 72x72 (1.5x)
- xhdpi:超高像素密度屏幕(约320dpi)| 96x96 (2.0x)
- xxhdpi:超超高像素密度屏幕(约480dpi)| 144x144 (3.0x)
- xxxhdpi:超超超高像素密度屏幕(约640dpi)| 192x192 (4.0x)
- nodpi:适用于所有密度的资源。这些是与密度无关的资源。无论当前屏幕的密度是多少,系统都不会缩放以此限定符标记的资源。
- tvdpi:适用于密度介于 mdpi 和 hdpi 之间的屏幕(约 213dpi)的资源。这不属于“主要”密度组。它主要用于电视,而大多数应用都不需要它。对于大多数应用而言,提供 mdpi 和 hdpi 资源便已足够,系统将视情况对其进行缩放。如果您发现有必要提供 tvdpi 资源,应按一个系数来确定其大小,即 1.33*mdpi。例如,如果某张图片在 mdpi 屏幕上的大小为 100px x 100px,那么它在 tvdpi 屏幕上的大小应该为 133px x 133px。
将应用图标放在 mipmap 目录中!
屏幕像素密度
要在密度不同的屏幕上保留界面的可见尺寸,您必须使用密度无关像素 (dp) 作为度量单位来设计界面。dp 是一个虚拟像素单位,1 dp 约等于中密度屏幕(160dpi;“基准”密度)上的 1 像素。对于其他每个密度,Android 会将此值转换为相应的实际像素数。
在定义文本大小时,您应改用可缩放像素 (sp) 作为单位(但切勿将 sp 用于布局尺寸)。默认情况下,sp 单位与 dp 大小相同,但它会根据用户的首选文本大小来调整大小。
- 矢量图形:(适配用,可以缩放到任何尺寸而不会出现缩放失真,通常最适合图标等插图,而不太适合照片)
Android 仅仅支持将 SVG 文件转换为 Android 的矢量图格式。
- 在 Project 窗口中,右键点击 res 目录
- New > Vector Asset,选择 Local file (SVG, PSD)
- 找到要导入的文件并进行任何调整,点击下一步再到 Finish
针对所有像素密度测试
- Android 模拟器
- Firebase 测试实验室
在设备上运行
用真机测试安装应用,需要连接上真机噢。如果在Mac系统上开发,系统应该会立即识别出所有设备。如果是Windows系统,则可能要安装adb(Android Debug Bridger)驱动。
真机要打开USB调试模式:
- Android 4.2或之后版本的设备,开发选项默认不可见。设置->关于手机,多点击几次版本号启动它,然后回到设置->更多设置->开发者选项,勾选USB调试 USB安装
- Android 4.0或4.1版本,设置->开发,找到勾选USB调试.
- Android 4.0版本以前的设备,设置->应用项目->开发,找到勾选USB调试。
当然,也可以用AS创建一个模拟器,去运行应用程序。
挑战练习:为Textview添加监听器
(单击应用的TextView文字区域,也可以跳转到下一道题)
代码语言:javascript复制questionTextView.setOnClickListener {
currentIndex = (currentIndex 1) % questionBank.size
updateQuestion()
}
挑战练习:添加后退按钮
在 XML 中加个Button,用 LinearLayout 将 NEXT 和 PRE 按钮包裹起来,Acitvity 中拿到 PRE 按钮,再加个点击事件。
代码语言:javascript复制preButton.setOnClickListener {
currentIndex = (currentIndex questionBank.size - 1) % questionBank.size
updateQuestion()
}
❝
1❞
挑战练习:从按钮到图标按钮
将普通的 Button 替换成 ImageButton 即可,图片资源引用的话,用 src 。这里可为 ImageButton 添加android:contentDescription 属性,这样子,在用户点击图形按钮时,设备便会读出属性值的内容。
❝
2❞
❝
2❞
Demo 源码地址:https://github.com/visiongem/AndroidGuideApp