历史tab 初步
This commit is contained in:
parent
5c19a45350
commit
14c9cfa4a7
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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 |
Loading…
Reference in New Issue