I have a viewPager with CubeTransformer, which is transforming every fragment. And inside every fragment is Image or Video view (with Exoplayer). And when you are trying to change a framgnet with transformation, exoplayer losses preview (I've got a black screen), even it's not playing. But after you changing condition to normal, preview is coming back
Ohterwise, if you will remove pageTransformer, review is not dissapears. How to keep preview always on screen?
CubeTransformer
class CubeTransformer : ViewPager.PageTransformer {
override fun transformPage(view: View, position: Float) {
if (view.visibility != View.VISIBLE) return
view.apply {
cameraDistance = (view.width * distanceMultiplier).toFloat()
pivotX = if (position < 0f) view.width.toFloat() else 0f
pivotY = view.height * 0.5f
rotationY = 90f * position
if (position < -1 || position > 1) {
alpha = 0f // item not visible
} else {
alpha = 1f
}
}
}
private companion object {
private const val distanceMultiplier: Int = 20
}
}
VideoView
class VideoView(context: Context) : ConstraintLayout(context, null) {
private val player = ExoPlayerFactory.newSimpleInstance(context, DefaultTrackSelector(), DefaultLoadControl())
private val dataSourceFactory = DefaultDataSourceFactory(context, "android")
private lateinit var model: Model
init {
inflate(context, R.layout.story_item_video, this)
video_view.player = player
video_view.keepScreenOn = true
video_view.setBackgroundColor(Color.TRANSPARENT)
video_view.setShutterBackgroundColor(Color.TRANSPARENT)
}
fun setData(model: Model?) {
if (model== null) return
this.model = model
val mediaSource = HlsMediaSource
.Factory(dataSourceFactory)
.setExtractorFactory(DefaultHlsExtractorFactory())
.createMediaSource(Uri.parse(model.streamLink))
player.playWhenReady = true
player.prepare(mediaSource)
player.addListener(object: Player.EventListener {
override fun onPlaybackParametersChanged(playbackParameters: PlaybackParameters?) {
}
override fun onSeekProcessed() {}
override fun onTracksChanged(trackGroups: TrackGroupArray?, trackSelections: TrackSelectionArray?) {
}
override fun onPlayerError(error: ExoPlaybackException?) {
}
override fun onLoadingChanged(isLoading: Boolean) {
}
override fun onPositionDiscontinuity(reason: Int) {
}
override fun onRepeatModeChanged(repeatMode: Int) {
}
override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {
}
override fun onTimelineChanged(timeline: Timeline?, manifest: Any?, reason: Int) {
}
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
}
})
}
}
After a day of searching, I've found an answer to my question. You just need to add app:surface_type="texture_view" to your PlayerView
Related
What I would want to do:
I would want to filter through list and show values that match the search phrase. Additionally I would want to show correct current list size in the UI.
What is the issue:
The issue is that I can filter through list, but on UI, my list size doesn't update. For example if I've downloaded 5 items to offline mode, it would show that there are still 5 items total, but there would be only 2 for example (and only 2 visible on the screen).
The next issue is that if I try to empty the search bar, the list doesn't go back to it's initial state. It's just empty and list size on UI shows that there are 5 items.
What I've tried:
I've tried adding notifyDataSetChanged() in adapter, but it doesn't work as intended. While debugging, The list is filtered and list after filtering is smaller, but it doesn't emit that value to the fragment.
Adapter:
class OssOfflineDevicesListAdapter(
private val offlineDevices: MutableList<OssOfflineDevicesI> = mutableListOf(),
private val removeDevicesFromQueue: (Long) -> Unit,
private val retryDownloadingDevices: (Long) -> Unit
) : RecyclerView.Adapter<OssOfflineDevicesListAdapter.OssOfflineDevicesListItemViewHolder>() {
private val filteredDevices: MutableList<OssOfflineDevicesI> = offlineDevices
override fun getItemCount(): Int = filteredDevices.size
fun filter(searchPhrase: String) {
val newOfflineDevices = offlineDevices.filter {
it.name().contains(searchPhrase, true)
}
updateListView(newOfflineDevices)
filteredDevices.clear()
filteredDevices.addAll(newOfflineDevices)
notifyDataSetChanged()
}
fun update(newValues: List<OssOfflineDevicesI>) {
updateListView(newValues)
filteredDevices.clear()
filteredDevices.addAll(newValues)
notifyDataSetChanged()
}
private fun updateListView(newValues: List<OssOfflineDevicesI>) {
DiffUtil.calculateDiff(object : DiffUtil.Callback() {
override fun getOldListSize(): Int = filteredDevices.size
override fun getNewListSize(): Int = newValues.size
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return filteredDevices[oldItemPosition].id() == newValues[newItemPosition].id()
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldDevices = filteredDevices[oldItemPosition]
val newDevices = newValues[newItemPosition]
return oldDevices.externalId() == newDevices.externalId() &&
oldDevices.downloadingStatus() == newDevices.downloadingStatus() &&
oldDevices.name() == newDevices.name()
}
}).dispatchUpdatesTo(this)
}
Fragment:
class OssOfflineDevicesListFragment : CoreFragment() {
private val disposableBag = CompositeDisposable()
private val viewModel by viewModel<OssOfflineDevicesListViewModel>()
private val offlineDevicesListAdapter = OssOfflineDevicesListAdapter(
removeDevicesFromQueue = { devicesExternalId -> removeDevicesFromQueue(devicesExternalId) },
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setUpUI()
observeOssActionTransmitter()
setUpQuickSearch()
viewModel.offlineDevices().observe(viewLifecycleOwner, { offlineDevices ->
if (offlineDevices.isNullOrEmpty()) {
showEmptyView()
} else {
showContentView(offlineDevices)
}
})
}
private fun setUpQuickSearch() {
search.searchEdit
.textChanges()
.skipInitialValue()
.skip(1, TimeUnit.SECONDS)
.debounce(1, TimeUnit.SECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
offlineDevicesListAdapter.filter("$it")
}, {
offlineDevicesListAdapter.filter("")
})
.addTo(disposableBag)
}
private fun showEmptyView() {
recycler_view.gone()
empty_state_list_info.visible()
empty_list_state_image.visible()
updateResultCount(0)
}
private fun showContentView(offlineDevices: List<OssOfflineDevicesI>) {
empty_state_list_info.gone()
empty_list_state_image.gone()
offlineDevicesListAdapter.update(offlineDevices)
recycler_view.visible()
updateResultCount(offlineDevices.size)
}
private fun updateResultCount(resultCount: Int) {
search.countText.text = String.format("%s %d",
com.comarch.fsm.android.core.extensions.getString("total_results"), resultCount)
}
}
I have two buttons to play and pause a track in a RecyclerView item. When play button tapped, I want to hide it and show pause button. I've done this and it's working but I have a problem. Once I scroll to (down or up), the play button appears again and pause button disappears. I also have a progress bar to show the time of the track. As the track play, the bar fills out and its progress is zero at the beginning. When I scroll the list, this progress bar also resets to zero and doesn't move but the track continues to play. I tried three ways to fix this:
Setting setIsRecyclable to false
Adding and else condition to views
Adding default visibility to the views in the XML file
Here's my complate code:
class BackstageProcessorAdapter(private val stickyHeaderChangedCallback: (ProcessorGroupId) -> Unit) : RecyclerView.Adapter<RecyclerView.ViewHolder>(),
StickyHeaderItemDecoration.StickyHeaderInterface {
private var callback: ProcessorViewHolderCallback? = null
private var backStageProcessorItemList = emptyList<BackStageProcessorItem>()
private var stickyHeaderPosition = 0
private val processorGroupHeaderPositionMap = mutableMapOf<ProcessorGroupId, Int>()
private var parentRecyclerViewHeight = 0
private var lastItemPosition = 0
private var currentPreviewSound: String = ""
private var processorHeaderNameForEvent: String = ""
private lateinit var timer: CountDownTimer
var prevHolder: ProcessorViewHolder? = null
var mediaPlayer: MediaPlayer? = null
fun registerCallback(callback: ProcessorViewHolderCallback) {
this.callback = callback
}
fun setItems(items: List<BackStageProcessorItem>) {
if (backStageProcessorItemList.isNotEmpty()) return
backStageProcessorItemList = items
var headerPos = 0
for ((index, item) in items.withIndex()) {
if (item is BackStageProcessorItem.Header) {
headerPos = index
processorGroupHeaderPositionMap[item.processorGroupUiModel.processorGroupId] =
headerPos
}
item.headerPosition = headerPos
}
lastItemPosition = items.lastIndex
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
HEADER_ITEM -> HeaderViewHolder(parent.inflate(R.layout.item_processor_header))
else -> ProcessorViewHolder(parent.inflate(R.layout.item_backstage_processor))
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (val backStageProcessorItem = backStageProcessorItemList[position]) {
is BackStageProcessorItem.Header -> {
(holder as HeaderViewHolder).bindTo(backStageProcessorItem)
}
is BackStageProcessorItem.Content -> {
(holder as ProcessorViewHolder).bindTo(backStageProcessorItem.processorUiModel)
holder.setMargin(position)
}
}
}
override fun getItemViewType(position: Int): Int {
return when (backStageProcessorItemList.get(position)) {
is BackStageProcessorItem.Header -> HEADER_ITEM
else -> PROCESSOR_ITEM
}
}
override fun getItemCount() = backStageProcessorItemList.size
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
recyclerView.post {
parentRecyclerViewHeight = recyclerView.height
}
}
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
callback = null
}
override fun getHeaderPositionForItem(itemPosition: Int) =
backStageProcessorItemList[itemPosition].headerPosition
override fun getHeaderLayout(headerPosition: Int) = R.layout.item_processor_header
override fun bindHeaderData(header: View, headerPosition: Int) {
val headerItem = backStageProcessorItemList[headerPosition] as BackStageProcessorItem.Header
(header as TextView).setText(headerItem.processorGroupUiModel.nameResId)
if (headerPosition != stickyHeaderPosition) {
stickyHeaderPosition = headerPosition
stickyHeaderChangedCallback(headerItem.processorGroupUiModel.processorGroupId)
}
}
override fun isHeader(itemPosition: Int): Boolean {
if (itemPosition == backStageProcessorItemList.size) return true
return backStageProcessorItemList[itemPosition] is BackStageProcessorItem.Header
}
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
super.onViewDetachedFromWindow(holder)
}
fun getHeaderPositionViewGroupId(processorGroupId: ProcessorGroupId): Int {
return processorGroupHeaderPositionMap[processorGroupId]!!
}
inner class HeaderViewHolder(itemView: View) :
RecyclerView.ViewHolder(itemView) {
fun bindTo(header: BackStageProcessorItem.Header) {
(itemView as TextView).setText(header.processorGroupUiModel.nameResId)
}
}
inner class ProcessorViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val textViewProcessorName = itemView.findViewById<TextView>(R.id.textViewProcessorName)
private val textViewProcessorDescription = itemView.findViewById<TextView>(R.id.textViewProcessorDescription)
private val imageViewProcessorImage = itemView.findViewById<ImageView>(R.id.imageViewProcessorImage)
private val buttonAddProcessor = itemView.findViewById<Button>(R.id.buttonAddProcessor)
private val buttonUnlockEverything = itemView.findViewById<TextView>(R.id.buttonUnlockEverything)
private val buttonPlayPreview = itemView.findViewById<Button>(R.id.buttonPlayPreview)
private val buttonPausePreview = itemView.findViewById<Button>(R.id.buttonPausePreview)
fun setMargin(position: Int) {
val margin =
if (position != lastItemPosition) dpToPx(20)
else {
val contentHeight = getDimen(R.dimen.backstage_processor_item_height)
val headerHeight = getDimen(R.dimen.processor_header_height)
val topMargin = dpToPx(20)
parentRecyclerViewHeight - (contentHeight + headerHeight + topMargin)
}
(itemView.layoutParams as ViewGroup.MarginLayoutParams).bottomMargin = margin
}
#SuppressLint("ClickableViewAccessibility")
fun bindTo(processor: ProcessorUiModel) {
val processorId = processor.processorId
val canProcessorBeEnabled = callback?.canProcessorBeEnabled(processorId) == true
val isProcessorAdded = callback?.isProcessorAddedBefore(processorId) == true
val processorName = itemView.context.resources.getText(processor.nameId).toString()
val processorNameForEvent = processorName.toLowerCase().replace(" ", "_")
this.setIsRecyclable(false)
if (prevHolder != null) prevHolder?.setIsRecyclable(false)
imageViewProcessorImage.setImageResource(processor.storeIconResId)
textViewProcessorName.setText(processor.nameId)
textViewProcessorDescription.setText(processor.descriptionId)
buttonUnlockEverything.isVisible = canProcessorBeEnabled.not()
buttonAddProcessor.isGone = canProcessorBeEnabled.not()
buttonAddProcessor.isEnabled = isProcessorAdded.not()
this.setIsRecyclable(false)
buttonAddProcessor.setOnTouchListener { v, event ->
return#setOnTouchListener when (event.action) {
KeyEvent.ACTION_DOWN -> {
v.alpha = 0.75f
true
}
KeyEvent.ACTION_UP -> {
v.alpha = 1f
callback?.addProcessor(processorId)
true
}
else -> v.onTouchEvent(event)
}
}
buttonPlayPreview.setOnClickListener {
if (currentPreviewSound.isNotEmpty()) {
pausePreviewSound()
}
if (currentPreviewSound.isNotEmpty() && prevHolder != this) {
currentPreviewSound = ""
prevHolder?.itemView?.buttonPausePreview?.isVisible = false
prevHolder?.itemView?.buttonPlayPreview?.isVisible = true
} else {
prevHolder?.itemView?.buttonPausePreview?.isVisible = true
prevHolder?.itemView?.buttonPlayPreview?.isVisible = false
}
processorName.playPreviewSound(processorNameForEvent)
prevHolder = this
notifyDataSetChanged()
}
buttonPausePreview.setOnClickListener() {
pausePreviewSound()
}
buttonUnlockEverything.setOnClickListener {
getHeaderNameClickProcessorForEvent()
callback!!.sendEvent("goPremiumClicked", processorHeaderNameForEvent, processorName)
callback?.openInAppBilling()
}
}
private fun String.playPreviewSound(processorNameForEvent: String) {
callback?.stopVG()
currentPreviewSound = this
buttonPlayPreview.isVisible = false
buttonPausePreview.isVisible = true
mediaPlayer = MediaPlayer.create(itemView.context, AmpSoundType.getAmpType(this))
mediaPlayer?.start()
val maxTrackDuration = mediaPlayer?.duration!!
itemView.progressBarPreview.max = maxTrackDuration
itemView.progressBarPreview.progress = 0
// The first arg of the CountDownTimer is the tick count. Which is (maxTrackDuration (lets say this is 18000) / 1000) = 18 ticks in total duration with 200ms interval
timer = object : CountDownTimer(maxTrackDuration.toLong(), 200) {
override fun onTick(millisUntilFinished: Long) {
updatePreviewSoundProgressBar()
}
override fun onFinish() {
setPlayButton()
}
}
timer.start()
callback!!.sendEvent("playClicked", processorHeaderNameForEvent, processorNameForEvent)
}
private fun pausePreviewSound() {
setPlayButton()
mediaPlayer?.stop()
timer.cancel()
}
private fun setPlayButton() {
buttonPlayPreview.isVisible = true
buttonPausePreview.isVisible = false
}
private fun updatePreviewSoundProgressBar() {
itemView.progressBarPreview.progress += 200
}
private fun getHeaderNameClickProcessorForEvent() {
val processorHeaderPosition = backStageProcessorItemList[getHeaderPositionForItem(position)]
val processorHeaderData = (processorHeaderPosition as BackStageProcessorItem.Header).processorGroupUiModel.nameResId
val processorHeaderName = itemView.context.resources.getString(processorHeaderData)
processorHeaderNameForEvent = processorHeaderName.toLowerCase().substring(0, 3)
}
private fun dpToPx(dp: Int) = (dp * itemView.resources.displayMetrics.density).toInt()
private fun getDimen(dimenRes: Int) = itemView.resources.getDimensionPixelSize(dimenRes)
}
}
And a part of my layout:
<LinearLayout
android:id="#+id/layoutHearTone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:gravity="center"
android:orientation="horizontal"
app:layout_constraintBottom_toTopOf="#id/buttons"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.46"
app:layout_constraintStart_toStartOf="parent">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="12dp">
<Button
android:id="#+id/buttonPausePreview"
android:layout_width="30dp"
android:layout_height="30dp"
android:visibility="invisible"
tools:visibility="invisible"
android:background="#drawable/ic_preset_view_pause" />
<Button
android:id="#+id/buttonPlayPreview"
android:layout_width="30dp"
android:layout_height="30dp"
android:visibility="visible"
tools:visibility="visible"
android:background="#drawable/ic_preset_view_play" />
</RelativeLayout>
<ProgressBar
android:id="#+id/progressBarPreview"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="false"
android:minWidth="140dp"
android:progress="0" />
</LinearLayout>
RecyclerViews work by creating a pool of ViewHolder objects (got by calling onCreateViewHolder) which are used to display stuff. No matter how many items the view represents, there are only a handful of ViewHolders being used, enough to fill the visible part of the RecyclerView and a few either side so you can peek to the next item.
So it works by shuffling those ViewHolders around to put them ahead of the scrolling, and the stuff they're displaying gets updated to represent a particular item in the list. This is done in onBindViewHolder.
Basically, if you have items with state, i.e. whether the play button is visible, whether a seek bar is at a particular position, if it has some kind of controller attached that updates the seek bar - you need to restore all that in onBindViewHolder when that item comes into view and a ViewHolder is being told to display that item. That means you have to keep track of that state somewhere (usually in the adapter), so you can restore it when an item pops into view.
I'm trying to get worked a segmented SeekBar for font site with step of 2.
It's working, but I can't keep thumb position, it is always on 0.
private fun fontSize() {
val view = LayoutInflater.from(this).inflate(R.layout.font_size_layout, null)
size = view.findViewById(R.id.font_size_sb)
val preference = PrefManager(this)
font = view.findViewById(R.id.font_size_tv)
font.textSize = preference.getFontSize().toFloat()
font.text = preference.getFontSize().toString()
size.apply {
max = (36 - 12) / 2
progress = preference.getFontSize()
setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
updateFontSize(12 + (progress * 2))
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {
}
override fun onStopTrackingTouch(seekBar: SeekBar?) {
}
})
}
AlertDialog.Builder(
this,
R.style.AlertDialogSlider
).apply {
setView(view)
create()
show()
}
}
private fun updateFontSize(i: Int) {
note.textSize = i.toFloat()
font.text = i.toString()
font.textSize = i.toFloat()
preference.saveFontSize(i)
}
My preference class PrefManager:
class PrefManager(private var context: Context) {
fun saveFontSize(size: Int) {
context.getSharedPreferences("font_size", AppCompatActivity.MODE_PRIVATE).edit().apply {
putInt("fontSize", size)
apply()
}
}
fun getFontSize(): Int {
return context.getSharedPreferences("font_size", AppCompatActivity.MODE_PRIVATE)
.getInt("fontSize", -1)
}
For example, I set the font size to 18:
That's good and works for me, but when I want to change the size one more time, the SeekBar's position does not stay there, where I left it lastly. It goes to end:
How can I keep the position?
After spending a lot of time, I finally found a solution and it's very simple:
class PrefManager(private var context: Context) {
fun saveFontSize(size: Int, progress: Int) {
context.getSharedPreferences("font_size", AppCompatActivity.MODE_PRIVATE).edit().apply {
putInt("fontSize", size)
putInt("progress", progress)
apply()
}
}
fun getFontSize(): Int {
return context.getSharedPreferences("font_size", AppCompatActivity.MODE_PRIVATE)
.getInt("fontSize", 18)
}
fun getProgress(): Int {
return context.getSharedPreferences("font_size", AppCompatActivity.MODE_PRIVATE)
.getInt("progress", 3)
}
}
And piece of code for SeekBar:
size.apply {
progress = preference.getProgress()
setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(
seekBar: SeekBar?,
progress: Int,
fromUser: Boolean
) {
val fontSize = 12 + (progress * 2)
note.textSize = fontSize.toFloat()
font.text = fontSize.toString()
preference.saveFontSize(fontSize, progress)
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {
}
override fun onStopTrackingTouch(seekBar: SeekBar?) {
}
})
}
I had just to keep saved the original values of progress within the onProgressChanged(). That's all!
I'm trying to use Exoplayer inside the ViewPager. It will play only when I open it by clicking on the thumbnail which I'm using to display the videos. But it won't play when I reach the page by Scrolling.
I tried calling seekTo(0, 0) & playWhenReady = true inside onPageSelected() and playWhenReady = false inside the onScrolled() methods of the VPAdapter using the Lambdas but didn't work.
Here is the Adapter I'm using for the viewPager.
class MediaVPAdapter(
val context: Context,
#LayoutRes val layoutRes: Int,
val medias: List<MediaFiles> = listOf()
) : PagerAdapter() {
private var player: SimpleExoPlayer? = null
override fun isViewFromObject(view: View, o: Any): Boolean {
return view == o
}
override fun getCount() = medias.size
override fun instantiateItem(container: ViewGroup, position: Int): Any {
return LayoutInflater.from(context).inflate(layoutRes, container, false).apply {
if (medias.isNotEmpty()) {
when (medias[position].mediaType) {
MediaType.PHOTO -> {
// Display the Image
}
MediaType.VIDEO -> {
ssImageView.makeInvisible(true)
exoPlayerView.makeVisible()
player = ExoPlayerFactory.newSimpleInstance(
DefaultRenderersFactory(context),
DefaultTrackSelector(),
DefaultLoadControl()
)
if (medias[position].uri != null)
initPlayer(this, medias[position].uri!!)
}
null -> {
}
}
}
container.addView(this, 0)
}
}
private fun initPlayer(view: View, uri: Uri) {
Timber.i("initPlayer: uri: $uri")
with(view) {
player!!.let {
exoPlayerView.player = it
it.prepare(buildMediaSource(uri), true, false)
it.playWhenReady = false
}
}
}
private fun buildMediaSource(uri: Uri) = ExtractorMediaSource.Factory(
DefaultDataSourceFactory(context, Util.getUserAgent(context, context.getString(R.string.app_name)))
)
.setExtractorsFactory(DefaultExtractorsFactory())
.createMediaSource(uri)
override fun destroyItem(container: ViewGroup, position: Int, obj: Any) {
releasePlayer()
}
private fun releasePlayer() {
if (player != null) {
playWhenReady = false
player!!.release()
player = null
}
}
}
val mediaAdapter = MediaVPAdapter(
this,
R.layout.fragment_media,
true,
medias?.toList() ?: listOf()
)
vpMedia.adapter = mediaAdapter
vpMedia.currentItem = position ?: 0
vpMedia.offscreenPageLimit = 1
If I click on a Video, the selected Video could be played but when I scroll to some other item, none of them were playing. When I try to toggle the Play/Pause button, only the button was toggling.
As far i can see you you code misses the setPlayWhenReady listener.
You should fix it by adding it.setPlayWhenReady(playWhenReady) in the initPlayer method.
I'm having this issue, with recyclerView, may you check two screenshots below:
So that's my issue, when onNotifyItemChange runs, other info are changed, incorrectlty. Now here goes my adapter:
class TimelineAdapter(var timeline: TimelineDTO,
var toggleLikeClicked: OnRowClick,
var onCommentClicked: OnRowClick,
var onMediaClick: OnRowClick,
val onUserClicked: OnRowClick,
val reportPost: OnRowClick,
val editPost : OnRowClick,
val deletePost: OnRowClick,
val contract: TimelineViewContract) : BaseAdapter<RecyclerView.ViewHolder>() {
init {
setHasStableIds(true)
}
private var currentItem: Int = 0
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
when (PostType.fromInt(viewType)) {
PostType.BASIC -> {
return PostViewHolder(parent.inflate(R.layout.row_post_default_item),
toggleLikeClicked, onCommentClicked, onMediaClick,
onUserClicked, reportPost,
editPost,
deletePost,
FirebaseAnalytics.getInstance(contract.returnContext()))
}
PostType.NEXT_TALKS -> {
return PostNextTalksViewHolder(parent.inflate(R.layout.row_post_next_talks_item),
contract)
}
else -> {
if(!BuildConfig.DEBUG) {
Crashlytics.log("Should not come here")
}
logE("adapter else!!")
return PostViewHolder(parent.inflate(R.layout.row_post_default_item),
toggleLikeClicked, onCommentClicked, onMediaClick,
onUserClicked, reportPost,
editPost,
deletePost,
FirebaseAnalytics.getInstance(contract.returnContext()))
}
}
}
override fun getItemCount(): Int {
var count = timeline.posts.size
if(hasValue(timeline.nextTalks.size)){
count++
}
return count
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
currentItem = position
val alignedPositon = getAlignedPosition(position)
when (holder) {
is PostViewHolder -> holder.bind(timeline.posts[alignedPositon])
is PostNextTalksViewHolder -> {
holder.bind(timeline.nextTalks)
}
is PostCarousselViewHolder -> {
holder.bind(ArrayList<String>())
}
}
}
fun getPostAt(position: Int): PostDTO {
val post: PostDTO
val alignedPositon = getAlignedPosition(position)
post = timeline.posts[alignedPositon]
return post
}
override fun getItemId(position: Int): Long {
val aligned = getAlignedPosition(position)
return aligned.toLong()
}
private fun getAlignedPosition(position: Int): Int {
var alignedPositon = position
if (hasValue(timeline.nextTalks.size)){
alignedPositon--
}
return alignedPositon
}
override fun getItemViewType(position: Int): Int {
val hasPinned = timeline.posts.any { it.postType == PostType.PINNED.id }
if(hasPinned) {
if(position == 1 && timeline.nextTalks.any()){
return PostType.NEXT_TALKS.id
}
}
else {
if(position == 0 && timeline.nextTalks.any()){
return PostType.NEXT_TALKS.id
}
}
return timeline.posts[getAlignedPosition(position)].postType
}
fun updateItemAt(postLocal: PostLocal, commentIndexPost: Int) {
timeline.posts.removeAt(commentIndexPost)
timeline.posts.add(commentIndexPost, PostDTO(postLocal))
notifyItemChanged(commentIndexPost)
}
fun addItems(newPosts: TimelineDTO) {
timeline.posts.addAll(newPosts.posts)
timeline.nextTalks.addAll(newPosts.nextTalks)
notifyItemRangeInserted(itemCount, newPosts.posts.size)
}
fun resetItems(nextPosts: TimelineDTO) {
timeline.posts.clear()
timeline.nextTalks.clear()
timeline.posts.addAll(nextPosts.posts)
timeline.nextTalks.addAll(nextPosts.nextTalks)
notifyDataSetChanged()
}
fun removeAt(position: Int) {
timeline.posts.removeAt(position)
notifyItemRemoved(position)
notifyItemRangeChanged(position, timeline.posts.size)
}
}
Using notifyItemChanged() might trigger "fading in and out" effect which is not necessarily desired (unless You use stable IDs or killed change animation in animator).
If You know what was changed in an item, it's better to use an update payload (see an example here) to partially update your ViewHolders without triggering full rebind.
Otherwise if list is relatively small and You don't know what changed, you can also use DiffUtil to help generate list of changes/change payloads "semi-automatically".