历史tab 初步

This commit is contained in:
renhaoting 2025-11-04 19:35:37 +08:00
parent 5c19a45350
commit 14c9cfa4a7
9 changed files with 315 additions and 3 deletions

View File

@ -27,8 +27,7 @@ import java.util.Date
class ChatSettingView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : android.widget.LinearLayout(context, attrs, defStyleAttr) {
defStyleAttr: Int = 0) : LinearLayout(context, attrs, defStyleAttr) {
private var mBinding = LayoutChatMenuViewBinding.inflate(LayoutInflater.from(context), this, true)

View File

@ -5,9 +5,10 @@ import androidx.fragment.app.viewModels
import com.alibaba.android.arouter.facade.annotation.Route
import com.alibaba.android.arouter.launcher.ARouter
import com.dylanc.loadingstateview.BgColorType
import com.gyf.immersionbar.ImmersionBar
import com.remax.visualnovel.R
import com.remax.visualnovel.app.base.BaseBindingFragment
import com.remax.visualnovel.databinding.FragmentMainHistoryBinding
import com.remax.visualnovel.ui.main.history.customui.beans.TabItem
import com.remax.visualnovel.utils.Routers
import com.remax.visualnovel.utils.StatusBarUtil3
import dagger.hilt.android.AndroidEntryPoint
@ -33,6 +34,27 @@ class HistoryFragment : BaseBindingFragment<FragmentMainHistoryBinding>() {
with (binding.toolbar) {
setPadding(paddingLeft, paddingTop + StatusBarUtil3.getStatusBarHeight(context), paddingRight, paddingBottom)
}
initTabLayout()
}
private fun initTabLayout() {
with (binding.tabLayout) {
val tabItems = listOf(
TabItem("Manga", R.mipmap.history_tab_manga),
TabItem("Motion Comics", R.mipmap.history_tab_comics),
TabItem("Character", R.mipmap.history_tab_books)
)
setTabItems(tabItems)
setOnTabSelectedListener { position ->
switchFragment(position)
}
setSelectedTab(0)
}
}
private fun switchFragment(fragmentIndex: Int) {
}
companion object {

View File

@ -0,0 +1,239 @@
package com.remax.visualnovel.ui.main.history.customui
import android.animation.AnimatorSet
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import com.remax.visualnovel.R
import com.remax.visualnovel.ui.main.history.customui.beans.TabConfig
import com.remax.visualnovel.ui.main.history.customui.beans.TabItem
class RoundAnimTabLayout @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {
private var tabConfig = TabConfig()
private val tabItems = mutableListOf<TabItem>()
private var selectedTabPosition = 0
private var onTabSelectedListener: ((Int) -> Unit)? = null
// 选中的Tab背景渐变
private val selectedTabGradient = GradientDrawable().apply {
shape = GradientDrawable.RECTANGLE
cornerRadius = tabConfig.cornerRadius
colors = intArrayOf(
0xFF6366F1.toInt(), // 紫色
0xFF3B82F6.toInt() // 蓝色
)
orientation = GradientDrawable.Orientation.LEFT_RIGHT
}
init {
orientation = HORIZONTAL
setWillNotDraw(false)
initAttributes(attrs)
setupLayout()
}
private fun initAttributes(attrs: AttributeSet?) {
attrs?.let {
val typedArray = context.obtainStyledAttributes(it, R.styleable.CustomTabLayout)
tabConfig = tabConfig.copy(
normalTextColor = typedArray.getColor(
R.styleable.CustomTabLayout_textColorNormal, Color.GRAY
),
selectedTextColor = typedArray.getColor(
R.styleable.CustomTabLayout_textColorSelected, Color.WHITE
),
textSize = typedArray.getDimension(
R.styleable.CustomTabLayout_textSize, 14f
),
iconSize = typedArray.getDimensionPixelSize(
R.styleable.CustomTabLayout_iconSize, 20
),
cornerRadius = typedArray.getDimension(
R.styleable.CustomTabLayout_tabCornerRadius, 20f
),
animationDuration = typedArray.getInt(
R.styleable.CustomTabLayout_animDuration, 300
).toLong(),
tabSpacing = typedArray.getDimensionPixelSize(
R.styleable.CustomTabLayout_tabSpacing, 8
)
)
typedArray.recycle()
}
// 更新渐变背景的圆角
selectedTabGradient.cornerRadius = tabConfig.cornerRadius
}
private fun setupLayout() {
background = createNormalBackground()
setPadding(tabConfig.tabSpacing, tabConfig.tabSpacing,
tabConfig.tabSpacing, tabConfig.tabSpacing)
}
private fun createNormalBackground(): Drawable {
return GradientDrawable().apply {
shape = GradientDrawable.RECTANGLE
cornerRadius = tabConfig.cornerRadius
setColor(Color.WHITE)
setStroke(1, Color.LTGRAY)
}
}
fun setTabItems(items: List<TabItem>) {
tabItems.clear()
tabItems.addAll(items)
removeAllViews()
createTabViews()
}
private fun createTabViews() {
tabItems.forEachIndexed { index, tabItem ->
val tabView = createTabView(tabItem, index)
addView(tabView)
// 设置权重使tab均匀分布
val params = LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f)
params.marginEnd = if (index < tabItems.size - 1) tabConfig.tabSpacing else 0
tabView.layoutParams = params
}
// 设置初始选中状态
setSelectedTab(selectedTabPosition, animate = false)
}
private fun createTabView(tabItem: TabItem, position: Int): View {
return LayoutInflater.from(context).inflate(R.layout.item_smooth_tab, this, false).apply {
val iconView = findViewById<ImageView>(R.id.ivTabIcon)
val textView = findViewById<TextView>(R.id.tvTabText)
// 设置图标和文字
iconView.setImageResource(tabItem.iconRes)
textView.text = tabItem.text
textView.setTextColor(tabConfig.normalTextColor)
textView.textSize = tabConfig.textSize / resources.displayMetrics.density
// 设置图标大小
iconView.layoutParams = iconView.layoutParams.apply {
width = tabConfig.iconSize
height = tabConfig.iconSize
}
// 设置点击事件
setOnClickListener {
setSelectedTab(position)
onTabSelectedListener?.invoke(position)
}
// 设置标签以便后续查找
tag = position
}
}
fun setSelectedTab(position: Int, animate: Boolean = true) {
if (position !in 0 until tabItems.size) return
val previousPosition = selectedTabPosition
selectedTabPosition = position
// 更新所有tab的状态
updateTabAppearance(previousPosition, position, animate)
}
private fun updateTabAppearance(previousPos: Int, newPos: Int, animate: Boolean) {
// 更新之前的选中tab
getTabViewAt(previousPos)?.let { previousView ->
updateTabViewAppearance(previousView, previousPos, isSelected = false, animate)
}
// 更新新的选中tab
getTabViewAt(newPos)?.let { newView ->
updateTabViewAppearance(newView, newPos, isSelected = true, animate)
}
}
private fun getTabViewAt(position: Int): View? {
return findViewWithTag(position)
}
private fun updateTabViewAppearance(tabView: View, position: Int,
isSelected: Boolean, animate: Boolean) {
val iconView = tabView.findViewById<ImageView>(R.id.ivTabIcon)
val textView = tabView.findViewById<TextView>(R.id.tvTabText)
tabItems[position].isSelected = isSelected
val targetBackground = if (isSelected) selectedTabGradient else null
val targetTextColor = if (isSelected) tabConfig.selectedTextColor else tabConfig.normalTextColor
val targetAlpha = if (isSelected) 1.0f else 0.6f
if (animate) {
// 背景颜色动画
val backgroundAnim = ValueAnimator.ofInt(0, 255).apply {
duration = tabConfig.animationDuration
addUpdateListener { animator ->
val alpha = animator.animatedValue as Int
if (isSelected) {
selectedTabGradient.alpha = alpha
tabView.background = selectedTabGradient
} else {
tabView.background = null
}
}
}
// 文字颜色动画
val textColorAnim = ValueAnimator.ofArgb(
textView.currentTextColor, targetTextColor
).apply {
duration = tabConfig.animationDuration
addUpdateListener { animator ->
textView.setTextColor(animator.animatedValue as Int)
}
}
// 透明度动画
val alphaAnim = ValueAnimator.ofFloat(iconView.alpha, targetAlpha).apply {
duration = tabConfig.animationDuration
addUpdateListener { animator ->
val alpha = animator.animatedValue as Float
iconView.alpha = alpha
}
}
// 同时执行所有动画
AnimatorSet().apply {
playTogether(backgroundAnim, textColorAnim, alphaAnim)
start()
}
} else {
// 无动画直接设置
tabView.background = targetBackground
textView.setTextColor(targetTextColor)
iconView.alpha = targetAlpha
}
}
fun setOnTabSelectedListener(listener: (Int) -> Unit) {
this.onTabSelectedListener = listener
}
fun getSelectedTabPosition(): Int = selectedTabPosition
fun getTabCount(): Int = tabItems.size
}

View File

@ -0,0 +1,26 @@
package com.remax.visualnovel.ui.main.history.customui.beans
import android.graphics.Color
import androidx.annotation.DrawableRes
import com.remax.visualnovel.R
import com.remax.visualnovel.utils.ResUtil
import com.remax.visualnovel.utils.ResUtil.dpToPx
data class TabItem(
val text: String,
@DrawableRes val iconRes: Int = 0,
var isSelected: Boolean = false
)
data class TabConfig(
val normalTextColor: Int = ResUtil.getColor(R.color.gray6),
val selectedTextColor: Int = Color.WHITE,
val textSize: Float = 14f,
val iconSize: Int = 20.dpToPx,
val cornerRadius: Float = 20f.dpToPx.toFloat(),
val animationDuration: Long = 300,
val tabSpacing: Int = 8.dpToPx,
val tabPadding: Int = 16.dpToPx,
val selectedBackground: Int = Color.BLUE
)

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"
android:paddingVertical="@dimen/dp_12"
android:paddingHorizontal="@dimen/dp_16">
<ImageView
android:id="@+id/ivTabIcon"
android:layout_width="@dimen/dp_20"
android:layout_height="@dimen/dp_20"
android:layout_marginEnd="@dimen/dp_8"
android:alpha="0.6"
android:scaleType="centerInside" />
<TextView
android:id="@+id/tvTabText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/gray6"
android:textSize="@dimen/sp_14"
android:fontFamily="sans-serif-medium" />
</LinearLayout>

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 930 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB