Android入门之旅
注:这是一个学习笔记。很可惜今年的 Google Study Jams 活动取消了,于是就只能自己来了,幸好 Google 官方有很多面向新手的教程,那么,开干!本文基于https://developers.google.cn/training/android/ 给出的教程(而且是中文教程,感觉很良心)
2018©Fu_Qingchen,Typora
本文的运行环境是:1
2
3
4
5Android Studio 3.1
Build #AI-173.4670197, built on March 22, 2018
JRE: 1.8.0_152-release-1024-b02 amd64
JVM: OpenJDK 64-Bit Server VM by JetBrains s.r.o
Windows 10 10.0
总体规划
参照 Google Study Jams 的活动进行
第一阶段
- L1 (3:38:59):1A (1:04:07) 、1B (1:13:59)、1C (1:20:53)
- L2 (5:07:02):2A (2:00:40)、2B (1:03:40)、2C (2:02:42)
- L3 (4:25:20):3A (2:15:36)、3B (2:09:44)
- 拓展 (2:06:44):Firebase 介绍(0:34:25) + Firebase 周末(1:32:19)
第二阶段
- 实践教程:制作结业 App、上载结业 App 到 Google Play
总时长 15:18:05,建议 40 - 45 小时的空闲时间学习 ,比如工作日每天一个小时,周末每天两个小时。
用户界面
1C为AndroidStudio的运用,暂不介绍
View
简单来说一个展示内容的长方形,有很多,像什么TestView,ImageView,Button等等
XML
描述Android外观的语言
注:下面的代码块中,注释的写法是错误的,但是为了方便,还是这么写了
例如
1 |
|
TextView
1 |
|
相关链接:TextView官方文档
ImageView
1 |
|
相关链接:ImageView官方文档
ViewGroup
ViewGroup是一个组,相当于是一个容器,他能管理底下的TextView,ImageView,Button等等
LinearLayout
线性布局,分为水平和竖直两种。一般竖直的比较常用
在下面的例子中会介绍线性布局的一些基础内容,还有布局权重
1 |
|
RelativeLayout
相对布局
顾名思义嘛,同样的,在例子中解释
1 |
|
注意:为了避免过于复杂,能够使用线性视图就使用线性视图
内边距与外边距
内边距(padding)呢就是View本身往外边扩展一层,如果View有背景颜色的话,背景颜色也会扩充
外边距(margin)就是把View隔开一下,隔开的地方是没有颜色的。外边距讲究一种隔开的情况,因此需要建立在ViewGroup里面
下面的图形就是内边距和外边距的例子(PS:Google的教程截图)
其中蓝色部分代表TextView,红色部分代表ViewGroup,左边是内边距,右边是外边距
其实感觉外边距的运用广泛一些,可能跟最近卡片风格流行有关吧
控制内边距(padding)的语句有
1 |
|
控制外边距的语句有
1 |
|
当然,这个是可以对各个View分别设置的,也可以对ViewGroup设置
ConstraintLayout
约束布局,能够非常快速地制作自适应 UI的一种布局。
※ 构建布局的步骤
选择视图类型
3 Button, 4 TextView
确定视图的位置
标题下面是一行按钮和一个TextView,然后是另一个标题和TextView,最后面是一个Button
设置视图的样式
内边距外边距什么的
用户交互
什么是Activity?
Activity是应用中用户可以操作的东西,有时候Activity就是应用中的一个屏幕。
比如在Google日历中,你点开时看到的就是一个Activity,让你查看日程的,而且这个Activity有一个侧边栏。Google日历的设置是另外一个Activity,让你设置一些东西的。
XML与Java在Android中有什么作用?
XML其实就是一个控制布局的,你可以在里边编辑你的UI
Java呢,就是控制程序的,实现各种各样的功能
XML如何调用Java?
在XML中,我们可以看到这样的语句:
1 |
|
其中android:onClick="submitOrders"
就是用来调用Java中的submitOrders
方法的
如果submitOrders
方法是这样的,那么当用户点击ORDER
按钮时,就会执行相对应的display方法和displayPrice方法
1 |
|
Debug
debug是消除bug的调试过程
系统日志
System Log: Android开发中常用logcat这个工具来分层次、分类型地显示不同等级的日志,方便程序员查找感兴趣的信息(错误信息或者更琐碎、更底层的信息)。
断点功能
为了快速找到错误指令,我们可以在希望设备暂停的指令位置插入一个断点,类似停车标志或路障。然后我们可以让调试器从第一条指令开始全速运行应用,运行至断点时,设备一定会暂停供我们进行检查。
使用方法
进行Debugging必须首先进入debug模式,Android的debug是用一只小瓢虫代替。
进入Debug模式之后,程序会和运行时一样重新编译这个App,此时手机会弹出提示,过一会等弄好了,提示也就自己消失了。
当在手机上操作时,在Android Studio的程序框里面就会显示信息,程序运行到断点就会停止,你可以看到一些数据的值
https://blog.csdn.net/Yaoobs/article/details/51296198 这里非常详细的介绍了Debug模式的使用方法
Nested ViewGroups
嵌套视图组,简单的说就是可以在一个布局里面嵌套多个布局。比如说在竖直的线性布局里面嵌套水平布局。
比如说下面这个图例:
- 图例首先是包含两个子视图一个竖直的线性布局(vertical LinearLayout)
- 第一个子视图是一个包含3个子视图的水平线性布局(horizontal LinearLayout)
- 第二个子视图是一个包含4个子视图的相对布局(RelativeLayout)
- 其中一个子视图还包含着一个子视图
Java字符串
1 |
|
其他的都是Java基础了,这里就不写了
控制对齐的属性
主要有两个:layout_gravity 以及 gravity
关于两者的区别:
- layout_gravity是对整个view的布局。view整体发生变化,但是view里面的内容改咋样还是咋样
- gravity是对view内的布局。view本身不变,变的是里面的东西
下图是一个很好的例子(来源:stackoverflow)
Java常量
在编程语言中,常量是指在整个程序中永远不会改变的值。在 Java 中,你可以使用关键字 final
来强制规定某个值不得被更改。例如:
1 |
|
Then if you wrote the following, you’d get an error:
1 |
|
注意,按照惯例,常量的名称全为大写形式。此外,单词之间用下划线连接,而不用空格或用骆驼拼写法。你可以将变量声明为常量,防止自己意外地更改了变量的值。
Android的构成
一个Android应用主要由资源文件和java代码构成。
- java代码负责处理一些事情,比如说按按钮之后还会干啥。。。
- 资源文件就是资源是代码使用的附加文件和静态内容,例如位图,布局定义,用户界面字符串,动画说明等。像一些什么布局文件(XML)啊,图片啊,音乐啊等等
相关资源在 这里
Java获取资源
在运用编译时,Android中有一个叫 aapt
的工具,这个工具会产生 R
类(就是一个简单的R.java文件)这个里面S目录里包含所有资源文件的ID
有两种方式使用ID:
在Java代码中用
R.资源类型.资源名
的形式调用。比如
R.string.hello
、R.drawable.photo
在XML文件中,用
@资源类型/资源名
的形式调用比如
@string/hello
、@drawable/photo
XML&JAVA
当你点击APP的时候,它会打开MainActivity
,此时什么都看不到,因为它在初始化。然后java中的onCreate
方法会被自动调用,activity会被创建。
在MainActivity
中的 onCreate
方法中有一句setContentView(R.layout.activity_main);
,这个对应着布局文件的资源ID。此时XML文件与Java文件就结合起来了 。Android程序就开始解析XML文件,识别XML文件中的各种布局还有View等等。
findViewById方法
这个方法会在XML文件中找到与传入ID相对应的View,然后在onCreate方法中处理它
例如下面这个:
1 |
|
这个语句的意思就是:我们传入一个id参数 R.id.order_summary_text_view
给 findViewById
方法。这个方法会遍历整个XML文件,直到找到id为 R.id.order_summary_text_view
的一个View,然后把这个View作为一个Java对象返回,然后把这个对象存放在名字为textView
的View类对象中。
注:如果ID为为 R.id.order_summary_text_view
的View是TextView
的话,它是无法使用TextView
类的方法的,因为findViewById
创建的是一个View
类型的对象,如果要用TextView
的特殊方法,就必须要使用以下的语句
1 |
|
向 Android 日志中写消息
注:这个是抄的Udacity上面的
本质上,你是在代码中写出类似于以下内容的 Java 语句:
1 |
|
第一个参数是日志语句所来自的类的名称。第二个参数是你想要显示的文字。
这里,我们使用了 Log.i(),表示“信息”级别的日志。其他级别的选项如下所示:
- e(String, String))(错误)
- w(String, String))(警告)
- i(String, String))(信息)
- d(String, String))(调试)
- v(String, String)) (详情)
它们对应的是不同的日志级别,当你运行应用时可以在下图所示位置进行设置:
当你设置日志级别时,它将显示该日志级别及更高级别的所有日志消息,所以“详情 (verbose)”日志级别显示的消息最多,而“错误”日志级别仅显示最严重的日志。
本地化
在Android中,你可以把所有的字符内容都储存在一个文件里面,每一个字符都有一个唯一的ID。这样的话,当你创建一个想要翻译这个APP是,就只需要改变这个文件里面的东西了,而不用跑到每个文件里更改。这个文件就是 res/values/strings.xml 文件。
看上去是下面这个样子的
1 |
|
其中第一个" "
里的就是id,后面的那个就是翻译的内容。
注:除了编写 string.xml 文件外,还可以使用 Android Studio 自身的 Translations Editor ,用起来很舒服。界面简单易懂,这里就不做介绍了。
调用这个文件的方式还是使用id进行调用,具体实现方式如下
- Java:
getString(R.string.name)
,得到字符串 - XML:
@string/name
Style & Theme
emmmmmm不想写了,有生之年再来填坑吧
到时候看官方文档得了
让你的应用连接到别的应用?
这里是运用 Intent 知识。Intent 是要求其他应用组件完成的一个信息,让我们的应用调用其他的应用,比如说连接到邮箱啊,相机啊日历啊等等。当然必须要确保有应用可以实现这个功能,要不然程序就会崩溃。
一般的 Intent 的里面有动作、数据URI、目录还有一些其他的消息等等
在 这里 有一个比较基础的介绍。
例如,你想链接到邮件,就可以在 Java 文件中写入下面类似的语句
1 |
|
多屏应用
Android资源文件
这里只说 res 文件夹里的东西。这个里面的一般都是控制程序外观的文件,包括一些XML文件、图片、还有其他媒体文件
- layout文件夹:这个里面存放着的就是一些 activity 的XML文件,定义了应用的布局
- mipmap文件夹:放应用图标的文件夹
- value 文件夹
- color.xml:定义了应用的颜色,反正是各种颜色
- dimensions.xml:包括不同的维度,高度,宽度等等,这一系列的
- strings.xml:放置字符的,让我们很容易的将应用翻译成其他语言,以及更改文字等等
- style.xml:包含主题和样式
AndroidManifest.xml
每个文件都必须有的一个文件。这个里面存放着应用的一些重要信息,比如java的包名、应用组件、Intent权限等等,以及所有的activity。
在这个里面,有一个 intent-filter 代码。具体长这样:
1 |
|
这个Intent是一种代码,用来要求其他应用执行操作;这个Intent-filter就厉害些,它相当于表明程序的入口。当点击应用图标时,Android将会发送一个Intent来启动我们的应用
当然在这里还可以更改每一个 Activity 的 lable 名称
1 |
|
使用Intent打开其他Activity
要使用Intent必须首先要创建一个Intent对象。然后传入两个值,一个 context 和一个 class 组件
1 |
|
其中 this
表示当前 Activity 的 context
然后呢,打开这个Activity
1 |
|
这样的话安卓就会启动我们的Activity,应用也就相对应的切换到 ActivityName 这个里面了。
当然这个与上文 让你的应用连接到别的应用 的Intent是有所不同的。因此Intent可以被分为两类:显式的 intent和 隐式的 intent 。
显式的 intent 是指知道执行那个操作的是谁,是十分具体的指向。就是 本块 中的示例
隐式的 intent 是指你不知道执行那个操作的是谁,只要是可以执行的都可以执行操作。到最后是谁执行还不清楚。 让你的应用连接到别的应用 中的就是隐式的 intent
事件监视器Listener
之前点击应用,完成一系列的操作,我们是在 XML 文件中进行的
1 |
|
下面介绍的这个可以不用 XML 文件执行(其实也就是探究 onclick 的本质)
首先了解一下 过程,在用户点击按钮时(用户输入事件),物理设备会检测到一些数据,然后通知Android发生了这个事件,安卓就会通过点击的位置信息定位到相对应的View上。事件监视器 就是来获取这些信息的,如果有特定的信息传入[比如点击],你知道这个事件发生了,就可以执行一些交互了,如 methodInJava
方法。
注:事件监视器在Java中其实是一个接口类。接口类中,方法全为抽象方法,也没有数据成员。
了解了过程,接下来就是要使用了,使用事件监视器可以按照以下3步走:
建立一个事件监视器类
在 Name.java文件:
1
2
3
4
5
6public class ClickTest implements View.OnClickListener {
@Override
public void onClick(View view) {
//做你想做的事情
}
}创建事件监视器的实例
1
ClickTest cickTest = new ClickTest();
连接你的事件监视器和对应的 View
1
2
3
4TextView about = (TextView) findViewById(R.id.about);
//首先要构造一个TextView的实例才能对它进行操作嘛
about.setOnClickListener(cickTest)
//连接你的事件监视器和对应的 View
这样的话,事件监视器就被使用了。当然,为了代码的可读性,还可以进行简化,该文件为 MainActivity.java
1 |
|
ArrayList[数组列表]
其实跟数组差不多。与数组的区别有以下几个方面:
- 数组列表的长度是可以改变的,数组列表中有多少元素其长度就是多少
- 数组列表是一种类型,数组的话,就比较基本
- 既然是一种类型,就可以调用各种各样的方法,你还可以继承它,覆盖一些方法等等(可玩性更高吧)
- 数组列表只能包含对象数据类型,向 int 这种就只能使用它们的包装类了
注:一般来说固定长度的使用数组,不固定长度的使用数组列表
ArrayList 的文档在:这里
在 Java 中,ArrayList 继承自AbstractList
1 |
|
下面列举一些常见的ArrayList使用方法
用途 | Java |
---|---|
创建数组列表 | ArrayList<Word> familyList = new ArrayList<Word>(); |
增加元素 | familyList.add(new Word(getString(R.string.phrase_Where), "minto wuksus")); |
去除元素 | familyList.remove(new Word(getString(R.string.phrase_Where), "minto wuksus")); |
查看元素 | familyList.get(0); 、familyList.get(1); 、familyList.get(2); |
查看元素数量 | familyList.size(); |
内存优化——ListView+ArrayAdapter
我们之前要创建一串列表时,肯定是这样的
1 |
|
但是随着数据的增大,一次显示的东西也曰来越多,所占用的内存也会越来越大,从而导致卡顿。这个是我们很不愿意看到了。为了解决这个问题,有以下的思路:
我们不一次显示所有的啊东西,只是显示屏幕上需要的东西。就是根据屏幕,创建所需要的视图。如果屏幕不需要,我们就不显示,把它抛弃了;如果需要就建立一个。这样成了一个可循环的了,屏幕上不需要的循环到屏幕上需要的。
做到这一步需要 一个可循环的ViewGroup + 一个配适器(ArrayAdapter) 。可循环的ViewGroup有:ListView、GridView、RecylcerView 等等
下面来以ListView为例说原理:
ArrayAdapter会处理数据,将他们调用或传输到ListView上,然后在上面显示
ListView可以看成是用户界面,ArrayAdapter是处理数据的。
ListView首先会向ArrayAdapter询问数据的长度。然后ListView会向ArrayAdapter调用一个方法(相当于一个请求),传入一个position,即用户正在查看的列表的位置。得到了请求之后,ArrayAdapter会查看数据结构,然后创造出一个View出来。当屏幕占满的时候,ListView就会停止请求。当屏幕滚动的时候,不在可见的View就会到一个 Scrap Views 的地方,然后ListView请求新的东西,传入 Scrap Views 的position,然后达到循环利用。
原理部分说明完毕。(其实最后还是有些不清楚。。。)
实现这个还是要三步走:
构造一个ListView对象
1
ListView listview = (ListView) findViewById(R.id.list)
这样才能对ListView进行操作嘛。当然,为了实现这个必须想要建立一个id为R.id.list的ListView在xml文件中,比如这个
1
2
3
4
5
6
7<ListView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/list"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/activity_vertical_margin"/>构造一个ArrayAdapter对象
1
ArrayAdapter arrayAdapter = new WordAdapter(this, arrayListName);
第一个参数为context,第二个参数为要传入其中的arrayList,数组啊等等
将ListView对象与ArrayAdapter对象联系起来
1
listView.setAdapter(arrayAdapter);
这样的话,一个列表就形成了。不过目前还有一个问题:ArrayList一次只能传入一个参数。ArrayAdapter只能生成一个TextView。如果想要产生一个比较复杂的列表(比如通讯录既有图片又有文字),采用刚刚那样就会报错。
如果需要解决就必须要自定义一些内容,下面介绍自定义的东西:
数据类
这是由于ArrayList造成的。ArrayList一次只能传入一个参数。如果一个列有两个项目(比如一个单词的翻译放在List就有中文和英文两项),就必须要自己创造一种类型。这种类型包含两个项目。比如说这里就可以创造一个 Word 类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class Word {
private String defaultWord;
private String anotherWord;
//获取default语言
public String getDefaultWord() {return defaultWord;}
//获取另外一种语言
public String getAnotherWord() {return anotherWord;}
//构造函数无返回值
Word(String defaultWord, String anotherWord) {
this.defaultWord = defaultWord;
this.anotherWord = anotherWord;
}
}配适器(Adapter)类
这回是要创建自己的Adapter,让它能够一次生成多个View出来(改写getView方法)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34public class WordAdapter extends ArrayAdapter<Word> {
//构造函数,不写这个不给用ArrayAdapter
WordAdapter(Activity context, ArrayList<Word> wordArrayList) {
//第二个参数设置,在 getView 方法中会得到,因此可以是任何数字
super(context, 0, wordArrayList);
}
//为 AdapterView 提供(构件)View
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// Check if the existing view is being reused, otherwise inflate the view
View listItemView = convertView;
if (listItemView == null) {
listItemView = LayoutInflater.from(getContext()).inflate(
R.layout.list_item, parent, false);
}
// 获取 restaurantsToTry[ArrayList类] 在position这个位置上的值
// 在这个例子中,这个值是一个 Word 类型的
Word currentAndroidFlavor = getItem(position);
// 建立一个指定id的 TextView 的实例,并且命名为 nameTextView
// 注意:findViewById方法在 View中有,在这里没有
//因此要通过 listItemView.findViewById调用
TextView nameTextView = (TextView) listItemView.findViewById(R.id.numbers_translate_TextView);
nameTextView.setText(currentAndroidFlavor.getDefaultWord());
TextView numberTextView = (TextView) listItemView.findViewById(R.id.numbers_micwok_TextView);
numberTextView.setText(currentAndroidFlavor.getMiwokWord());
return listItemView;
}
}
这样的话一些自定义也结束了。