1
1
package com.xyoye.anime_component.ui.activities.anime_detail
2
2
3
- import android.graphics.Color
4
- import androidx.core.graphics.BlendModeColorFilterCompat
5
- import androidx.core.graphics.BlendModeCompat
6
- import androidx.fragment.app.Fragment
7
- import androidx.fragment.app.FragmentManager
8
- import androidx.fragment.app.FragmentPagerAdapter
3
+ import android.graphics.drawable.ShapeDrawable
4
+ import android.graphics.drawable.shapes.RoundRectShape
9
5
import com.alibaba.android.arouter.facade.annotation.Autowired
10
6
import com.alibaba.android.arouter.facade.annotation.Route
11
7
import com.alibaba.android.arouter.launcher.ARouter
8
+ import com.google.android.material.tabs.TabLayoutMediator
12
9
import com.gyf.immersionbar.ImmersionBar
13
10
import com.xyoye.anime_component.BR
14
11
import com.xyoye.anime_component.R
15
12
import com.xyoye.anime_component.databinding.ActivityAnimeDetailBinding
16
- import com.xyoye.anime_component.ui.fragment.anime_episode.AnimeEpisodeFragment
17
- import com.xyoye.anime_component.ui.fragment.anime_intro.AnimeIntroFragment
18
- import com.xyoye.anime_component.ui.fragment.anime_recommend.AnimeRecommendFragment
13
+ import com.xyoye.anime_component.ui.adapter.AnimeDetailPageAdapter
14
+ import com.xyoye.anime_component.utils.loadAnimeCover
19
15
import com.xyoye.common_component.base.BaseActivity
20
16
import com.xyoye.common_component.config.RouteTable
21
- import com.xyoye.common_component.extension.*
22
- import com.xyoye.common_component.weight.ToastCenter
23
- import com.xyoye.data_component.data.BangumiData
24
- import kotlin.math.abs
25
- import kotlin.math.max
17
+ import com.xyoye.common_component.extension.clamp
18
+ import com.xyoye.common_component.extension.dp
19
+ import com.xyoye.common_component.extension.isNightMode
20
+ import com.xyoye.common_component.extension.loadImage
21
+ import com.xyoye.common_component.extension.opacity
22
+ import com.xyoye.common_component.extension.setTextColorRes
23
+ import com.xyoye.common_component.extension.toResColor
24
+ import com.xyoye.data_component.bean.AnimeArgument
25
+ import com.xyoye.data_component.bean.ColorRange
26
+ import com.xyoye.data_component.enums.AnimeDetailTab
27
+ import kotlin.math.absoluteValue
26
28
27
29
@Route(path = RouteTable .Anime .AnimeDetail )
28
30
class AnimeDetailActivity : BaseActivity <AnimeDetailViewModel , ActivityAnimeDetailBinding >() {
29
31
30
32
@Autowired
31
33
@JvmField
32
- var animeId: Int = - 1
34
+ var animeArgument: AnimeArgument = AnimeArgument ()
35
+
36
+ // 标题栏字体颜色变化范围
37
+ private val textColorRange by lazy {
38
+ ColorRange (
39
+ R .color.text_white_immutable.toResColor(this ),
40
+ R .color.text_theme.toResColor(this )
41
+ )
42
+ }
43
+
44
+ // 页面颜色变化范围
45
+ private val pageColorRange by lazy {
46
+ ColorRange (
47
+ R .color.item_bg_color.toResColor(this ),
48
+ R .color.theme.toResColor(this )
49
+ )
50
+ }
51
+
52
+ // 上一次滚动的百分比
53
+ private var lastScrollLimit = 0
54
+
55
+ // 默认的tab列表
56
+ private val defaultTabs = AnimeDetailTab .values()
57
+
58
+ // 返回事件拦截器
59
+ private var backPressInterceptor: (() -> Boolean )? = null
33
60
34
61
override fun initViewModel () = ViewModelInit (
35
62
BR .viewModel,
@@ -48,83 +75,56 @@ class AnimeDetailActivity : BaseActivity<AnimeDetailViewModel, ActivityAnimeDeta
48
75
override fun initView () {
49
76
ARouter .getInstance().inject(this )
50
77
51
- postponeEnterTransition()
52
-
53
78
title = " "
54
79
55
- dataBinding.toolbar.setNavigationOnClickListener { finishAfterTransition() }
80
+ initViewPager(defaultTabs)
81
+ initListener()
56
82
57
- if (animeId == - 1 ) {
58
- ToastCenter .showError(" 获取番剧信息失败" )
59
- return
60
- }
83
+ updateUIByScroll(0f )
84
+ updateTabLayoutRound(0f )
85
+ updateUIbyAnime(animeArgument.title, animeArgument.imageUrl)
61
86
62
- dataBinding.appBarLayout.addOnOffsetChangedListener { appBarLayout, verticalOffset ->
63
- // 用于计算的偏移及高度,从1/2位置开始计算
64
- val calcRange = appBarLayout.totalScrollRange / 2f
65
- val calcOffset = max(0f , abs(verticalOffset) - calcRange)
66
- val offsetPercent = calcOffset / calcRange
67
-
68
- // 返回图标颜色
69
- val color = getBackIconColor(offsetPercent)
70
- val colorFilter =
71
- BlendModeColorFilterCompat .createBlendModeColorFilterCompat(
72
- color,
73
- BlendModeCompat .SRC_IN
74
- )
75
- dataBinding.toolbar.navigationIcon?.colorFilter = colorFilter
76
-
77
- // 标题颜色
78
- val alpha = (255 * offsetPercent).toInt()
79
- val titleTextColor = R .color.text_theme.toResColor()
80
- val titleColor = Color .argb(
81
- alpha,
82
- Color .red(titleTextColor),
83
- Color .green(titleTextColor),
84
- Color .blue(titleTextColor)
85
- )
86
- dataBinding.toolbar.setTitleTextColor(titleColor)
87
- dataBinding.followTv.background?.alpha = 255 - alpha
88
-
89
- // 状态栏文字颜色
90
- // tips: MIUI深色模式下状态栏字体颜色不受此控制
91
- val isDarkFont = calcOffset > 0 && ! isNightMode()
92
- ImmersionBar .with (this )
93
- .transparentBar()
94
- .statusBarDarkFont(isDarkFont)
95
- .init ()
96
- }
97
-
98
- dataBinding.tabLayout.setupWithViewPager(dataBinding.viewpager)
99
-
100
- initObserver()
101
-
102
- viewModel.animeIdField.set(animeId.toString())
103
- viewModel.getAnimeDetail(animeId.toString())
87
+ viewModel.animeIdField.set(animeArgument.id.toString())
88
+ viewModel.animeTitleField.set(animeArgument.title)
89
+ viewModel.getAnimeDetail(animeArgument.id.toString())
104
90
}
105
91
106
- private fun getBackIconColor (percent : Float ): Int {
107
- // 颜色由白->蓝变化
108
- val startColor = R .color.text_white_immutable.toResColor()
109
- val endColor = R .color.text_theme.toResColor()
110
-
111
- val startRed = Color .red(startColor)
112
- val startGreen = Color .green(startColor)
113
- val startBlue = Color .blue(startColor)
114
-
115
- val endRed = Color .red(endColor)
116
- val endGreen = Color .green(endColor)
117
- val endBlue = Color .blue(endColor)
92
+ private fun initViewPager (tabs : Array <AnimeDetailTab >) {
93
+ dataBinding.viewpager.adapter = AnimeDetailPageAdapter (this , tabs)
94
+ val mediator = TabLayoutMediator (
95
+ dataBinding.tabLayout,
96
+ dataBinding.viewpager
97
+ ) { tab, position ->
98
+ tab.text = tabs[position].title
99
+ }
100
+ mediator.attach()
101
+ }
118
102
119
- // 红绿蓝统一变化即得到相应比例颜色
120
- val diffRed = startRed + ((endRed - startRed) * percent).toInt()
121
- val diffGreen = startRed + ((endGreen - startGreen) * percent).toInt()
122
- val diffBlue = startRed + ((endBlue - startBlue) * percent).toInt()
103
+ @Deprecated(" Deprecated in Java" , ReplaceWith (" finish()" ))
104
+ override fun onBackPressed () {
105
+ // 如果有返回事件拦截器,优先执行拦截器
106
+ if (backPressInterceptor?.invoke() == true ) {
107
+ return
108
+ }
123
109
124
- return Color .rgb(diffRed, diffGreen, diffBlue)
110
+ // 重写以防止返回动画
111
+ finish()
125
112
}
126
113
127
- private fun initObserver () {
114
+ private fun initListener () {
115
+ dataBinding.appBarLayout.addOnOffsetChangedListener { layout, offset ->
116
+ val percent = offset.absoluteValue.toFloat() / layout.totalScrollRange * 2f
117
+ // 降低滚动更新UI的频率
118
+ val scrollLimit = (percent * 100 ).toInt()
119
+ if (scrollLimit != lastScrollLimit) {
120
+ lastScrollLimit = scrollLimit
121
+ // 前半段滚动,更新颜色
122
+ updateUIByScroll(percent.clamp(0f , 1f ))
123
+ // 后半段滚动,更新圆角
124
+ val roundedPercent = (scrollLimit - 100 ) / 100f
125
+ updateTabLayoutRound(roundedPercent.clamp(0f , 1f ))
126
+ }
127
+ }
128
128
129
129
viewModel.followLiveData.observe(this ) { followed ->
130
130
if (followed) {
@@ -138,58 +138,85 @@ class AnimeDetailActivity : BaseActivity<AnimeDetailViewModel, ActivityAnimeDeta
138
138
}
139
139
}
140
140
141
- viewModel.animeDetailLiveData.observe(this ) { bangumiData ->
142
- bangumiData.apply {
143
- dataBinding.collapsingToolbarLayout.title = animeTitle
144
-
145
- dataBinding.backgroundCoverIv.loadImage(imageUrl)
146
-
147
- dataBinding.coverIv.loadImageWithCallback(
148
- source = imageUrl,
149
- dpRadius = 3f ,
150
- errorRes = R .drawable.ic_load_image_failed
151
- ) { supportStartPostponedEnterTransition() }
152
-
153
- dataBinding.viewpager.apply {
154
- adapter = AnimeDetailAdapter (supportFragmentManager, bangumiData)
155
- offscreenPageLimit = 2
156
- currentItem = 0
157
- }
141
+ viewModel.animeDetailLiveData.observe(this ) {
142
+ if (it.animeTitle != animeArgument.title || it.imageUrl != animeArgument.imageUrl) {
143
+ updateUIbyAnime(it.animeTitle.orEmpty(), it.imageUrl.orEmpty())
158
144
}
159
145
146
+ // 如果没有相关推荐和相似番剧,移除推荐tab
147
+ val dynamicTabs = defaultTabs.toMutableList()
148
+ if (it.relateds.isEmpty() && it.similars.isEmpty()) {
149
+ dynamicTabs.remove(AnimeDetailTab .RECOMMEND )
150
+ initViewPager(dynamicTabs.toTypedArray())
151
+ }
160
152
}
153
+ }
161
154
162
- viewModel.transitionFailedLiveData.observe(this ) {
163
- finishAfterTransition()
164
- }
155
+ /* *
156
+ * 根据滚动百分比更新UI
157
+ */
158
+ private fun updateUIByScroll (percent : Float ) {
159
+ // 标题栏颜色
160
+ dataBinding.toolbar.navigationIcon?.setTint(textColorRange.take(percent))
161
+ dataBinding.toolbar.setTitleTextColor(pageColorRange.end.opacity(percent))
162
+ dataBinding.toolbar.setBackgroundColor(pageColorRange.start.opacity(percent))
163
+ // 追番按钮背景
164
+ dataBinding.followTv.background?.alpha = 255 - (255 * percent).toInt()
165
+
166
+ // 状态栏文字颜色
167
+ // tips: MIUI深色模式下状态栏字体颜色不 受此控制
168
+ val isDarkFont = percent > 0.5f && ! isNightMode()
169
+ ImmersionBar .with (this )
170
+ .statusBarColorInt(pageColorRange.start.opacity(percent))
171
+ .statusBarDarkFont(isDarkFont)
172
+ .init ()
165
173
}
166
174
167
- inner class AnimeDetailAdapter (
168
- fragmentManager : FragmentManager ,
169
- private val bangumiData : BangumiData
170
- ) : FragmentPagerAdapter(
171
- fragmentManager,
172
- BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
173
- ) {
174
- private var titles = if (bangumiData.similars.size == 0 && bangumiData.relateds.size == 0 ) {
175
- arrayOf(" 信息" , " 剧集" )
176
- } else {
177
- arrayOf(" 信息" , " 剧集" , " 推荐" )
175
+ /* *
176
+ * 根据滚动百分比更新TabLayout圆角
177
+ */
178
+ private fun updateTabLayoutRound (percent : Float ) {
179
+ val topRadius = 12 .dp() * (1 - percent)
180
+ val bottomRadius = 0f
181
+ val roundCorners = floatArrayOf(
182
+ topRadius, topRadius,
183
+ topRadius, topRadius,
184
+ bottomRadius, bottomRadius,
185
+ bottomRadius, bottomRadius
186
+ )
187
+ dataBinding.tabLayout.background = ShapeDrawable ().apply {
188
+ shape = RoundRectShape (roundCorners, null , null )
189
+ paint.color = pageColorRange.start
178
190
}
191
+ }
179
192
180
- override fun getItem ( position : Int ): Fragment {
181
- return when (position) {
182
- 0 -> AnimeIntroFragment .newInstance(bangumiData)
183
- 1 -> AnimeEpisodeFragment .newInstance(bangumiData)
184
- 2 -> AnimeRecommendFragment .newInstance(bangumiData)
185
- else -> AnimeIntroFragment .newInstance(bangumiData )
186
- }
187
- }
193
+ /* *
194
+ * 根据番剧信息更新UI
195
+ */
196
+ private fun updateUIbyAnime ( title : String , image : String ) {
197
+ dataBinding.collapsingToolbarLayout.title = title
198
+ dataBinding.backgroundCoverIv.loadImage(image )
199
+ dataBinding.coverIv.loadAnimeCover(image)
200
+ }
188
201
189
- override fun getCount () = titles.size
202
+ /* *
203
+ * 刷新番剧详情
204
+ */
205
+ fun refreshAnimeDetail () {
206
+ viewModel.getAnimeDetail(animeArgument.id.toString())
207
+ }
190
208
191
- override fun getPageTitle (position : Int ): CharSequence {
192
- return titles[position]
193
- }
209
+ /* *
210
+ * 注册返回事件拦截器
211
+ */
212
+ fun registerBackPressInterceptor (interceptor : () -> Boolean ) {
213
+ backPressInterceptor = interceptor
214
+ }
215
+
216
+ /* *
217
+ * 取消返回事件拦截器
218
+ */
219
+ fun unregisterBackPressInterceptor () {
220
+ backPressInterceptor = null
194
221
}
195
222
}
0 commit comments