iT邦幫忙

2022 iThome 鐵人賽

DAY 9
0

延續之前實作的「登錄頁面」在Day05~Day07學會MDC的Icon ButtonsSegmented ButtonsMaterialCardViewAppBarLayout (Top app bars)

參考了官方的教學MDC-102 Android:Material Structure and Layout (Kotlin),然後改用Material Design 3的Components 實作,以及新增前幾天的 Icon Button和Segmented Button的使用,以下就是這次要完成的畫面。
https://ithelp.ithome.com.tw/upload/images/20220929/201444699r5XECYhqA.png

介紹的內容:

  • 使用 IDE版本 : Android Studio Chipmunk | 2021.2.1 Canary 7
  • 使用 MDC Android Components
  • 實作介紹 (提供完整程式碼)
    • Add Top App Bar
      • 新增 Toolbar上的menu和NavigationIcon的點擊邏輯
    • Add Segmented Buttons 切換 卡片網格和垂直列表
      • 新增 點擊切換網格和垂直的卡片邏輯
    • Add Card、設定卡片網格
      • 新增 RecyclerView、Card item.xml、Adapter
    • Card Add Icon Button
      • 新增點擊切換顏色邏輯

使用 MDC Android Components

  • AppBarLayout (Top app bars)
  • MaterialCardView (Card)
    • Elevated Card
  • Icon Button
    • Standard Icon Button
  • Segmented Button
    • 必須選擇一個,最多只能選一個

Add Top App Bar

新增AppBarLayout、MaterialToolbar
https://ithelp.ithome.com.tw/upload/images/20220921/20144469jHUJ3WLvJV.png

頁面 xml

<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=".ProductGridFragment">

		<!--Top App Bar -->
		<com.google.android.material.appbar.AppBarLayout
        android:id="@+id/app_bar_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/purple_200"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <com.google.android.material.appbar.MaterialToolbar
            android:id="@+id/top_app_bar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            app:menu="@menu/top_app_bar"
            app:navigationIcon="@drawable/ic_menu"
            app:title="Title" />
    </com.google.android.material.appbar.AppBarLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

res/menu/top_app_bar.xml

<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@+id/search"
        android:icon="@drawable/ic_search"
        android:title="搜尋"
        app:showAsAction="ifRoom" />

    <item
        android:id="@+id/filter"
        android:icon="@drawable/ic_filter"
        android:title="篩選"
        app:showAsAction="ifRoom" />

</menu>

Toolbar上的menuNavigationIcon的點擊事件:

  • menu:setOnMenuItemClickListener
  • NavigationIcon:setNavigationOnClickListener
binding?.topAppBar?.setOnMenuItemClickListener { menu ->
		when(menu.itemId){
			    R.id.search -> {
                    Toast.makeText(context, "測試 menu item search", Toast.LENGTH_LONG).show()
                    true
          }
          R.id.filter -> {
                    Toast.makeText(context, "測試 menu item filter", Toast.LENGTH_LONG).show()
                    true
          }
          else -> false            
    }
}

binding?.topAppBar?.setNavigationOnClickListener {
   Toast.makeText(context, "測試Navigation", Toast.LENGTH_LONG).show()
}

Add Segmented buttons 切換 卡片網格和垂直

新增MaterialButtonToggleGroup、MaterialButton

  • 必選擇單個選項:用於選擇單個選項,在檢視之間切換。

    單選設定app:singleSelection="true"
    必選設定app:selectionRequired="true"

  • 預設呈現網格:設定Button ID,app:checkedButton="@+id/btn_grid"

  • MaterialButton 的style style="?attr/materialButtonOutlinedStyle"

頁面 xml

<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=".ProductGridFragment">

		<!--Top App Bar -->
    <com.google.android.material.appbar.AppBarLayout
        .....>

	        <com.google.android.material.appbar.MaterialToolbar
	            ...../>

    </com.google.android.material.appbar.AppBarLayout>

		<!--Segmented buttons -->
		<com.google.android.material.button.MaterialButtonToggleGroup
        android:id="@+id/toggle_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        app:singleSelection="true"
        app:selectionRequired="true"
        app:checkedButton="@+id/btn_grid"
        app:layout_constraintTop_toBottomOf="@id/app_bar_layout"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent">

	        <com.google.android.material.button.MaterialButton
	            android:id="@+id/btn_grid"
	            style="?attr/materialButtonOutlinedStyle"
	            android:layout_width="wrap_content"
	            android:layout_height="wrap_content"
	            android:text="網格" />
	
	        <com.google.android.material.button.MaterialButton
	            android:id="@+id/btn_vertical"
	            style="?attr/materialButtonOutlinedStyle"
	            android:layout_width="wrap_content"
	            android:layout_height="wrap_content"
	            android:text="垂直" />
	
    </com.google.android.material.button.MaterialButtonToggleGroup>
</androidx.constraintlayout.widget.ConstraintLayout>

點擊切換網格和垂直的卡片

// 點擊初始化RecyclerView和指定不同的LayoutManager
binding?.toggleButton?.addOnButtonCheckedListener { _, checkedId, isChecked ->
   if (isChecked) {
         when (checkedId) {
              R.id.btn_grid -> {
                        binding?.recyclerView?.adapter = CardRecyclerViewAdapter(listOf(1,2, 3, 4, 5, 6, 7, 8),CardsType.GRID , itemClickListener())
                        binding?.recyclerView?.layoutManager =
                            GridLayoutManager(context, 2, RecyclerView.VERTICAL, false)
                    }
                    R.id.btn_vertical -> {
                        binding?.recyclerView?.adapter = CardRecyclerViewAdapter(listOf(1,2, 3, 4, 5, 6, 7, 8),CardsType.VERTICAL , itemClickListener())
                        binding?.recyclerView?.layoutManager =
                            LinearLayoutManager(context, RecyclerView.VERTICAL, false)
                    }
         }
   }
}

Add Card、設定卡片網格

頁面 xml 新增 RecyclerView

**Add widget.RecyclerView **

<FrameLayout 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=".ProductGridFragment">

		<!--Top App Bar -->
    <com.google.android.material.appbar.AppBarLayout
        .....>

	        <com.google.android.material.appbar.MaterialToolbar
	            ...../>

    </com.google.android.material.appbar.AppBarLayout>

		<!--Segmented buttons -->
		<com.google.android.material.button.MaterialButtonToggleGroup
		    ......>

			    <com.google.android.material.button.MaterialButton
			       ....../>
			
			    <com.google.android.material.button.MaterialButton
			        ....../>
			
		</com.google.android.material.button.MaterialButtonToggleGroup>

		
		<!--widget.RecyclerView-->
		<androidx.core.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_margin="5dp"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/toggle_button">

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
            app:spanCount="2"
            tools:itemCount="6"
            tools:listitem="@layout/item_cards_grid" />

    </androidx.core.widget.NestedScrollView>
</FrameLayout>

新增 RecyclerView 的 Card item.xml、Adapter

https://ithelp.ithome.com.tw/upload/images/20220921/20144469hkfr0r6Z79.png
Elevated Card style="@style/Widget.Material3.CardView.Elevated"
網格 item_cards_grid.xml

<androidx.constraintlayout.widget.ConstraintLayout 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:layout_gravity="center_horizontal"
    xmlns:tools="http://schemas.android.com/tools">

    <!--Elevated card-->
    <com.google.android.material.card.MaterialCardView
        android:id="@+id/filledCard"
        style="@style/Widget.Material3.CardView.Elevated"
        android:layout_width="150dp"
        android:layout_height="wrap_content"
        android:layout_margin="15dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <ImageView
                android:id="@+id/image"
                android:layout_width="match_parent"
                android:layout_height="100dp"
                android:scaleType="centerCrop"
                app:srcCompat="@color/gray" />

            <androidx.constraintlayout.widget.ConstraintLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@color/white"
                android:orientation="vertical">

                <TextView
                    android:id="@+id/tvTitle"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    tools:text="Title"
                    android:layout_marginTop="10dp"
                    android:layout_marginStart="10dp"
                    android:textAppearance="?attr/textAppearanceTitleMedium"
                    app:layout_constraintTop_toTopOf="parent"
                    app:layout_constraintStart_toStartOf="parent"/>

                <TextView
                    android:id="@+id/tvSubtitle"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    tools:text="subtitle"
                    android:layout_marginStart="10dp"
                    android:layout_marginBottom="10dp"
                    android:textAppearance="?attr/textAppearanceBodyMedium"
                    android:textColor="?android:attr/textColorSecondary"
                    app:layout_constraintTop_toBottomOf="@id/tvTitle"
                    app:layout_constraintBottom_toBottomOf="parent"
                    app:layout_constraintStart_toStartOf="parent"/>

                <com.google.android.material.button.MaterialButton
                    android:id="@+id/iconButton"
                    style="@style/Widget.Material3.Button.IconButton"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    app:icon="@drawable/ic_favorite"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintBottom_toBottomOf="parent"/>

            </androidx.constraintlayout.widget.ConstraintLayout>

        </LinearLayout>

    </com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>

垂直 item_cards_vertical.xml

<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">

    <!--Elevated card-->
    <com.google.android.material.card.MaterialCardView
        android:id="@+id/filledCard"
        style="@style/Widget.Material3.CardView.Elevated"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="15dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <ImageView
                android:id="@+id/image"
                android:layout_width="match_parent"
                android:layout_height="100dp"
                android:scaleType="centerCrop"
                app:srcCompat="@color/gray" />

            <androidx.constraintlayout.widget.ConstraintLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@color/white"
                android:orientation="vertical">

                <TextView
                    android:id="@+id/tvTitle"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="10dp"
                    android:layout_marginTop="10dp"
                    android:textAppearance="?attr/textAppearanceTitleMedium"
                    tools:text="Title"
                    app:layout_constraintTop_toTopOf="parent"
                    app:layout_constraintStart_toStartOf="parent" />

                <TextView
                    android:id="@+id/tvSubtitle"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="10dp"
                    android:layout_marginBottom="10dp"
                    android:textAppearance="?attr/textAppearanceBodyMedium"
                    android:textColor="?android:attr/textColorSecondary"
                    tools:text="subtitle"
                    app:layout_constraintTop_toBottomOf="@id/tvTitle"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintBottom_toBottomOf="parent"/>

                <com.google.android.material.button.MaterialButton
                    android:id="@+id/iconButton"
                    style="@style/Widget.Material3.Button.IconButton"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    app:icon="@drawable/ic_favorite"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintBottom_toBottomOf="parent"/>

            </androidx.constraintlayout.widget.ConstraintLayout>
        </LinearLayout>
    </com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>

Adapter: CardRecyclerViewAdapter

class CardRecyclerViewAdapter(
    private val productList: List<Int>,
    private val type: CardsType,
    private var mItemButtonClickListener: ((position: Int) -> Unit)? = null
) :
    RecyclerView.Adapter<RecyclerView.ViewHolder>() {


    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return when (type) {
            CardsType.GRID -> {
                GridViewHolder(
                    ItemCardsGridBinding.inflate(LayoutInflater.from(parent.context), parent, false),
                    mItemButtonClickListener
                )
            }
            CardsType.VERTICAL -> {
                GridViewHolder(
                    ItemCardsVerticalBinding.inflate(LayoutInflater.from(parent.context), parent, false),
                    mItemButtonClickListener
                )
            }
        }
    }

    override fun getItemCount(): Int = productList.size

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        (holder as GridViewHolder).bind(position)
    }

}

ViewHolder:CardsViewHolder
CardViewHolder:建構子傳入兩個參數

  1. ViewBinding提供Layout的判斷
  2. onItemClickListener將使用者點擊Cards的第幾項目傳給View頁面
class GridViewHolder(
		private val binding: ViewBinding, 
		private val onItemClickListener: ((position: Int) -> Unit)?) 
: RecyclerView.ViewHolder(binding.root) {


    private var isCanFavorite = true // 是否可以被收藏

    fun bind(position: Int){

        when (binding) {
            is ItemCardsGridBinding -> {
                // 呈現網格狀的
                binding.image.setBackgroundResource(R.color.gray)
                binding.tvTitle.text = "Title"
                binding.tvSubtitle.text = "Subtitle"

								binding.filledCard.setOnClickListener {
										// 點擊第幾格Cards,將傳遞給View層
                    onItemClickListener?.invoke(position)
                }
            }

            is ItemCardsVerticalBinding -> {
                // 呈現垂直狀的
                binding.image.setBackgroundResource(R.color.gray)
                binding.tvTitle.text = "Title"
                binding.tvSubtitle.text = "Subtitle"
								
								binding.filledCard.setOnClickListener {
										// 點擊第幾格Cards,將傳遞給View層
                    onItemClickListener?.invoke(position)
                }
               
            }
        }
    }
}

View頁面可接收點擊後的position實作對應流程
https://ithelp.ithome.com.tw/upload/images/20220922/201444691HI96hHT90.png

private fun itemClickListener(): ((position: Int) -> Unit) {
        return { position ->
						//可以實作點擊後續流程
            Toast.makeText(context,"點擊第幾項目 : $position", Toast.LENGTH_SHORT).show()
        }
 }

Card Add icon Button

https://ithelp.ithome.com.tw/upload/images/20220922/20144469mgv9GUJSt1.png
style="@style/Widget.Material3.Button.IconButton"

<com.google.android.material.button.MaterialButton
	android:id="@+id/iconButton"
	style="@style/Widget.Material3.Button.IconButton"
	android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:icon="@drawable/ic_favorite"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintBottom_toBottomOf="parent"/>

點擊切換愛心顏色邏輯,會寫在CardViewHolder中。

private var isCanFavorite = true // 是否可以被收藏

binding.iconButton.setOnClickListener {
		 if (isCanFavorite) {
       binding.iconButton.icon = ResourcesCompat.getDrawable(itemView.resources, R.drawable.ic_select_favorite, null)
           isCanFavorite = false
     } else {
       binding.iconButton.icon = ResourcesCompat.getDrawable(itemView.resources, R.drawable.ic_favorite, null)
           isCanFavorite = true
     }                       
}

以上是這次延續登入頁面後實作卡片資料呈現Demo。
歡迎下載程式碼

感謝您看到這邊/images/emoticon/emoticon29.gif

參考資料

MDC-102 Android:Material Structure and Layout (Kotlin)


上一篇
Day08 使用 M3 的 AppBarLayout
下一篇
Day10 使用 M3 的 Floating Action Buttons (FAB) (上)
系列文
Kotlin 實踐 Material Design 懶人包30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言