feat: 专题合集新增自定义设置—客户端 https://jira.shanqu.cc/browse/GHZSCY-7496

This commit is contained in:
叶子维
2025-04-27 11:03:31 +08:00
parent 2e806d77e1
commit d874b91b7c
55 changed files with 1964 additions and 467 deletions

View File

@ -2,12 +2,10 @@ package com.gh.common.util
import android.os.Bundle
import androidx.fragment.app.Fragment
import com.therouter.TheRouter
import com.gh.common.iinterface.ISuperiorChain
import com.gh.gamecenter.amway.AmwayFragment
import com.gh.gamecenter.category2.CategoryV2Fragment
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.provider.IHelpAndFeedbackProvider
import com.gh.gamecenter.discovery.DiscoveryFragment
@ -38,6 +36,7 @@ import com.gh.gamecenter.toolbox.ToolboxFragment
import com.gh.gamecenter.video.detail.HomeVideoFragment
import com.gh.gamecenter.wrapper.ToolbarWrapperFragment
import com.halo.assistant.fragment.WebFragment
import com.therouter.TheRouter
/**
* 通用跳转Fragment方法
@ -148,6 +147,15 @@ object ViewPagerFragmentHelper {
bundle.putString(EntranceConsts.KEY_QUESTIONS_ID, linkEntity.link)
NewQuestionDetailFragment().with(bundle)
}
// 专题合集详情页
TYPE_COLUMN_COLLECTION -> {
bundle.putString(EntranceConsts.KEY_COLLECTION_ID, linkEntity.link)
bundle.putInt(EntranceConsts.KEY_POSITION, 0)
bundle.putString(EntranceConsts.KEY_COLUMNNAME, linkEntity.text)
bundle.putBoolean(EntranceConsts.KEY_IS_COLUMN_COLLECTION, true)
bundle.putString(EntranceConsts.KEY_SUBJECT_TYPE, "tab")
ColumnCollectionDetailFragment().with(bundle)
}
// 其他原来带Toolbar的Fragment
else -> createToolbarWrapperFragment(bundle, linkEntity, isTabWrapper)
}
@ -177,15 +185,6 @@ object ViewPagerFragmentHelper {
bundle.putString(EntranceConsts.KEY_SUBJECT_TYPE, "detail")
bundle.putBoolean(EntranceConsts.KEY_SHOW_DOWNLOAD_MENU, !isTabWrapper)
}
// 专题合集详情页
TYPE_COLUMN_COLLECTION -> {
className = ColumnCollectionDetailFragment::class.java.name
bundle.putString(EntranceConsts.KEY_COLLECTION_ID, entity.link)
bundle.putInt(EntranceConsts.KEY_POSITION, 0)
bundle.putString(EntranceConsts.KEY_COLUMNNAME, entity.text)
bundle.putBoolean(EntranceConsts.KEY_IS_COLUMN_COLLECTION, true)
bundle.putString(EntranceConsts.KEY_SUBJECT_TYPE, "tab")
}
// 开服表
TYPE_SERVER -> {
className = GameServersPublishFragment::class.java.name

View File

@ -1,18 +1,23 @@
package com.gh.common.view
import android.content.Context
import android.graphics.Typeface
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CheckedTextView
import android.widget.LinearLayout
import android.widget.PopupWindow
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.forEach
import androidx.core.view.setPadding
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.setDrawableEnd
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.toDrawable
import com.gh.gamecenter.entity.SubjectSettingEntity
import com.google.android.flexbox.FlexboxLayout
@ -30,11 +35,15 @@ class ConfigFilterView @JvmOverloads constructor(
var recommendedTv: TextView
var updateTv: TextView //更新
var container: View
private var dot1: View
private var dot2: View
private var dot3: View
var sizeFilterArray: ArrayList<SubjectSettingEntity.Size>? = null
private var sizeFilterArray: ArrayList<SubjectSettingEntity.Size>? = null
private var mOnConfigFilterSetupListener: OnConfigFilterSetupListener? = null
private var mSelectionTvList: ArrayList<TextView>
private var highlightedSortedType: SortType? = null
init {
View.inflate(context, R.layout.layout_config_filter, this)
@ -45,6 +54,9 @@ class ConfigFilterView @JvmOverloads constructor(
updateTv = findViewById(R.id.updateTv)
recommendedTv = findViewById(R.id.recommended_tv)
container = findViewById(R.id.config_controller)
dot1 = findViewById(R.id.dot1)
dot2 = findViewById(R.id.dot2)
dot3 = findViewById(R.id.dot3)
mSelectionTvList = arrayListOf(newestTv, ratingTv, updateTv, recommendedTv)
@ -54,24 +66,30 @@ class ConfigFilterView @JvmOverloads constructor(
}
ratingTv.setOnClickListener {
highlightedSortedType = SortType.RATING
mOnConfigFilterSetupListener?.onSetupSortType(SortType.RATING)
updateHighlightedTextView(ratingTv)
}
updateTv.setOnClickListener {
highlightedSortedType = SortType.UPDATE
mOnConfigFilterSetupListener?.onSetupSortType(SortType.UPDATE)
updateHighlightedTextView(updateTv)
}
newestTv.setOnClickListener {
highlightedSortedType = SortType.NEWEST
mOnConfigFilterSetupListener?.onSetupSortType(SortType.NEWEST)
updateHighlightedTextView(newestTv)
}
recommendedTv.setOnClickListener {
highlightedSortedType = SortType.RECOMMENDED
mOnConfigFilterSetupListener?.onSetupSortType(SortType.RECOMMENDED)
updateHighlightedTextView(recommendedTv)
}
updateAllTextView(SortType.UPDATE)
}
private fun updateHighlightedTextView(highlightedTv: TextView) {
@ -80,14 +98,17 @@ class ConfigFilterView @JvmOverloads constructor(
}
}
fun updateAllTextView(sortType: SortType) {
when (sortType) {
SortType.RECOMMENDED -> updateHighlightedTextView(recommendedTv)
SortType.NEWEST -> updateHighlightedTextView(newestTv)
SortType.RATING -> updateHighlightedTextView(ratingTv)
SortType.UPDATE -> updateHighlightedTextView(updateTv)
fun updateAllTextView(sortType: SortType? = highlightedSortedType) {
highlightedSortedType = sortType
sortType?.let {
when (it) {
SortType.RECOMMENDED -> updateHighlightedTextView(recommendedTv)
SortType.NEWEST -> updateHighlightedTextView(newestTv)
SortType.RATING -> updateHighlightedTextView(ratingTv)
SortType.UPDATE -> updateHighlightedTextView(updateTv)
}
mSizeTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
}
mSizeTv.setTextColor(com.gh.gamecenter.common.R.color.text_757575.toColor(context))
}
fun setOnConfigSetupListener(onConfigFilterSetupListener: OnConfigFilterSetupListener) {
@ -96,11 +117,11 @@ class ConfigFilterView @JvmOverloads constructor(
private fun toggleHighlightedTextView(targetTextView: TextView, highlightIt: Boolean) {
if (highlightIt) {
targetTextView.background = R.drawable.bg_tag_text.toDrawable()
targetTextView.setTextColor(com.gh.gamecenter.common.R.color.text_white.toColor(context))
targetTextView.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(context))
targetTextView.setTypeface(Typeface.DEFAULT, Typeface.BOLD)
} else {
targetTextView.background = null
targetTextView.setTextColor(com.gh.gamecenter.common.R.color.text_757575.toColor(context))
targetTextView.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
targetTextView.setTypeface(Typeface.DEFAULT, Typeface.NORMAL)
}
}
@ -112,8 +133,8 @@ class ConfigFilterView @JvmOverloads constructor(
}
private fun showSelectionPopupWindow(containerView: View, sizeTv: TextView, sizeText: String) {
sizeTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(sizeTv.context))
sizeTv.setDrawableEnd(R.drawable.ic_filter_arrow_up)
sizeTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(sizeTv.context))
sizeTv.setDrawableEnd(R.drawable.ic_auxiliary_arrow_up_primary_8)
val inflater = LayoutInflater.from(sizeTv.context)
val layout = inflater.inflate(R.layout.layout_filter_size, null)
@ -136,43 +157,39 @@ class ConfigFilterView @JvmOverloads constructor(
}
}
flexboxLayout.setOnClickListener { }
backgroundView.setOnClickListener {
popupWindow.dismiss()
}
for (size in sizeFilterArray!!) {
val item = inflater.inflate(R.layout.item_filter_size, flexboxLayout, false)
val item = inflater.inflate(R.layout.item_config_filter_size, flexboxLayout, false)
// 单列 4 个,强行设置宽度为屏幕的 1/4
val width = sizeTv.context.resources.displayMetrics.widthPixels / 4
val width = (sizeTv.context.resources.displayMetrics.widthPixels - 56F.dip2px()) / 4
val height = item.layoutParams.height
item.layoutParams = ViewGroup.LayoutParams(width, height)
flexboxLayout.addView(item)
val tv = item.findViewById<TextView>(R.id.size_tv)
val tv = item.findViewById<CheckedTextView>(R.id.size_tv)
tv.text = size.text
if (sizeText == size.text) {
toggleHighlightedTextView(tv, true)
} else {
toggleHighlightedTextView(tv, false)
}
tv.isChecked = sizeText == size.text
tv.tag = size.text
item.setOnClickListener {
toggleHighlightedTextView(tv, true)
tv.setOnClickListener {
flexboxLayout.forEach { checkedTv ->
(checkedTv as CheckedTextView).isChecked = checkedTv == tv
}
popupWindow.dismiss()
sizeTv.text = size.text
mOnConfigFilterSetupListener?.onSetupSortSize(size)
}
}
popupWindow.setOnDismissListener {
sizeTv.setTextColor(com.gh.gamecenter.common.R.color.text_757575.toColor(sizeTv.context))
sizeTv.setDrawableEnd(R.drawable.ic_filter_arrow_down)
sizeTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(sizeTv.context))
sizeTv.setDrawableEnd(R.drawable.ic_auxiliary_arrow_down_8)
mPopupWindow = null
}
@ -193,6 +210,49 @@ class ConfigFilterView @JvmOverloads constructor(
}
}
fun initSubjectFilterView(subjectSetting: SubjectSettingEntity) {
ratingTv.visibility = View.VISIBLE
if (subjectSetting.filterOptions.size > 1) {
// 重排序
subjectSetting.filterOptions.forEachIndexed { index, s ->
when (index) {
0 -> updateTv.text = s
1 -> recommendedTv.text = s
2 -> newestTv.text = s
3 -> ratingTv.text = s
}
}
} else {
updateTv.setPadding(0)
updateTv.setTypeface(Typeface.DEFAULT, Typeface.NORMAL)
updateTv.isClickable = false
updateTv.text = when (subjectSetting.filterOptions.first()) {
"推荐" -> "根据光环推荐排序"
"最新" -> "根据游戏上新排序"
"评分" -> "根据游戏评分排序"
"更新" -> "根据更新时间排序"
else -> subjectSetting.filterOptions.first()
}
}
// 隐藏相关选项
updateTv.goneIf(subjectSetting.filterOptions.isEmpty())
recommendedTv.goneIf(subjectSetting.filterOptions.size <= 1)
dot1.goneIf(subjectSetting.filterOptions.size <= 1)
newestTv.goneIf(subjectSetting.filterOptions.size <= 2)
dot2.goneIf(subjectSetting.filterOptions.size <= 2)
ratingTv.goneIf(subjectSetting.filterOptions.size <= 3)
dot3.goneIf(subjectSetting.filterOptions.size <= 3)
sizeFilterArray = subjectSetting.filterSizes
if (subjectSetting.filterOptions.size == 1) {
updateTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
updateTv.setTypeface(Typeface.DEFAULT, Typeface.NORMAL)
highlightedSortedType = null
}
}
interface OnConfigFilterSetupListener {
fun onShowSortSize()
fun onSetupSortSize(sortSize: SubjectSettingEntity.Size)

View File

@ -7,5 +7,8 @@ class GameColumnCollection(
val id: String = "",
val name: String = "",
// 取值为 "1-1" 或 "1-2" 或 "top" 相应地代表 1行1个 或 1行2个 或 排行榜
val style: String = ""
val style: String = "",
val custom: Boolean = false, // 自定义设置
@SerializedName("custom_size")
val customSize: Int = 0 // 默认显示前X个专题
)

View File

@ -7,7 +7,7 @@ import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
@Parcelize
class SubjectData(
data class SubjectData(
// 入口必填
var subjectId: String?,
var subjectName: String?,
@ -21,13 +21,28 @@ class SubjectData(
var subjectStyle: String = "",
var showDetailSubtitle: Boolean = false,
var showDetailIconSubscript: Boolean = false,
var customLimit: String = "", // unlimited无限制、forbidden禁止移出
var requireUpdateSetting: Boolean = false, // 多专题页面需要专题页面自行获取专题配置
var isAdData: Boolean = false,
var adId: String = "", // 广告ID(本地字段),不为空时为广告专题
var codeId: String = "" // 广告CODE_ID(本地字段),不为空时为广告专题
var codeId: String = "", // 广告CODE_ID(本地字段),不为空时为广告专题
var tag: String = "" // 分类标签,埋点用
) : Parcelable, Cloneable {
@IgnoredOnParcel
val isForbidden
get() = customLimit == "forbidden"
@IgnoredOnParcel
val sortChinese
get() = when {
sort.contains("publish") -> "最新"
sort.contains("star") -> "评分"
sort.contains("update") -> "更新"
else -> "推荐"
}
@IgnoredOnParcel
val subjectStyleChinese: String
get() = CustomPageItem.subjectTypeToComponentStyle[subjectStyle] ?: ""

View File

@ -13,7 +13,7 @@ class SubjectSettingEntity(
@SerializedName("type")
var typeEntity: TypeEntity = TypeEntity(),
var tag: String = "",
var filter: String = "", // rows: off/on
var filter: String = "", // off/on
var order: Boolean = false, // 是否显示序号
@SerializedName("brief_style")
@ -34,6 +34,9 @@ class SubjectSettingEntity(
private val _showDetailIconSubscript: Boolean? = null
) : Parcelable {
val isFilterEnabled: Boolean
get() = filter == "on"
val showDetailSubtitle: Boolean
get() = _showDetailSubtitle ?: false

View File

@ -2,7 +2,7 @@ package com.gh.gamecenter.game.columncollection.detail
import android.os.Bundle
import android.view.View
import androidx.lifecycle.ViewModelProviders
import androidx.core.view.isVisible
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.common.baselist.LazyListFragment
@ -14,6 +14,8 @@ import com.gh.gamecenter.common.json.json
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.observeNonNull
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.viewModelProviderFromParent
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.databinding.FragmentColumnCollectionDetailBinding
import com.gh.gamecenter.entity.GameColumnCollection
import com.gh.gamecenter.entity.SubjectData
@ -23,6 +25,7 @@ class ColumnCollectionDetailFragment : LazyListFragment<LinkEntity, ColumnCollec
private var mAdapter: ColumnCollectionDetailAdapter? = null
private var mBinding: FragmentColumnCollectionDetailBinding? = null
private var mFragment: SubjectTabFragment? = null
private var mTabIndex = -1
private var mBasicExposureSourceList: ArrayList<ExposureSource>? = null
@ -56,6 +59,11 @@ class ColumnCollectionDetailFragment : LazyListFragment<LinkEntity, ColumnCollec
override fun onFragmentFirstVisible() {
super.onFragmentFirstVisible()
if (mIsFromMainWrapper) {
DisplayUtils.setLightStatusBar(requireActivity(), !mIsDarkModeOn)
mBinding?.statusBar?.isVisible = true
}
mListViewModel.getGameColumnCollection()
mListViewModel.columnCollection.observeNonNull(this) {
setNavigationTitle(it.name)
@ -91,7 +99,7 @@ class ColumnCollectionDetailFragment : LazyListFragment<LinkEntity, ColumnCollec
}
override fun onChanged(list: MutableList<LinkEntity>?) {
if (list != null && list.isNotEmpty()) {
if (!list.isNullOrEmpty()) {
showSubjectTab(list)
}
}
@ -115,25 +123,28 @@ class ColumnCollectionDetailFragment : LazyListFragment<LinkEntity, ColumnCollec
isOrder = false,
requireUpdateSetting = true,
filter = "type:全部", // 默认显示大图
subjectType = subjectType
subjectType = subjectType,
customLimit = link.customLimit
)
)
}
val fragment = childFragmentManager.findFragmentByTag(SubjectTabFragment::class.java.name)
mFragment = childFragmentManager.findFragmentByTag(SubjectTabFragment::class.java.name) as? SubjectTabFragment
?: SubjectTabFragment()
val bundle = arguments
mListViewModel.columnCollection.value?.let {
bundle?.putString(EntranceConsts.KEY_COLUMN_COLLECTION_ID, it.id)
bundle?.putString(EntranceConsts.KEY_COLUMN_COLLECTION_NAME, it.name)
bundle?.putString(EntranceConsts.KEY_COLUMN_COLLECTION_STYLE, it.style)
bundle?.putBoolean(EntranceConsts.KEY_COLUMN_COLLECTION_CUSTOM, it.custom)
bundle?.putInt(EntranceConsts.KEY_COLUMN_COLLECTION_CUSTOM_SIZE, it.customSize)
}
bundle?.putParcelableArrayList(EntranceConsts.KEY_DATA, subjectDataList)
bundle?.putBoolean(EntranceConsts.KEY_IS_COLUMN_COLLECTION, true)
fragment.arguments = bundle
mFragment!!.arguments = bundle
mBinding?.placeholder?.visibility = View.VISIBLE
childFragmentManager.beginTransaction().replace(R.id.placeholder, fragment, SubjectTabFragment::class.java.name)
childFragmentManager.beginTransaction().replace(R.id.placeholder, mFragment!!, SubjectTabFragment::class.java.name)
.commitAllowingStateLoss()
}
@ -167,9 +178,18 @@ class ColumnCollectionDetailFragment : LazyListFragment<LinkEntity, ColumnCollec
arguments?.getString(EntranceConsts.KEY_COLLECTION_ID)
?: ""
)
return ViewModelProviders.of(this, factory).get(ColumnCollectionDetailViewModel::class.java)
return viewModelProviderFromParent(factory, arguments?.getString(EntranceConsts.KEY_COLLECTION_ID) ?: "")
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
if (mIsFromMainWrapper) {
DisplayUtils.setLightStatusBar(requireActivity(), !mIsDarkModeOn)
}
}
override fun onBackPressed(): Boolean = mFragment?.onBackPressed() ?: false
companion object {
const val TYPE_QQ_MINI_GAME_COLUMN = "qq_mini_game_column"
const val TYPE_WECHAT_MINI_GAME_COLUMN = "wechat_mini_game_column"

View File

@ -42,6 +42,7 @@ class SubjectAdapter(
private val mSubjectViewModel: SubjectViewModel?,
private val mEntrance: String?,
private val mIsColumnCollection: Boolean,
private val mColumnCollectionCustomPosition: Int = -1,
private val mCollectionId: String,
private val mCollectionName: String,
private val mCollectionStyle: String
@ -217,9 +218,10 @@ class SubjectAdapter(
mSubjectViewModel?.subjectSettingLD?.value?.let { subjectSetting ->
subjectStyle = subjectSetting.typeEntity.layout
isFilterOn = subjectSetting.filter == "on"
isFilterOn = subjectSetting.isFilterEnabled
}
gameEntity.sequence = position
val exposureEvent = generateExposureEvent(gameEntity, subjectStyle, isFilterOn)
val humanReadablePosition = position + 1
mExposureEventSparseArray.put(position, exposureEvent)
@ -266,7 +268,10 @@ class SubjectAdapter(
gameColumnName = subjectData.subjectName ?: "",
gameId = gameEntity.id,
gameName = gameEntity.name ?: "",
text = "游戏"
text = "游戏",
gameTag = mViewModel.selectedLabelList.ifEmpty { listOf(mViewModel.subjectData.tag) },
sort = mViewModel.subjectData.sortChinese,
inclusionSize = mViewModel.selectedFilterSize.text ?: ""
)
}
}
@ -326,7 +331,10 @@ class SubjectAdapter(
gameColumnName = subjectData.subjectName ?: "",
gameId = gameEntity.id,
gameName = gameEntity.name ?: "",
text = "按钮"
text = "按钮",
gameTag = mViewModel.selectedLabelList.ifEmpty { listOf(mViewModel.subjectData.tag) },
sort = mViewModel.subjectData.sortChinese,
inclusionSize = mViewModel.selectedFilterSize.text ?: ""
)
}
@ -446,7 +454,10 @@ class SubjectAdapter(
linkId = linkEntity.link ?: "",
linkText = linkEntity.text ?: "",
linkType = linkEntity.type ?: "",
text = "头图"
text = "头图",
gameTag = mViewModel.selectedLabelList.ifEmpty { listOf(mViewModel.subjectData.tag) },
sort = mViewModel.subjectData.sortChinese,
inclusionSize = mViewModel.selectedFilterSize.text ?: ""
)
}
}
@ -471,13 +482,12 @@ class SubjectAdapter(
"$mCollectionName+${CustomPageItem.collectionTypeToComponentName[mCollectionStyle] ?: mCollectionStyle}+$mCollectionId"
)
)
exposureSourceList.add(ExposureSource("合集详情"))
exposureSourceList.add(
ExposureSource(
"专题",
"${subjectData.subjectName}+${subjectData.subjectStyleChinese}+${subjectData.subjectId}"
)
)
exposureSourceList.add(ExposureSource(if (mColumnCollectionCustomPosition == -1) "合集详情" else "自定义合集详情"))
var value = "${subjectData.subjectName}+${subjectData.subjectStyleChinese}+${subjectData.subjectId}"
if (mColumnCollectionCustomPosition != -1) {
value += "+${mColumnCollectionCustomPosition}"
}
exposureSourceList.add(ExposureSource("专题", value))
} else {
var sourceString = ""
exposureSourceList.add(
@ -493,12 +503,7 @@ class SubjectAdapter(
sourceString = filter
if (isFilterOn) {
val sort = when {
subjectData.sort.contains("publish") -> "最新"
subjectData.sort.contains("star") -> "评分"
else -> "最热"
}
val sort = subjectData.sortChinese
val filterSize = mViewModel.selectedFilterSize.text
sourceString =
@ -516,12 +521,7 @@ class SubjectAdapter(
}
if (isFilterOn) {
val sort = when {
subjectData.sort.contains("publish") -> "最新"
subjectData.sort.contains("star") -> "评分"
else -> "推荐"
}
val sort = subjectData.sortChinese
val filterSize = mViewModel.selectedFilterSize.text
sourceString =
@ -532,12 +532,7 @@ class SubjectAdapter(
}
else -> {
val sort = when {
subjectData.sort.contains("publish") -> "最新"
subjectData.sort.contains("star") -> "评分"
else -> "推荐"
}
val sort = subjectData.sortChinese
val filterSize = mViewModel.selectedFilterSize.text
if (isFilterOn) {

View File

@ -125,7 +125,7 @@ open class SubjectFragment : LazyFragment() {
}
val bundle = arguments
bundle?.putParcelable(EntranceConsts.KEY_SUBJECT_DATA, mViewModel?.subjectData)
bundle?.putParcelable(SubjectSettingEntity::class.java.simpleName, entity)
bundle?.putParcelable(EntranceConsts.KEY_SUBJECT_SETTING_DATA, entity)
fragment.arguments = bundle
transaction.replace(R.id.subject_content, fragment, tag)
transaction.commitAllowingStateLoss()

View File

@ -6,6 +6,8 @@ import androidx.recyclerview.widget.RecyclerView
import com.ethanhua.skeleton.Skeleton
import com.gh.common.exposure.ExposureListener
import com.gh.common.util.DialogUtils
import com.gh.common.view.ConfigFilterView
import com.gh.common.view.ConfigFilterView.SortType
import com.gh.common.xapk.XapkInstaller
import com.gh.common.xapk.XapkUnzipStatus
import com.gh.download.DownloadManager
@ -14,11 +16,12 @@ import com.gh.gamecenter.common.baselist.LazyListFragment
import com.gh.gamecenter.common.baselist.LoadType
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.viewModelProviderFromParent
import com.gh.gamecenter.common.view.SpacingItemDecoration
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.core.utils.UrlFilterUtils
import com.gh.gamecenter.entity.SubjectData
import com.gh.gamecenter.entity.SubjectSettingEntity
import com.gh.gamecenter.eventbus.EBDownloadStatus
@ -44,6 +47,8 @@ open class SubjectListFragment : LazyListFragment<GameEntity, SubjectListViewMod
private var mCollectionName = ""
private var mCollectionStyle = ""
private var mScrollTop = false
private var mMaxSize = ""
private var mMinSize = ""
var selectedLabelList = arrayListOf<String>()
@ -79,6 +84,7 @@ open class SubjectListFragment : LazyListFragment<GameEntity, SubjectListViewMod
mSubjectViewModel,
arguments?.getString(EntranceConsts.KEY_ENTRANCE),
mIsColumnCollection,
arguments?.getInt(EntranceConsts.KEY_COLUMN_COLLECTION_CUSTOM_POSITION, -1) ?: -1,
mCollectionId,
mCollectionName,
mCollectionStyle
@ -120,10 +126,14 @@ open class SubjectListFragment : LazyListFragment<GameEntity, SubjectListViewMod
mCollectionName = arguments?.getString(EntranceConsts.KEY_COLUMN_COLLECTION_NAME, "") ?: ""
mCollectionStyle = arguments?.getString(EntranceConsts.KEY_COLUMN_COLLECTION_STYLE, "") ?: ""
super.onFragmentFirstVisible()
(mListViewModel as SubjectListViewModel).lastPageDataMap =
arguments?.get(SubjectFragment.LAST_PAGE_DATA) as? HashMap<String, String>
(mListViewModel as SubjectListViewModel).run {
lastPageDataMap =
arguments?.get(SubjectFragment.LAST_PAGE_DATA) as? HashMap<String, String>
arguments?.getString(EntranceConsts.KEY_SUBJECT_SORT_SIZE)?.let { selectedFilterSize = SubjectSettingEntity.Size(text = it) }
}
mCachedView?.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_surface.toColor(requireContext()))
initFilterView()
val skeletonLayoutId = when (arguments?.getString(EntranceConsts.KEY_SUBJECT_TYPE) ?: "") {
"detail" -> R.layout.fragment_subject_detail_skeleton
"tab" -> R.layout.fragment_subject_tab_skeleton
@ -141,7 +151,62 @@ open class SubjectListFragment : LazyListFragment<GameEntity, SubjectListViewMod
mListViewModel.load(LoadType.REFRESH)
}
override fun getRealLayoutId() = R.layout.fragment_list_base_skeleton
private fun initFilterView() {
val subjectSetting= arguments?.getParcelable<SubjectSettingEntity>(EntranceConsts.KEY_SUBJECT_SETTING_DATA) ?: return
val originalFilter = mListViewModel.subjectData.filter
mCachedView?.findViewById<ConfigFilterView>(R.id.filterView)?.run {
goneIf(!subjectSetting.isFilterEnabled) {
initSubjectFilterView(subjectSetting)
when (updateTv.text) {
"更新" -> mListViewModel.subjectData.sort = UrlFilterUtils.getFilterQuery("update_time", "-1")
"最新" -> mListViewModel.subjectData.sort = UrlFilterUtils.getFilterQuery("publish", "-1")
"评分" -> mListViewModel.subjectData.sort = UrlFilterUtils.getFilterQuery("star", "-1")
else -> mListViewModel.subjectData.sort = UrlFilterUtils.getFilterQuery("position", "1")
}
setOnConfigSetupListener(object :
ConfigFilterView.OnConfigFilterSetupListener {
override fun onShowSortSize() {}
override fun onSetupSortSize(sortSize: SubjectSettingEntity.Size) {
mMinSize = sortSize.min.toString()
mMaxSize = sortSize.max.toString()
var filter = originalFilter.ifEmpty { "type:全部" }
if (mMinSize.isNotEmpty() && mMinSize != "-1") {
filter += ",${UrlFilterUtils.getFilterQuery("min_size", mMinSize)}"
}
if (mMaxSize.isNotEmpty() && mMaxSize != "-1") {
filter += ",${UrlFilterUtils.getFilterQuery("max_size", mMaxSize)}"
}
refreshPage(filter = filter, size = sortSize)
}
override fun onSetupSortType(sortType: SortType) {
val clickTv = when (sortType) {
SortType.RATING -> ratingTv
SortType.NEWEST -> newestTv
SortType.UPDATE -> updateTv
else -> recommendedTv
}
val sort = when (clickTv.text) {
"更新" -> UrlFilterUtils.getFilterQuery("update_time", "-1")
"最新" -> UrlFilterUtils.getFilterQuery("publish", "-1")
"评分" -> UrlFilterUtils.getFilterQuery("star", "-1")
else -> UrlFilterUtils.getFilterQuery("position", "1")
}
if (mListViewModel.subjectData.sort != sort) {
refreshPage(sort = sort)
}
}
})
}
}
}
override fun getRealLayoutId() = R.layout.fragment_subject_list
override fun initRealView() {
super.initRealView()
@ -161,7 +226,11 @@ open class SubjectListFragment : LazyListFragment<GameEntity, SubjectListViewMod
mListRv?.addOnScrollListener(mExposureListener)
}
fun refreshPage(filter: String, sort: String, size: SubjectSettingEntity.Size) {
fun refreshPage(
filter: String = mListViewModel.subjectData.filter,
sort: String = mListViewModel.subjectData.sort,
size: SubjectSettingEntity.Size = mListViewModel.selectedFilterSize
) {
mListViewModel.selectedFilterSize = size
mListViewModel.selectedLabelList = selectedLabelList
mListViewModel.subjectData.filter = filter
@ -182,10 +251,6 @@ open class SubjectListFragment : LazyListFragment<GameEntity, SubjectListViewMod
}
}
override fun onResume() {
super.onResume()
}
override fun onFragmentResume() {
super.onFragmentResume()
DownloadManager.getInstance().addObserver(dataWatcher)
@ -230,5 +295,10 @@ open class SubjectListFragment : LazyListFragment<GameEntity, SubjectListViewMod
override fun onDarkModeChanged() {
super.onDarkModeChanged()
mAdapter?.let { it.notifyItemRangeChanged(0, it.itemCount) }
mCachedView?.findViewById<ConfigFilterView>(R.id.filterView)?.run {
if (isVisible) {
updateAllTextView()
}
}
}
}

View File

@ -15,7 +15,6 @@ import com.gh.gamecenter.entity.SubjectSettingEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.PageLocation
import com.gh.gamecenter.feature.entity.TagStyleEntity
import com.gh.gamecenter.retrofit.RetrofitManager
import io.reactivex.Observable
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
@ -29,7 +28,7 @@ open class SubjectListViewModel(
var exposureSourceList: List<ExposureSource>?
) : ListViewModel<GameEntity, GameEntity>(application) {
// 供专题类型为 rows 时统计用
// 供统计用
var selectedLabelList = arrayListOf<String>()
var selectedFilterSize = SubjectSettingEntity.Size(text = "全部大小")

View File

@ -1,19 +1,24 @@
package com.gh.gamecenter.subject.rows
import android.annotation.SuppressLint
import android.graphics.Typeface
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import android.widget.CheckedTextView
import android.widget.LinearLayout
import androidx.core.view.children
import androidx.core.view.updateLayoutParams
import com.gh.common.view.ConfigFilterView
import com.gh.common.view.ConfigFilterView.SortType
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.BaseFragment
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.core.utils.MtaHelper
import com.gh.gamecenter.core.utils.UrlFilterUtils
import com.gh.gamecenter.common.utils.goneIf
import com.gh.common.view.ConfigFilterView
import com.gh.gamecenter.R
import com.gh.gamecenter.entity.SubjectData
import com.gh.gamecenter.entity.SubjectSettingEntity
import com.gh.gamecenter.subject.SubjectListFragment
@ -32,13 +37,15 @@ class SubjectRowsFragment : BaseFragment<Any>() {
// 统计用的大小筛选选中文案
private var mSelectedFilterSize = SubjectSettingEntity.Size(text = "全部大小")
private lateinit var filterView: ConfigFilterView
override fun getLayoutId(): Int {
return R.layout.fragment_subject_rows
}
override fun initView(view: View) {
super.initView(view)
mSettingEntity = arguments?.getParcelable(SubjectSettingEntity::class.java.simpleName)
mSettingEntity = arguments?.getParcelable(EntranceConsts.KEY_SUBJECT_SETTING_DATA)
?: return
mSubjectData = arguments?.getParcelable(EntranceConsts.KEY_SUBJECT_DATA) ?: return
@ -55,7 +62,7 @@ class SubjectRowsFragment : BaseFragment<Any>() {
mListFragment =
childFragmentManager.findFragmentByTag(SubjectListFragment::class.java.name) as? SubjectListFragment
?: SubjectListFragment()
mListFragment.arguments = arguments
mListFragment.arguments = (arguments?.clone() as? Bundle)?.apply { remove(EntranceConsts.KEY_SUBJECT_SETTING_DATA) }
childFragmentManager.beginTransaction()
.replace(R.id.rows_list_container, mListFragment, SubjectListFragment::class.java.name)
.commitAllowingStateLoss()
@ -64,18 +71,29 @@ class SubjectRowsFragment : BaseFragment<Any>() {
@SuppressLint("InflateParams")
private fun createLabelsLayout() {
val inflater = LayoutInflater.from(context)
for (labels in mSettingEntity.typeEntity.labels) {
mSettingEntity.typeEntity.labels.forEachIndexed { index, labels ->
val labelsItem = inflater.inflate(R.layout.subject_rows_label, mLabelsContainer, false)
val labelContainer = labelsItem.findViewById<LinearLayout>(R.id.label_container)
for (index in 0 until labels.label.size) {
val labelName = labels.label[index]
labelsItem.updateLayoutParams<MarginLayoutParams> {
topMargin = if (index == 0) 0 else 12F.dip2px()
}
for (labelIndex in 0 until labels.label.size) {
val labelName = labels.label[labelIndex]
val labelItem =
inflater.inflate(R.layout.subject_rows_label_item, labelContainer, false)
val label = labelItem.findViewById<CheckedTextView>(R.id.label)
if (index == 0) label.isChecked = true
label.updateLayoutParams<MarginLayoutParams> {
leftMargin = if (labelIndex == 0) 0 else 8F.dip2px()
}
if (labelIndex == 0) {
label.isChecked = true
label.setTypeface(Typeface.DEFAULT, Typeface.BOLD)
} else {
label.setTypeface(Typeface.DEFAULT, Typeface.NORMAL)
}
label.text = labelName
label.setOnClickListener {
mFilterMap[labels.name] = if (index == 0) {
mFilterMap[labels.name] = if (labelIndex == 0) {
""
} else {
labelName
@ -92,26 +110,18 @@ class SubjectRowsFragment : BaseFragment<Any>() {
private fun selectLabel(labelContainer: LinearLayout, label: CheckedTextView) {
for (i in 0 until labelContainer.childCount) {
val view = labelContainer.getChildAt(i)
if (view is CheckedTextView && view.isChecked) view.isChecked = false
if (view is CheckedTextView && view.isChecked) {
view.isChecked = false
view.setTypeface(Typeface.DEFAULT, Typeface.NORMAL)
}
}
label.isChecked = true
label.setTypeface(Typeface.DEFAULT, Typeface.BOLD)
}
private fun createFilterLayout() {
if (mSettingEntity.filter == "on") {
val filterView = ConfigFilterView(requireContext())
filterView.ratingTv.visibility = View.VISIBLE
// 重排序
mSettingEntity.filterOptions.forEachIndexed { index, s ->
when (index) {
0 -> filterView.updateTv.text = s
1 -> filterView.recommendedTv.text = s
2 -> filterView.newestTv.text = s
3 -> filterView.ratingTv.text = s
}
}
if (mSettingEntity.isFilterEnabled) {
filterView = ConfigFilterView(requireContext()).apply { initSubjectFilterView(mSettingEntity) }
// 设置默认页面链接
when (filterView.updateTv.text) {
"更新" -> mSubjectData.sort = UrlFilterUtils.getFilterQuery("update_time", "-1")
@ -119,15 +129,14 @@ class SubjectRowsFragment : BaseFragment<Any>() {
"评分" -> mSubjectData.sort = UrlFilterUtils.getFilterQuery("star", "-1")
else -> mSubjectData.sort = UrlFilterUtils.getFilterQuery("position", "1")
}
// 隐藏相关选项
filterView.updateTv.goneIf(mSettingEntity.filterOptions.isEmpty())
filterView.recommendedTv.goneIf(mSettingEntity.filterOptions.size <= 1)
filterView.newestTv.goneIf(mSettingEntity.filterOptions.size <= 2)
filterView.ratingTv.goneIf(mSettingEntity.filterOptions.size <= 3)
filterView.sizeFilterArray = mSettingEntity.filterSizes
mLabelsContainer.addView(filterView)
mLabelsContainer.addView(
filterView,
LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
).apply {
topMargin = 8F.dip2px()
})
filterView.setOnConfigSetupListener(object :
ConfigFilterView.OnConfigFilterSetupListener {
@ -150,11 +159,11 @@ class SubjectRowsFragment : BaseFragment<Any>() {
)
}
override fun onSetupSortType(sortType: ConfigFilterView.SortType) {
override fun onSetupSortType(sortType: SortType) {
val clickTv = when (sortType) {
ConfigFilterView.SortType.RATING -> filterView.ratingTv
ConfigFilterView.SortType.NEWEST -> filterView.newestTv
ConfigFilterView.SortType.UPDATE -> filterView.updateTv
SortType.RATING -> filterView.ratingTv
SortType.NEWEST -> filterView.newestTv
SortType.UPDATE -> filterView.updateTv
else -> filterView.recommendedTv
}
@ -171,6 +180,8 @@ class SubjectRowsFragment : BaseFragment<Any>() {
}
}
})
} else {
mLabelsContainer.setPadding(0, 8F.dip2px(), 0, 8F.dip2px())
}
}
@ -242,4 +253,11 @@ class SubjectRowsFragment : BaseFragment<Any>() {
mListFragment.selectedLabelList = findAllSelectedLabel(mLabelsContainer)
mListFragment.refreshPage(mSubjectData.filter, mSubjectData.sort, mSelectedFilterSize)
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
if (::filterView.isInitialized) {
filterView.updateAllTextView()
}
}
}

View File

@ -0,0 +1,97 @@
package com.gh.gamecenter.subject.tab
import android.app.Application
import android.content.Context
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.toJson
import com.gh.gamecenter.core.utils.GsonUtils
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.entity.SubjectData
import com.gh.gamecenter.livedata.Event
import com.halo.assistant.HaloApp
class ColumnCollectionTabViewModel(
application: Application,
private val columnCollectionId: String,
private val customSize: Int,
private val subjectDataList: List<SubjectData>
) : AndroidViewModel(application) {
private val columnCollectionSp by lazy {
HaloApp.getInstance().getSharedPreferences(Constants.SP_COLUMN_COLLECTION_CUSTOM_TAB, Context.MODE_PRIVATE)
}
val tabListLiveData = MutableLiveData<List<SubjectData>>()
val followListLiveData = MutableLiveData<List<SubjectData>>()
val moreListLiveData = MutableLiveData<List<SubjectData>>()
val addFollowLiveData = MutableLiveData<Event<SubjectData>>()
val addMoreLiveData = MutableLiveData<Event<SubjectData>>()
init {
initData()
}
private fun initData() {
val customTabIdList = GsonUtils.fromJsonList<String>(SPUtils.getString(columnCollectionSp, columnCollectionId))
if (customTabIdList.isEmpty()) {
val followTabList = subjectDataList.take(customSize)
tabListLiveData.postValue(followTabList)
followListLiveData.postValue(followTabList)
moreListLiveData.postValue(subjectDataList - followTabList.toSet())
} else {
val newAddedForbiddenList = subjectDataList.filter { it.isForbidden && it.subjectId !in customTabIdList }
val tabList = arrayListOf<SubjectData>().apply { addAll(newAddedForbiddenList) }
customTabIdList.forEach { subjectId->
subjectDataList.find { subjectId == it.subjectId }?.let {
tabList.add(it)
}
}
val followTabList = tabList.take(CUSTOM_MAX_TAB).ifEmpty { subjectDataList.take(customSize) }
tabListLiveData.postValue(followTabList)
followListLiveData.postValue(followTabList)
moreListLiveData.postValue(subjectDataList - followTabList.toSet())
}
}
fun addFollow(subjectData: SubjectData) {
addFollowLiveData.postValue(Event(subjectData))
}
fun addMore(subjectData: SubjectData) {
addMoreLiveData.postValue(Event(subjectData))
}
fun saveCustomTab(followTabList: List<SubjectData>) {
tabListLiveData.postValue(followTabList)
followListLiveData.postValue(followTabList)
moreListLiveData.postValue(subjectDataList - followTabList.toSet())
val followSubjectIdList = followTabList.map { it.subjectId ?: "" }.distinct()
SPUtils.setString(columnCollectionSp, columnCollectionId, followSubjectIdList.toJson())
}
companion object {
const val CUSTOM_MAX_TAB = 15
}
class Factory(
private val columnCollectionId: String,
private val customSize: Int,
private val subjectDataList: List<SubjectData>
) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return ColumnCollectionTabViewModel(
HaloApp.getInstance().application,
columnCollectionId,
customSize,
subjectDataList
) as T
}
}
}

View File

@ -0,0 +1,56 @@
package com.gh.gamecenter.subject.tab
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.recyclerview.widget.DiffUtil.ItemCallback
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.view.Chips
import com.gh.gamecenter.databinding.ItemColumnCollectionCustomTabBinding
import com.gh.gamecenter.entity.SubjectData
class CustomTabFollowAdapter(private val viewModel: ColumnCollectionTabViewModel?) :
ListAdapter<SubjectData, CustomTabFollowAdapter.CustomTabFollowItemViewHolder>(createDiffItemCallBack()) {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): CustomTabFollowItemViewHolder {
return CustomTabFollowItemViewHolder(parent.toBinding())
}
override fun onBindViewHolder(holder: CustomTabFollowItemViewHolder, position: Int) {
val subjectData = currentList.getOrNull(position) ?: return
val isForbidden = subjectData.isForbidden
holder.binding.chips.run {
setState(if (isForbidden) Chips.STATE_FILL else Chips.STATE_DEFAULT)
setText(subjectData.subjectName ?: "")
endIcon.isVisible = !isForbidden
endIcon.setImageResource(R.drawable.ic_basic_x_8_secondary)
endIcon.imageTintList = null
setOnClickListener {
if (!isForbidden) {
viewModel?.addMore(subjectData)
}
}
}
}
class CustomTabFollowItemViewHolder(val binding: ItemColumnCollectionCustomTabBinding) :
ViewHolder(binding.root)
companion object {
fun createDiffItemCallBack() = object : ItemCallback<SubjectData>() {
override fun areItemsTheSame(oldItem: SubjectData, newItem: SubjectData): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: SubjectData, newItem: SubjectData): Boolean {
return oldItem == newItem
}
}
}
}

View File

@ -0,0 +1,49 @@
package com.gh.gamecenter.subject.tab
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.recyclerview.widget.DiffUtil.ItemCallback
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.view.Chips
import com.gh.gamecenter.databinding.ItemColumnCollectionCustomTabBinding
import com.gh.gamecenter.entity.SubjectData
class CustomTabMoreAdapter(private val viewModel: ColumnCollectionTabViewModel?) :
ListAdapter<SubjectData, CustomTabMoreAdapter.CustomTabMoreItemViewHolder>(createDiffItemCallBack()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomTabMoreItemViewHolder {
return CustomTabMoreItemViewHolder(parent.toBinding())
}
override fun onBindViewHolder(holder: CustomTabMoreItemViewHolder, position: Int) {
val subjectData = currentList.getOrNull(position) ?: return
holder.binding.chips.run {
setState(Chips.STATE_DEFAULT)
setText(subjectData.subjectName ?: "")
startIcon.isVisible = true
startIcon.setImageResource(R.drawable.ic_auxiliary_plus_secondary_8)
startIcon.imageTintList = null
setOnClickListener {
viewModel?.addFollow(subjectData)
}
}
}
class CustomTabMoreItemViewHolder(val binding: ItemColumnCollectionCustomTabBinding) : ViewHolder(binding.root)
companion object {
fun createDiffItemCallBack() = object : ItemCallback<SubjectData>() {
override fun areItemsTheSame(oldItem: SubjectData, newItem: SubjectData): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: SubjectData, newItem: SubjectData): Boolean {
return oldItem == newItem
}
}
}
}

View File

@ -1,56 +1,154 @@
package com.gh.gamecenter.subject.tab
import android.animation.AnimatorSet
import android.animation.ValueAnimator
import android.graphics.Typeface
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import android.view.animation.AccelerateDecelerateInterpolator
import android.widget.CheckedTextView
import android.widget.TextView
import android.widget.FrameLayout
import android.widget.LinearLayout
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.content.ContextCompat
import androidx.core.graphics.ColorUtils
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentStatePagerAdapter
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager.widget.ViewPager
import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.BaseFragment
import com.gh.gamecenter.common.base.fragment.ToolbarFragment
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.toDrawable
import com.gh.gamecenter.core.utils.MtaHelper
import com.gh.gamecenter.core.utils.UrlFilterUtils
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.utils.DialogHelper.Config
import com.gh.gamecenter.core.utils.*
import com.gh.gamecenter.databinding.FragmentSubjectTabBinding
import com.gh.gamecenter.databinding.LayoutColumnCollectionSettingBinding
import com.gh.gamecenter.databinding.TabItemMainBinding
import com.gh.gamecenter.entity.SubjectData
import com.gh.gamecenter.entity.SubjectSettingEntity
import com.gh.gamecenter.home.custom.model.CustomPageItem
import com.gh.gamecenter.livedata.EventObserver
import com.gh.gamecenter.subject.SubjectFragment
import com.gh.gamecenter.subject.SubjectListFragment
import com.google.android.flexbox.FlexboxLayoutManager
import com.google.android.flexbox.JustifyContent
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayout.OnTabSelectedListener
import org.json.JSONException
import org.json.JSONObject
import java.util.*
import kotlin.math.abs
import kotlin.math.roundToInt
class SubjectTabFragment : BaseFragment<Any>() {
class SubjectTabFragment : ToolbarFragment() {
private val mBinding by lazy { FragmentSubjectTabBinding.inflate(layoutInflater) }
private val binding by lazy { FragmentSubjectTabBinding.inflate(layoutInflater) }
private var settingBinding: LayoutColumnCollectionSettingBinding? = null
private var isSubject = false
private var isCustomEnabled = false
private var tabStyle = TabStyle.SUBJECT_NORMAL
private var tabBindingList = arrayListOf<TabItemMainBinding>()
private var isSettingAnimating = false
private var columnCollectionTabViewModel: ColumnCollectionTabViewModel? = null
private val fragmentTag by lazy { "android:switcher:${binding.subjectViewpager.id}:" }
private var collectionId = ""
private var collectionName = ""
private var collectionStyle = ""
private val followAdapter by lazy { CustomTabFollowAdapter(columnCollectionTabViewModel) }
private val moreAdapter by lazy { CustomTabMoreAdapter(columnCollectionTabViewModel) }
private val showOrHideMore: (Boolean) -> Unit = { isShow ->
settingBinding?.run {
if (isShow == moreContainer.isVisible) return@run
val startHeight = if (isShow) 0 else moreContainer.height
val endHeight = if (isShow) {
moreContainer.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
moreContainer.measuredHeight
} else 0
ValueAnimator.ofInt(startHeight, endHeight).apply {
duration = ANIMATOR_DURATION
interpolator = AccelerateDecelerateInterpolator()
doOnStart {
if (isShow) {
moreContainer.updateLayoutParams<MarginLayoutParams> { height = 0 }
moreContainer.isVisible = true
} else {
moreContainer.isInvisible = true
}
}
doOnEnd {
if (isShow) {
moreContainer.updateLayoutParams<MarginLayoutParams> { height = MarginLayoutParams.WRAP_CONTENT }
} else {
moreContainer.isVisible = false
}
}
addUpdateListener { animation ->
moreContainer.updateLayoutParams<MarginLayoutParams> { height = animation.animatedValue as Int }
}
}.start()
}
}
private val itemTouchHelper by lazy {
ItemTouchHelper(object : ItemTouchHelper.Callback() {
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
return makeMovementFlags(ItemTouchHelper.UP or ItemTouchHelper.DOWN or ItemTouchHelper.START or ItemTouchHelper.END, 0)
}
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
val from = viewHolder.bindingAdapterPosition
val to = target.bindingAdapterPosition
val newList = followAdapter.currentList.toMutableList()
Collections.swap(newList, from, to)
followAdapter.submitList(newList)
return true
}
override fun isLongPressDragEnabled(): Boolean = true
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) = Unit
})
}
override fun getLayoutId() = 0
override fun getInflatedLayout() = mBinding.root
override fun getInflatedLayout() = binding.root
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val subjectList = arguments?.getParcelableArrayList<SubjectData>(EntranceConsts.KEY_DATA)
val subjectList = arguments?.getParcelableArrayList<SubjectData>(EntranceConsts.KEY_DATA) ?: arrayListOf()
isSubject = subjectList.isEmpty()
tabStyle = when {
mIsFromMainWrapper && !isSubject -> TabStyle.COLUMN_COLLECTION_MAIN_WRAPPER
isSubject -> TabStyle.SUBJECT_NORMAL
else -> TabStyle.COLUMN_COLLECTION_NORMAL
}
val fragments = ArrayList<Fragment>()
var tagList = arrayListOf<String>()
val fragmentTag = "android:switcher:${mBinding.subjectViewpager.id}:"
if (subjectList.isNullOrEmpty()) {
val tagList: ArrayList<String>
if (isSubject) {
// 专题详情
val subjectData = arguments?.getParcelable<SubjectData>(EntranceConsts.KEY_SUBJECT_DATA)
?: return
val settingsEntity =
arguments?.getParcelable<SubjectSettingEntity>(SubjectSettingEntity::class.java.simpleName)
arguments?.getParcelable<SubjectSettingEntity>(EntranceConsts.KEY_SUBJECT_SETTING_DATA)
?: return
tagList = settingsEntity.typeEntity.content as ArrayList<String>
if (tagList.size > 1) {
mBinding.subjectTabContainer.visibility = View.VISIBLE
binding.subjectTabContainer.visibility = View.VISIBLE
}
tagList.forEachIndexed { index, tag ->
val element = childFragmentManager.findFragmentByTag("${fragmentTag}$index")
@ -60,69 +158,192 @@ class SubjectTabFragment : BaseFragment<Any>() {
copyData.filter = if (tag == "全部") {
UrlFilterUtils.getFilterQuery("tags", tag, "type", "全部")
} else {
copyData.tag = tag
UrlFilterUtils.getFilterQuery("tags", tag)
}
bundle?.putParcelable(EntranceConsts.KEY_SUBJECT_DATA, copyData)
bundle?.putParcelable(EntranceConsts.KEY_SUBJECT_SETTING_DATA, settingsEntity)
element.arguments = bundle
fragments.add(element)
}
if (binding.subjectTabContainer.isVisible) {
binding.subjectTabIndicator.setupWithTabLayout(binding.subjectTab)
binding.subjectTabIndicator.setupWithViewPager(binding.subjectViewpager)
}
val adapter = object : FragmentStatePagerAdapter(childFragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
override fun getItem(position: Int): Fragment {
return fragments[position]
}
override fun getCount(): Int {
return fragments.size
}
override fun getPageTitle(position: Int): CharSequence {
return tagList[position]
}
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
// super.destroyItem(container, position, `object`)
}
}
binding.subjectViewpager.adapter = adapter
binding.subjectTab.setupWithViewPager(binding.subjectViewpager)
initTabLayout()
} else {
// 专题合集详情
mBinding.subjectTabContainer.visibility = View.VISIBLE
isCustomEnabled = arguments?.getBoolean(EntranceConsts.KEY_COLUMN_COLLECTION_CUSTOM) ?: false
val customSize = (arguments?.getInt(EntranceConsts.KEY_COLUMN_COLLECTION_CUSTOM_SIZE)
?: ColumnCollectionTabViewModel.CUSTOM_MAX_TAB).coerceAtMost(ColumnCollectionTabViewModel.CUSTOM_MAX_TAB)
collectionId = arguments?.getString(EntranceConsts.KEY_COLUMN_COLLECTION_ID, "") ?: ""
collectionName = arguments?.getString(EntranceConsts.KEY_COLUMN_COLLECTION_NAME, "") ?: ""
collectionStyle = arguments?.getString(EntranceConsts.KEY_COLUMN_COLLECTION_STYLE, "") ?: ""
binding.optionIv.isVisible = isCustomEnabled
binding.maskView.isVisible = isCustomEnabled
binding.optionIv.setOnClickListener {
showSettingView()
SensorsBridge.trackColumnCollectionClick(
location = "合集详情",
columnCollectionName = collectionName,
columnCollectionId = collectionId,
text = "自定义设置",
columnCollectionPattern = CustomPageItem.collectionTypeToComponentName[collectionStyle]
?: collectionStyle
)
}
binding.subjectTabContainer.visibility = View.VISIBLE
if (isCustomEnabled) {
val columnCollectionId = arguments?.getString(EntranceConsts.KEY_COLUMN_COLLECTION_ID) ?: ""
columnCollectionTabViewModel = viewModelProvider(ColumnCollectionTabViewModel.Factory(columnCollectionId, customSize, subjectList))
observerData()
if (SPUtils.getBoolean(Constants.SP_SHOW_COLUMN_COLLECTION_CUSTOM_TAB_SETTING, true)) {
mBaseHandler.post {
showSettingView()
}
SPUtils.setBoolean(Constants.SP_SHOW_COLUMN_COLLECTION_CUSTOM_TAB_SETTING, false)
}
} else {
initColumnCollectionViewPager(subjectList)
}
}
}
subjectList.filterIndexed { index, subject ->
val element = childFragmentManager.findFragmentByTag("${fragmentTag}$index")
?: SubjectFragment()
val bundle = arguments?.clone() as Bundle?
bundle?.putParcelable(EntranceConsts.KEY_SUBJECT_DATA, subject)
bundle?.putParcelableArrayList(EntranceConsts.KEY_DATA, null)
element.arguments = bundle
fragments.add(element)
tagList.add(subject.subjectName ?: "")
private fun observerData() {
columnCollectionTabViewModel?.tabListLiveData?.observe(viewLifecycleOwner) {
binding.subjectTab.clearOnTabSelectedListeners()
initColumnCollectionViewPager(it)
}
columnCollectionTabViewModel?.followListLiveData?.observe(viewLifecycleOwner) {
followAdapter.submitList(it)
}
columnCollectionTabViewModel?.moreListLiveData?.observe(viewLifecycleOwner) {
moreAdapter.submitList(it) {
showOrHideMore.invoke(it.isNotEmpty())
}
}
columnCollectionTabViewModel?.addFollowLiveData?.observe(viewLifecycleOwner, EventObserver {
val newFollowList = followAdapter.currentList.toMutableList()
if (newFollowList.size < ColumnCollectionTabViewModel.CUSTOM_MAX_TAB) {
val newMoreList = moreAdapter.currentList.toMutableList().apply { remove(it) }
followAdapter.submitList(newFollowList.apply { add(it) })
moreAdapter.submitList(newMoreList) {
showOrHideMore.invoke(newMoreList.isNotEmpty())
}
} else {
toast("最多可以添加15个关注内容哦~")
}
})
columnCollectionTabViewModel?.addMoreLiveData?.observe(viewLifecycleOwner, EventObserver {
if (followAdapter.currentList.size == 1) {
toast("至少添加1个内容哦~")
return@EventObserver
}
mBinding.subjectViewpager.post {
mBinding.subjectViewpager.offscreenPageLimit = fragments.size
var position = arguments?.getInt(EntranceConsts.KEY_POSITION, 0) ?: 0
val columnName = arguments?.getString(EntranceConsts.KEY_COLUMNNAME) ?: ""
if (columnName.isNotEmpty()) {
position = subjectList.indexOfFirst { it.subjectName == columnName }
}
if (position < subjectList.size) {
mBinding.subjectViewpager.setCurrentItem(position, false)
}
followAdapter.submitList(followAdapter.currentList.toMutableList().apply { remove(it) })
val newMoreList = moreAdapter.currentList.toMutableList().apply { add(it) }
moreAdapter.submitList(newMoreList) {
showOrHideMore.invoke(newMoreList.isNotEmpty())
}
val categoryId = requireArguments().getString(EntranceConsts.KEY_COLUMN_COLLECTION_ID, "")
val categoryName = requireArguments().getString(EntranceConsts.KEY_COLUMN_COLLECTION_NAME, "")
})
}
mBinding.subjectViewpager.addOnPageChangeListener(object : ViewPager.SimpleOnPageChangeListener() {
override fun onPageSelected(position: Int) {
subjectList[position]?.let {
MtaHelper.onEvent("游戏专题合集", "详情Tab", it.subjectName)
NewLogUtils.logColumnCategoryDetailContentClick(
it.subjectName ?: "", it.subjectId ?: "", categoryName, categoryId
)
private fun initColumnCollectionViewPager(subjectList: List<SubjectData>) {
val fragments = arrayListOf<Fragment>()
val tagList = arrayListOf<String>()
subjectList.filterIndexed { index, subject ->
val element = childFragmentManager.findFragmentByTag("${fragmentTag}$index")
?: SubjectFragment()
val bundle = arguments?.clone() as Bundle?
bundle?.putParcelable(EntranceConsts.KEY_SUBJECT_DATA, subject)
bundle?.putParcelableArrayList(EntranceConsts.KEY_DATA, null)
bundle?.putBoolean(EntranceConsts.KEY_IS_FROM_MAIN_WRAPPER, false)
bundle?.putBoolean(EntranceConsts.KEY_IS_FROM_TAB_WRAPPER, false)
bundle?.putBoolean(EntranceConsts.KEY_COLUMN_COLLECTION_CUSTOM, false)
if (isCustomEnabled) {
bundle?.putInt(EntranceConsts.KEY_COLUMN_COLLECTION_CUSTOM_POSITION, index)
}
element.arguments = bundle
fragments.add(element)
if (categoryName?.contains("排行榜") == true) {
val trackEvent = JSONObject()
try {
trackEvent.put("list_name", it.subjectName)
trackEvent.put("position", position)
} catch (e: JSONException) {
e.printStackTrace()
}
SensorsBridge.trackEvent("GameListPageSelected", trackEvent)
tagList.add(subject.subjectName ?: "")
}
binding.subjectViewpager.post {
binding.subjectViewpager.offscreenPageLimit = fragments.size
var position = arguments?.getInt(EntranceConsts.KEY_POSITION, 0) ?: 0
val columnName = arguments?.getString(EntranceConsts.KEY_COLUMNNAME) ?: ""
if (columnName.isNotEmpty()) {
position = subjectList.indexOfFirst { it.subjectName == columnName }
}
if (position < subjectList.size) {
binding.subjectViewpager.setCurrentItem(position, false)
}
}
val categoryId = requireArguments().getString(EntranceConsts.KEY_COLUMN_COLLECTION_ID, "")
val categoryName = requireArguments().getString(EntranceConsts.KEY_COLUMN_COLLECTION_NAME, "")
binding.subjectViewpager.addOnPageChangeListener(object : ViewPager.SimpleOnPageChangeListener() {
override fun onPageSelected(position: Int) {
subjectList.getOrNull(position)?.let {
MtaHelper.onEvent("游戏专题合集", "详情Tab", it.subjectName)
NewLogUtils.logColumnCategoryDetailContentClick(
it.subjectName ?: "", it.subjectId ?: "", categoryName, categoryId
)
if (categoryName?.contains("排行榜") == true) {
val trackEvent = JSONObject()
try {
trackEvent.put("list_name", it.subjectName)
trackEvent.put("position", position)
} catch (e: JSONException) {
e.printStackTrace()
}
SensorsBridge.trackEvent("GameListPageSelected", trackEvent)
}
}
})
}
}
if (mBinding.subjectTabContainer.visibility == View.VISIBLE) {
mBinding.subjectTabIndicator.setupWithTabLayout(mBinding.subjectTab)
mBinding.subjectTabIndicator.setupWithViewPager(mBinding.subjectViewpager)
}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels)
if (mIsFromMainWrapper) {
// 这里的 selectedPosition 指的是应该被高亮显示的 position
val selectedPosition = try {
(position + positionOffset).roundToInt()
} catch (e: IllegalArgumentException) {
// roundToInt() 方法有时候会报 Cannot round NaN value. 错误 positionOffset 的值为 NAN
// https://sentry.shanqu.cc/organizations/lightgame/issues/301377/?project=22
position
}
val positionOffsetOnRealSelectedPosition = if (positionOffset >= 0.5) {
positionOffset - 1
} else {
positionOffset
}
updateTabStyle(selectedPosition, positionOffsetOnRealSelectedPosition)
}
}
})
val adapter = object : FragmentStatePagerAdapter(childFragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
override fun getItem(position: Int): Fragment {
@ -133,7 +354,7 @@ class SubjectTabFragment : BaseFragment<Any>() {
return fragments.size
}
override fun getPageTitle(position: Int): CharSequence? {
override fun getPageTitle(position: Int): CharSequence {
return tagList[position]
}
@ -141,76 +362,478 @@ class SubjectTabFragment : BaseFragment<Any>() {
// super.destroyItem(container, position, `object`)
}
}
mBinding.subjectViewpager.adapter = adapter
mBinding.subjectTab.setupWithViewPager(mBinding.subjectViewpager)
// 首页样式的 tabLayout
if (arguments?.getBoolean(EntranceConsts.KEY_IS_HOME) == true) {
mBinding.subjectTabIndicator.visibility = View.GONE
for (i in 0 until mBinding.subjectTab.tabCount) {
val tab = mBinding.subjectTab.getTabAt(i) ?: continue
binding.subjectViewpager.adapter = adapter
binding.subjectTab.setupWithViewPager(binding.subjectViewpager)
binding.subjectTabIndicator.setupWithTabLayout(binding.subjectTab)
binding.subjectTabIndicator.setupWithViewPager(binding.subjectViewpager)
initTabLayout()
}
private fun initTabLayout() {
if (tabStyle == TabStyle.COLUMN_COLLECTION_NORMAL) {
if (SPUtils.getBoolean(Constants.SP_SHOW_COLUMN_COLLECTION_CUSTOM_TAB_GUIDE, true)) {
binding.subjectTab.addOnTabSelectedListener(object : OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {
showSettingGuideIfNeeded(this)
}
override fun onTabUnselected(tab: TabLayout.Tab?) {}
override fun onTabReselected(tab: TabLayout.Tab?) {
showSettingGuideIfNeeded(this)
}
})
}
binding.subjectTabIndicator.run {
setIndicatorWidth(12)
updateLayoutParams<ConstraintLayout.LayoutParams> {
bottomMargin = 10F.dip2px()
}
}
for (i in 0 until binding.subjectTab.tabCount) {
binding.subjectTab.getTabAt(i)?.view?.run {
background = null
if (i == 0) {
updateLayoutParams<MarginLayoutParams> { leftMargin = 4F.dip2px() }
}
}
}
} else {
tabBindingList.clear()
val tabCount = binding.subjectTab.tabCount
for (i in 0 until tabCount) {
val tab = binding.subjectTab.getTabAt(i) ?: continue
val tabTitle = if (tab.text != null) tab.text.toString() else ""
tab.view.background = null
tab.customView = provideTabView(tabTitle)
if (i == 0) {
tab.customView?.background = R.drawable.border_round_theme_14.toDrawable(requireContext())
tab.customView?.findViewById<TextView>(R.id.tab_title)
?.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(requireContext()))
if (tabStyle == TabStyle.COLUMN_COLLECTION_MAIN_WRAPPER) {
tab.view.setPadding(0, 0, 0, 0)
if (i == 0) {
tab.view.updateLayoutParams {
this as LinearLayout.LayoutParams
setMargins(10F.dip2px(), 0, 0, 0)
}
} else if (i == tabCount - 1) {
tab.view.updateLayoutParams {
this as LinearLayout.LayoutParams
setMargins(0, 0, 10F.dip2px(), 0)
}
}
} else {
tab.customView?.background = R.drawable.border_round_eee_14.toDrawable(requireContext())
tab.customView?.findViewById<TextView>(R.id.tab_title)
?.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(requireContext()))
}
when (i) {
0 -> tab.view.setPadding(12F.dip2px(), 0, 0, 0)
mBinding.subjectTab.tabCount - 1 -> tab.view.setPadding(8F.dip2px(), 0, 12F.dip2px(), 0)
else -> tab.view.setPadding(8F.dip2px(), 0, 0, 0)
when (i) {
0 -> tab.view.setPadding(16F.dip2px(), 0, 0, 0)
binding.subjectTab.tabCount - 1 -> tab.view.setPadding(8F.dip2px(), 0, 16F.dip2px(), 0)
else -> tab.view.setPadding(8F.dip2px(), 0, 0, 0)
}
}
}
mBinding.subjectTab.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabReselected(tab: TabLayout.Tab?) {}
override fun onTabUnselected(tab: TabLayout.Tab?) {
tab?.customView?.background = R.drawable.border_round_eee_14.toDrawable(requireContext())
tab?.customView?.findViewById<TextView>(R.id.tab_title)
?.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(requireContext()))
binding.subjectTabIndicator.isVisible = mIsFromMainWrapper
if (tabStyle == TabStyle.COLUMN_COLLECTION_MAIN_WRAPPER) {
binding.subjectTabIndicator.run {
setIndicatorWidth(18)
updateIndicatorDrawable(R.drawable.ic_commodity_selected.toDrawable(requireContext()))
updateLayoutParams<ConstraintLayout.LayoutParams> {
height = 8F.dip2px()
bottomMargin = 8F.dip2px()
}
}
override fun onTabSelected(tab: TabLayout.Tab?) {
tab?.customView?.background = R.drawable.border_round_theme_14.toDrawable(requireContext())
tab?.customView?.findViewById<TextView>(R.id.tab_title)
?.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(requireContext()))
binding.settingGuideIv.updateLayoutParams<ConstraintLayout.LayoutParams> { topMargin = 40F.dip2px() }
binding.subjectTabContainer.updateLayoutParams<ConstraintLayout.LayoutParams> { height = 56F.dip2px() }
binding.subjectTab.updateLayoutParams<ConstraintLayout.LayoutParams> {
setMargins(0, 8F.dip2px(), 8F.dip2px(), 8F.dip2px())
}
})
} else {
binding.subjectTab.addOnTabSelectedListener(object : OnTabSelectedListener {
override fun onTabReselected(tab: TabLayout.Tab?) {}
override fun onTabUnselected(tab: TabLayout.Tab?) {
tab?.customView?.findViewById<CheckedTextView>(R.id.tab_title)?.isChecked = false
}
override fun onTabSelected(tab: TabLayout.Tab?) {
tab?.customView?.findViewById<CheckedTextView>(R.id.tab_title)?.isChecked = true
}
})
}
}
}
private fun provideTabView(title: String): View {
val view = LayoutInflater.from(requireContext()).inflate(R.layout.tab_item_ranking_home, null)
val tabTitle = view.findViewById<View>(R.id.tab_title)
if (tabTitle is CheckedTextView) {
tabTitle.text = title
if (tabStyle == TabStyle.COLUMN_COLLECTION_MAIN_WRAPPER) {
val tabBinding = TabItemMainBinding.inflate(LayoutInflater.from(requireContext()))
tabBinding.titleTv.run {
visibility = View.VISIBLE
text = title
textSize = 16F
setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(requireContext()))
}
tabBinding.invisibleTitleTv.run {
visibility = View.INVISIBLE
text = title
textSize = 16F
}
tabBindingList.add(tabBinding)
return tabBinding.root
} else {
val view = LayoutInflater.from(requireContext()).inflate(R.layout.tab_item_subject_tab, null)
val tabTitle = view.findViewById<View>(R.id.tab_title)
if (tabTitle is CheckedTextView) {
tabTitle.text = title
}
return view
}
return view
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
for (i in 0 until mBinding.subjectTab.tabCount) {
val tab = mBinding.subjectTab.getTabAt(i) ?: continue
tab.customView?.background =
if (tab.isSelected) R.drawable.border_round_theme_14.toDrawable(requireContext()) else R.drawable.border_round_eee_14.toDrawable(
requireContext()
)
tab.customView?.findViewById<TextView>(R.id.tab_title)
?.setTextColor(
if (tab.isSelected) com.gh.gamecenter.common.R.color.text_theme.toColor(requireContext()) else com.gh.gamecenter.common.R.color.text_primary.toColor(
requireContext()
)
)
when (tabStyle) {
TabStyle.COLUMN_COLLECTION_NORMAL -> {
binding.subjectTabIndicator.updateIndicatorColor(com.gh.gamecenter.common.R.color.primary_theme)
binding.subjectTab.setTabTextColors(com.gh.gamecenter.common.R.color.text_secondary.toColor(requireContext()), com.gh.gamecenter.common.R.color.text_primary.toColor(requireContext()))
}
TabStyle.COLUMN_COLLECTION_MAIN_WRAPPER -> updateTabStyle(binding.subjectViewpager.currentItem, 0F)
TabStyle.SUBJECT_NORMAL -> {
for (i in 0 until binding.subjectTab.tabCount) {
val tab = binding.subjectTab.getTabAt(i) ?: continue
tab.customView?.findViewById<CheckedTextView>(R.id.tab_title)?.run {
background = R.drawable.subject_tab_background_selector.toDrawable(context)
setTextColor(ContextCompat.getColorStateList(context, com.gh.gamecenter.common.R.color.text_tabbar_style))
}
}
}
}
settingBinding?.run {
saveTv.background = com.gh.gamecenter.common.R.drawable.button_blue_oval.toDrawable(requireContext())
cancelTv.background = com.gh.gamecenter.feature.R.drawable.button_round_gray_light.toDrawable(requireContext())
cancelTv.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(requireContext()))
followAdapter.notifyItemRangeChanged(0, followAdapter.itemCount)
moreAdapter.notifyItemRangeChanged(0, moreAdapter.itemCount)
}
}
private fun updateTabStyle(selectedPosition: Int, positionOffset: Float) {
if (tabBindingList.isEmpty()) return
val currentTabDefaultColor = com.gh.gamecenter.common.R.color.text_secondary.toColor(requireContext())
val currentTabSelectedColor = com.gh.gamecenter.common.R.color.text_primary.toColor(requireContext())
val prePosition = selectedPosition - 1
val nextPosition = selectedPosition + 1
// positionOffset 小于零,表示 indicator 当前位置处于选中的 tab 的左边
val indicatorOnLeft = positionOffset < 0F
val selectedTabBinding = tabBindingList.safelyGetInRelease(selectedPosition) ?: return
// 前一个 tab、当前选中的 tab、后一个 tab 的显示比例
val preScaleRatio = 1 + abs(positionOffset) / 4
val selectedScaleRatio = 1 + (1 - abs(positionOffset)) / 4
val nextScaleRatio = 1 + positionOffset / 4
// 处理前一个 tab
if (prePosition != -1) {
tabBindingList[prePosition].run {
if (indicatorOnLeft) {
titleTv.scaleX = preScaleRatio
titleTv.scaleY = preScaleRatio
titleTv.setTextColor(
ColorUtils.blendARGB(
currentTabDefaultColor,
currentTabSelectedColor,
abs(positionOffset)
)
)
} else {
titleTv.setTextColor(currentTabDefaultColor)
}
}
}
// 更新当前选中的 tab
selectedTabBinding.run {
titleTv.scaleX = selectedScaleRatio
titleTv.scaleY = selectedScaleRatio
titleTv.setTextColor(
ColorUtils.blendARGB(
currentTabDefaultColor,
currentTabSelectedColor,
1 - abs(positionOffset)
)
)
}
// 处理后一个 tab
if (nextPosition < tabBindingList.size) {
tabBindingList[nextPosition].run {
if (!indicatorOnLeft) {
titleTv.scaleX = nextScaleRatio
titleTv.scaleY = nextScaleRatio
titleTv.setTextColor(
ColorUtils.blendARGB(
currentTabDefaultColor,
currentTabSelectedColor,
positionOffset
)
)
} else {
titleTv.setTextColor(currentTabDefaultColor)
}
}
}
// 多 tab 切换的时候可能会出现某些 tab 的文字没有回归到原始大小的问题的问题 (positionOffset 不保证连续)
for ((index, tabBinding) in tabBindingList.withIndex()) {
if (index != prePosition && index != selectedPosition && index != nextPosition) {
if (tabBinding.titleTv.scaleX != 1F) {
tabBinding.titleTv.scaleX = 1F
tabBinding.titleTv.scaleY = 1F
}
tabBinding.titleTv.setTextColor(currentTabDefaultColor)
if (tabBinding.titleIv.scaleX != 1F) {
tabBinding.titleIv.scaleX = 1F
tabBinding.titleIv.scaleY = 1F
}
}
if (index == selectedPosition) {
if (positionOffset == 0F) {
tabBinding.titleTv.setTextColor(currentTabSelectedColor)
}
tabBinding.titleTv.setTypeface(tabBinding.titleTv.typeface, Typeface.BOLD)
} else {
if (positionOffset == 0F) {
tabBinding.titleTv.setTextColor(currentTabDefaultColor)
}
tabBinding.titleTv.setTypeface(null, Typeface.NORMAL)
}
}
}
private fun showSettingGuideIfNeeded(listener: OnTabSelectedListener) {
if (SPUtils.getBoolean(Constants.SP_SHOW_COLUMN_COLLECTION_CUSTOM_TAB_GUIDE, true)) {
binding.skipView.setOnClickListener {
dismissSettingGuide()
SensorsBridge.trackColumnCollectionClick(
location = "合集详情",
columnCollectionName = collectionName,
columnCollectionId = collectionId,
text = "新手引导-取消",
columnCollectionPattern = CustomPageItem.collectionTypeToComponentName[collectionStyle]
?: collectionStyle
)
}
binding.settingClickView.setOnClickListener {
dismissSettingGuide()
showSettingView()
SensorsBridge.trackColumnCollectionClick(
location = "合集详情",
columnCollectionName = collectionName,
columnCollectionId = collectionId,
text = "新手引导-立即设置",
columnCollectionPattern = CustomPageItem.collectionTypeToComponentName[collectionStyle]
?: collectionStyle
)
}
binding.settingGuideContainer.setOnClickListener {
dismissSettingGuide()
SensorsBridge.trackColumnCollectionClick(
location = "合集详情",
columnCollectionName = collectionName,
columnCollectionId = collectionId,
text = "新手引导-关闭引导",
columnCollectionPattern = CustomPageItem.collectionTypeToComponentName[collectionStyle]
?: collectionStyle
)
}
mBaseHandler.post {
binding.settingGuideContainer.isVisible = true
}
SPUtils.setBoolean(Constants.SP_SHOW_COLUMN_COLLECTION_CUSTOM_TAB_GUIDE, false)
binding.subjectTab.removeOnTabSelectedListener(listener)
}
}
private fun dismissSettingGuide() {
binding.settingGuideIv.animate()?.alpha(0F)?.setDuration(200L)?.doOnEnd {
binding.settingGuideContainer.isVisible = false
}?.start()
}
private fun showSettingView() {
if (isSettingAnimating) return
binding.background.setOnClickListener {
showDismissSettingDialogIfNeeded()
}
settingBinding = LayoutColumnCollectionSettingBinding.inflate(layoutInflater, null, false).apply {
followRv.isNestedScrollingEnabled = false
moreRv.isNestedScrollingEnabled = false
columnCollectionTabViewModel?.followListLiveData?.value?.let {
followAdapter.submitList(it) {
followRv.adapter = followAdapter
}
} ?: run {
followRv.adapter = followAdapter
}
columnCollectionTabViewModel?.moreListLiveData?.value?.let {
moreAdapter.submitList(it) {
moreRv.adapter = moreAdapter
}
} ?: run {
moreRv.adapter = moreAdapter
}
followRv.layoutManager = FlexboxLayoutManager(requireContext()).apply { justifyContent = JustifyContent.FLEX_START }
moreRv.layoutManager = FlexboxLayoutManager(requireContext()).apply { justifyContent = JustifyContent.FLEX_START }
itemTouchHelper.attachToRecyclerView(followRv)
moreContainer.isVisible = !columnCollectionTabViewModel?.moreListLiveData?.value.isNullOrEmpty()
saveTv.setOnClickListener {
dismissSettingView {
saveCustomTab()
}
}
cancelTv.setOnClickListener {
dismissSettingView()
}
root.setOnClickListener {}
}
binding.settingContainer.addView(
settingBinding!!.root,
FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT)
)
binding.settingContainer.post {
val height = binding.settingContainer.height
binding.background.visibility = View.VISIBLE
AnimatorSet().apply {
val translateAnimator = ValueAnimator.ofFloat(0F, height.toFloat()).apply {
addUpdateListener { va -> binding.settingContainer.translationY = (va.animatedValue as Float) }
}
val alphaAnimator = ValueAnimator.ofFloat(0F, 0.2F).apply {
addUpdateListener { va -> binding.background.alpha = (va.animatedValue as Float) }
}
playTogether(translateAnimator, alphaAnimator)
duration = ANIMATOR_DURATION
doOnStart {
isSettingAnimating = true
}
doOnEnd {
ConstraintSet().apply {
clone(binding.root)
clear(binding.settingContainer.id, ConstraintSet.BOTTOM)
connect(binding.settingContainer.id, ConstraintSet.TOP, binding.subjectTabContainer.id, ConstraintSet.TOP)
}.applyTo(binding.root)
binding.settingContainer.translationY = 0F
isSettingAnimating = false
}
}.start()
}
}
private fun saveCustomTab() {
if (followAdapter.currentList.isEmpty()) {
toast("至少添加1个内容哦~")
return
}
SensorsBridge.trackEvent("ColumnCollectionCustomSetting",
"column_collection_name", collectionName,
"column_collection_id", collectionId,
"original_text", columnCollectionTabViewModel?.followListLiveData?.value?.joinToString("") { it.subjectName ?: "" } ?: "",
"save_text", followAdapter.currentList.joinToString("") { it.subjectName ?: "" },
)
columnCollectionTabViewModel?.saveCustomTab(followAdapter.currentList)
}
private fun showDismissSettingDialogIfNeeded() {
if (followAdapter.currentList == columnCollectionTabViewModel?.followListLiveData?.value) {
dismissSettingView()
} else {
DialogHelper.showDialog(
requireContext(),
title = "提示",
content = "是否保存当前自定义设置",
confirmText = "保存设置",
cancelText = "退出",
confirmClickCallback = {
dismissSettingView {
saveCustomTab()
}
},
cancelClickCallback = {
dismissSettingView()
},
extraConfig = Config(centerTitle = true, centerContent = true)
)
}
}
private fun dismissSettingView(doOnEnd: (() -> Unit)? = null) {
if (settingBinding == null || isSettingAnimating) return
val height = binding.settingContainer.height
AnimatorSet().apply {
val translateAnimator = ValueAnimator.ofFloat(0F, -height.toFloat()).apply {
addUpdateListener { va -> binding.settingContainer.translationY = (va.animatedValue as Float) }
}
val alphaAnimator = ValueAnimator.ofFloat(0.2F, 0F).apply {
addUpdateListener { va -> binding.background.alpha = (va.animatedValue as Float) }
}
playTogether(translateAnimator, alphaAnimator)
duration = ANIMATOR_DURATION
doOnStart {
isSettingAnimating = true
}
doOnEnd {
binding.background.visibility = View.GONE
binding.settingContainer.removeAllViews()
binding.settingContainer.translationY = 0F
ConstraintSet().apply {
clone(binding.root)
clear(binding.settingContainer.id, ConstraintSet.TOP)
connect(binding.settingContainer.id, ConstraintSet.BOTTOM, binding.subjectTabContainer.id, ConstraintSet.TOP)
}.applyTo(binding.root)
settingBinding = null
isSettingAnimating = false
doOnEnd?.invoke()
}
}.start()
}
override fun onBackPressed(): Boolean {
if (binding.settingGuideContainer.isVisible) {
binding.settingGuideContainer.performClick()
return true
}
if (settingBinding != null) {
showDismissSettingDialogIfNeeded()
return true
}
return super.onBackPressed()
}
enum class TabStyle {
/**
* 专题详情样式
*/
SUBJECT_NORMAL,
/**
* 专题合集-关联首页底部Tab样式
*/
COLUMN_COLLECTION_MAIN_WRAPPER,
/**
* 专题合集-常规样式
*/
COLUMN_COLLECTION_NORMAL,
}
companion object {
private const val ANIMATOR_DURATION = 200L
}
}

View File

@ -4,19 +4,24 @@ import android.os.Bundle
import android.text.TextUtils
import android.view.MotionEvent
import android.view.View
import androidx.core.view.isVisible
import androidx.recyclerview.widget.GridLayoutManager
import com.gh.common.view.ConfigFilterView
import com.gh.common.view.ConfigFilterView.SortType
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.BaseFragment
import com.gh.gamecenter.common.base.fragment.ToolbarController
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.eventbus.EBReuse
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.view.GridSpacingItemDecoration
import com.gh.gamecenter.core.utils.StringUtils
import com.gh.gamecenter.core.utils.UrlFilterUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.databinding.FragmentSubjectTiledBinding
import com.gh.gamecenter.entity.SubjectData
import com.gh.gamecenter.entity.SubjectSettingEntity
import com.gh.gamecenter.common.eventbus.EBReuse
import com.gh.gamecenter.common.base.fragment.ToolbarController
import com.gh.gamecenter.subject.SubjectListFragment
import com.google.android.material.appbar.AppBarLayout
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import kotlin.math.abs
@ -32,6 +37,28 @@ class SubjectTileFragment : BaseFragment<Any>() {
private var mIsTouchScreen: Boolean = false
private var mMaxSize = ""
private var mMinSize = ""
private var mSelectedFilterSize = SubjectSettingEntity.Size(text = "全部大小")
private val finalFilter: String
get() {
var filter = if (mSelectedTypeName == "全部") {
// 全部的时候添加头图
UrlFilterUtils.getFilterQuery("tags", mSelectedTypeName, "type", mSelectedTypeName)
} else {
UrlFilterUtils.getFilterQuery("tags", mSelectedTypeName)
}
if (mMinSize.isNotEmpty() && mMinSize != "-1") {
filter += ",${UrlFilterUtils.getFilterQuery("min_size", mMinSize)}"
}
if (mMaxSize.isNotEmpty() && mMaxSize != "-1") {
filter += ",${UrlFilterUtils.getFilterQuery("max_size", mMaxSize)}"
}
return filter
}
override fun getLayoutId() = 0
override fun getInflatedLayout() = mBinding.root
@ -40,45 +67,75 @@ class SubjectTileFragment : BaseFragment<Any>() {
// 初始化数据
mSubjectData = arguments?.getParcelable(EntranceConsts.KEY_SUBJECT_DATA) ?: return
mSettingsEntity =
arguments?.getParcelable(SubjectSettingEntity::class.java.simpleName) ?: return
arguments?.getParcelable(EntranceConsts.KEY_SUBJECT_SETTING_DATA) ?: return
mSelectedTypeName = "全部"
mSubjectData.filter = UrlFilterUtils.getFilterQuery("type", "全部")
mSubjectData.sort = UrlFilterUtils.getFilterQuery("position", "1")
mBinding.subjectFilterContainer.visibility =
if (mSettingsEntity.filter == "on") View.VISIBLE else View.GONE
// 初始化顶部类型列表
val layoutManager = object : GridLayoutManager(context, 4) {
override fun canScrollVertically(): Boolean {
return false
mBinding.subjectTypeList.goneIf(mSettingsEntity.typeEntity.content.size <= 1) {
// 初始化顶部类型列表
val layoutManager = object : GridLayoutManager(context, 4) {
override fun canScrollVertically(): Boolean {
return false
}
}
}
mBinding.subjectTypeList.isNestedScrollingEnabled = false
mBinding.subjectTypeList.layoutManager = layoutManager
mBinding.subjectTabbarHottest.setOnClickListener(this)
mBinding.subjectTabbarNewest.setOnClickListener(this)
if (mSettingsEntity.typeEntity.content.size > 1) {
mBinding.subjectTypeList.isNestedScrollingEnabled = false
mBinding.subjectTypeList.layoutManager = layoutManager
mBinding.subjectTypeList.addItemDecoration(GridSpacingItemDecoration(4, 8F.dip2px(), false))
val adapter = SubjectTypeListAdapter(requireContext(), mItemClickListener = {
mSelectedTypeName = it
if (it == "全部") {
// 全部的时候添加头图
mSubjectData.filter = UrlFilterUtils.getFilterQuery("tags", it, "type", it)
} else {
mSubjectData.filter = UrlFilterUtils.getFilterQuery("tags", it)
}
mSubjectData.filter = finalFilter
mSubjectData.tag = it
loadData()
}, mGameType = mSettingsEntity.typeEntity.content)
mBinding.subjectTypeList.adapter = adapter
}
mBinding.filterView.goneIf(!mSettingsEntity.isFilterEnabled) {
mBinding.filterView.run {
initSubjectFilterView(mSettingsEntity)
setOnConfigSetupListener(object :
ConfigFilterView.OnConfigFilterSetupListener {
override fun onShowSortSize() {}
override fun onSetupSortSize(sortSize: SubjectSettingEntity.Size) {
mMinSize = sortSize.min.toString()
mMaxSize = sortSize.max.toString()
mSelectedFilterSize = sortSize
mSubjectData.filter = finalFilter
loadData()
}
override fun onSetupSortType(sortType: SortType) {
val clickTv = when (sortType) {
SortType.RATING -> ratingTv
SortType.NEWEST -> newestTv
SortType.UPDATE -> updateTv
else -> recommendedTv
}
val sort = when (clickTv.text) {
"更新" -> UrlFilterUtils.getFilterQuery("update_time", "-1")
"最新" -> UrlFilterUtils.getFilterQuery("publish", "-1")
"评分" -> UrlFilterUtils.getFilterQuery("star", "-1")
else -> UrlFilterUtils.getFilterQuery("position", "1")
}
if (mSubjectData.sort != sort) {
mSubjectData.sort = sort
loadData()
}
}
})
}
}
// 是否为专题合集详情子页面
val isColumnCollection = arguments?.getBoolean(EntranceConsts.KEY_IS_COLUMN_COLLECTION) ?: false
if (!isColumnCollection) {
// Appbar 交互
mBinding.subjectAppbar.addOnOffsetChangedListener(AppBarLayout.OnOffsetChangedListener { appBarLayout, verticalOffset ->
mBinding.subjectAppbar.addOnOffsetChangedListener { appBarLayout, verticalOffset ->
if (!TextUtils.isEmpty(mSubjectData.subjectName) && activity is ToolbarController) {
val totalScrollRange = appBarLayout.totalScrollRange
if (abs(verticalOffset) < totalScrollRange / 2) {
@ -94,7 +151,7 @@ class SubjectTileFragment : BaseFragment<Any>() {
)
}
}
})
}
}
loadData()
@ -116,23 +173,6 @@ class SubjectTileFragment : BaseFragment<Any>() {
}
}
override fun onClick(view: View) {
when (view.id) {
R.id.subject_tabbar_hottest -> {
mBinding.subjectTabbarHottest.isChecked = true
mBinding.subjectTabbarNewest.isChecked = false
mSubjectData.sort = UrlFilterUtils.getFilterQuery("position", "1")
loadData()
}
R.id.subject_tabbar_newest -> {
mBinding.subjectTabbarHottest.isChecked = false
mBinding.subjectTabbarNewest.isChecked = true
mSubjectData.sort = UrlFilterUtils.getFilterQuery("publish", "-1")
loadData()
}
}
}
fun loadData() {
val transaction = childFragmentManager.beginTransaction()
hideFragments(transaction)
@ -151,8 +191,10 @@ class SubjectTileFragment : BaseFragment<Any>() {
transaction.show(fragmentByTag)
} else {
fragmentByTag = SubjectListFragment()
val bundle = arguments ?: Bundle()
val bundle = arguments?.clone() as? Bundle ?: Bundle()
bundle.remove(EntranceConsts.KEY_SUBJECT_SETTING_DATA)
bundle.putParcelable(EntranceConsts.KEY_SUBJECT_DATA, mSubjectData)
bundle.putString(EntranceConsts.KEY_SUBJECT_SORT_SIZE, mSelectedFilterSize.text)
fragmentByTag.arguments = bundle
transaction.add(R.id.subject_content_rl, fragmentByTag, fmTag)
}
@ -161,6 +203,13 @@ class SubjectTileFragment : BaseFragment<Any>() {
}
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
if (mBinding.filterView.isVisible) {
mBinding.filterView.updateAllTextView()
}
}
companion object {
private const val OPEN_APPBAR = "openAppBar"
}

View File

@ -1,14 +1,16 @@
package com.gh.gamecenter.subject.tile
import android.content.Context
import android.graphics.Color
import android.graphics.Typeface
import android.text.TextUtils
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import android.widget.CheckedTextView
import androidx.core.content.ContextCompat
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.toDrawable
import com.lightgame.adapter.BaseRecyclerAdapter
class SubjectTypeListAdapter(
@ -20,28 +22,26 @@ class SubjectTypeListAdapter(
private var mCurType = "全部"
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SubjectTypeViewHolder {
return SubjectTypeViewHolder(mLayoutInflater.inflate(R.layout.item_filter_size, parent, false))
return SubjectTypeViewHolder(mLayoutInflater.inflate(R.layout.subject_rows_label_item, parent, false))
}
override fun onBindViewHolder(holder: SubjectTypeViewHolder, position: Int) {
holder.itemView.layoutParams = holder.itemView.layoutParams.apply {
holder.type.updateLayoutParams<ViewGroup.MarginLayoutParams> {
width = ViewGroup.LayoutParams.MATCH_PARENT
}
holder.type.background = R.drawable.subject_label_background_style.toDrawable(mContext)
holder.type.setTextColor(ContextCompat.getColorStateList(mContext, R.color.subject_label_text_style))
holder.type.text = mGameType[position]
if (!TextUtils.isEmpty(mCurType) && mCurType == mGameType[position]) {
holder.type.background = ContextCompat.getDrawable(mContext, R.drawable.bg_tag_text)
holder.type.setTextColor(Color.WHITE)
} else {
holder.type.background = null
holder.type.setTextColor(ContextCompat.getColor(mContext, com.gh.gamecenter.common.R.color.text_757575))
}
val isChecked = !TextUtils.isEmpty(mCurType) && mCurType == mGameType[position]
holder.type.isChecked = isChecked
holder.type.setTypeface(Typeface.DEFAULT, if (isChecked) Typeface.BOLD else Typeface.NORMAL)
holder.type.setOnClickListener {
if (holder.adapterPosition == -1) return@setOnClickListener
if (holder.bindingAdapterPosition == -1) return@setOnClickListener
mCurType = mGameType[holder.adapterPosition]
mItemClickListener.invoke(mGameType[holder.adapterPosition])
mCurType = mGameType[holder.bindingAdapterPosition]
mItemClickListener.invoke(mGameType[holder.bindingAdapterPosition])
notifyDataSetChanged()
}
}
@ -51,6 +51,6 @@ class SubjectTypeListAdapter(
}
inner class SubjectTypeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val type by lazy { itemView.findViewById<TextView>(R.id.size_tv) }
val type: CheckedTextView by lazy { itemView.findViewById(R.id.label) }
}
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/text_tertiary" android:state_checked="false" />
<item android:color="@color/text_theme" android:state_checked="true" />
</selector>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/text_757575" android:state_checked="false" />
<item android:color="@color/white" android:state_checked="true" />
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

View File

@ -1,9 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/title" />
<corners android:radius="4dp" />
<corners android:radius="6dp" />
<solid android:color="@color/primary_theme_10" />
</shape>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/bg_shape_primary_theme_alpha_10_radius_6" android:state_checked="true" />
<item android:drawable="@drawable/bg_shape_primary_theme_alpha_10_radius_6" android:state_focused="true" />
<item android:drawable="@drawable/bg_shape_f8_radius_6" />
</selector>

View File

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="8dp"
android:height="6dp"
android:viewportWidth="8"
android:viewportHeight="6">
<path
android:strokeWidth="1"
android:pathData="M7,4.5L4,1.5L1,4.5"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:strokeColor="@color/text_primary"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="8dp"
android:height="8dp"
android:viewportWidth="8"
android:viewportHeight="8">
<group>
<clip-path
android:pathData="M0,0h8v8h-8z"/>
<path
android:pathData="M4,0C4.2761,0 4.5,0.2239 4.5,0.5V3.5H7.5C7.7761,3.5 8,3.7239 8,4C8,4.2761 7.7761,4.5 7.5,4.5H4.5V7.5C4.5,7.7761 4.2761,8 4,8C3.7239,8 3.5,7.7761 3.5,7.5V4.5H0.5C0.2239,4.5 0,4.2761 0,4C0,3.7239 0.2239,3.5 0.5,3.5H3.5V0.5C3.5,0.2239 3.7239,0 4,0Z"
android:fillColor="@color/text_secondary"
android:fillType="evenOdd"/>
</group>
</vector>

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="8dp"
android:height="8dp"
android:viewportWidth="8"
android:viewportHeight="8">
<group>
<clip-path
android:pathData="M0,0h8v8h-8z"/>
<path
android:pathData="M0.6464,0.6464C0.8417,0.4512 1.1583,0.4512 1.3535,0.6464L4,3.2929L6.6465,0.6464C6.8417,0.4512 7.1583,0.4512 7.3535,0.6464C7.5488,0.8417 7.5488,1.1583 7.3535,1.3535L4.7071,4L7.3535,6.6465C7.5488,6.8417 7.5488,7.1583 7.3535,7.3535C7.1583,7.5488 6.8417,7.5488 6.6465,7.3535L4,4.7071L1.3535,7.3535C1.1583,7.5488 0.8417,7.5488 0.6464,7.3535C0.4512,7.1583 0.4512,6.8417 0.6464,6.6465L3.2929,4L0.6464,1.3535C0.4512,1.1583 0.4512,0.8417 0.6464,0.6464Z"
android:fillColor="@color/text_secondary"
android:fillType="evenOdd"/>
</group>
</vector>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/ui_divider" />
</shape>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/bg_shape_primary_theme_alpha_10_radius_6" android:state_checked="true" />
<item android:drawable="@drawable/bg_shape_primary_theme_alpha_10_radius_6" android:state_focused="true" />
</selector>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/button_round_theme_alpha_10" android:state_pressed="true" />
<item android:drawable="@drawable/button_round_theme_alpha_10" android:state_focused="true" />
<item android:drawable="@drawable/button_round_theme_alpha_10" android:state_selected="true" />
<item android:drawable="@drawable/button_round_theme_alpha_10" android:state_checked="true" />
<item android:drawable="@drawable/bg_shape_ui_container_1_radius_999" />
</selector>

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/subject_tab_down" android:state_selected="true" />
<item android:drawable="@color/white" android:state_focused="false" />
</selector>

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/bg_tag_text" android:state_checked="true" />
<item android:drawable="@drawable/bg_tag_text" android:state_focused="true" />
</selector>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
@ -7,9 +8,18 @@
android:id="@+id/contentContainer"
layout="@layout/fragment_list_base_skeleton" />
<com.gh.gamecenter.common.view.StatusBarView
android:id="@+id/statusBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/ui_surface"
android:visibility="gone"
tools:visibility="visible" />
<FrameLayout
android:id="@+id/placeholder"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="match_parent"
android:layout_below="@+id/statusBar" />
</RelativeLayout>

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/ui_surface">
<com.gh.common.view.ConfigFilterView
android:id="@+id/filterView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
tools:visibility="visible" />
<FrameLayout
android:id="@+id/list_skeleton"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/filterView"/>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/list_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/filterView">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list_rv"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<include
layout="@layout/reuse_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true" />
<include layout="@layout/reuse_no_connection" />
<include layout="@layout/reuse_none_data" />
<include layout="@layout/reuse_data_exception" />
</RelativeLayout>

View File

@ -1,48 +1,146 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<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"
android:orientation="vertical">
<RelativeLayout
android:id="@+id/subject_tab_container"
android:layout_width="match_parent"
android:layout_height="@dimen/tab_layout_height"
android:background="@color/ui_surface"
android:visibility="gone">
<androidx.viewpager.widget.ViewPager
android:id="@+id/subject_viewpager"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/subject_tab_container" />
<!-- <View-->
<!-- android:id="@+id/subject_tab_line"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="1dp"-->
<!-- android:layout_alignParentBottom="true"-->
<!-- android:background="@color/background"-->
<!-- android:visibility="gone" />-->
<View
android:id="@+id/background"
android:layout_width="0dp"
android:layout_height="0dp"
android:alpha="0"
android:background="@color/black"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/subject_tab_container"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/subject_tab_container"
android:layout_width="0dp"
android:layout_height="48dp"
android:background="@color/ui_surface"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/subject_viewpager"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible">
<com.gh.gamecenter.common.view.TabIndicatorView
android:id="@+id/subject_tab_indicator"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="@dimen/default_tab_indicator_height"
android:layout_alignParentBottom="true" />
android:layout_marginEnd="8dp"
app:disableIndicatorScaling="true"
app:indicatorColor="@color/primary_theme"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/optionIv"
app:layout_constraintStart_toStartOf="parent"
app:layout_goneMarginEnd="0dp" />
<com.google.android.material.tabs.TabLayout
<com.gh.gamecenter.common.view.NoDefaultMinWidthTabLayout
android:id="@+id/subject_tab"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:tabIndicatorColor="@color/primary_theme"
app:tabMode="scrollable"
app:tabIndicatorHeight="0dp"
app:tabRippleColor="@color/transparent"
app:tabSelectedTextColor="@color/primary_theme"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginEnd="8dp"
android:overScrollMode="never"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/optionIv"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_goneMarginEnd="0dp"
app:tabIndicator="@null"
app:tabTextAppearance="@style/TabLayoutTextAppearance" />
app:tabIndicatorHeight="0dp"
app:tabMaxWidth="0dp"
app:tabMode="scrollable"
app:tabRippleColor="@color/transparent"
app:tabTextAppearance="@style/ColumnCollectionTabDetailTabTextAppearance" />
</RelativeLayout>
<ImageView
android:id="@+id/optionIv"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_marginEnd="16dp"
android:background="@drawable/bg_shape_ui_container_2_radius_999"
android:padding="6dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_options"
tools:visibility="visible" />
<androidx.viewpager.widget.ViewPager
android:id="@+id/subject_viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<View
android:id="@+id/maskView"
android:layout_width="16dp"
android:layout_height="match_parent"
android:layout_marginEnd="8dp"
android:background="@drawable/bg_game_detail_tab_gradient_mask"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/optionIv"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<FrameLayout
android:id="@+id/settingContainer"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@+id/subject_tab_container" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/settingGuideContainer"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible">
<ImageView
android:id="@+id/settingGuideIv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="36dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/guide_column_collection_custom_setting" />
<View
android:id="@+id/skipView"
android:layout_width="24dp"
android:layout_height="32dp"
android:layout_marginEnd="64dp"
app:layout_constraintBottom_toBottomOf="@+id/settingClickView"
app:layout_constraintEnd_toStartOf="@+id/settingClickView"
app:layout_constraintTop_toTopOf="@+id/settingClickView" />
<View
android:id="@+id/settingClickView"
android:layout_width="80dp"
android:layout_height="32dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="24dp"
app:layout_constraintBottom_toBottomOf="@+id/settingGuideIv"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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"
android:orientation="vertical">
@ -17,44 +18,17 @@
android:id="@+id/subject_type_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingTop="8dp"
android:paddingBottom="12dp"
app:layout_scrollFlags="scroll|enterAlwaysCollapsed" />
<LinearLayout
android:id="@+id/subject_filter_container"
android:layout_width="wrap_content"
<com.gh.common.view.ConfigFilterView
android:id="@+id/filterView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:layout_marginBottom="16dp"
android:background="@drawable/subject_tiled_tab_background">
<CheckedTextView
android:id="@+id/subject_tabbar_hottest"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:background="@drawable/subject_tiled_tab_selector"
android:checked="true"
android:gravity="center"
android:paddingLeft="24dp"
android:paddingRight="24dp"
android:text="最热"
android:textAlignment="center"
android:textColor="@color/subject_tiled_tab_color_selector"
android:textSize="12sp" />
<CheckedTextView
android:id="@+id/subject_tabbar_newest"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:background="@drawable/subject_tiled_tab_selector"
android:checked="false"
android:gravity="center"
android:paddingLeft="24dp"
android:paddingRight="24dp"
android:text="最新"
android:textAlignment="center"
android:textColor="@color/subject_tiled_tab_color_selector"
android:textSize="12sp" />
</LinearLayout>
android:visibility="gone"
tools:visibility="visible" />
</com.google.android.material.appbar.AppBarLayout>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingEnd="8dp"
android:paddingBottom="8dp">
<com.gh.gamecenter.common.view.Chips
android:id="@+id/chips"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</FrameLayout>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/size_tv"
android:layout_width="match_parent"
android:layout_height="28dp"
android:background="@drawable/config_filter_size_background_style"
android:gravity="center"
android:includeFontPadding="false"
android:text="全部大小"
android:textAlignment="center"
android:textColor="@color/subject_label_text_style"
android:textSize="@dimen/little_secondary_size" />

View File

@ -0,0 +1,153 @@
<?xml version="1.0" encoding="utf-8"?>
<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:background="@drawable/background_shape_white_radius_12_bottom_only">
<ScrollView
android:id="@+id/scrollView"
android:layout_width="0dp"
android:layout_height="328dp"
android:overScrollMode="never"
android:scrollbars="none"
app:layout_constraintBottom_toTopOf="@+id/actionContainer"
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">
<TextView
android:id="@+id/followTv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:text="关注内容"
android:textColor="@color/text_primary"
android:textSize="@dimen/secondary_title_text_size"
android:textStyle="bold" />
<TextView
android:id="@+id/followDesTv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="6dp"
android:layout_marginEnd="16dp"
android:text="长按拖动排序最少添加1个内容上限为15个"
android:textColor="@color/text_tertiary"
android:textSize="@dimen/little_secondary_size" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/followRv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="8dp"
android:overScrollMode="never" />
<LinearLayout
android:id="@+id/moreContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="16dp"
android:paddingBottom="8dp">
<TextView
android:id="@+id/moreTv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="更多内容"
android:textColor="@color/text_primary"
android:textSize="@dimen/secondary_title_text_size"
android:textStyle="bold" />
<TextView
android:id="@+id/moreDesTv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="6dp"
android:layout_marginEnd="16dp"
android:text="点击可添加至关注内容"
android:textColor="@color/text_tertiary"
android:textSize="@dimen/little_secondary_size" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/moreRv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="8dp"
android:overScrollMode="never" />
</LinearLayout>
</LinearLayout>
</ScrollView>
<LinearLayout
android:id="@+id/actionContainer"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="16dp"
android:paddingLeft="16dp"
android:paddingTop="16dp"
android:paddingEnd="20dp"
android:paddingRight="16dp"
android:paddingBottom="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/nestedScrollView">
<TextView
android:id="@+id/cancelTv"
style="@style/GrayLightButton"
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_weight="1"
android:gravity="center"
android:text="取消" />
<TextView
android:id="@+id/saveTv"
style="@style/PrimaryButton"
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_marginStart="12dp"
android:layout_weight="3"
android:gravity="center"
android:text="保存" />
</LinearLayout>
<View
android:id="@+id/topDivider"
android:layout_width="0dp"
android:layout_height="@dimen/divider_1px"
android:background="@color/ui_divider"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/bottomDivider"
android:layout_width="0dp"
android:layout_height="@dimen/divider_1px"
android:background="@color/ui_divider"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/actionContainer" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -4,47 +4,71 @@
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/config_controller"
android:layout_width="match_parent"
android:layout_height="@dimen/default_filter_row_height"
android:layout_height="32dp"
android:background="@color/ui_surface"
android:paddingLeft="@dimen/default_filter_row_padding"
android:paddingRight="@dimen/default_filter_row_padding">
<TextView
android:id="@+id/updateTv"
style="@style/filterOptionItem"
android:background="@drawable/bg_tag_text"
style="@style/ConfigFilterOptionItem"
android:text="更新"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/dot1"
android:layout_width="2dp"
android:layout_height="2dp"
android:background="@drawable/ic_filter_dot"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/recommended_tv"
app:layout_constraintStart_toEndOf="@+id/updateTv"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/recommended_tv"
style="@style/filterOptionItem"
android:layout_marginLeft="@dimen/default_filter_row_item_margin"
style="@style/ConfigFilterOptionItem"
android:text="推荐"
android:textColor="@color/text_757575"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@id/updateTv"
app:layout_constraintTop_toTopOf="parent"
app:layout_goneMarginLeft="0dp" />
<View
android:id="@+id/dot2"
android:layout_width="2dp"
android:layout_height="2dp"
android:background="@drawable/ic_filter_dot"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/newest_tv"
app:layout_constraintStart_toEndOf="@+id/recommended_tv"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/newest_tv"
style="@style/filterOptionItem"
android:layout_marginLeft="@dimen/default_filter_row_item_margin"
style="@style/ConfigFilterOptionItem"
android:text="最新"
android:textColor="@color/text_757575"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@id/recommended_tv"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/dot3"
android:layout_width="2dp"
android:layout_height="2dp"
android:background="@drawable/ic_filter_dot"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/rating_tv"
app:layout_constraintStart_toEndOf="@+id/newest_tv"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/rating_tv"
style="@style/filterOptionItem"
android:layout_marginLeft="@dimen/default_filter_row_item_margin"
style="@style/ConfigFilterOptionItem"
android:text="评分"
android:textColor="@color/text_757575"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@id/newest_tv"
@ -55,14 +79,14 @@
<TextView
android:id="@+id/size_tv"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:drawableRight="@drawable/ic_filter_arrow_down"
android:drawablePadding="8dp"
android:layout_height="match_parent"
android:drawablePadding="4dp"
android:gravity="center_vertical"
android:text="全部大小"
android:textColor="@color/text_757575"
android:textColor="@color/text_tertiary"
android:textSize="12sp"
android:textStyle="bold"
app:drawableEndCompat="@drawable/ic_auxiliary_arrow_down_8"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />

View File

@ -1,19 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout 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="match_parent"
android:background="@color/transparent"
android:orientation="vertical">
<com.google.android.flexbox.FlexboxLayout
android:id="@+id/flexbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/ui_surface"
android:paddingBottom="8dp"
app:flexDirection="row"
app:flexWrap="wrap" />
android:background="@color/transparent">
<View
android:id="@+id/background"
@ -22,4 +12,17 @@
android:alpha="0.3"
android:background="@color/black" />
</LinearLayout>
<com.google.android.flexbox.FlexboxLayout
android:id="@+id/flexbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/background_shape_white_radius_12_bottom_only"
android:paddingHorizontal="16dp"
android:paddingTop="8dp"
android:paddingBottom="16dp"
app:showDivider="middle"
app:dividerDrawable="@drawable/shape_flexbox_divider_w8_h8"
app:flexDirection="row"
app:flexWrap="wrap" />
</FrameLayout>

View File

@ -1,22 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
android:layout_height="24dp"
android:overScrollMode="never"
android:scrollbars="none">
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="40dp"
android:scrollbars="none">
<LinearLayout
android:id="@+id/label_container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingLeft="@dimen/default_filter_row_padding"
android:paddingRight="@dimen/default_filter_row_padding" />
</HorizontalScrollView>
</LinearLayout>
<LinearLayout
android:id="@+id/label_container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingLeft="@dimen/default_filter_row_padding"
android:paddingRight="@dimen/default_filter_row_padding" />
</HorizontalScrollView>

View File

@ -3,7 +3,6 @@
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/label"
style="@style/filterOptionItem"
android:layout_marginRight="@dimen/default_filter_row_item_margin"
android:background="@drawable/text_blue_or_white_style"
android:textColor="@color/text_black_or_white_color_style"
android:background="@drawable/subject_label_background_style"
android:textColor="@color/subject_label_text_style"
tools:text="全部" />

View File

@ -2,17 +2,19 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/border_round_eee_14"
android:gravity="center">
<CheckedTextView
android:id="@+id/tab_title"
android:layout_width="68dp"
android:layout_height="28dp"
android:layout_width="wrap_content"
android:layout_height="32dp"
android:background="@drawable/subject_tab_background_selector"
android:gravity="center"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:text="人气榜"
android:textAlignment="center"
android:textColor="@color/text_tabbar_video_style"
android:textColor="@color/text_tabbar_style"
android:textSize="12sp" />
</LinearLayout>

View File

@ -199,6 +199,7 @@
<item name="android:paddingLeft">8dp</item>
<item name="android:paddingRight">8dp</item>
<item name="android:textColor">@color/white</item>
<item name="android:textAlignment">center</item>
<item name="android:textStyle">bold</item>
<item name="android:textSize">12sp</item>
</style>
@ -280,4 +281,15 @@
<item name="android:textSize">16sp</item>
<item name="android:textStyle">bold</item>
</style>
<style name="ConfigFilterOptionItem">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">match_parent</item>
<item name="android:gravity">center</item>
<item name="android:singleLine">true</item>
<item name="android:ellipsize">end</item>
<item name="android:paddingStart">8dp</item>
<item name="android:paddingEnd">8dp</item>
<item name="android:textSize">@dimen/secondary_size</item>
</style>
</resources>

View File

@ -94,7 +94,7 @@ ext {
picasso = "2.5.2"
lottie = "3.7.0"
lottieCompose = "5.2.0"
flexbox = "1.1.0"
flexbox = "3.0.0"
pickerView = "4.1.8"
verifier = "1.0.6"
skeleton = "1.1.5"

View File

@ -93,7 +93,7 @@ dependencies {
}
api "io.github.sinaweibosdk:core:${weiboSDK}"
api "com.lg:skeleton:${skeleton}"
api "com.google.android:flexbox:${flexbox}"
api "com.google.android.flexbox:flexbox:${flexbox}"
api "com.blankj:utilcodex:${blankjUtilCodex}"
api(project(path: ":module_core"))

View File

@ -502,4 +502,8 @@ public class Constants {
public static final String PUSH_MESSAGE_ID = "push_message_id";
public static final String PUSH_LINK_ENTITY = "push_link_entity";
public static final String SP_COLUMN_COLLECTION_CUSTOM_TAB = "column_collection_custom_tab";
public static final String SP_SHOW_COLUMN_COLLECTION_CUSTOM_TAB_SETTING = "show_column_collection_custom_tab_setting";
public static final String SP_SHOW_COLUMN_COLLECTION_CUSTOM_TAB_GUIDE = "show_column_collection_custom_tab_guide";
}

View File

@ -379,9 +379,9 @@ public class EntranceConsts {
public static final String KEY_PERFORM_CONTENT_CARD_CLICK_TYPE = "perform_content_card_click_type";
public static final String KEY_FROM = "from";
public static final String TAB_TYPE_DESC = "详情";
public static final String TAB_TYPE_TRENDS = "专区";
public static final String TAB_TYPE_ARCHIVE = "云存档";
public static final String TAB_TYPE_RATING = "评价";
public static final String TAB_TYPE_BBS = "论坛";
public static final String KEY_COLUMN_COLLECTION_CUSTOM = "column_collection_custom"; //是否开启专题合集自定义设置
public static final String KEY_COLUMN_COLLECTION_CUSTOM_SIZE = "column_collection_custom_size"; //默认显示前X个专题
public static final String KEY_COLUMN_COLLECTION_CUSTOM_POSITION = "column_collection_custom_position"; //默认显示前X个专题
public static final String KEY_SUBJECT_SETTING_DATA = "subject_setting_data";
public static final String KEY_SUBJECT_SORT_SIZE = "subject_sort_size";
}

View File

@ -38,6 +38,8 @@ open class LinkEntity(
@SerializedName("game_id")
var gameId: String = "",
var explain: String = "", // 游戏单合集说明
@SerializedName("custom_limit")
var customLimit: String = "", // 自定义限制
//本地字段 (埋点用)
var blockId: String = "",

View File

@ -1808,8 +1808,8 @@ object SensorsBridge {
columnCollectionName: String,
columnCollectionId: String,
position: Int = -1,
gameColumnName: String,
gameColumnId: String,
gameColumnName: String = "",
gameColumnId: String = "",
text: String,
columnCollectionPattern: String = "",
bottomTab: String = "",
@ -3087,7 +3087,10 @@ object SensorsBridge {
location: String = "",
columnPattern: String = "",
text: String = "",
buttonType: String = ""
buttonType: String = "",
gameTag: List<String> = listOf(),
sort: String = "",
inclusionSize: String = "",
) {
val json = json {
KEY_BOTTOM_TAB to bottomTab
@ -3110,6 +3113,9 @@ object SensorsBridge {
KEY_COLUMN_PATTERN to columnPattern
KEY_TEXT to text
KEY_BUTTON_TYPE to buttonType
KEY_GAME_TAG to gameTag
"sort" to sort
"inclusion_size" to inclusionSize
}
trackEvent(EVENT_COLUMN_CLICK, json)
}

View File

@ -31,6 +31,13 @@ class Chips @JvmOverloads constructor(
private var chipsStyle = STYLE_REGULAR
private var chipsState = STATE_DEFAULT
val startIcon
get() = binding.ivStart
val endIcon
get() = binding.ivEnd
init {
val inflater = LayoutInflater.from(context)
binding = LayoutChipsBinding.inflate(inflater, this, true)
@ -147,9 +154,9 @@ class Chips @JvmOverloads constructor(
private const val STYLE_MINI = 2
// state
private const val STATE_DEFAULT = 0
private const val STATE_FILL = 1
private const val STATE_SELECTED = 2
private const val STATE_DISABLE = 3
const val STATE_DEFAULT = 0
const val STATE_FILL = 1
const val STATE_SELECTED = 2
const val STATE_DISABLE = 3
}
}

View File

@ -83,6 +83,15 @@ public class TabIndicatorView extends View implements ViewPager.OnPageChangeList
invalidate();
}
public void updateIndicatorColor(int color) {
mIndicatorColor = ContextCompat.getColor(getContext(), color);
mGradientDrawable = new GradientDrawable();
mGradientDrawable.setShape(GradientDrawable.RECTANGLE);
mGradientDrawable.setColor(mIndicatorColor);
mGradientDrawable.setCornerRadius(Integer.MAX_VALUE);
invalidate();
}
public void setupWithTabLayout(final TabLayout tableLayout) {
mTabLayout = tableLayout;

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/text_primary" android:state_pressed="true" />
<item android:color="@color/text_primary" android:state_focused="true" />
<item android:color="@color/text_primary" android:state_selected="true" />
<item android:color="@color/text_primary" android:state_checked="true" />
<item android:color="@color/text_secondary" />
</selector>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="999dp" />
<solid android:color="@color/ui_container_1" />
</shape>

View File

@ -12,6 +12,7 @@
android:layout_height="8dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/tv_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
@ -21,11 +22,15 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:ellipsize="end"
android:lines="1"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/iv_end"
app:layout_constraintStart_toEndOf="@id/iv_start"
app:layout_constraintTop_toTopOf="parent"
app:layout_goneMarginStart="0dp"
tools:text="长的标签名称" />
tools:text="长的标签名称长的标签名称长的标签名称长的标签名称长的标签名称长的标签名称" />
<ImageView
android:id="@+id/iv_end"
@ -34,6 +39,7 @@
android:layout_marginStart="4dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/tv_content"
app:layout_constraintTop_toTopOf="parent"
app:tint="@color/text_theme" />

View File

@ -105,6 +105,13 @@
<item name="tabMode">fixed</item>
</style>
<style name="ColumnCollectionTabDetailTabTextAppearance" parent="TextAppearance.AppCompat.Widget.ActionBar.Title">
<item name="android:textSize">14sp</item>
<item name="android:textColor">@color/tab_text_primary_secondary_selector</item>
<item name="android:textAllCaps">false</item>
<item name="android:textStyle">bold</item>
</style>
<style name="community_publication_animation">
<item name="android:windowEnterAnimation">@anim/community_publication_enter</item>
<!-- 指定显示的动画xml -->