历史tab 初步
This commit is contained in:
parent
5c19a45350
commit
14c9cfa4a7
|
|
@ -27,8 +27,7 @@ import java.util.Date
|
||||||
class ChatSettingView @JvmOverloads constructor(
|
class ChatSettingView @JvmOverloads constructor(
|
||||||
context: Context,
|
context: Context,
|
||||||
attrs: AttributeSet? = null,
|
attrs: AttributeSet? = null,
|
||||||
defStyleAttr: Int = 0
|
defStyleAttr: Int = 0) : LinearLayout(context, attrs, defStyleAttr) {
|
||||||
) : android.widget.LinearLayout(context, attrs, defStyleAttr) {
|
|
||||||
|
|
||||||
private var mBinding = LayoutChatMenuViewBinding.inflate(LayoutInflater.from(context), this, true)
|
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.facade.annotation.Route
|
||||||
import com.alibaba.android.arouter.launcher.ARouter
|
import com.alibaba.android.arouter.launcher.ARouter
|
||||||
import com.dylanc.loadingstateview.BgColorType
|
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.app.base.BaseBindingFragment
|
||||||
import com.remax.visualnovel.databinding.FragmentMainHistoryBinding
|
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.Routers
|
||||||
import com.remax.visualnovel.utils.StatusBarUtil3
|
import com.remax.visualnovel.utils.StatusBarUtil3
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
|
@ -33,6 +34,27 @@ class HistoryFragment : BaseBindingFragment<FragmentMainHistoryBinding>() {
|
||||||
with (binding.toolbar) {
|
with (binding.toolbar) {
|
||||||
setPadding(paddingLeft, paddingTop + StatusBarUtil3.getStatusBarHeight(context), paddingRight, paddingBottom)
|
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 {
|
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