二十七、Android-Material控件

308 阅读3分钟

27.1 Toolbar

res/values/themes.xml

<resources xmlns:tools="http://schemas.android.com/tools">
    <!-- Base application theme. -->
    <style name="Base.Theme.MaterialTest" parent="Theme.Material3.DayNight.NoActionBar">
        <!-- Customize your light theme here. -->
         <item name="colorPrimary">@color/design_default_color_primary</item>
        <item name="colorPrimaryDark">@color/design_default_color_primary_dark</item>
    </style>
​
    <style name="Theme.MaterialTest" parent="Base.Theme.MaterialTest" />
</resources>

Theme.Material3.DayNight.NoActionBar指定了主题,为DayNight.NoActionBar,无ActionBar。

在activity_main.xml中添加Toolbar

<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=".MainActivity">
​
    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:background="@color/design_default_color_primary"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>
...
​
</androidx.constraintlayout.widget.ConstraintLayout>

在MainActvity中

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(viewBinding.root)
​
        setSupportActionBar(viewBinding.toolbar)
    }

新建toolbar布局文件,在res下新建menu文件夹,menu中新建Menu resource file文件,命名toolbar.xml

toolbar.xml文件

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/backup"
        android:icon="@drawable/back_white"
        android:title="Backup"
        app:showAsAction="always"
        />
    <item
        android:id="@+id/delete"
        android:icon="@drawable/delete_white"
        android:title="Delete"
        app:showAsAction="ifRoom"
        />
    <item
        android:id="@+id/settings"
        android:icon="@drawable/setting_white"
        android:title="Settings"
        app:showAsAction="never"
        />
</menu>

showAsAction来指定按钮显示的位置

  • always: 永远显示在Toolbar中,如果屏幕空间不够则不显示
  • ifRoom: 屏幕空间足够的情况下显示在Toolbar中,不够的话就显示在菜单中
  • never:永远显示在菜单中

注意: Toolbar中action按钮只会显示图标,菜单中的action只会显示文字。

最后再修改MainActvity

class MainActivity : AppCompatActivity() {
​
    private lateinit var viewBinding: ActivityMainBinding
​
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(viewBinding.root)
​
        setSupportActionBar(viewBinding.toolbar)
    }
​
    // 导航栏
    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        menuInflater.inflate(R.menu.toolbar, menu)
        return true
    }
​
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        var msg = ""
        when (item.itemId) {
            R.id.backup -> msg = "Backup"
            R.id.delete -> msg = "Delete"
            R.id.settings -> msg = "Settings"
        }
        Toast.makeText(this, "You clicked $msg", Toast.LENGTH_SHORT).show()
        return true
    }
}

27.2 滑动菜单-DrawerLayout

xml

android:layout_gravity="start" 是必须设置的,告诉drawer从那边出来

  • left: 左边
  • right: 右边
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout
    android:id="@+id/drawerLayout"
    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=".MainActivity">
​
    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:background="@color/design_default_color_primary"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"/>
    </FrameLayout>
​
​
    <TextView
        android:text="This is menu"
        android:textSize="30sp"
        android:layout_gravity="start"
        android:background="#ffffff"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
​
</androidx.drawerlayout.widget.DrawerLayout>

activity

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(viewBinding.root)
​
        setSupportActionBar(viewBinding.toolbar)
​
        supportActionBar?.let {
            it.setDisplayHomeAsUpEnabled(true)
            it.setHomeAsUpIndicator(R.drawable.home_white)
        }
    }
    
    
        override fun onOptionsItemSelected(item: MenuItem): Boolean {
        var msg = ""
        when (item.itemId) {
            R.id.backup -> msg = "Backup"
            R.id.delete -> msg = "Delete"
            R.id.settings -> msg = "Settings"
            android.R.id.home -> viewBinding.drawerLayout.openDrawer(GravityCompat.START)
        }
        if (msg.isNotEmpty()) {
            Toast.makeText(this, "You clicked $msg", Toast.LENGTH_SHORT).show()
        }
        return true
    }

getSupportActionBar()方法得到了ActionBar的实例。setDisplayHomeAsUpEnabled()方法让ActionBar显示出来返回按钮,setHomeAsUpIndicator()方法来设置一个导航按钮图标。

27.3 NavigationView

menu/nav_menu.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <group android:checkableBehavior="single">
        <item
            android:id="@+id/navCall"
            android:icon="@drawable/call"
            android:title="Call"/>
        <item
            android:id="@+id/navFriends"
            android:icon="@drawable/friend"
            android:title="Friends"/>
        <item
            android:id="@+id/navLocation"
            android:icon="@drawable/location"
            android:title="Location"/>
        <item
            android:id="@+id/navMail"
            android:icon="@drawable/mail"
            android:title="Mail"/>
    </group>
</menu>

layout/nav_header.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="180dp"
    android:padding="10dp"
    android:background="@color/design_default_color_primary"
    xmlns:app="http://schemas.android.com/apk/res-auto">
​
    <de.hdodenhof.circleimageview.CircleImageView
        android:id="@+id/iconImage"
        android:src="@drawable/img_2"
        android:layout_centerInParent="true"
        android:layout_width="70dp"
        android:layout_height="70dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"/>
​
    <TextView
        android:id="@+id/mailText"
        android:text="123456789@gmail.com"
        android:textColor="#ffffff"
        android:textSize="14sp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@+id/iconImage"
        app:layout_constraintStart_toStartOf="parent"
        />
    <TextView
        android:id="@+id/userText"
        android:text="Loong"
        android:textColor="#ffffff"
        android:textSize="14sp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@+id/mailText"
        app:layout_constraintStart_toStartOf="parent"
        />
​
</androidx.constraintlayout.widget.ConstraintLayout>

activity.xml使用

    <com.google.android.material.navigation.NavigationView
        android:id="@+id/navView"
        android:layout_gravity="start"
        app:menu="@menu/nav_menu"
        app:headerLayout="@layout/nav_header"
        android:background="#ffffff"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
​

activity

// 默认选中
viewBinding.navView.setCheckedItem(R.id.navCall)
// 点击回调
viewBinding.navView.setNavigationItemSelectedListener {
    // 关闭drawerLayout抽屉
    viewBinding.drawerLayout.closeDrawers()
    // 返回true,表示已处理
    true
}

27.4 悬浮按钮-FloatiingActionButton

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/floatingButton"
        android:src="@drawable/home_gray"
        android:elevation="18dp"
        android:layout_margin="30dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        />

app:elevation属性给FloatingActionButton指定一个高度值。高度值越大,投影范围也越大,但是投影效果越淡;高度值越小,投影范围也越小,但是投影效果越浓。

        viewBinding.floatingButton.setOnClickListener {
            Toast.makeText(this, "FAB clicked", Toast.LENGTH_SHORT).show()
        }

其它和普通Button一样。

27.5 Snackbar

Snackbar并不是Toast的替代品,它们有着不同的应用场景。Toast的作用是告诉用户现在发生了什么事情,但用户只能被动接收这个事情,因为没有什么办法能让用户进行选择。而Snackbar则在这方面进行了扩展,它允许在提示中加入一个可交互按钮,当用户点击按钮的时候,可以执行一些额外的逻辑操作。

        viewBinding.floatingButton.setOnClickListener {
            Snackbar.make(it, "Data deleted", Snackbar.LENGTH_SHORT)
                .setAction("Undo") {
                    Toast.makeText(this, "Data restored", Toast.LENGTH_SHORT).show()
                }
                .show()
        }

27.6 卡片式布局-MaterialCardView

<com.google.android.material.card.MaterialCardView
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   app:cardCornerRadius="4dp"
  app:elevation="5dp">
     <TextView
     android:id="@+id/infoText"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"/>
</com.google.android.material.card.MaterialCardView>
  • app:cardCornerRadius: 卡片圆角弧度
  • app:elevation:卡片阴影大小

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <com.google.android.material.card.MaterialCardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        >
        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <ImageView
                android:id="@+id/fruitImage"
                android:scaleType="centerCrop"
                android:layout_width="match_parent"
                android:layout_height="100dp"/>
            <TextView
                android:id="@+id/fruitName"
                android:layout_gravity="center_horizontal"
                android:textSize="16sp"
                android:layout_margin="5dp"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
        </LinearLayout>
    </com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>

fruit_item.xml

cell布局

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <com.google.android.material.card.MaterialCardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        >
        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <ImageView
                android:id="@+id/fruitImage"
                android:scaleType="centerCrop"
                android:layout_width="match_parent"
                android:layout_height="100dp"/>
            <TextView
                android:id="@+id/fruitName"
                android:layout_gravity="center_horizontal"
                android:textSize="16sp"
                android:layout_margin="5dp"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
        </LinearLayout>
    </com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>

Model类

class Fruit {
    var name: String = ""
    var imageUrl: String = ""
​
    constructor(name: String, url: String) {
        this.name = name
        this.imageUrl = url
    }
}

Adapter类

class FruitAdapter(val context: Context, 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(context).inflate(R.layout.fruit_item, parent, false)
        return ViewHolder(view)
    }
​
    override fun getItemCount(): Int {
        return fruitList.size
    }
​
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val fruit = fruitList[position]
        holder.fruitName.text = fruit.name
        Log.d("Adapter", "${fruit.imageUrl}")
        Glide.with(context).load(fruit.imageUrl).into(holder.fruitImage)
    }
}

Activty使用

class MainActivity : AppCompatActivity() {
​
    private lateinit var viewBinding: ActivityMainBinding
    val fruitList = ArrayList<Fruit>()
​
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(viewBinding.root)
        //...
        initFruitList()
        val layoutManager = GridLayoutManager(this, 2)
        val recyclerView = viewBinding.recyclerView
        recyclerView.layoutManager = layoutManager
        val adapter = FruitAdapter(this, fruitList)
        recyclerView.adapter = adapter
    }
​
    fun initFruitList() {
        val u1 = "https://img2.baidu.com/it/u=2045021150,2452876524&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500"
        // ...
        val fruits = mutableListOf(
            Fruit("Banana", u1)
        )
        repeat(20) {
            val index = (0 until fruits.size).random()
            fruitList.add(fruits[index])
        }
    }
}

27.7 AppBarLayout

可结合CoordinatorLayout使用

<androidx.drawerlayout.widget.DrawerLayout
    android:id="@+id/drawerLayout"
    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=".MainActivity">
​
    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >
        <com.google.android.material.appbar.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <androidx.appcompat.widget.Toolbar
                android:id="@+id/toolbar"
                android:background="@color/design_default_color_primary"
                android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_scrollFlags="scroll|enterAlways|snap"
                />
        </com.google.android.material.appbar.AppBarLayout>
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="0dp"
            app:layout_behavior="@string/appbar_scrolling_view_behavior"
            />
​
    </androidx.coordinatorlayout.widget.CoordinatorLayout>
//...
</androidx.drawerlayout.widget.DrawerLayout>

RecyclerView中使用app:layout_behavior属性指定了一个布局行为。其中appbar_scrolling_view_behavior这个字符串也是由Material库提供的。

在Toolbar中添加了一个app:layout_scrollFlags属性:

  • scroll: 表示当RecyclerView向上滚动的时候,Toolbar会跟着一起向上滚动并实现隐藏;

  • enterAlways: 表示当RecyclerView向下滚动的时候,Toolbar会跟着一起向下滚动并重新显示;

  • snap: 表示当Toolbar还没有完全隐藏或显示的时候,会根据当前滚动的距离,自动选择是隐藏还是显示。

27.8 下拉刷新-swiperefreshlayout

swiperefreshlayout

    implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0'

在要添加下拉刷新的控件外面,包装一层swiperefreshlayout

        <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
            android:id="@+id/swipeRefresh"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            
            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/recyclerView"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="0dp"
                />
        </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
    </androidx.coordinatorlayout.widget.CoordinatorLayout>

设置颜色、刷新事件

        viewBinding.swipeRefresh.setColorSchemeResources(R.color.black)
        viewBinding.swipeRefresh.setOnRefreshListener {
            refreshFruits(adapter)
        }