new memu style

This commit is contained in:
Li ZongYing
2024-02-21 15:14:06 +08:00
parent 8b33c7c94c
commit a4da01adf4
11 changed files with 136 additions and 126 deletions
+5
View File
@@ -14,6 +14,10 @@
## 更新日志 ## 更新日志
### v1.6.2(通用)
* 新的频道列表样式
### v1.6.0(通用) ### v1.6.0(通用)
* 通用(春晚緊急修復) * 通用(春晚緊急修復)
@@ -201,6 +205,7 @@ adb install my-tv.apk
* 1.5.0 无法安装,1.5.1 可以安装 * 1.5.0 无法安装,1.5.1 可以安装
* 选中的图标比例能否相差更大 * 选中的图标比例能否相差更大
* 节目增加预告 * 节目增加预告
* 频道列表优化
## 赞赏 ## 赞赏
@@ -1,10 +1,10 @@
package com.lizongying.mytv package com.lizongying.mytv
import android.graphics.Color import android.graphics.Color
import android.util.Log
import android.view.ContextThemeWrapper import android.view.ContextThemeWrapper
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.animation.Animation
import android.view.animation.ScaleAnimation import android.view.animation.ScaleAnimation
import android.widget.ImageView import android.widget.ImageView
import androidx.leanback.widget.ImageCardView import androidx.leanback.widget.ImageCardView
@@ -18,21 +18,28 @@ import com.lizongying.mytv.models.TVViewModel
class CardAdapter( class CardAdapter(
private val recyclerView: RecyclerView, private val recyclerView: RecyclerView,
private val owner: LifecycleOwner, private val owner: LifecycleOwner,
private var tvListViewModel: TVListViewModel private var tvListViewModel: TVListViewModel,
var defaultFocus: Int,
) : ) :
RecyclerView.Adapter<CardAdapter.ViewHolder>() { RecyclerView.Adapter<CardAdapter.ViewHolder>() {
private var listener: ItemListener? = null private var listener: ItemListener? = null
private var focused: View? = null
private var defaultFocused = false
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val cardView = object : val cardView = object :
ImageCardView(ContextThemeWrapper(parent.context, R.style.CustomImageCardTheme)) {} ImageCardView(ContextThemeWrapper(parent.context, R.style.CustomImageCardTheme)) {}
cardView.isFocusable = true cardView.isFocusable = true
cardView.isFocusableInTouchMode = true cardView.isFocusableInTouchMode = true
return ViewHolder(cardView) return ViewHolder(cardView)
} }
fun clear() {
focused?.clearFocus()
recyclerView.invalidate()
}
private fun startScaleAnimation(view: View, fromScale: Float, toScale: Float, duration: Long) { private fun startScaleAnimation(view: View, fromScale: Float, toScale: Float, duration: Long) {
val scaleAnimation = ScaleAnimation( val scaleAnimation = ScaleAnimation(
fromScale, toScale, fromScale, toScale,
@@ -41,19 +48,7 @@ class CardAdapter(
ScaleAnimation.RELATIVE_TO_SELF, 0.5f ScaleAnimation.RELATIVE_TO_SELF, 0.5f
) )
scaleAnimation.duration = duration scaleAnimation.duration = duration
scaleAnimation.fillAfter = true scaleAnimation.fillAfter = false
scaleAnimation.setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationStart(animation: Animation?) {
}
override fun onAnimationEnd(animation: Animation?) {
view.tag = toScale
}
override fun onAnimationRepeat(animation: Animation?) {
}
})
view.startAnimation(scaleAnimation) view.startAnimation(scaleAnimation)
} }
@@ -63,20 +58,19 @@ class CardAdapter(
val tvViewModel = item as TVViewModel val tvViewModel = item as TVViewModel
val cardView = viewHolder.view as ImageCardView val cardView = viewHolder.view as ImageCardView
startScaleAnimation(cardView, 1.0f, 0.9f, 0) if (!defaultFocused && item.id.value == defaultFocus) {
cardView.requestFocus()
defaultFocused = true
}
val onFocusChangeListener = View.OnFocusChangeListener { view, hasFocus -> val onFocusChangeListener = View.OnFocusChangeListener { _, hasFocus ->
listener?.onItemFocusChange(item, hasFocus) listener?.onItemFocusChange(item, hasFocus)
Log.i(TAG, "defaultFocus $defaultFocus ${item.id.value}")
// if (hasFocus && defaultFocus == item.id.value) {
if (hasFocus) { if (hasFocus) {
focused = cardView
startScaleAnimation(cardView, 0.9f, 1.0f, 200) startScaleAnimation(cardView, 0.9f, 1.0f, 200)
for (i in 0 until recyclerView.childCount) {
val v = recyclerView.getChildAt(i)
if (v != view && v.tag != 0.9f) {
startScaleAnimation(v, 1.0f, 0.9f, 200)
}
}
} }
} }
@@ -1,82 +0,0 @@
package com.lizongying.mytv
import android.graphics.Color
import android.view.ContextThemeWrapper
import android.view.ViewGroup
import android.widget.ImageView
import androidx.leanback.widget.ImageCardView
import androidx.leanback.widget.Presenter
import androidx.lifecycle.LifecycleOwner
import com.bumptech.glide.Glide
import com.lizongying.mytv.models.TVViewModel
class CardPresenter(
private val owner: LifecycleOwner,
) : Presenter() {
override fun onCreateViewHolder(parent: ViewGroup): ViewHolder {
val cardView = object :
ImageCardView(ContextThemeWrapper(parent.context, R.style.CustomImageCardTheme)) {}
cardView.isFocusable = true
cardView.isFocusableInTouchMode = true
return ViewHolder(cardView)
}
override fun onBindViewHolder(viewHolder: ViewHolder, item: Any) {
val tvViewModel = item as TVViewModel
val cardView = viewHolder.view as ImageCardView
cardView.titleText = tvViewModel.title.value
cardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT)
cardView.tag = tvViewModel.videoUrl.value
when (tvViewModel.title.value) {
"CCTV8K 超高清" -> Glide.with(viewHolder.view.context)
.load(R.drawable.cctv8k)
.centerInside()
.into(cardView.mainImageView)
"天津卫视" -> Glide.with(viewHolder.view.context)
.load(R.drawable.tianjin)
.centerInside()
.into(cardView.mainImageView)
"新疆卫视" -> Glide.with(viewHolder.view.context)
.load(R.drawable.xinjiang)
.centerInside()
.into(cardView.mainImageView)
"兵团卫视" -> Glide.with(viewHolder.view.context)
.load(R.drawable.bingtuan)
.centerInside()
.into(cardView.mainImageView)
else -> Glide.with(viewHolder.view.context)
.load(tvViewModel.logo.value)
.centerInside()
.into(cardView.mainImageView)
}
cardView.setBackgroundColor(Color.WHITE)
cardView.setMainImageScaleType(ImageView.ScaleType.CENTER_INSIDE)
tvViewModel.program.observe(owner) { _ ->
val program = tvViewModel.getProgramOne()
if (program != null) {
cardView.contentText = program.name
}
}
}
override fun onUnbindViewHolder(viewHolder: ViewHolder) {
val cardView = viewHolder.view as ImageCardView
cardView.mainImage = null
}
companion object {
private const val TAG = "CardPresenter"
private const val CARD_WIDTH = 300
private const val CARD_HEIGHT = 101
}
}
@@ -0,0 +1,48 @@
package com.lizongying.mytv
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Rect
import android.view.View
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
class GrayOverlayItemDecoration(private val context: Context) : RecyclerView.ItemDecoration() {
private val grayOverlayPaint = Paint().apply {
color = ContextCompat.getColor(context, R.color.gray_overlay)
style = Paint.Style.FILL
}
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDrawOver(c, parent, state)
val childCount = parent.childCount
for (i in 0 until childCount) {
val child = parent.getChildAt(i)
if (!child.hasFocus()) {
// 计算遮罩层的大小
val overlayRect = Rect(
child.left,
child.top,
child.right,
child.bottom
)
// 绘制灰色遮罩层
c.drawRect(overlayRect, grayOverlayPaint)
}
}
}
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
super.getItemOffsets(outRect, view, parent, state)
// 在此处设置偏移量为0,以防止遮罩层影响项的布局
outRect.setEmpty()
}
}
@@ -335,6 +335,7 @@ class MainActivity : FragmentActivity() {
} }
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
Log.i(TAG, "keyCode $keyCode")
when (keyCode) { when (keyCode) {
KeyEvent.KEYCODE_0 -> { KeyEvent.KEYCODE_0 -> {
showChannel("0") showChannel("0")
@@ -446,6 +447,7 @@ class MainActivity : FragmentActivity() {
} }
KeyEvent.KEYCODE_DPAD_LEFT -> { KeyEvent.KEYCODE_DPAD_LEFT -> {
channelUp()
// if (mainFragment.isHidden) { // if (mainFragment.isHidden) {
// prevSource() // prevSource()
// } else { // } else {
@@ -459,6 +461,7 @@ class MainActivity : FragmentActivity() {
} }
KeyEvent.KEYCODE_DPAD_RIGHT -> { KeyEvent.KEYCODE_DPAD_RIGHT -> {
channelDown()
// if (mainFragment.isHidden) { // if (mainFragment.isHidden) {
// nextSource() // nextSource()
// } else { // } else {
@@ -11,7 +11,7 @@ import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.lizongying.mytv.Utils.dpToPx import com.lizongying.mytv.Utils.dpToPx
import com.lizongying.mytv.Utils.getDateTimestamp import com.lizongying.mytv.Utils.getDateTimestamp
@@ -22,6 +22,7 @@ import com.lizongying.mytv.models.TVViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class MainFragment : Fragment(), CardAdapter.ItemListener { class MainFragment : Fragment(), CardAdapter.ItemListener {
private var itemPosition = 0 private var itemPosition = 0
@@ -56,6 +57,11 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
activity?.let { request.initYSP(it) } activity?.let { request.initYSP(it) }
sharedPref = (activity as? MainActivity)?.sharedPref!! sharedPref = (activity as? MainActivity)?.sharedPref!!
itemPosition = sharedPref.getInt(POSITION, 0)
if (itemPosition >= tvListViewModel.size()) {
itemPosition = 0
}
view?.post { view?.post {
val content = binding.content val content = binding.content
@@ -75,15 +81,23 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
tvListViewModel.maxNum.add(v.size) tvListViewModel.maxNum.add(v.size)
val adapter = val adapter =
CardAdapter(itemBinding.rowItems, viewLifecycleOwner, tvListViewModelCurrent) CardAdapter(
itemBinding.rowItems,
viewLifecycleOwner,
tvListViewModelCurrent,
itemPosition
)
rowList.add(itemBinding.rowItems) rowList.add(itemBinding.rowItems)
adapter.setItemListener(this) adapter.setItemListener(this)
itemBinding.rowHeader.text = k itemBinding.rowHeader.text = k
itemBinding.rowItems.tag = idx.toInt()
itemBinding.rowItems.adapter = adapter itemBinding.rowItems.adapter = adapter
itemBinding.rowItems.layoutManager = itemBinding.rowItems.layoutManager =
LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) GridLayoutManager(context, 6)
itemBinding.rowItems.layoutParams.height =
dpToPx(100 * ((tvListViewModelCurrent.size() + 6 - 1) / 6))
itemBinding.rowItems.addOnScrollListener(object : RecyclerView.OnScrollListener() { itemBinding.rowItems.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
@@ -92,6 +106,27 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
} }
}) })
itemBinding.rowItems.setOnKeyListener { v, keyCode, event ->
Log.i(TAG, "itemBinding.rowItems.setOnKeyListener ")
false
}
itemBinding.row.setOnKeyListener { v, keyCode, event ->
Log.i(TAG, "itemBinding.row.setOnKeyListener ")
false
}
itemBinding.root.setOnKeyListener { v, keyCode, event ->
Log.i(TAG, "itemBinding.root.setOnKeyListener ")
false
}
val itemDecoration = context?.let { GrayOverlayItemDecoration(it) }
if (itemDecoration != null) {
itemBinding.rowItems.addItemDecoration(itemDecoration)
}
val layoutParams = itemBinding.row.layoutParams as ViewGroup.MarginLayoutParams val layoutParams = itemBinding.row.layoutParams as ViewGroup.MarginLayoutParams
layoutParams.topMargin = dpToPx(11F) layoutParams.topMargin = dpToPx(11F)
itemBinding.row.layoutParams = layoutParams itemBinding.row.layoutParams = layoutParams
@@ -103,12 +138,7 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
mUpdateProgramRunnable = UpdateProgramRunnable() mUpdateProgramRunnable = UpdateProgramRunnable()
handler.post(mUpdateProgramRunnable) handler.post(mUpdateProgramRunnable)
itemPosition = sharedPref.getInt(POSITION, 0)
if (itemPosition >= tvListViewModel.size()) {
itemPosition = 0
}
tvListViewModel.setItemPosition(itemPosition) tvListViewModel.setItemPosition(itemPosition)
setPosition()
tvListViewModel.tvListViewModel.value?.forEach { tvViewModel -> tvListViewModel.tvListViewModel.value?.forEach { tvViewModel ->
tvViewModel.errInfo.observe(viewLifecycleOwner) { _ -> tvViewModel.errInfo.observe(viewLifecycleOwner) { _ ->
if (tvViewModel.errInfo.value != null if (tvViewModel.errInfo.value != null
@@ -154,7 +184,6 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
} }
} }
} }
(activity as MainActivity).fragmentReady() (activity as MainActivity).fragmentReady()
} }
} }
@@ -166,11 +195,21 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
override fun onItemFocusChange(tvViewModel: TVViewModel, hasFocus: Boolean) { override fun onItemFocusChange(tvViewModel: TVViewModel, hasFocus: Boolean) {
if (hasFocus) { if (hasFocus) {
tvListViewModel.setItemPositionCurrent(tvViewModel.id.value!!) tvListViewModel.setItemPositionCurrent(tvViewModel.id.value!!)
val row = tvViewModel.getRowPosition()
for (i in rowList) {
if (i.tag as Int != row) {
((i as RecyclerView).adapter as CardAdapter).clear()
}
}
(activity as MainActivity).mainActive() (activity as MainActivity).mainActive()
} }
} }
override fun onItemClicked(tvViewModel: TVViewModel) { override fun onItemClicked(tvViewModel: TVViewModel) {
Log.i(TAG, "onItemClicked")
if (this.isHidden) { if (this.isHidden) {
(activity as? MainActivity)?.switchMainFragment() (activity as? MainActivity)?.switchMainFragment()
return return
@@ -186,10 +225,8 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
fun setPosition() { fun setPosition() {
val tvViewModel = tvListViewModel.getTVViewModel(itemPosition) val tvViewModel = tvListViewModel.getTVViewModel(itemPosition)
Log.i(TAG, "tvViewModel $tvViewModel")
Log.i(TAG, "rowList ${rowList.size}")
rowList[tvViewModel!!.getRowPosition()].post { rowList[tvViewModel!!.getRowPosition()].post {
((rowList[tvViewModel.getRowPosition()] as RecyclerView).layoutManager as LinearLayoutManager).findViewByPosition( ((rowList[tvViewModel.getRowPosition()] as RecyclerView).layoutManager as GridLayoutManager).findViewByPosition(
tvViewModel.getItemPosition() tvViewModel.getItemPosition()
)?.requestFocus() )?.requestFocus()
} }
@@ -197,10 +234,12 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
fun setPosition(rowPosition: Int, itemPosition: Int) { fun setPosition(rowPosition: Int, itemPosition: Int) {
rowList[rowPosition].post { rowList[rowPosition].post {
((rowList[rowPosition] as RecyclerView).layoutManager as LinearLayoutManager).findViewByPosition( ((rowList[rowPosition] as RecyclerView).layoutManager as GridLayoutManager).findViewByPosition(
itemPosition itemPosition
)?.requestFocus() )?.requestFocus()
} }
((rowList[rowPosition] as RecyclerView).adapter as CardAdapter).defaultFocus = itemPosition
} }
fun check(tvViewModel: TVViewModel): Boolean { fun check(tvViewModel: TVViewModel): Boolean {
@@ -263,7 +263,7 @@ class Request {
fun fetchAuth(tvModel: TVViewModel) { fun fetchAuth(tvModel: TVViewModel) {
if (token == "") { if (token == "") {
yspTokenService.getInfo() yspTokenService.getInfo("")
.enqueue(object : Callback<Info> { .enqueue(object : Callback<Info> {
override fun onResponse(call: Call<Info>, response: Response<Info>) { override fun onResponse(call: Call<Info>, response: Response<Info>) {
if (response.isSuccessful) { if (response.isSuccessful) {
@@ -310,7 +310,7 @@ class Request {
fun fetchVideo(tvModel: TVViewModel) { fun fetchVideo(tvModel: TVViewModel) {
if (token == "") { if (token == "") {
yspTokenService.getInfo() yspTokenService.getInfo("")
.enqueue(object : Callback<Info> { .enqueue(object : Callback<Info> {
override fun onResponse(call: Call<Info>, response: Response<Info>) { override fun onResponse(call: Call<Info>, response: Response<Info>) {
if (response.isSuccessful && response.body()?.data?.token != null) { if (response.isSuccessful && response.body()?.data?.token != null) {
@@ -375,7 +375,7 @@ class Request {
} }
fun fetchToken() { fun fetchToken() {
yspTokenService.getInfo() yspTokenService.getInfo(token)
.enqueue(object : Callback<Info> { .enqueue(object : Callback<Info> {
override fun onResponse(call: Call<Info>, response: Response<Info>) { override fun onResponse(call: Call<Info>, response: Response<Info>) {
if (response.isSuccessful) { if (response.isSuccessful) {
@@ -2,10 +2,12 @@ package com.lizongying.mytv.api
import retrofit2.Call import retrofit2.Call
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Query
interface YSPTokenService { interface YSPTokenService {
@GET("my-tv/v1/info") @GET("my-tv/v1/info")
fun getInfo( fun getInfo(
@Query("token") token: String = "",
): Call<Info> ): Call<Info>
} }
+1 -1
View File
@@ -2,7 +2,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/row" android:id="@+id/row"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<TextView <TextView
+1
View File
@@ -2,4 +2,5 @@
<color name="fastlane_background">#30000000</color> <color name="fastlane_background">#30000000</color>
<color name="black">#000</color> <color name="black">#000</color>
<color name="white">#FFF</color> <color name="white">#FFF</color>
<color name="gray_overlay">#7F000000</color>
</resources> </resources>
+2 -2
View File
@@ -10,8 +10,8 @@
</style> </style>
<style name="CustomImageCardViewImageStyle" parent="Widget.Leanback.ImageCardView.ImageStyle"> <style name="CustomImageCardViewImageStyle" parent="Widget.Leanback.ImageCardView.ImageStyle">
<item name="android:layout_width">143dp</item> <item name="android:layout_width">160dp</item>
<item name="android:layout_height">50dp</item> <item name="android:layout_height">40dp</item>
<item name="android:adjustViewBounds">true</item> <item name="android:adjustViewBounds">true</item>
<item name="android:contentDescription">@null</item> <item name="android:contentDescription">@null</item>
<item name="android:scaleType">centerCrop</item> <item name="android:scaleType">centerCrop</item>