常用控制元件的使用方法
TextView
理論
| 屬性 | 含義 | 用法 |
|---|---|---|
android:id | 唯一識別符號 | \ |
android:layout_width | 控制元件寬度 | match_parent:讓當前控制元件的大小和父布局的一樣; wrap_content:讓當前控制元件的大小正好適配里面的內容; |
android:layout_height | 空間高度 | 固定值:單位用dp能保證不同解析度效果下螢屏顯示效果盡量一致(與上欄用法相同) |
android:gravity | 文字對齊方式 | center、top、bottom、start、end(字面意思) |
android:textColor | 文字顏色 | #000000,六位16進制數字表示 |
android:textSize | 文字大小 | 單位用sp能在用戶調整文字大小的時候跟隨調整 |
android:text | 文字內容 | \ |
實踐
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="#00ff00"
android:textSize="24sp"
android:text="This is TextView"
/>

Button
理論
大部分與TextView相同,
Button中默認顯示出來的字母都是大寫,如果不需要,則加上:android:textAllCaps="false"
實踐
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button"
android:textAllCaps="false"
/>

點擊事件監聽器
函式式API方式
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.button.setOnClickListener {
}
}
}

補充:這里使用了ViewBinding
實作介面方式
class MainActivity : AppCompatActivity(),View.OnClickListener {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.button.setOnClickListener(this)
}
override fun onClick(v: View?) {
when(v?.id){
R.id.button -> {
}
}
}
}

EditText
理論
| 屬性 | 含義 | 用法 |
|---|---|---|
android:hint | 輸入提示字 | 提示性文本,當用戶需要輸入的時候,文字自動消失 |
android:maxLines | 最大行數 | 當輸入內容超過規定的最大行數時就向上滾動,不會繼續拉伸 |
實踐
<EditText
android:id="@+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Type something here"
android:maxLines="2"
/>

獲取輸入功能
class MainActivity : AppCompatActivity(),View.OnClickListener {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.button.setOnClickListener(this)
}
override fun onClick(v: View?) {
when(v?.id){
R.id.button -> {
val inputText = binding.editText.text.toString()
Toast.makeText(this, inputText, Toast.LENGTH_SHORT).show()
}
}
}
}

ImageView
由于現在主流手機螢屏的解析度都是xxhdpi,所以再res目錄下再新建一個drawable-xxhdpi的目錄用于存放圖片,
放入圖片
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/apple_pic"
/>

改變圖片
override fun onClick(v: View?) {
when(v?.id){
R.id.button -> {
binding.imageView.setImageResource(R.drawable.banana_pic)
}
}
}
這樣在點擊button后,蘋果就會變為香蕉

ProgressBar
圓形進度條
放入進度條
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>

消失和出現
實作點擊button后進度條消失,再點擊后進度條出現,
override fun onClick(v: View?) {
when(v?.id){
R.id.button -> {
if(binding.progressBar.visibility == View.VISIBLE){
binding.progressBar.visibility = View.GONE
}else{
binding.progressBar.visibility = View.VISIBLE
}
}
}
}
水平進度條
activity_main.xml
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleHorizontal"
android:max="100"
/>
MainActivity
override fun onClick(v: View?) {
when(v?.id){
R.id.button -> {
binding.progressBar.progress = binding.progressBar.progress + 10
}
}
}

每點擊一次,進度就會增加
1
/
10
1/10
1/10,
AlertDialog
override fun onClick(v: View?) {
when(v?.id){
R.id.button -> {
AlertDialog.Builder(this).apply {
setTitle("This is Dialog")
setMessage("Something important")
setCancelable(false)
setPositiveButton("OK"){dialog,which -> }
setNegativeButton("Cancel"){dialog,which -> }
show()
}
}
}
}

基本布局
LinearLayout
LinearLayout又稱作線性布局,這個布局會將它所包含的控制元件在線性方向上依次排列,
布局的方向
horizontal:水平方向vertical:豎直方向


控制元件的排列
- 因為當前布局的排列方向為
horizontal,所以layout_gravity只能指定垂直方向上的排列方向

控制元件大小比例


RelativeLayout
RelativeLayout又稱作相對布局,它可以通過相對定位的方式讓控制元件出現在布局的任何位置,
相對于父布局進行定位
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:text="button 1"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:text="button 2"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="button 3"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentBottom="true"
android:text="button 4"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:text="button 5"
/>
</RelativeLayout>

相對于控制元件進行定位
注意:當控制元件a要去參考控制元件b時,控制元件b要定義在控制元件a前面
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="button 3"
/>
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/button3"
android:layout_toLeftOf="@id/button3"
android:text="button 1"
/>
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/button3"
android:layout_toRightOf="@id/button3"
android:text="button 2"
/>
<Button
android:id="@+id/button4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/button3"
android:layout_toLeftOf="@id/button3"
android:text="button 4"
/>
<Button
android:id="@+id/button5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/button3"
android:layout_toRightOf="@id/button3"
android:text="button 5"
/>
</RelativeLayout>

FrameLayout
FrameLayout又稱作幀布局,這種布局沒有豐富的定位方式,所有的控制元件都會默認擺放在布局的左上角,

因為Button是在TextView之后添加的,所以按鈕在文字的上面,

自定義控制元件
引入布局
嘗試定義一個標題欄布局 title.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
tools:viewBindingIgnore="true"
android:layout_height="wrap_content">
<Button
android:id="@+id/titleBack"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="5dp"
android:background="@drawable/apple_pic"
android:text="Back"
android:textColor="#fff"
/>
<TextView
android:id="@+id/titleText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
android:gravity="center"
android:text="Title Text"
android:textColor="#000"
android:textSize="24sp"
/>
<Button
android:id="@+id/titleEdit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="5dp"
android:background="@drawable/banana_pic"
android:text="Edit"
android:textColor="#fff"
/>
</LinearLayout>

在activity_main.xml中使用

注意在MainActivity中將系統自帶的標題欄隱藏
supportActionBar?.hide()
這樣重復使用這個標題的時候就可以大大減少代碼量
創建自定義控制元件
新建TitleLayout繼承自LinearLayout,讓它成為自定義的標題欄控制元件
class TitleLayout(context: Context, attrs: AttributeSet) :
LinearLayout(context,attrs) {
init {
LayoutInflater.from(context).inflate(R.layout.title,this)
val titleBack = findViewById<Button>(R.id.titleBack)
val titleEdit = findViewById<Button>(R.id.titleEdit)
titleBack.setOnClickListener {
val activity = context as Activity
activity.finish()
}
titleEdit.setOnClickListener {
Toast.makeText(context, "You clicked Edit button", Toast.LENGTH_SHORT).show()
}
}
}
注意這里沒有使用 viewBinding
然后在布局檔案中添加這個自定義控制元件
<com.example.uiwidgettest.TitleLayout
android:id="@+id/titlelayout"
android:layout_width="match_parent"
android:layout_height="67dp"
/>

ListView
簡單用法
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val data = listOf("Apple","Banana","Orange","Watermelon","Pear",
"Grape","Pineapple","Strawberry","Cherry","Mango","Apple","Banana",
"Orange","Watermelon","Pear","Grape","Pineapple","Strawberry","Cherry","Mango")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val adapter = ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,data)
binding.listView.adapter = adapter
}
}
可以滾動的list

定制ListView界面
定義物體類fruit
class Fruit(val name: String,val imageId: Int) {
}
在layout目錄下新建一個fruit_item.xml用于自定義布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="60dp">
<ImageView
android:id="@+id/fruitImage"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center_vertical"
android:layout_marginLeft="10dp"
/>
<TextView
android:id="@+id/fruitName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="10dp"
/>
</LinearLayout>
創建繼承于ArrayAdapter的自定義配接器FruitAdapter,并將泛型指定為Fruit類
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.fruitImage)
val fruitName: TextView = view.findViewById(R.id.fruitName)
val fruit = getItem(position)
if (fruit != null){
fruitImage.setImageResource(fruit.imageId)
fruitName.text = fruit.name
}else{
Log.d("++++++++++","null")
}
return view
}
}
最后撰寫MainActivity中的代碼
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val fruitList = ArrayList<Fruit>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
initFruits()
val adapter = FruitAdapter(this, R.layout.fruit_item,fruitList)
binding.listView.adapter = adapter
}
private fun initFruits(){
repeat(2){
fruitList.add(Fruit("Apple",R.drawable.apple_pic))
fruitList.add(Fruit("Banana",R.drawable.banana_pic))
fruitList.add(Fruit("Orange",R.drawable.orange_pic))
fruitList.add(Fruit("Watermelon",R.drawable.watermelon_pic))
fruitList.add(Fruit("Pear",R.drawable.pear_pic))
fruitList.add(Fruit("Grape",R.drawable.grape_pic))
fruitList.add(Fruit("Pineapple",R.drawable.pineapple_pic))
fruitList.add(Fruit("Strawberry",R.drawable.strawberry_pic))
fruitList.add(Fruit("Cherry",R.drawable.cherry_pic))
fruitList.add(Fruit("Mango",R.drawable.mango_pic))
}
}
}

提升ListView的運行效率
當前ListView的運行效率是很低的,因為在FruitAdapter的getView()方法中,每次都將布局重新加載了一遍,當ListView快速滾動的時候,這就會成為性能的瓶頸,
使用convertView引數
getView()方法中還有?個convertView引數,這個引數用于將之前加載好的布局進行快取,以便之后進行重用,我們可以借助這個引數來進行性能優化,
val view: View
if (convertView == null){
view = LayoutInflater.from(context).inflate(resourceId,parent,false)
}else{
view = convertView
}
- 如果
convertView為null,則使用LayoutInflater去加載布局 - 如果不為
null,則直接對convertView進行重用,
使用內部類ViewHolder
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.fruitImage)
val fruitName: TextView = view.findViewById(R.id.fruitName)
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
}
}
內部類ViewHolder,用于對ImageView和TextView的控制元件實體進行快取
Kotlin中使用 inner class關鍵字來定義內部類,
- 當
convertView為null的時候,創建?個ViewHolder物件,并將控制元件的實體存放在ViewHolder里,然后呼叫View的setTag()方法,將ViewHolder物件存盤在View中, - 當
convertView不為null的時候,則呼叫View的getTag()方法,把ViewHolder重新取出,這樣所有控制元件的實體都快取在了ViewHolder里,就沒有必要每次都通過findViewById()方法來獲取控制元件實體了,
ListView的點擊事件
使用setOnItemClickListener()方法為ListView注冊了一個監聽器,當用戶點擊了ListView中的任何?個子項時,就會回呼到Lambda運算式中,這里我們可以通過position引數判斷用戶點擊的是哪一個子項,然后獲取到相應的水果,
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val fruitList = ArrayList<Fruit>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
initFruits()
val adapter = FruitAdapter(this, R.layout.fruit_item, fruitList)
binding.listView.adapter = adapter
binding.listView.setOnItemClickListener{ parent, view, position, id ->
val fruit = fruitList[position]
Toast.makeText(this, fruit.name, Toast.LENGTH_SHORT).show()
}
}
private fun initFruits(){
repeat(2){
fruitList.add(Fruit("Apple",R.drawable.apple_pic))
fruitList.add(Fruit("Banana",R.drawable.banana_pic))
fruitList.add(Fruit("Orange",R.drawable.orange_pic))
fruitList.add(Fruit("Watermelon", R.drawable.watermelon_pic))
fruitList.add(Fruit("Pear",R.drawable.pear_pic))
fruitList.add(Fruit("Grape",R.drawable.grape_pic))
fruitList.add(Fruit("Pineapple",R.drawable.pineapple_pic))
fruitList.add(Fruit("Strawberry",R.drawable.strawberry_pic))
fruitList.add(Fruit("Cherry",R.drawable.cherry_pic))
fruitList.add(Fruit("Mango",R.drawable.mango_pic))
}
}
}

Kotlin允許將沒有用到的引數使用下劃線來替代,這種寫法也是合法且更加推薦
binding.listView.setOnItemClickListener{ _, _, position, _ ->
val fruit = fruitList[position]
Toast.makeText(this, fruit.name, Toast.LENGTH_SHORT).show()
}
RecyclerView
先在app/build.gradle將RecyclerView庫匯入到專案中
implementation 'androidx.recyclerview:recyclerview:1.0.0'
基本用法
還是之前的fruit、fruit_item.xml代碼
在activity_main.xml中加入
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
新建FruitAdapter類為RecyclerView的配接器,讓這個配接器繼承自RecyclerView.Adapter,并將泛型指定為FruitAdapter.ViewHolder,其中,ViewHolder是我們在FruitAdapter中定義的?個內部類
class FruitAdapter(val fruitList: List<Fruit>) :
RecyclerView.Adapter<FruitAdapter.ViewHolder>(){
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view){
val fruitImage: ImageView = view.findViewById(R.id.fruitImage)
val fruitName: TextView = view.findViewById(R.id.fruitName)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.fruit_item,parent,false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val fruit = fruitList[position]
holder.fruitImage.setImageResource(fruit.imageId)
holder.fruitName.text = fruit.name
}
override fun getItemCount() = fruitList.size
}
這是RecyclerView配接器標準的寫法
首先定義了?個內部類ViewHolder,它要繼承自RecyclerView.ViewHolder,然后ViewHolder的主建構式中要傳入?個View引數,這個引數通常就是RecyclerView子項的最外層布局,那么就可以通過findViewById()方法來獲取布局中ImageView和TextView的實體了,
FruitAdapter中也有?個主建構式,它?于把要展示的資料源傳進 來,后續的操作都將在這個資料源的基礎上進行,
onCreateViewHolder()方法是用于創建ViewHolder實體的,我們在這個方法中將fruit_item布局加載進來,然后創建?個ViewHolder實體,并把加載出來的布局傳入建構式當中,最后將ViewHolder的實體回傳onBindViewHolder()方法用于對RecyclerView子項的資料進行賦值,會在每個子項被滾動到螢屏內的時候執行,這里我們通過position引數得到當前項的Fruit實體,然后再將資料設定到ViewHolder的ImageView和TextView當中即可,getItemCount()方法用于告訴RecyclerView?共有多少子項,直接回傳資料源的長度就可以了,
上面的準備作業就緒后,就可以在MainActivity中使用RecyclerView了
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val fruitList = ArrayList<Fruit>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
initFruits()
val layoutManager = LinearLayoutManager(this)
binding.recyclerView.layoutManager = layoutManager
val adapter = FruitAdapter(fruitList)
binding.recyclerView.adapter = adapter
}
private fun initFruits(){
repeat(2){
fruitList.add(Fruit("Apple",R.drawable.apple_pic))
fruitList.add(Fruit("Banana",R.drawable.banana_pic))
fruitList.add(Fruit("Orange",R.drawable.orange_pic))
fruitList.add(Fruit("Watermelon", R.drawable.watermelon_pic))
fruitList.add(Fruit("Pear",R.drawable.pear_pic))
fruitList.add(Fruit("Grape",R.drawable.grape_pic))
fruitList.add(Fruit("Pineapple",R.drawable.pineapple_pic))
fruitList.add(Fruit("Strawberry",R.drawable.strawberry_pic))
fruitList.add(Fruit("Cherry",R.drawable.cherry_pic))
fruitList.add(Fruit("Mango",R.drawable.mango_pic))
}
}
}
LayoutManager用于指定RecyclerView的布局方式,這里使用的LinearLayoutManager是線性布局的意思,可以實作和ListView類似的效果,

橫向滾動
ListView中無法實作橫向滾動,而RecyclerView中就可以輕易做到
首先要調整一下fruit_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="80dp"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/fruitImage"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="10dp"
/>
<TextView
android:id="@+id/fruitName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="10dp"
/>
</LinearLayout>
在MainActivity的layoutManager后添加一行代碼
layoutManager.orientation = LinearLayoutManager.HORIZONTAL
呼叫LinearLayoutManager的setOrientation()方法設定布局的排列方向,默認是縱向排列的,傳入LinearLayoutManager.HORIZONTAL表示讓布局橫行排列,這樣RecyclerView就可以橫向滾動了,

瀑布流布局
首先要調整一下fruit_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp">
<ImageView
android:id="@+id/fruitImage"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="10dp"
/>
<TextView
android:id="@+id/fruitName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:layout_marginTop="10dp"
/>
</LinearLayout>
然后修改MainActivity
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val fruitList = ArrayList<Fruit>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
initFruits()
val layoutManager = StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL)
binding.recyclerView.layoutManager = layoutManager
val adapter = FruitAdapter(fruitList)
binding.recyclerView.adapter = adapter
}
private fun initFruits(){
repeat(2){
fruitList.add(Fruit(getRandomLengthString("Apple"),R.drawable.apple_pic))
fruitList.add(Fruit(getRandomLengthString("Banana"),R.drawable.banana_pic))
fruitList.add(Fruit(getRandomLengthString("Orange"),R.drawable.orange_pic))
fruitList.add(Fruit(getRandomLengthString("Watermelon"),R.drawable.watermelon_pic))
fruitList.add(Fruit(getRandomLengthString("Pear"),R.drawable.pear_pic))
fruitList.add(Fruit(getRandomLengthString("Grape"),R.drawable.grape_pic))
fruitList.add(Fruit(getRandomLengthString("Pineapple"),R.drawable.pineapple_pic))
fruitList.add(Fruit(getRandomLengthString("Strawberry"),R.drawable.strawberry_pic))
fruitList.add(Fruit(getRandomLengthString("Cherry"),R.drawable.cherry_pic))
fruitList.add(Fruit(getRandomLengthString("Mango"),R.drawable.mango_pic))
}
}
private fun getRandomLengthString(str: String): String{
val n = (1..20).random()
val builder = StringBuilder()
repeat(n){
builder.append(str)
}
return builder.toString()
}
}
在onCreate()方法中創建?個StaggeredGridLayoutManager的實體,
StaggeredGridLayoutManager的建構式接收兩個引數:
第一個引數用于指定布局的列數,傳入3表示會把布局分為3列;
第二個引數用于指定布局的排列方向,傳入StaggeredGridLayoutManager.VERTICAL表示會讓布局縱向排列,
最后把創建好的實體設定到RecyclerView當中就可以了

設定點擊事件
只需修改FruitAdapter中inCreateViewHolder方法的代碼
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.fruit_item,parent,false)
val viewHolder = ViewHolder(view)
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
}

撰寫界面
制作9-Patch圖片
普通圖片作為背景時,就會被均勻拉伸,如下圖所示,效果很不好,

9-Patch圖片是一種被特殊處理過的png圖片,能夠指定哪些區域可以被拉伸、哪些區域不可以,
插入聊天框圖片,右擊選擇下圖中藍色框的Creat 9-Patch file

進入界面后可以在圖片的4個邊框繪制?個個的小黑點
- 在上邊框和左邊框繪制的部分表示當圖片需要拉伸時就拉伸黑點標記的區域
- 在下邊框和右邊框繪制的部分表示內容允許被放置的區域,
使用滑鼠在圖片的邊緣拖動就可以進行繪制了,按住Shift鍵拖動可以進行擦除,

將新圖片設為背景時:

撰寫聊天界面
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#d8e0e8"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<EditText
android:id="@+id/inputText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="Type someThing here"
android:maxLines="2"
/>
<Button
android:id="@+id/send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Send"/>
</LinearLayout>
</LinearLayout>
物體類Msg
class Msg(val content: String, val type: Int) {
companion object{
const val TYPE_RECEIVED = 0
const val TYPE_SENT = 1
}
}
msg_left_item.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:background="@drawable/message_left">
<TextView
android:id="@+id/leftMsg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="10dp"
android:textColor="#fff"
/>
</LinearLayout>
</FrameLayout>
msg_right_item.xml
與msg_left_item.xml左右對稱,不再贅述
配接器類MsgAdapter
class MsgAdapter(val msgList: List<Msg>) : RecyclerView.Adapter<RecyclerView.ViewHolder>(){
inner class LeftViewHolder(view: View) : RecyclerView.ViewHolder(view){
val leftMsg: TextView = view.findViewById(R.id.leftMsg)
}
inner class RightViewHolder(view: View) : RecyclerView.ViewHolder(view){
val rightMsg: TextView = view.findViewById(R.id.rightMsg)
}
override fun getItemViewType(position: Int): Int {
val msg = msgList[position]
return msg.type
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = if (viewType == Msg.TYPE_RECEIVED){
val view = LayoutInflater.from(parent.context).inflate(R.layout.msg_left_item,parent,false)
LeftViewHolder(view)
}else{
val view = LayoutInflater.from(parent.context).inflate(R.layout.msg_right_item,parent,false)
RightViewHolder(view)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val msg = msgList[position]
when (holder){
is LeftViewHolder -> holder.leftMsg.text = msg.content
is RightViewHolder -> holder.rightMsg.text = msg.content
else -> throw IllegalArgumentException()
}
}
override fun getItemCount() = msgList.size
}
根據不同的viewType創建不同的界面,
首先先定義了LeftViewHolder和RightViewHolder這兩個ViewHolder,分別用于快取msg_left_item.xml和msg_right_item.xml布局中的控制元件,
然后要重寫getItemViewType()方法,并在這個方法中回傳當前position對應的訊息型別,
MainActivity
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val msgList = ArrayList<Msg>()
private var adapter: MsgAdapter ?= null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
initMsg()
val layoutManager = LinearLayoutManager(this)
binding.recyclerView.layoutManager = layoutManager
adapter = MsgAdapter(msgList)
binding.recyclerView.adapter = adapter
binding.send.setOnClickListener {
val content = binding.inputText.text.toString()
if (content.isNotEmpty()){
val msg = Msg(content, Msg.TYPE_SENT)
msgList.add(msg)
adapter?.notifyItemInserted(msgList.size - 1)
binding.recyclerView.scrollToPosition(msgList.size - 1)
binding.inputText.setText("")
}
}
}
private fun initMsg(){
val msg1 = Msg("Hello guy.",Msg.TYPE_RECEIVED)
msgList.add(msg1)
val msg2 = Msg("Hello. Who is that?",Msg.TYPE_SENT)
msgList.add(msg2)
val msg3 = Msg("This is Tom.",Msg.TYPE_RECEIVED)
msgList.add(msg3)
}
}
在發送按鈕的點擊事件里獲取EditText中的內容,如果內容不為空字串,則創建?個新的Msg物件并添加到msgList串列中去,之后又呼叫了配接器的notifyItemInserted()方法,用于通知串列有新的資料插入,這樣新增的?條訊息才能夠在RecyclerView中顯示出來,接著呼叫RecyclerView的scrollToPosition()方法將顯示的資料定位到最后一行,以保證一定可以看得到最后發出的?條訊息,最后呼叫EditText的setText()方法將輸入的內容清空,

這個實踐真的太妙了
Kotlin小課堂
延遲初始化
參考傳送門
當你對一個全域變數使用了lateinit關鍵字時,請?定要確保它在被任何地方呼叫之前已經完成了初始化作業,否則Kotlin將無法保證程式的安全性,

對結果進行取反,如果還沒有初始化,那么就立即對adapter變數進行初始化,否則什么都不用做,
密封類

在這里不得不再撰寫?個else條件,否則Kotlin編譯器會認為這里缺少條件分支,代碼無法編譯通過,另外,如果現在新增了?個Unknown類并實作Result介面,用于表示未知的執行結果,但是忘記在getResultMsg()方法中添加相應的條件分支,編譯器在這種情況下是不會提醒的,而是在運行的時候進入else條件里面,從而拋出例外并導致程式崩潰,
通過密封類就可以解決這個問題
關鍵字sealed cladd

在when陳述句中傳入一個密封類變數作為條件時,Kotlin編譯器會自動檢查該密封類有哪些子類,并強制要求你將每一個子類所對應的條件全部處理,這樣就可以保證,即使沒有撰寫else條件,也不可能會出現漏寫條件分支的情況,而如果我們現在新增一個Unknown類,并也讓它繼承自Result,此時
getResultMsg()方法就一定會報錯,必須增加一個Unknown的條件分支才能讓代碼編譯通過,
密封類及其所有子類只能定義在同一個檔案的頂層位置,不能嵌套在其他類中,這是被密封類底層的實作機制所限制的,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/382018.html
標籤:其他
上一篇:如何訪問手機瀏覽器cookies
下一篇:UI繪制流程之測量流程
