mirror of https://github.com/lizongying/my-tv.git
pull/731/head
parent
49cd7b3af2
commit
4364f66ec5
@ -0,0 +1,111 @@ |
||||
package com.lizongying.mytv |
||||
|
||||
import android.graphics.Color |
||||
import android.view.ContextThemeWrapper |
||||
import android.view.View |
||||
import android.view.ViewGroup |
||||
import android.view.animation.ScaleAnimation |
||||
import android.widget.ImageView |
||||
import androidx.leanback.widget.ImageCardView |
||||
import androidx.lifecycle.LifecycleOwner |
||||
import androidx.recyclerview.widget.RecyclerView |
||||
import com.bumptech.glide.Glide |
||||
import com.lizongying.mytv.models.TVListViewModel |
||||
import com.lizongying.mytv.models.TVViewModel |
||||
|
||||
|
||||
class CardAdapter(private val owner: LifecycleOwner, private var tvListViewModel: TVListViewModel) : |
||||
RecyclerView.Adapter<CardAdapter.ViewHolder>() { |
||||
|
||||
private var listener: ItemListener? = null |
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { |
||||
val cardView = object : |
||||
ImageCardView(ContextThemeWrapper(parent.context, R.style.CustomImageCardTheme)) {} |
||||
startScaleAnimation(cardView, 1.0f, 0.9f) |
||||
cardView.isFocusable = true |
||||
cardView.isFocusableInTouchMode = true |
||||
|
||||
return ViewHolder(cardView) |
||||
} |
||||
|
||||
private fun startScaleAnimation(view: View, fromScale: Float, toScale: Float) { |
||||
val scaleAnimation = ScaleAnimation( |
||||
fromScale, toScale, |
||||
fromScale, toScale, |
||||
ScaleAnimation.RELATIVE_TO_SELF, 0.5f, |
||||
ScaleAnimation.RELATIVE_TO_SELF, 0.5f |
||||
) |
||||
scaleAnimation.duration = 200 |
||||
scaleAnimation.fillAfter = true |
||||
|
||||
view.startAnimation(scaleAnimation) |
||||
} |
||||
|
||||
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) { |
||||
val item = tvListViewModel.getTVViewModel(position) |
||||
|
||||
val tvViewModel = item as TVViewModel |
||||
val cardView = viewHolder.view as ImageCardView |
||||
|
||||
cardView.setOnFocusChangeListener { _, hasFocus -> |
||||
listener?.onItemFocusChange(item, hasFocus) |
||||
if (hasFocus) { |
||||
startScaleAnimation(cardView, 0.9f, 1.0f) |
||||
} else { |
||||
startScaleAnimation(cardView, 1.0f, 0.9f) |
||||
} |
||||
} |
||||
|
||||
cardView.setOnClickListener { _ -> |
||||
listener?.onItemClicked(item) |
||||
} |
||||
|
||||
cardView.titleText = tvViewModel.title.value |
||||
cardView.tag = tvViewModel.videoUrl.value |
||||
|
||||
if (tvViewModel.logo.value != null) { |
||||
if (tvViewModel.title.value == "CCTV8K 超高清") { |
||||
Glide.with(viewHolder.view.context) |
||||
.load(R.drawable.cctv8k) |
||||
.centerInside() |
||||
.into(cardView.mainImageView) |
||||
} else { |
||||
Glide.with(viewHolder.view.context) |
||||
.load(tvViewModel.logo.value) |
||||
.centerInside() |
||||
.into(cardView.mainImageView) |
||||
} |
||||
|
||||
cardView.mainImageView.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 getItemCount() = tvListViewModel.size() |
||||
|
||||
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { |
||||
val view = itemView |
||||
} |
||||
|
||||
interface ItemListener { |
||||
fun onItemFocusChange(tvViewModel: TVViewModel, hasFocus: Boolean) |
||||
fun onItemClicked(tvViewModel: TVViewModel) |
||||
} |
||||
|
||||
fun setItemListener(listener: ItemListener) { |
||||
this.listener = listener |
||||
} |
||||
|
||||
companion object { |
||||
private const val TAG = "CardAdapter" |
||||
} |
||||
} |
||||
|
||||
@ -0,0 +1,284 @@ |
||||
package com.lizongying.mytv |
||||
|
||||
import android.content.Context |
||||
import android.content.SharedPreferences |
||||
import android.os.Bundle |
||||
import android.os.Handler |
||||
import android.os.Looper |
||||
import android.util.Log |
||||
import android.view.LayoutInflater |
||||
import android.view.View |
||||
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.RecyclerView |
||||
import com.lizongying.mytv.Utils.dpToPx |
||||
import com.lizongying.mytv.databinding.RowBinding |
||||
import com.lizongying.mytv.databinding.ShowBinding |
||||
import com.lizongying.mytv.models.TVListViewModel |
||||
import com.lizongying.mytv.models.TVViewModel |
||||
import kotlinx.coroutines.Dispatchers |
||||
import kotlinx.coroutines.launch |
||||
|
||||
class MainFragment2 : Fragment(), CardAdapter.ItemListener { |
||||
|
||||
private var itemPosition = 0 |
||||
|
||||
private var rowList: MutableList<View> = mutableListOf() |
||||
|
||||
private var _binding: ShowBinding? = null |
||||
private val binding get() = _binding!! |
||||
|
||||
private var request: Request = Request() |
||||
|
||||
var tvListViewModel = TVListViewModel() |
||||
|
||||
private var sharedPref: SharedPreferences? = null |
||||
|
||||
private var lastVideoUrl: String = "" |
||||
|
||||
private val handler = Handler(Looper.getMainLooper()) |
||||
private lateinit var mUpdateProgramRunnable: UpdateProgramRunnable |
||||
|
||||
private var ready = 0 |
||||
|
||||
override fun onCreateView( |
||||
inflater: LayoutInflater, container: ViewGroup?, |
||||
savedInstanceState: Bundle? |
||||
): View { |
||||
_binding = ShowBinding.inflate(inflater, container, false) |
||||
return binding.root |
||||
} |
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) { |
||||
super.onActivityCreated(savedInstanceState) |
||||
|
||||
activity?.let { request.initYSP(it) } |
||||
sharedPref = activity?.getPreferences(Context.MODE_PRIVATE) |
||||
|
||||
view?.post { |
||||
val content = binding.content |
||||
|
||||
var idx: Long = 0 |
||||
for ((k, v) in TVList.list) { |
||||
val itemBinding: RowBinding = |
||||
RowBinding.inflate(layoutInflater, content, false) |
||||
|
||||
val tvListViewModelCurrent = TVListViewModel() |
||||
for ((idx2, v1) in v.withIndex()) { |
||||
val tvViewModel = TVViewModel(v1) |
||||
tvViewModel.setRowPosition(idx.toInt()) |
||||
tvViewModel.setItemPosition(idx2) |
||||
tvListViewModelCurrent.addTVViewModel(tvViewModel) |
||||
tvListViewModel.addTVViewModel(tvViewModel) |
||||
} |
||||
tvListViewModel.maxNum.add(v.size) |
||||
|
||||
val adapter = CardAdapter(viewLifecycleOwner, tvListViewModelCurrent) |
||||
rowList.add(itemBinding.rowItems) |
||||
|
||||
adapter.setItemListener(this) |
||||
|
||||
itemBinding.rowHeader.text = k |
||||
itemBinding.rowItems.adapter = adapter |
||||
itemBinding.rowItems.layoutManager = |
||||
LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) |
||||
|
||||
val layoutParams = itemBinding.row.layoutParams as ViewGroup.MarginLayoutParams |
||||
layoutParams.topMargin = dpToPx(11F) |
||||
itemBinding.row.layoutParams = layoutParams |
||||
content.addView(itemBinding.row) |
||||
|
||||
idx++ |
||||
} |
||||
|
||||
mUpdateProgramRunnable = UpdateProgramRunnable() |
||||
handler.post(mUpdateProgramRunnable) |
||||
|
||||
itemPosition = sharedPref?.getInt("position", 0)!! |
||||
if (itemPosition >= tvListViewModel.size()) { |
||||
itemPosition = 0 |
||||
} |
||||
setPosition() |
||||
tvListViewModel.setItemPosition(itemPosition) |
||||
tvListViewModel.getTVListViewModel().value?.forEach { tvViewModel -> |
||||
tvViewModel.errInfo.observe(viewLifecycleOwner) { _ -> |
||||
if (tvViewModel.errInfo.value != null |
||||
&& tvViewModel.id.value == itemPosition |
||||
) { |
||||
Toast.makeText(context, tvViewModel.errInfo.value, Toast.LENGTH_SHORT).show() |
||||
} |
||||
} |
||||
tvViewModel.ready.observe(viewLifecycleOwner) { _ -> |
||||
|
||||
// not first time && channel not change |
||||
if (tvViewModel.ready.value != null |
||||
&& tvViewModel.id.value == itemPosition |
||||
&& check(tvViewModel) |
||||
) { |
||||
Log.i(TAG, "ready ${tvViewModel.title.value}") |
||||
(activity as? MainActivity)?.play(tvViewModel) |
||||
} |
||||
} |
||||
tvViewModel.change.observe(viewLifecycleOwner) { _ -> |
||||
if (tvViewModel.change.value != null) { |
||||
val title = tvViewModel.title.value |
||||
Log.i(TAG, "switch $title") |
||||
if (tvViewModel.pid.value != "") { |
||||
Log.i(TAG, "request $title ${tvViewModel.pid.value}") |
||||
lifecycleScope.launch(Dispatchers.IO) { |
||||
tvViewModel.let { request.fetchData(it) } |
||||
} |
||||
(activity as? MainActivity)?.showInfoFragment(tvViewModel) |
||||
setPosition( |
||||
tvViewModel.getRowPosition(), tvViewModel.getItemPosition() |
||||
) |
||||
} else { |
||||
if (check(tvViewModel)) { |
||||
(activity as? MainActivity)?.play(tvViewModel) |
||||
(activity as? MainActivity)?.showInfoFragment(tvViewModel) |
||||
setPosition( |
||||
tvViewModel.getRowPosition(), tvViewModel.getItemPosition() |
||||
) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
fragmentReady() |
||||
} |
||||
} |
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
||||
super.onViewCreated(view, savedInstanceState) |
||||
} |
||||
|
||||
override fun onItemFocusChange(tvViewModel: TVViewModel, hasFocus: Boolean) { |
||||
if (hasFocus) { |
||||
tvListViewModel.setItemPositionCurrent(tvViewModel.id.value!!) |
||||
(activity as MainActivity).keepRunnable() |
||||
} |
||||
} |
||||
|
||||
override fun onItemClicked(tvViewModel: TVViewModel) { |
||||
if (itemPosition != tvViewModel.id.value!!) { |
||||
itemPosition = tvViewModel.id.value!! |
||||
tvListViewModel.setItemPosition(itemPosition) |
||||
tvListViewModel.getTVViewModel(itemPosition)?.changed() |
||||
} |
||||
(activity as? MainActivity)?.switchMainFragment() |
||||
} |
||||
|
||||
fun setPosition() { |
||||
val tvViewModel = tvListViewModel.getTVViewModel(itemPosition) |
||||
rowList[tvViewModel!!.getRowPosition()].post { |
||||
((rowList[tvViewModel.getRowPosition()] as RecyclerView).layoutManager as LinearLayoutManager).findViewByPosition( |
||||
tvViewModel.getItemPosition() |
||||
)?.requestFocus() |
||||
} |
||||
} |
||||
|
||||
fun setPosition(rowPosition: Int, itemPosition: Int) { |
||||
rowList[rowPosition].post { |
||||
((rowList[rowPosition] as RecyclerView).layoutManager as LinearLayoutManager).findViewByPosition( |
||||
itemPosition |
||||
)?.requestFocus() |
||||
} |
||||
} |
||||
|
||||
fun check(tvViewModel: TVViewModel): Boolean { |
||||
val title = tvViewModel.title.value |
||||
val videoUrl = tvViewModel.videoIndex.value?.let { tvViewModel.videoUrl.value?.get(it) } |
||||
if (videoUrl == null || videoUrl == "") { |
||||
Log.e(TAG, "$title videoUrl is empty") |
||||
return false |
||||
} |
||||
|
||||
if (videoUrl == lastVideoUrl) { |
||||
Log.e(TAG, "$title videoUrl is duplication") |
||||
return false |
||||
} |
||||
|
||||
return true |
||||
} |
||||
|
||||
fun fragmentReady() { |
||||
ready++ |
||||
Log.i(TAG, "ready $ready") |
||||
if (ready == 3) { |
||||
// request.fetchPage() |
||||
tvListViewModel.getTVViewModel(itemPosition)?.changed() |
||||
(activity as? MainActivity)?.switchMainFragment() |
||||
} |
||||
} |
||||
|
||||
fun prev() { |
||||
view?.post { |
||||
itemPosition-- |
||||
if (itemPosition == -1) { |
||||
itemPosition = tvListViewModel.size() - 1 |
||||
} |
||||
tvListViewModel.setItemPosition(itemPosition) |
||||
tvListViewModel.getTVViewModel(itemPosition)?.changed() |
||||
} |
||||
} |
||||
|
||||
fun next() { |
||||
view?.post { |
||||
itemPosition++ |
||||
if (itemPosition == tvListViewModel.size()) { |
||||
itemPosition = 0 |
||||
} |
||||
tvListViewModel.setItemPosition(itemPosition) |
||||
tvListViewModel.getTVViewModel(itemPosition)?.changed() |
||||
} |
||||
} |
||||
|
||||
fun updateProgram(tvViewModel: TVViewModel) { |
||||
val timestamp = Utils.getDateTimestamp() |
||||
if (timestamp - tvViewModel.programUpdateTime > 60) { |
||||
if (tvViewModel.program.value!!.isEmpty()) { |
||||
tvViewModel.programUpdateTime = timestamp |
||||
request.fetchProgram(tvViewModel) |
||||
} else { |
||||
if (timestamp - tvViewModel.program.value!!.last().et < 600) { |
||||
tvViewModel.programUpdateTime = timestamp |
||||
request.fetchProgram(tvViewModel) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
inner class UpdateProgramRunnable : Runnable { |
||||
override fun run() { |
||||
tvListViewModel.getTVListViewModel().value?.filter { it.programId.value != null } |
||||
?.forEach { tvViewModel -> |
||||
updateProgram( |
||||
tvViewModel |
||||
) |
||||
} |
||||
handler.postDelayed(this, 60000) |
||||
} |
||||
} |
||||
|
||||
override fun onDestroy() { |
||||
super.onDestroy() |
||||
handler.removeCallbacks(mUpdateProgramRunnable) |
||||
with(sharedPref!!.edit()) { |
||||
putInt("position", itemPosition) |
||||
apply() |
||||
} |
||||
} |
||||
|
||||
override fun onResume() { |
||||
super.onResume() |
||||
view!!.requestFocus() |
||||
} |
||||
|
||||
companion object { |
||||
private const val TAG = "MainFragment" |
||||
} |
||||
} |
||||
@ -0,0 +1,25 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<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:orientation="vertical"> |
||||
|
||||
<TextView |
||||
android:id="@+id/row_header" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:layout_gravity="start" |
||||
android:layout_marginTop="0dp" |
||||
android:layout_marginStart="10dp" |
||||
android:layout_marginBottom="5dp" |
||||
android:gravity="start" |
||||
android:textColor="#FFEEEEEE" |
||||
android:textSize="22sp" /> |
||||
|
||||
<androidx.recyclerview.widget.RecyclerView |
||||
android:id="@+id/row_items" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content"> |
||||
</androidx.recyclerview.widget.RecyclerView> |
||||
</LinearLayout> |
||||
@ -0,0 +1,17 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||
android:id="@+id/show_fragment" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent"> |
||||
<ScrollView |
||||
android:id="@+id/scroll_view" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent"> |
||||
<LinearLayout |
||||
android:id="@+id/content" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" |
||||
android:orientation="vertical"> |
||||
</LinearLayout> |
||||
</ScrollView> |
||||
</FrameLayout> |
||||
@ -1,11 +1,25 @@ |
||||
<resources> |
||||
|
||||
|
||||
<style name="CustomImageCardViewStyle" parent="Widget.Leanback.ImageCardViewStyle"> |
||||
<item name="lbImageCardViewType">Title|Content</item> |
||||
<item name="cardType">infoUnder</item> |
||||
<item name="infoVisibility">always</item> |
||||
<!-- In order to keep backward compatibility we have to create an icon on right. --> |
||||
<item name="lbImageCardViewType">Title|Content|IconOnRight</item> |
||||
<!-- Deprecated. Use 'Widget.Leanback.ImageCardView.InfoAreaStyle' instead. --> |
||||
<item name="infoAreaBackground">@null</item> |
||||
</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:adjustViewBounds">true</item> |
||||
<item name="android:contentDescription">@null</item> |
||||
<item name="android:scaleType">centerCrop</item> |
||||
<item name="layout_viewType">main</item> |
||||
</style> |
||||
|
||||
<style name="CustomImageCardTheme" parent="Theme.Leanback"> |
||||
<item name="imageCardViewStyle">@style/CustomImageCardViewStyle</item> |
||||
<item name="imageCardViewImageStyle">@style/CustomImageCardViewImageStyle</item> |
||||
</style> |
||||
</resources> |
||||
Loading…
Reference in new issue