new memu style

pull/731/head
Li ZongYing 2 years ago
parent 8b33c7c94c
commit a4da01adf4
  1. 5
      README.md
  2. 44
      app/src/main/java/com/lizongying/mytv/CardAdapter.kt
  3. 82
      app/src/main/java/com/lizongying/mytv/CardPresenter.kt
  4. 48
      app/src/main/java/com/lizongying/mytv/GrayOverlayItemDecoration.kt
  5. 3
      app/src/main/java/com/lizongying/mytv/MainActivity.kt
  6. 65
      app/src/main/java/com/lizongying/mytv/MainFragment.kt
  7. 6
      app/src/main/java/com/lizongying/mytv/Request.kt
  8. 2
      app/src/main/java/com/lizongying/mytv/api/YSPTokenService.kt
  9. 2
      app/src/main/res/layout/row.xml
  10. 1
      app/src/main/res/values/colors.xml
  11. 4
      app/src/main/res/values/styles.xml

@ -14,6 +14,10 @@
## 更新日志
### v1.6.2(通用)
* 新的频道列表样式
### v1.6.0(通用)
* 通用(春晚緊急修復)
@ -201,6 +205,7 @@ adb install my-tv.apk
* 1.5.0 无法安装,1.5.1 可以安装
* 选中的图标比例能否相差更大
* 节目增加预告
* 频道列表优化
## 赞赏

@ -1,10 +1,10 @@
package com.lizongying.mytv
import android.graphics.Color
import android.util.Log
import android.view.ContextThemeWrapper
import android.view.View
import android.view.ViewGroup
import android.view.animation.Animation
import android.view.animation.ScaleAnimation
import android.widget.ImageView
import androidx.leanback.widget.ImageCardView
@ -18,21 +18,28 @@ import com.lizongying.mytv.models.TVViewModel
class CardAdapter(
private val recyclerView: RecyclerView,
private val owner: LifecycleOwner,
private var tvListViewModel: TVListViewModel
private var tvListViewModel: TVListViewModel,
var defaultFocus: Int,
) :
RecyclerView.Adapter<CardAdapter.ViewHolder>() {
private var listener: ItemListener? = null
private var focused: View? = null
private var defaultFocused = false
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val cardView = object :
ImageCardView(ContextThemeWrapper(parent.context, R.style.CustomImageCardTheme)) {}
cardView.isFocusable = true
cardView.isFocusableInTouchMode = true
return ViewHolder(cardView)
}
fun clear() {
focused?.clearFocus()
recyclerView.invalidate()
}
private fun startScaleAnimation(view: View, fromScale: Float, toScale: Float, duration: Long) {
val scaleAnimation = ScaleAnimation(
fromScale, toScale,
@ -41,19 +48,7 @@ class CardAdapter(
ScaleAnimation.RELATIVE_TO_SELF, 0.5f
)
scaleAnimation.duration = duration
scaleAnimation.fillAfter = true
scaleAnimation.setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationStart(animation: Animation?) {
}
override fun onAnimationEnd(animation: Animation?) {
view.tag = toScale
}
override fun onAnimationRepeat(animation: Animation?) {
}
})
scaleAnimation.fillAfter = false
view.startAnimation(scaleAnimation)
}
@ -63,20 +58,19 @@ class CardAdapter(
val tvViewModel = item as TVViewModel
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)
Log.i(TAG, "defaultFocus $defaultFocus ${item.id.value}")
// if (hasFocus && defaultFocus == item.id.value) {
if (hasFocus) {
focused = cardView
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 {
Log.i(TAG, "keyCode $keyCode")
when (keyCode) {
KeyEvent.KEYCODE_0 -> {
showChannel("0")
@ -446,6 +447,7 @@ class MainActivity : FragmentActivity() {
}
KeyEvent.KEYCODE_DPAD_LEFT -> {
channelUp()
// if (mainFragment.isHidden) {
// prevSource()
// } else {
@ -459,6 +461,7 @@ class MainActivity : FragmentActivity() {
}
KeyEvent.KEYCODE_DPAD_RIGHT -> {
channelDown()
// if (mainFragment.isHidden) {
// nextSource()
// } else {

@ -11,7 +11,7 @@ import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.lizongying.mytv.Utils.dpToPx
import com.lizongying.mytv.Utils.getDateTimestamp
@ -22,6 +22,7 @@ import com.lizongying.mytv.models.TVViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class MainFragment : Fragment(), CardAdapter.ItemListener {
private var itemPosition = 0
@ -56,6 +57,11 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
activity?.let { request.initYSP(it) }
sharedPref = (activity as? MainActivity)?.sharedPref!!
itemPosition = sharedPref.getInt(POSITION, 0)
if (itemPosition >= tvListViewModel.size()) {
itemPosition = 0
}
view?.post {
val content = binding.content
@ -75,15 +81,23 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
tvListViewModel.maxNum.add(v.size)
val adapter =
CardAdapter(itemBinding.rowItems, viewLifecycleOwner, tvListViewModelCurrent)
CardAdapter(
itemBinding.rowItems,
viewLifecycleOwner,
tvListViewModelCurrent,
itemPosition
)
rowList.add(itemBinding.rowItems)
adapter.setItemListener(this)
itemBinding.rowHeader.text = k
itemBinding.rowItems.tag = idx.toInt()
itemBinding.rowItems.adapter = adapter
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() {
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
layoutParams.topMargin = dpToPx(11F)
itemBinding.row.layoutParams = layoutParams
@ -103,12 +138,7 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
mUpdateProgramRunnable = UpdateProgramRunnable()
handler.post(mUpdateProgramRunnable)
itemPosition = sharedPref.getInt(POSITION, 0)
if (itemPosition >= tvListViewModel.size()) {
itemPosition = 0
}
tvListViewModel.setItemPosition(itemPosition)
setPosition()
tvListViewModel.tvListViewModel.value?.forEach { tvViewModel ->
tvViewModel.errInfo.observe(viewLifecycleOwner) { _ ->
if (tvViewModel.errInfo.value != null
@ -154,7 +184,6 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
}
}
}
(activity as MainActivity).fragmentReady()
}
}
@ -166,11 +195,21 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
override fun onItemFocusChange(tvViewModel: TVViewModel, hasFocus: Boolean) {
if (hasFocus) {
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()
}
}
override fun onItemClicked(tvViewModel: TVViewModel) {
Log.i(TAG, "onItemClicked")
if (this.isHidden) {
(activity as? MainActivity)?.switchMainFragment()
return
@ -186,10 +225,8 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
fun setPosition() {
val tvViewModel = tvListViewModel.getTVViewModel(itemPosition)
Log.i(TAG, "tvViewModel $tvViewModel")
Log.i(TAG, "rowList ${rowList.size}")
rowList[tvViewModel!!.getRowPosition()].post {
((rowList[tvViewModel.getRowPosition()] as RecyclerView).layoutManager as LinearLayoutManager).findViewByPosition(
((rowList[tvViewModel.getRowPosition()] as RecyclerView).layoutManager as GridLayoutManager).findViewByPosition(
tvViewModel.getItemPosition()
)?.requestFocus()
}
@ -197,10 +234,12 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
fun setPosition(rowPosition: Int, itemPosition: Int) {
rowList[rowPosition].post {
((rowList[rowPosition] as RecyclerView).layoutManager as LinearLayoutManager).findViewByPosition(
((rowList[rowPosition] as RecyclerView).layoutManager as GridLayoutManager).findViewByPosition(
itemPosition
)?.requestFocus()
}
((rowList[rowPosition] as RecyclerView).adapter as CardAdapter).defaultFocus = itemPosition
}
fun check(tvViewModel: TVViewModel): Boolean {

@ -263,7 +263,7 @@ class Request {
fun fetchAuth(tvModel: TVViewModel) {
if (token == "") {
yspTokenService.getInfo()
yspTokenService.getInfo("")
.enqueue(object : Callback<Info> {
override fun onResponse(call: Call<Info>, response: Response<Info>) {
if (response.isSuccessful) {
@ -310,7 +310,7 @@ class Request {
fun fetchVideo(tvModel: TVViewModel) {
if (token == "") {
yspTokenService.getInfo()
yspTokenService.getInfo("")
.enqueue(object : Callback<Info> {
override fun onResponse(call: Call<Info>, response: Response<Info>) {
if (response.isSuccessful && response.body()?.data?.token != null) {
@ -375,7 +375,7 @@ class Request {
}
fun fetchToken() {
yspTokenService.getInfo()
yspTokenService.getInfo(token)
.enqueue(object : Callback<Info> {
override fun onResponse(call: Call<Info>, response: Response<Info>) {
if (response.isSuccessful) {

@ -2,10 +2,12 @@ package com.lizongying.mytv.api
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Query
interface YSPTokenService {
@GET("my-tv/v1/info")
fun getInfo(
@Query("token") token: String = "",
): Call<Info>
}

@ -2,7 +2,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/row"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView

@ -2,4 +2,5 @@
<color name="fastlane_background">#30000000</color>
<color name="black">#000</color>
<color name="white">#FFF</color>
<color name="gray_overlay">#7F000000</color>
</resources>

@ -10,8 +10,8 @@
</style>
<style name="CustomImageCardViewImageStyle" parent="Widget.Leanback.ImageCardView.ImageStyle">
<item name="android:layout_width">143dp</item>
<item name="android:layout_height">50dp</item>
<item name="android:layout_width">160dp</item>
<item name="android:layout_height">40dp</item>
<item name="android:adjustViewBounds">true</item>
<item name="android:contentDescription">@null</item>
<item name="android:scaleType">centerCrop</item>

Loading…
Cancel
Save