剛開始看購物車效果覺得挺復雜,但是把這個功能拆開來一步一步實作會發現并不難,其實就涉及到:ItemDecoration的繪制、recyclerview的滑動監聽、貝塞爾曲線和屬性影片相關內容,剩下的就是RecyclerView滑動和點擊時左右兩個串列聯動的相關邏輯,
復雜的東西拆開來看就簡單多了,下面我將一步一步帶你來實作購物車效果,
效果圖
實作步驟
1. 資料項格式
open class ProductItemParent {
//記錄當前類所選的商品數量
var typeSelectCount:Int=0
}
我們用ProductItem記錄左側的分類,Product記錄右側的產品
//總體分類
data class ProductItem (
var productList: List<Product>,
//類別ID
var typeId: String,
//類別名稱
var typeName: String
):ProductItemParent()
//該分類中的某個產品
data class Product(
//所屬類別ID
var parentId: String,
//產品ID
var productId: String,
//產品圖片
var productImg: Int,
//產品價格
var productMoney: Double,
//產品月銷售量
var productMonthSale: Int,
//產品名稱
var productName: String
)
2. 左側串列配接器
要實作效果:左側串列的點擊事件,點擊選中時顯示紅線,背景變色;顯示數量
思路:
- 左側串列商品數量的數字角標我們使用TextView通過其背景顯示紅色圓圈shape而表示,其顯示與隱藏通過判斷ProductItem中的typeSelectCount欄位是否>0來設定,數字角標的值設為typeSelectCount
- 選中與未選中左側串列,我們使用selector來更改ItemView的背景顏色
- 選中與未選中左側串列的紅色線,我們通過更改visibility屬性來控制
2.1 itemView布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/left_menu_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/selector_item_select"
android:clickable="true"
android:gravity="center"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="43dp">
<TextView
android:id="@+id/left_menu_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="零食"
android:textColor="#333333"
android:textSize="12dp" />
<!--數字角標-->
<TextView
android:id="@+id/tv_left_menu_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/iv_shopping_cart_img"
android:layout_alignRight="@+id/left_menu_textview"
android:layout_marginRight="-8dp"
android:background="@drawable/shape_shopping_cart_num_bg"
android:gravity="center"
android:padding="2dp"
android:text="0"
android:textColor="#FFFFFF"
android:textSize="8dp"
android:visibility="invisible" />
<!--左側豎線區域,選中展示-->
<View
android:id="@+id/v_left_menu_item_line"
android:layout_width="3dp"
android:layout_height="match_parent"
android:layout_marginTop="12dp"
android:background="#fe3939"
android:layout_marginBottom="12dp" />
</RelativeLayout>
</LinearLayout>
2.2 LeftTypeAdapter
左側串列配接器
class LeftTypeAdapter(private var dataList: MutableList<ProductItem>) : RecyclerView.Adapter<LeftTypeAdapter.MyLeftViewHolder>() {
companion object{
const val TAG = "ceshi_left"
}
//默認選中的位置
var selectPosition = 0
//要更新的左側分類的typeId號
var mUpdateTypeId=""
//要更新的typeId號的數量
var mUpdateTypeCount =0
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LeftTypeAdapter.MyLeftViewHolder {
var view = LayoutInflater.from(parent.context).inflate(R.layout.left_item,parent,false)
return MyLeftViewHolder(view)
}
override fun getItemCount(): Int {
return dataList.size
}
override fun onBindViewHolder(holder: LeftTypeAdapter.MyLeftViewHolder, position: Int) {
var productItem = dataList[position]
//設定分類名
holder.typeName.text = productItem.typeName
//是選中位置,小紅線顯示
if(selectPosition==position){
holder.typeLine.visibility = View.VISIBLE
//布局設定為選中,主要是為了更新itemView選中后的背景顏色(背景使用了selector)
holder.typeLayout.isSelected = true
}else{
//不是選中位置,則小紅線隱藏
holder.typeLine.visibility = View.INVISIBLE
//布局設定為未選中
holder.typeLayout.isSelected = false
}
//設定數量(要更新的類別的產品數量,將productItem類內typeSelectCount數量進行更新)
if(productItem.typeId==mUpdateTypeId){
productItem.typeSelectCount = mUpdateTypeCount
}
//設定數量顯示
if(productItem.typeSelectCount>0){
holder.typeSelectCount.visibility = View.VISIBLE
holder.typeSelectCount.text = productItem.typeSelectCount.toString()
}else{
holder.typeSelectCount.visibility = View.INVISIBLE
}
//左側布局的點擊事件
holder.typeLayout.setOnClickListener {
if(holder.adapterPosition in 0 until itemCount){
this.selectPosition = holder.adapterPosition
//這個就實作了selected的更新了
notifyDataSetChanged()
}
}
}
inner class MyLeftViewHolder(view: View):RecyclerView.ViewHolder(view){
//名稱
val typeName = view.findViewById<TextView>(R.id.left_menu_textview)
//已選中的數量
val typeSelectCount = view.findViewById<TextView>(R.id.tv_left_menu_count)
//左側布局(為了更改背景顏色)
val typeLayout = view.findViewById<LinearLayout>(R.id.left_menu_item)
//左側選中時的線條
val typeLine = view.findViewById<View>(R.id.v_left_menu_item_line)
}
}
2.3 添加資料顯示
var data = mutableListOf<ProductItem>()
lateinit var leftAdapter: LeftTypeAdapter
override fun onCreate(savedInstanceState: Bundle?) {
initData()
leftAdapter = LeftTypeAdapter(data)
left_menu.layoutManager = LinearLayoutManager(this)
left_menu.adapter = leftAdapter
}
/**
* 初始化資料
*/
private fun initData() {
var productItem1List = mutableListOf<Product>()
productItem1List.add(Product("1", "1", R.drawable.ic1, 3.5, 21, "威龍大面筋65g"))
productItem1List.add(Product("1", "2", R.drawable.ic2, 5.9, 22, "恰恰焦糖化子108g"))
var productItem2List = mutableListOf<Product>()
var productItem2 = ProductItem(productItem2List, "2", "新鮮水果")
productItem2List.add(Product("2", "6", R.drawable.ic6, 39.9, 30, "盒裝草莓約250g"))
productItem2List.add(Product("2", "7", R.drawable.ic7, 4.98, 31, "砂糖桔500g"))
......
}
2.5 效果
3.右側串列配接器
下面我們分步驟來實作
3.1 資料系結展示
思路: 看到右側布局,你可能第一眼想到多布局,確實多布局可以實作,但是,如果想要吸頂的影片效果,實作起來比較麻煩,你可以參考Android 美團購物車效果這篇文章的實作方法,它會默認顯示一個布局來實作吸頂,然后通過影片來實作效果,
對于右側配接器系結的資料,我原先系結的是集合MutableList<ProductItem>,getItemCount()回傳的是里面的子項Product的個數,雖然可以,但是每次在onBindViewHolder()里設定資料時,都要先根據position來計算獲取它在ProductItem集合里面的Product物件,然后才設定資料,倒不如我們先提前將右側Product的資料集合MutableList<Product>先獲取到,然后再系結資料,這樣我們用的時候就不需要這么麻煩了,
class RightProductAdapter(private var dataList: MutableList<Product>) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
/**
* 產品的ViewHolder
*/
inner class MyRightProductViewHolder(view: View) : RecyclerView.ViewHolder(view) {
//產品名稱
var productName = view.findViewById<TextView>(R.id.tv_item_life_product_name)
//產品月售個數
var productSaleCount = view.findViewById<TextView>(R.id.tv_item_life_product_monty)
//產品價格
var productPrice = view.findViewById<TextView>(R.id.tv_item_life_product_money)
//添加到購物車的數量
var selectCount = view.findViewById<TextView>(R.id.tv_group_list_item_count_num)
//減
var reduce = view.findViewById<ImageView>(R.id.iv_group_list_item_count_reduce)
//加
var add = view.findViewById<ImageView>(R.id.iv_group_list_item_count_add)
//圖片
var image = view.findViewById<ImageView>(R.id.iv_item)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
var view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_life_product_bf, parent, false)
return MyRightProductViewHolder(view)
}
override fun getItemCount(): Int {
return dataList.size
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
//根據下標獲取商品,這個position是右側資料展示的下標
var product = dataList[position]
var productHolder = holder as MyRightProductViewHolder
//商品名稱
productHolder.productName.text = product?.productName
//價格
productHolder.productPrice.text = product?.productMoney.toString()
//已售個數
productHolder.productSaleCount.text = product?.productMonthSale.toString()
//商品圖片
productHolder.image.setImageResource(product?.productImg)
}
}
效果:
3.2 繪制頭部
思路:
- 通過ItemDecoration來實作頭部效果
- 如果ItemDecoration還沒用過的話,可以看看我的這篇入門文章地址
3.2.1 整理資料
其實也是很簡單,因為我們的右側資料Product構成的集合是有類別區分的,所以我們只需要在每一組類別資料的開頭繪制即可,
這里我們可以將右側資料集合所對應的typeName組成一個頭部集合,內部typeName為標題名字,所以我們得到的集合就是{“零食”,“零食”,“零食”,…“新鮮水果”,“新鮮水果”,…}
那么怎么判斷該標題名字是不是該組型別資料中的第一個呢?我們將當前位置的標題與上一個位置的標題進行對比,如果相同則代表不是第一個,不同則代表是這組分類中的第一個,
var titleDataList = mutableListOf<String>()
override fun onCreate(savedInstanceState: Bundle?) {
initTitleDataList()
...
rightRecyclerview.addItemDecoration(MyItemDecoration(this,titleDataList))
...
}
/**
* 記錄右側每一項所對應的typeName()
*/
private fun initTitleDataList() {
for (i in data.indices) {
for (j in data[i].productList.indices) {
titleDataList.add(data[i].typeName)
}
}
}
3.2.2 頭部偏移要繪制矩形的高度
因為我們需要在每組的第一個itemView繪制一個矩形,所以我們在getItemOffsets()方法里偏移一定高度
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
super.getItemOffsets(outRect, view, parent, state)
val pos = parent.getChildAdapterPosition(view)
//只有是同一組的第一個才顯示懸浮欄
if (pos == 0 || isFirstInGroup(pos)) {
//距離ItemView的上方偏移topHeight高度
outRect.top = topHeight
} else {
outRect.top = 0
}
}
/**
* 判斷是不是組中的第一個位置
* @param pos
* @return
*/
private fun isFirstInGroup(pos: Int): Boolean {
return if (pos == 0) {
true
} else {
//當前位置所對應的型別名
val typeName = titleDataList[pos]
//前一個位置所對應的型別名
val previewTypeName = titleDataList[pos - 1]
//判斷前一個字串 與 當前字串 是否相同,相同回傳true,不同回傳false
previewTypeName != typeName
}
}
3.2.3 繪制矩形和文字
先初始化各種畫筆
class MyItemDecoration(
context: Context,
private val titleDataList: List<String>
) : ItemDecoration() {
//繪制標題文字的畫筆
private val textPaint: TextPaint
//繪制背景顏色的畫筆
private val paint: Paint
//想要的矩形的高度
private val topHeight: Int
//字體度量物件,記錄我們要繪制字體的有關資訊
private var fontMetrics: Paint.FontMetrics
init {
val res = context.resources
//設定懸浮欄的畫筆---paint
paint = Paint()
//設定繪制背景的畫筆顏色
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
paint.color = res.getColor(R.color.bgcolor, null)
} else {
paint.color = res.getColor(R.color.bgcolor)
}
//設定懸浮欄中文本的畫筆
textPaint = TextPaint()
textPaint.isAntiAlias = true
textPaint.textSize = DensityUtil.dip2px(context, 14f).toFloat()
textPaint.color = Color.DKGRAY
textPaint.textAlign = Paint.Align.LEFT
fontMetrics = Paint.FontMetrics()
//決定懸浮欄的高度等
topHeight = res.getDimensionPixelSize(R.dimen.sectioned_top)
}
}
在onDraw()中繪制我們的標題框
/**
* 針對的是每個Item
* @param c
* @param parent
* @param state
*/
override fun onDraw(
c: Canvas,
parent: RecyclerView,
state: RecyclerView.State
) {
super.onDraw(c, parent, state)
val left = parent.paddingLeft
val right = parent.width - parent.paddingRight
//獲取當前頁面總共的Item數量(測驗發現是當前頁面可以顯示的item數量)
val childCount = parent.childCount
for (i in 0 until childCount) {
val view = parent.getChildAt(i)
//獲取當前view在總共集合中的位置
val position = parent.getChildAdapterPosition(view)
//獲取當前view的分類名
val groupName = titleDataList[position]
//獲取textPaint中的字體資訊 setTextSize要在它前面
fontMetrics = textPaint.fontMetrics
//當是第一個或者是每組中的第一個時才繪制
if (position == 0 || isFirstInGroup(position)) {
//往上移動就要減
val top = view.top - topHeight.toFloat()
val bottom = view.top.toFloat()
//基線距離文字中心的距離
val distance =
(fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom
//基線的位置
val textBaseLine = (bottom + top) / 2 + distance
//繪制懸浮欄
c.drawRect(left.toFloat(), top, right.toFloat(), bottom, paint)
//繪制文本
c.drawText(groupName, left.toFloat(), textBaseLine, textPaint)
}
}
}
繪制標題框圖解:

繪制標題框矩形的話,可參照該圖進行理解,知道標題框的上下左右,用drawRect()方法即可繪制出矩形框
繪制文字圖解:

繪制標題文字,有一個難點是怎么將文字居中顯示,我們要知道繪制文本的時候,是根據baseline基線來繪制的,具體決議可按照如圖序號順序來理解,可參考這篇文章鏈接地址
我們在上面繪制的矩形框的中心點Y軸坐標為:(bottom+top)/2 注意: 這里的bottom和top是包含正負的
基線距離文字中心的距離(這里指距離,代表為正):distance=(fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom
所以文字的中心Y軸坐標為:baseline-distance(因為baseline對應的Y軸位置為0,向上為負)
要讓文字的中心和矩形框的中心一致,即baseline-distance=(bottom+top)/2
所以最終baseline的位置為:(bottom+top)/2+distance
3.3 效果
4. 懸浮頭部(有影片)
使用onDrawOver()方法, onDrawOver()是在RecyclerView繪制完內容之后再呼叫,相當于可以在RecyclerView之上再繪制一層內容,
因為我們在移動的時候,只有當下一個分組繪制的標題欄頂上來的時候,我們的懸浮頭部才會發生影片,慢慢的頂上去并消失,這里其實就是不斷改變懸浮頭部的高度,讓其變小,從而實作我們想要的效果,
圖解:

具體決議可看圖所示,其實和前面onDraw()中的繪制矩形和文字一樣,不同的只是top和bottom的位置不同,
繪制矩形所需要的引數top和bottom如圖解釋,繪制字體同理,
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDrawOver(c, parent, state)
val left = parent.paddingLeft
val right = parent.width - parent.paddingRight
//當前布局的名字
var groupTitleName =""
//因為我們只需要在當前界面可顯示的第一條資料上方繪制,所以這個就getChildAt(0)
val itemView = parent.getChildAt(0)
val position = parent.getChildAdapterPosition(itemView)
//獲取該位置的標題名稱
groupTitleName = titleDataList[position].toUpperCase()
//繪制懸浮標題欄在該布局
//默認是指定的高度
var viewTitleHeight: Float = topHeight.toFloat()
//獲取當前itemView的底部
val viewBottom = itemView.bottom
//-------這里實作了上升和下拉平滑的效果--------
//下一個item的標題名字
val nextGroupTitleName: String = titleDataList[position + 1]
//下一個item的名字和頂部0位置的標題名字不同,說明要更換新的標題了(viewBottom<viewTitleHeight,
// 說明itemView向上移動,此時要繪制的標題的高度其實就是頂部下標為0的itemView的bottom的距離,因為recyclerview最上面是0坐標)
if (nextGroupTitleName !== groupTitleName && viewBottom < viewTitleHeight) {
//所以此時的高度,就為剩下的itemView距離0點的高度了,因為是動態變化的,所有就有了效果
viewTitleHeight = viewBottom.toFloat()
}
//獲取paint中的字體資訊 settextSize要在他前面
val fontMetrics = textPaint.fontMetrics
//基線距離文字中心的距離
val distance = (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom
//因為按道理一直在第一項,所以高應該為0,但是這么寫相當于固定了頭部,因為要有影片效果,所以可能變化,這里的top和bottom都要發生變化
c.drawRect(left.toFloat(), viewTitleHeight - topHeight, right.toFloat(), viewTitleHeight, paint)
//繪制文字
c.drawText(groupTitleName, left.toFloat(), 2 * viewTitleHeight - topHeight - distance, textPaint)
}
4.1 效果
5. 左右聯動
- 點擊左側右側滑動
- 滑動右側左側變化
- 點擊加減號左側數字角標變化
5.1 點擊左側串列聯動滑動右側
首先在LeftTypeAdapter內設定監聽器來進行介面回呼
/**
* 介面回呼
*/
interface LeftTypeClickListener{
//點擊布局
fun onItemClick(position: Int)
}
override fun onBindViewHolder(holder: LeftTypeAdapter.MyLeftViewHolder, position: Int) {
......
holder.typeLayout.setOnClickListener {
...
//寫個介面回呼來滑動右側recyclerview布局
leftTypeClickListener?.onItemClick(position)
}
}
然后在MainActivity里進行監聽
leftAdapter.setLeftTypeClickListener(this)
/**
* 左側串列的點擊事件
*/
override fun onItemClick(position: Int) {
leftClickType = true
//計算右側所對應的組資料,然后得到該組內的第一條資料所在的位置
var sum = 0
for (i in 0 until position) {
sum += data[i].productList.size
}
var layoutManager = rightRecyclerview.layoutManager as LinearLayoutManager
//將該item移動到第一項
layoutManager.scrollToPositionWithOffset(sum, 0)
Log.i(TAG, "onItemClick: 左側點擊右側聯動")
}
這里,當我們點擊左側串列時,先通過計算,得到該分類在右側所對應的組,然后得到該組內的第一項資料在右側串列內所對應的位置,
然后通過scrollToPositionWithOffset()方法來將該條資料移動到頂部,
這里使用scrollToPositionWithOffset()而不是scrollToPosition(),因為scrollToPositionWithOffset() 會把 Item 移動到可見 Item 的第一項,即使它已經在可見 Item 之中, 而
scrollToPosition只會把不在螢屏的 Item 移動到螢屏上,原來在RecyclerView上方沒顯示的 Item 移動到可見 Item 的第一項,在RecyclerView下方沒顯示的移動到螢屏可見 Item 的最后一項,已經顯示的 Item 不會移動,
效果:
5.2 滑動右側串列帶動左側串列滑動
主要是為右側串列添加滑動監聽器進行監聽
-
當右側串列滑動到底部時,左側串列直接選中最后一項并更新視圖
-
當是因為點擊左側串列聯動的滑動右側串列時,右側串列不做處理,這里用leftClickType欄位判斷
-
當主動滑動右側串列時,我們找到右側串列中的第一項,并判斷該物件所對應左側串列的分類,然后將該分類設定為選中狀態即可
具體內容如下述代碼所示
//右側選單滑動聯動左側選單選擇
right_menu.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
//當無法上滑時,即右側串列滑動到底部時,移動左側串列到最后一項,將左側的最后一項Item變為選中狀態
if (!recyclerView.canScrollVertically(1)) {
leftAdapter.setSelectedNum(data.size - 1)
}else{
//當右側主動滑動時,才聯動左側串列;左側串列帶動的右側串列滑動時,不再回傳來聯動左側串列
if (leftClickType) {
leftClickType = false
} else {
//當右側串列滑動時,獲取右側串列的第一條資料,找其所對應的左側串列的Title物件即:productItem
var position =
(right_menu.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
var productItem = getTitleByPosition(position, data)
//找到該productItem在左側串列對應的position
for (i in data.indices) {
if (data[i] === productItem) {
//左側串列配接器設定選中項并進行更新
leftAdapter.setSelectedNum(i)
break
}
}
}
}
}
})
/**
* 根據position位置來獲取相應的標題Bean !!!(用于滑動右側時聯動左側)
*/
fun getTitleByPosition(_position: Int, dataList: MutableList<ProductItem>): ProductItem? {
var sum = 0
for (productItem in dataList) {
sum += productItem.productList.size
if (_position < sum) {
return productItem
}
}
return null
}
class LeftTypeAdapter(private var dataList: MutableList<ProductItem>) :
RecyclerView.Adapter<LeftTypeAdapter.MyLeftViewHolder>() {
......
/**
* 選中左側區域,主要用于滑動右側時需要聯動左側串列
*/
fun setSelectedNum(selectedNum: Int) {
if (selectedNum in 0 until itemCount) {
selectPosition = selectedNum
notifyDataSetChanged()
}
}
......
}
效果
5.3 點擊右側加減號,左側串列數值變化
5.3.1這里我們定義一個購物車類,用來記錄添加的商品的數量,即記錄某類商品的總數量
class ShopCart {
//總共的商品數量
var totalAmount:Int = 0
//總共的商品價格
var totalPrice:BigDecimal = BigDecimal.valueOf(0.0)
//保存各個產品添加的數量,將產品作為key,因為每個產品都不一樣
val productNumMap: MutableMap<Product, Int> = mutableMapOf()
//保存同類產品的數量
val typeCountMap:MutableMap<String,Int> = mutableMapOf()
//商品數量添加
fun addProductNum(product: Product){
//該商品數量+1
if(productNumMap.containsKey(product)){
productNumMap[product] = productNumMap[product]!!+1
}else{
productNumMap[product] = 1
}
//該商品所在類別數量+1
//雖然是第一次添加商品,但對于parentId來說,第一次添加的商品這個所屬的類的數量可能為0
if(typeCountMap.containsKey(product.parentId)){
typeCountMap[product.parentId] = typeCountMap[product.parentId]!!+1
}else{
typeCountMap[product.parentId] = 1
}
//總體數量+1
totalAmount += 1
//總體價格
totalPrice = totalPrice.add(BigDecimal.valueOf(product.productMoney))
}
//商品數量減少
fun reduceProductNum(product: Product){
//該商品數量-1
productNumMap[product] = productNumMap[product]!!-1
//該商品所在的類別數量-1
typeCountMap[product.parentId] = typeCountMap[product.parentId]!!-1
//總體數量-1
totalAmount -= 1
//總體價格
totalPrice = totalPrice.subtract(BigDecimal.valueOf(product.productMoney))
}
}
5.3.2 右側串列配接器增加item內的加減號點擊事件
-
在
onBindViewHolder()方法里,我們根據傳來的shopCart實體來根據當前商品的數量來動態的更改右側串列加減號中間的數字, -
為加減號增加點擊事件,當點擊的時候更改shopCart內該商品的數量,并更新該RecyclerView,來實作右側串列加減號中間的數字更新,同時這里通過shopCartListener來呼叫
onReduce()和onAdd()方法,目的是來更改左側串列的商品類別數量
class RightProductAdapter(private var dataList: MutableList<Product>,private var shopCart: ShopCart) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
//已下單的個數顯示與隱藏
if (shopCart.productNumMap.containsKey(product)) {
if (shopCart.productNumMap[product]!! > 0) {
//數量不為零
productHolder.selectCount.text = shopCart.productNumMap[product].toString()
productHolder.selectCount.visibility = View.VISIBLE
productHolder.reduce.visibility = View.VISIBLE
} else {
//數量為0不顯示
productHolder.selectCount.visibility = View.INVISIBLE
productHolder.reduce.visibility = View.INVISIBLE
}
} else {
productHolder.selectCount.visibility = View.INVISIBLE
productHolder.reduce.visibility = View.INVISIBLE
}
//增加商品
productHolder.add.setOnClickListener {
product?.let { it1->
shopCart.addProductNum(it1)
notifyItemChanged(position)
shopCartListener?.onAdd(
it,
product.parentId,
shopCart.typeCountMap[product.parentId]!!
)
}
}
//減少商品
productHolder.reduce.setOnClickListener {
product?.let {it1->
shopCart.reduceProductNum(it1)
notifyItemChanged(position)
shopCartListener?.onReduce(
product.parentId,
shopCart.typeCountMap[product.parentId]!!
)
}
}
}
......
}
interface ShopCartListener {
fun onReduce(parentId:String, parentCount:Int)
fun onAdd(view: View, parentId: String, parentCount: Int)
}
/**
* 設定監聽器
*/
fun setShopCartListener(_shopCartListener: ShopCartListener) {
shopCartListener = _shopCartListener
}
5.3.3 左側串列類別數量更改
再回呼來更改購物車中的產品數量
class MainActivity : AppCompatActivity(),
LeftTypeAdapter.LeftTypeClickListener, RightProductAdapter.ShopCartListener {
//購物車物體類
private var shopCart = ShopCart()
override fun onCreate(savedInstanceState: Bundle?) {
....
rightAdapter = RightProductAdapter(productDataList,shopCart)
rightAdapter.setShopCartListener(this)
}
/**
* 減少
*/
override fun onReduce(parentId: String, parentCount: Int) {
//更新左側串列類別數量
leftAdapter.updateTypeCount(parentId,parentCount)
//更新購物車的數量
if(shopCart.totalAmount>0){
tv_shopping_cart_count.visibility = View.VISIBLE
tv_shopping_cart_count.text = shopCart.totalAmount.toString()
}else{
tv_shopping_cart_count.visibility = View.INVISIBLE
}
//更新購物車的價格
tv_shopping_cart_money.text = shopCart.totalPrice.toString()
}
/**
* 增加
*/
override fun onAdd(view: View, parentId: String, parentCount: Int) {
//更新左側串列類別數量
leftAdapter.updateTypeCount(parentId,parentCount)
//更新購物車的數量
if(shopCart.totalAmount>0){
tv_shopping_cart_count.visibility = View.VISIBLE
tv_shopping_cart_count.text = shopCart.totalAmount.toString()
}else{
tv_shopping_cart_count.visibility = View.INVISIBLE
}
//更新購物車的價格
tv_shopping_cart_money.text = shopCart.totalPrice.toString()
}
}
class LeftTypeAdapter(private var dataList: MutableList<ProductItem>) :
RecyclerView.Adapter<LeftTypeAdapter.MyLeftViewHolder>() {
//要更新的ID號
var mUpdateTypeId = ""
//要更新的ID號的數量
var mUpdateTypeCount = 0
/**
* 更新左側串列角標(用于右側添加或減少商品時它數量的更新)
*/
fun updateTypeCount(parentId:String,parentCount:Int){
mUpdateTypeId = parentId
mUpdateTypeCount = parentCount
notifyDataSetChanged()
}
}
5.3.4 效果
6. 添加資料效果
主要用到了二階貝塞爾曲線、屬性影片、插值器
-
傳入控制點和終點通過
quadTo()方法形成二階貝塞爾曲線路徑 -
使用LinearInterpolator插值器來形成勻速滑動的效果
-
使用valueAnimator屬性影片來實作將小圓球按照二階貝塞爾曲線路徑勻速進行移動的效果
具體詳見代碼
效果:
總結
按照以上步驟即可完成購物車的功能,剛開始沒什么頭目,但按照步驟,一部分一部分實作就挺簡單的,
其實本片內容主要參考了兩篇文章:
一Android 美團購物車效果,參考了它的購物車類,這個思路很好,它繪制懸浮頭部的方式是xml里固定有個View,然后通過影片的方式來顯示隱藏實作效果,它的右側串列滑動到底部左側串列最后一項不會選中,
二Android-使用RecyclerView的ItemDecoration 實作炫酷的 吸頂效果它的繪制是使用itemDecoration,但是它的onDrawover()方法使用了回圈判斷,其實我們只需要對第一個itemView進行繪制即可,
多看看別人的找到思路后學會用自己的方式進行優化才能進步, 如果本文對你有幫助,請別忘記三連,如果有不恰當的地方也請提出來,下篇文章見,
專案Github地址
參考文章
Android 美團購物車效果
Android-使用RecyclerView的ItemDecoration 實作炫酷的 吸頂效果
?貝塞爾曲線
?插值器
?自定義控制元件三部曲之影片篇(四)——ValueAnimator基本使用
Android Path測量工具:PathMeasure
BIgDecimal()和BigInteger()運算
recycleview獲取第一個和最后一個可見item的位置
Android Canvas的drawText()和文字居中方案
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/246201.html
標籤:其他
