《Android第一行代码(第三版)》学习记录
1. 常见控件的使用方法
1.1 TextView
TextView主要用于在界面上显示一段文本信息:
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/text_view"
android:text="This is a TextView!"/>
</LinearLayout>
具体的相关属性参考: TextView | Android Developers
1.2 Button
与用户进行交互的一个重要控件:
<LinearLayout android:orientation="vertical"
android:layout_height="match_parent"
android:layout_width="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/button"
android:text="Button"/>
</LinearLayout>
具体的相关属性参考: Button | Android Developers
接下来在Activity.kt中为该按钮绑定监听事件:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_button)
val button = findViewById<Button>(R.id.button)
button.setOnClickListener {
Toast.makeText(this, "You click the button!", Toast.LENGTH_SHORT).show()
}
}
1.3 EditText
用户在该控件中输入和编辑文本:
<LinearLayout android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/edit_text"
android:hint="Type something here"/>
</LinearLayout>
具体的相关属性参考: EditText | Android Developers
该控件同样可以在Activity.kt中获取,结合Button或其他控件来执行相关操作:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_edit_text)
val editText = findViewById<EditText>(R.id.edit_text)
val buttonSend = findViewById<Button>(R.id.button_send)
buttonSend.setOnClickListener {
var inputText = editText.text.toString()
Toast.makeText(this, inputText, Toast.LENGTH_SHORT).show()
}
}
1.4 ImageView
ImageView用于在界面显示图片,图片通常放在drawable目录:
<LinearLayout android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/image_view"
android:src="@drawable/audio"/>
</LinearLayout>
具体的相关属性参考: ImageView | Android Developers
可以利用按钮的点击事件对ImageView中的图片进行动态替换:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_image_view)
val buttonChange = findViewById<Button>(R.id.button_change)
val imageView = findViewById<ImageView>(R.id.image_view)
buttonChange.setOnClickListener {
imageView.setImageResource(R.drawable.image2)
}
}
1.5 ProcessBar
PrograssBar是显示一个进度条,表示正在加载数据:
<LinearLayout android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android">
<ProgressBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/process_bar"
<!-- style决定了进度条为水平的 -->
style="?android:attr/progressBarStyleHorizontal"/>
</LinearLayout>
具体的相关属性参考: ProgressBar | Android Developers
可以通过按钮的点击事件或其他事件来对进度条的进度进行更新,或者显示/隐藏进度条:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_process_bar)
val buttonView = findViewById<Button>(R.id.button_view)
val buttonProgress = findViewById<Button>(R.id.button_progress)
val progressBar = findViewById<ProgressBar>(R.id.process_bar)
buttonView.setOnClickListener {
if (progressBar.visibility == View.VISIBLE) {
//调用了setVisibility
progressBar.visibility = View.GONE
} else {
progressBar.visibility = View.VISIBLE
}
}
buttonProgress.setOnClickListener {
progressBar.progress += 10
}
}
1.6 AlertDiaglog
AlertDialog在当前界面显示一个置顶于所有界面之上的对话框,屏蔽掉其他控件的交互:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_alert_diaglog)
val buttonAlert = findViewById<Button>(R.id.button_alert)
buttonAlert.setOnClickListener {
AlertDialog.Builder(this).apply {
setTitle("This is Dialog")
setMessage("Something important")
setCancelable(false)
setPositiveButton("OK") { dialog, which -> "yes" }
setNegativeButton("Cancel") { dialog, which -> "no" }
show()
}
}
}
具体的相关属性参考: AlertDialog | Android Developers
2.详解4种布局
布局是一种可以用于放置很多控件的容器,可以按照一定规律调整内部控件的为止,从而编写出精美的界面。当然,布局内部也可以放置布局,通过多层布局的嵌套,我们就能完成一些复杂界面的实现。
2.1 线性布局(LinearLayout)
线性布局会将它所包含的所有控件在线性方向上依次排列。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:android="http://schemas.android.com/apk/res/android">
<EditText
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:id="@+id/input_message"
android:hint="Type Something"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/button_send"
android:text="Send"/>
</LinearLayout>
需要注意的为控件的android:layout_weight
和android:layout_width
两个属性的配合使用:
- 系统先将LinearLayout下所有控件的layout_weight值相加,得到一个总值,然后每个控件大小为该控件的layout_weight值除以刚才算出的总值。
- 若有若干个控件width不为0dp,则除去这些控件所占长度,剩下长度由含有weight属性的组件按比例分配
具体的相关属性参考: Linear Layout | Android Developers
2.2 相对布局(RelativeLayout)
相对布局通过相对定位的方式让控件出现在布局的任何位置。控件不仅可以相对于父布局进行定位,也可以相对于其他控件进行定位。因此相对布局中对应的属性非常多,但都遵循一定的规律:
<RelativeLayout android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/button1"
android:text="Button 1"
android:layout_centerInParent="true"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/button2"
android:text="Button 2"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/button1"
android:layout_toRightOf="@+id/button1"
android:text="Button 3"/>
</RelativeLayout>
具体的相关属性参考: Relative Layout | Android Developers
2.3 帧布局(FrameLayout)
帧布局,所有空间都会默认摆放在布局左上角,可能存在控件重叠的情况。除了默认效果之外,可以使用layout_gravity属性来指定控件在布局中的对齐方式。总体来讲,应用场景很少。
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:text="This is TextView" />
<Button
android:id="@+id/btn_001"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:text="Button" />
</FrameLayout>
具体的相关属性参考: FrameLayout | Android Developers
3. 创建自定义控件
所有控件直接或者间接继承自View的,所有布局直接或者间接继承自ViewGroup的。ViewGroup是特殊的View,包含很多子View和ViewGroup是用于放置空间和布局的容器。View是Android中最基础的UI组件,我们用的控件就是在View基础上添加各自的特有功能。
3.1 引入布局
自定义一个标题栏并让所有Activity引用,防止代码大量重复。Layout目录下建立标题栏title.xml布局:
<LinearLayout android:orientation="horizontal"
android:layout_height="wrap_content"
android:layout_width="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/button_back"
android:layout_gravity="center"
android:layout_margin="5dp"
android:text="Back"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
android:gravity="center"
android:textSize="24sp"
android:text="Title Text"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/button_edit"
android:layout_gravity="center"
android:layout_margin="5dp"
android:text="Edit"/>
</LinearLayout>
在需要的activity对应的布局中引入:
<LinearLayout android:orientation="horizontal"
android:layout_height="wrap_content"
android:layout_width="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android">
<include layout="@layout/title"/>
</LinearLayout>
在activity.kt中对系统自带的标题栏进行隐藏:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (supportActionBar != null) {
supportActionBar?.hide()
}
}
4. ListView
ListView允许用户通过手指上下滑动的方式将屏幕外的数据滚动到屏幕内,同时屏幕上原有的数据则会滚出屏幕。
4.1 ListView的简单用法
在对应的布局文件中添加ListView组件:
<LinearLayout android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android">
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/lv_01"/>
</LinearLayout>
在对应的kotlin代码文件中为ListView添加值:
class ListViewActivity : AppCompatActivity() {
private val data = listOf(
"Apple", "Banana", "Orange", "WaterMelon", "Pear", "Grape", "Cherry","Mango", "Apple", "Banana", "Orange", "WaterMelon", "Pear", "Grape", "Cherry", "Mango"
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_list_view)
val adapter = ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, data)
val lv01 = findViewById<ListView>(R.id.lv_01)
lv01.adapter = adapter
}
}
4.2 定制ListView的界面
只能显示一段文本的ListView过于单调,我们可以通过自定义类,并修改Adapter的类型为自定义类来让ListView显示更为丰富的内容:
class Fruit(val name: String, val imageid: Int)
为ListView的子项创建一个布局:
<LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:android="http://schemas.android.com/apk/res/android">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/img_fruit"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/tv_fruit_name"
android:layout_gravity="center_vertical"
android:layout_marginLeft="10dp"/>
</LinearLayout>
创建自定义适配器:
class FruitAdapter(activity: Activity, val resourceId: Int, data: List<Fruit>) :
ArrayAdapter<Fruit>(activity, resourceId, data) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val view = LayoutInflater.from(context).inflate(resourceId, parent, false)
val fruitImage: ImageView = view.findViewById(R.id.img_fruit)
val fruitName: TextView = view.findViewById(R.id.tv_fruit_name)
val fruit = getItem(position)
if (fruit != null) {
fruitImage.setImageResource(fruit.imageid)
fruitName.text = fruit.name
}
return view
}
}
在Activity对应的代码中修改:
class ListViewActivity : AppCompatActivity() {
private val data = ArrayList<Fruit>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_list_view)
initFruits()
val adapter = FruitAdapter(this, R.layout.listview_fruit, data)
val lv01 = findViewById<ListView>(R.id.lv_01)
lv01.adapter = adapter
}
fun initFruits() {
//repeat函数将水果数据重复两遍
repeat(2) {
//构造方法中将水果名字和水果id传入,将创建好的对象添加到水果列表中。
data.add(Fruit("Apple", R.drawable.img_apple))
data.add(Fruit("Banana", R.drawable.img_banana))
data.add(Fruit("Orange", R.drawable.img_orange))
data.add(Fruit("WaterMelon", R.drawable.img_watermelon))
data.add(Fruit("Pear", R.drawable.img_pear))
data.add(Fruit("PineApple", R.drawable.img_pineapple))
data.add(Fruit("StrawBerry", R.drawable.img_strawBerry))
data.add(Fruit("Cherry", R.drawable.img_cherry))
}
}
}
4.3 提升ListView的效率
ListView快速滑动时,性能成为一个瓶颈,我们借助两种方案来提升ListView的加载效率:
- 通过判断convertView来决定是否重新加载布局
- 使用ViewHolder来避免多次对findViewById()的调用
class FruitAdapter(activity: Activity, val resourceId: Int, data: List<Fruit>) :
ArrayAdapter<Fruit>(activity, resourceId, data) {
inner class ViewHolder(val fruitImage: ImageView, val fruitName: TextView)
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val view: View
val viewHolder: ViewHolder
if (convertView == null) {
view = LayoutInflater.from(context).inflate(resourceId, parent, false)
val fruitImage: ImageView = view.findViewById(R.id.img_fruit)
val fruitName: TextView = view.findViewById(R.id.tv_fruit_name)
viewHolder = ViewHolder(fruitImage, fruitName)
view.tag = viewHolder
} else {
view = convertView
viewHolder = view.tag as ViewHolder
}
val fruit = getItem(position)
if (fruit != null) {
viewHolder.fruitImage.setImageResource(fruit.imageid)
viewHolder.fruitName.text = fruit.name
}
return view
}
}
4.4 ListView的点击事件
使用setOnItemClickListener()为ListView注册一个监听器,当点击子项时,调用Lambda表达式,通过position确定哪一个子项,并触发对应的逻辑操作:
class ListViewActivity : AppCompatActivity() {
private val data = listOf(
"Apple", "Banana", "Orange", "WaterMelon", "Pear", "Grape", "Cherry","Mango", "Apple", "Banana", "Orange", "WaterMelon", "Pear", "Grape", "Cherry", "Mango"
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_list_view)
val adapter = ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, data)
val lv01 = findViewById<ListView>(R.id.lv_01)
lv01.adapter = adapter
lv01.setOnItemClickListener { adapterView, view, i, l ->
val fruit = data[i]
Toast.makeText(this, data[i], Toast.LENGTH_SHORT).show()
}
}
}
具体的相关属性参考: ListView | Android Developers
5. RecyclerView
ListView的运行效率和扩展性(只能纵向滚动)仍然有待提高,因此我们介绍一下RecyclerView。
5.1 RecyclerView的简单用法
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android">
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="0dp"
android:id="@+id/rv_fruit"
android:layout_weight="1"/>
</LinearLayout>
创建Fruit数据对象类,并为该对象创建FruitAdapter:
class Fruit(val name: String, val imageId: Int)
class FruitAdapter(private val fruitList: List<Fruit>) : RecyclerView.Adapter<FruitAdapter.ViewHolder>() {
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val fruitName: TextView = view.findViewById(R.id.tv_fruit_name)
val fruitImage: ImageView = view.findViewById(R.id.img_fruit)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_fruit, parent, false)
return ViewHolder(view)
}
override fun getItemCount() = fruitList.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val fruit = fruitList[position]
holder.fruitImage.setImageResource(fruit.imageId)
holder.fruitName.text = fruit.name
}
}
最后修改Activity的代码:
class RecyclerViewActivity : AppCompatActivity() {
private val fruitList = ArrayList<Fruit>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_recycler_view)
val recyclerView = findViewById<RecyclerView>(R.id.rv_fruit)
initFruits() // 初始化水果数据
val layoutManager = LinearLayoutManager(this)
recyclerView.layoutManager = layoutManager
val adapter = FruitAdapter(fruitList)
recyclerView.adapter = adapter
}
private fun initFruits() {
repeat(2) {
fruitList.add(Fruit("Apple", R.drawable.pic_apple))
fruitList.add(Fruit("Banana", R.drawable.pic_banana))
fruitList.add(Fruit("Orange", R.drawable.pic_orange))
fruitList.add(Fruit("Watermelon", R.drawable.pic_watermelon))
fruitList.add(Fruit("Pear", R.drawable.pic_pear))
fruitList.add(Fruit("Grape", R.drawable.pic_grape))
fruitList.add(Fruit("Pineapple", R.drawable.pic_pineapple))
fruitList.add(Fruit("Strawberry", R.drawable.pic_strawberry))
fruitList.add(Fruit("Cherry", R.drawable.pic_cherry))
fruitList.add(Fruit("Mango", R.drawable.pic_mango))
}
}
}
5.2 实现横向滚动和瀑布流布局
横向滚动的核心是在Activity的代码中加入:
layoutmanager.orientation = LinearLayoutManager.HORIZONTAL
相较于ListView的自身管理,RecyclerView将其交给了LayoutManager
瀑布流布局则需删除横向滚动的修改,并进行如下修改:
class RecyclerViewActivity : AppCompatActivity() {
private val fruitList = ArrayList<Fruit>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_recycler_view)
val recyclerView = findViewById<RecyclerView>(R.id.rv_fruit)
initFruits() // 初始化水果数据
val layoutManager = LinearLayoutManager(this)
recyclerView.layoutManager = layoutManager
val adapter = FruitAdapter(fruitList)
recyclerView.adapter = adapter
}
private fun initFruits() {
//repeat函数将水果数据重复两遍
repeat(2) {
//构造方法中将水果名字和水果id传入,将创建好的对象添加到水果列表中。
fruitList.add(Fruit(getRandomLengthString("Apple"), R.drawable.pic_apple))
fruitList.add(Fruit(getRandomLengthString("Banana"), R.drawable.pic_banana))
fruitList.add(Fruit(getRandomLengthString("Orange"), R.drawable.pic_orange))
fruitList.add(Fruit(getRandomLengthString("WaterMelon"), R.drawable.pic_watermelon))
fruitList.add(Fruit(getRandomLengthString("Pear"), R.drawable.pic_pear))
fruitList.add(Fruit(getRandomLengthString("PineApple"), R.drawable.pic_pineapple))
fruitList.add(Fruit(getRandomLengthString("StrawBerry"), R.drawable.pic_strawberry))
fruitList.add(Fruit(getRandomLengthString("Cherry"), R.drawable.pic_cherry))
}
}
private fun getRandomLengthString(str:String):String{
val n = (1..20).random()
val builder = StringBuilder()
repeat(n){
builder.append(str)
}
return builder.toString()
}
}
注:需修改对应的layout文件使其达到完美的显示效果
5.3 RecyclerView的点击事件
RecyclerView没有setOnItemClickListener这样的注册监听器方法,而是需要我们给子项具体的View去注册点击事件。修改适配器FruitAdapter的onCreateViewHolder方法。
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_fruit, parent, false)
val viewholder = ViewHolder(view)
//为最外层的布局和ImageView都注册了点击事件,先获取用户点击的position,然后通过position获取相应的Fruit实例,最后Toast
viewholder.itemView.setOnClickListener {
val position = viewholder.adapterPosition
val fruit = fruitList[position]
Toast.makeText(parent.context, "you clicked view ${fruit.name}", Toast.LENGTH_SHORT)
.show()
}
viewholder.fruitImage.setOnClickListener{
val position = viewholder.adapterPosition
val fruit = fruitList[position]
Toast.makeText(parent.context, "you clicked image ${fruit.name}", Toast.LENGTH_SHORT)
.show()
}
return viewholder
}
具体的相关属性参考: Recyclerview | Android Developers