第四章 UI点滴

174 阅读3分钟

第四章 UI点滴

  1. 常用属性

    属性说明
    android:textAllCaps="false"是否全大写
    android:gravity=""控件内部的对齐方式(TextView中文字或者ViewGroup中View对齐方式)
    android:layout_gravity="start"View在ViewGroup中的对齐方式
    android:layout_weight="1"LinearLayout属性,权重,如果需要按权重分配大小,权重方向上大小设置为0dp。View权重设置为1并且权重方向上其它控件大小自适应,
  2. 自定义控件

    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>
      
    2. 创建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

    3. 在布局中使用

      <?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>
      
    4. 代码中使用

      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 = "第四章"
          }
      }
      
  3. ListView

    1. 创建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>
      
    2. 创建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)
      }
      
    3. 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遍

  4. RecyclerView

    9-Patch图片介绍

    上边框和左边框绘制的部分表示当图片拉伸时就拉伸黑点标记的区域,下边框和右边框绘制的部分表示内容允许被放置的区域。

    使用鼠标在图片的边缘拖动就可以绘制了,按住Shift键拖动可以擦除。

    1. 创建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>
        
    2. 消息类

      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关键字

    3. 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

    4. 主界面布局

      <?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)
              }
          }
      }
      
  5. Kotlin课堂

    1. 延迟初始化

      private lateinit var chatRecyclerView: RecyclerView
      

      判断延迟初始化的变量是否初始化

      if (!::chatRecyclerView.isInitialized) {
          
      }
      
    2. 密封类

      作用

      1. 使用when语句判断时,不必添加else条件
      2. 新增子类时会提醒

      优化举例

      1. 创建密封类

        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)
        }
        
      2. 优化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
            }
        }
        
      3. ⚠️密封类及其所有子类只能定义在同一个文件的顶层位置,不能嵌套在其他类中。