《Android编程权威指南》之Android与MVC篇

2021-08-20 17:01:08 浏览数 (1)

升级 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 的矢量图格式。

  1. 在 Project 窗口中,右键点击 res 目录
  2. New > Vector Asset,选择 Local file (SVG, PSD)
  3. 找到要导入的文件并进行任何调整,点击下一步再到 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

0 人点赞