软件也要拼脸蛋——UI开发的点点滴滴(二)

《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_weightandroid:layout_width两个属性的配合使用:

  1. 系统先将LinearLayout下所有控件的layout_weight值相加,得到一个总值,然后每个控件大小为该控件的layout_weight值除以刚才算出的总值。
  2. 若有若干个控件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的加载效率:

  1. 通过判断convertView来决定是否重新加载布局
  2. 使用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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值