第四章 UI点滴
-
常用属性
属性 说明 android:textAllCaps="false" 是否全大写 android:gravity="" 控件内部的对齐方式(TextView中文字或者ViewGroup中View对齐方式) android:layout_gravity="start" View在ViewGroup中的对齐方式 android:layout_weight="1" LinearLayout属性,权重,如果需要按权重分配大小,权重方向上大小设置为0dp。View权重设置为1并且权重方向上其它控件大小自适应, -
自定义控件
-
创建布局文件
<?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:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_vertical" android:orientation="horizontal"> <ImageView android:id="@+id/imageView3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="12dp" app:srcCompat="?attr/homeAsUpIndicator" /> <TextView android:id="@+id/textView2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:gravity="center" android:text="TextView" android:textSize="18sp" /> <ImageView android:id="@+id/imageView2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="12dp" app:srcCompat="@drawable/ic_search_black_24dp" /> </LinearLayout>
-
创建TitleLayout
class TitleLayout(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) { lateinit var backBtn: ImageView lateinit var searchBtn: ImageView lateinit var titleView: TextView init { val inflate = LayoutInflater.from(context).inflate(R.layout.linear, this) backBtn = inflate.findViewById(R.id.back) backBtn.setOnClickListener { val activity = context as Activity activity.finish() } searchBtn = inflate.findViewById(R.id.search) titleView = inflate.findViewById(R.id.title) } }
Kotlin小知识
类型转换
as
-
在布局中使用
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout 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" android:layout_height="match_parent" tools:context=".chapter4.Chapter4Activity"> <com.youngly.firstlineofcode.chapter4.TitleLayout android:id="@+id/titleView" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
-
代码中使用
class Chapter4Activity : AppCompatActivity() { lateinit var titleLayout: TitleLayout override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_chapter4) supportActionBar?.hide() titleLayout = findViewById(R.id.titleView) titleLayout.titleView.text = "第四章" } }
-
-
ListView
-
创建item
<?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:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_vertical" android:orientation="horizontal"> <ImageView android:id="@+id/fruit_img" android:layout_width="wrap_content" android:layout_height="wrap_content" app:srcCompat="@drawable/apple_pic" /> <TextView android:id="@+id/fruit_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="20dp" android:textSize="20sp" android:layout_weight="1" android:text="Apple" /> </LinearLayout>
-
创建Adapter
class FruitAdapter( context: Context, private val resource: Int, fruits: List<FruitListActivity.Fruit> ) : ArrayAdapter<FruitListActivity.Fruit>(context, resource, fruits) { override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { val view: View val viewHolder: ViewHolder // 复用convertView,省去每次inflate的性能损耗 if (convertView == null) { view = LayoutInflater.from(context).inflate(resource, parent, false) viewHolder = ViewHolder(view.findViewById(R.id.fruit_img), view.findViewById(R.id.fruit_name)) view.tag = viewHolder } else { view = convertView viewHolder = view.tag as ViewHolder } val item = getItem(position) if (item != null) { viewHolder.fruitImg.setImageResource(item.imgId) viewHolder.fruitName.text = item.name } return view } // 使用ViewHolder优化,省去每次遍历layout获取控件的性能损耗 private inner class ViewHolder(val fruitImg: ImageView, val fruitName: TextView) }
-
Activity展示
class FruitListActivity : AppCompatActivity() { private val fruitList = ArrayList<Fruit>() data class Fruit(val imgId: Int, val name: String) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_fruit_list) initData() val fruitListView = findViewById<ListView>(R.id.fruit_list_view) fruitListView.adapter = FruitAdapter(this, R.layout.fruit_item, fruitList) fruitListView.setOnItemClickListener { _, _, position, _ -> Toast.makeText(this, "${fruitList[position].name}被点击了", Toast.LENGTH_SHORT).show() } } private fun initData() { repeat(2) { fruitList.add(Fruit(R.drawable.apple_pic, "Apple")) fruitList.add(Fruit(R.drawable.banana_pic, "Banana")) fruitList.add(Fruit(R.drawable.orange_pic, "Orange")) fruitList.add(Fruit(R.drawable.watermelon_pic, "Watermelon")) fruitList.add(Fruit(R.drawable.pear_pic, "Pear")) fruitList.add(Fruit(R.drawable.grape_pic, "Grape")) fruitList.add(Fruit(R.drawable.pineapple_pic, "PineApple")) fruitList.add(Fruit(R.drawable.strawberry_pic, "Strawberry")) fruitList.add(Fruit(R.drawable.cherry_pic, "Cherry")) fruitList.add(Fruit(R.drawable.mango_pic, "Mango")) } } }
kotlin小知识
repeat
函数是Kotlin中标准函数,将lambda表达式执行n遍
-
-
RecyclerView
9-Patch图片介绍
上边框和左边框绘制的部分表示当图片拉伸时就拉伸黑点标记的区域,下边框和右边框绘制的部分表示内容允许被放置的区域。
使用鼠标在图片的边缘拖动就可以绘制了,按住Shift键拖动可以擦除。
-
创建item布局
-
接收的消息布局
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout 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" android:layout_height="wrap_content" android:minHeight="60dp" android:paddingEnd="100dp" tools:ignore="MissingDefaultResource"> <TextView android:id="@+id/left_content" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/message_left" android:gravity="start|center_vertical" android:paddingStart="40dp" android:paddingEnd="20dp" android:text="Hello, how about going to london?" android:textSize="20sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
-
发送的消息布局
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout 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" android:layout_height="wrap_content" android:minHeight="60dp" android:paddingStart="100dp" tools:ignore="MissingDefaultResource"> <TextView android:id="@+id/right_content" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/message_right" android:gravity="end|center_vertical" android:paddingStart="20dp" android:paddingEnd="40dp" android:text="Sounds great.But I'm busy now." android:textSize="20sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="1" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
-
-
消息类
data class Message(val message: String, val type: Int) { companion object { const val TYPE_RECEIVED = 0 const val TYPE_SENT = 1 } }
Kotlin小知识
const
关键字是定义常量的,⚠️只有在单例类,companion object
或顶层方法中才可以使用const
关键字 -
Adapter
class ChatAdapter(private val messages: List<Message>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder = when (viewType) { Message.TYPE_RECEIVED -> ReceivedMessageHolder( LayoutInflater.from(parent.context).inflate(R.layout.left_chatlayout, parent, false) ) else -> SentMessageHolder( LayoutInflater.from(parent.context) .inflate(R.layout.right_chatlayout, parent, false) ) } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) = when (holder) { is ReceivedMessageHolder -> holder.receivedMsg.text = messages[position].message is SentMessageHolder -> holder.sentMsg.text = messages[position].message else -> throw IllegalArgumentException() } override fun getItemCount(): Int = messages.size override fun getItemViewType(position: Int): Int { return messages[position].type } inner class ReceivedMessageHolder(view: View) : RecyclerView.ViewHolder(view) { val receivedMsg: TextView = view.findViewById(R.id.left_content) } inner class SentMessageHolder(view: View) : RecyclerView.ViewHolder(view) { val sentMsg: TextView = view.findViewById(R.id.right_content) } }
Kotlin小知识
is
关键字 用于判断类型,相当于Java的instanceof
-
主界面布局
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout 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" android:layout_height="match_parent" tools:context=".chapter4.ChatActivity"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/chat_recyclerview" android:layout_width="match_parent" android:layout_height="0dp" app:layout_constrainedHeight="true" app:layout_constraintBottom_toTopOf="@+id/message_edit" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/send" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button" app:layout_constraintBottom_toBottomOf="@+id/message_edit" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/message_edit" app:layout_constraintTop_toTopOf="@+id/message_edit" /> <EditText android:id="@+id/message_edit" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constrainedHeight="true" android:ems="10" android:inputType="textPersonName" android:layout_marginStart="20dp" android:text="Name" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
class ChatActivity : AppCompatActivity() { private lateinit var chatRecyclerView: RecyclerView private lateinit var sendBtn: Button private lateinit var messageEditText: EditText private val messages = ArrayList<Message>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_chat) chatRecyclerView = findViewById(R.id.chat_recyclerview) chatRecyclerView.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false) chatRecyclerView.adapter = ChatAdapter(messages) sendBtn = findViewById(R.id.send) messageEditText = findViewById(R.id.message_edit) sendBtn.setOnClickListener { val message = messageEditText.text.toString() messages.add(Message(message, messages.size % 2)) (chatRecyclerView.adapter as ChatAdapter).notifyItemInserted(messages.size - 1) chatRecyclerView.smoothScrollToPosition(messages.size - 1) } } }
-
-
Kotlin课堂
-
延迟初始化
private lateinit var chatRecyclerView: RecyclerView
判断延迟初始化的变量是否初始化
if (!::chatRecyclerView.isInitialized) { }
-
密封类
作用
- 使用when语句判断时,不必添加else条件
- 新增子类时会提醒
优化举例
-
创建密封类
sealed class ChatViewHolder(view: View) : RecyclerView.ViewHolder(view) class ReceivedMessageHolder(view: View) : ChatViewHolder(view) { val receivedMsg: TextView = view.findViewById(R.id.left_content) } class SentMessageHolder(view: View) : ChatViewHolder(view) { val sentMsg: TextView = view.findViewById(R.id.right_content) }
-
优化Adapter
class ChatAdapter(private val messages: List<Message>) : RecyclerView.Adapter<ChatViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChatViewHolder = when (viewType) { Message.TYPE_RECEIVED -> ReceivedMessageHolder( LayoutInflater.from(parent.context).inflate(R.layout.left_chatlayout, parent, false) ) else -> SentMessageHolder( LayoutInflater.from(parent.context) .inflate(R.layout.right_chatlayout, parent, false) ) } override fun onBindViewHolder(holder: ChatViewHolder, position: Int) = when (holder) { // 不再需要else分支 is ReceivedMessageHolder -> holder.receivedMsg.text = messages[position].message is SentMessageHolder -> holder.sentMsg.text = messages[position].message } override fun getItemCount(): Int = messages.size override fun getItemViewType(position: Int): Int { return messages[position].type } }
-
⚠️密封类及其所有子类只能定义在同一个文件的顶层位置,不能嵌套在其他类中。
-