I'm trying to do a proof of concept where a Webview loads local HTML files and users are able to swipe left/right to go to the next file, all while in fullscreen. I'm utilizing ViewPager2 and normal Webviews. I have that part working, but what I want to do is, upon the user doing a single tap, show or hide the toolbar, status bar and navigation controls. Right now I have the setOnTouchListener code on the viewPager, but it looks like the touch events are being consumed by the webview.
How can I accomplish where a single tap would do a toggle between fullscreen and non-fullscreen mode, without disrupting ViewPager paging and long press in the webview?
Here is most of my code. I'm leaving parts out for brevity that should not be related.
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_my)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
viewPager.setOnTouchListener { _, event ->
when(event.action) {
MotionEvent.ACTION_DOWN -> {
initialX = event.rawX
initialY = event.rawY
moved = false
}
MotionEvent.ACTION_MOVE -> {
if (event.rawX != initialX || event.rawY != initialY) {
moved = true
}
}
MotionEvent.ACTION_UP -> {
if (!moved) {
toggle()
}
}
}
true
}
viewPager.adapter = MyAdapter(items, this)
}
}
class MyAdapter(private val items: List<String>) : RecyclerView.Adapter<MyViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.view_pager_item, parent, false) as WebView
return MyViewHolder(view, items)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.bind(position)
}
override fun getItemCount(): Int {
return items.size
}
}
class MyViewHolder (private val webView: WebView, private val items: List<String>) :
RecyclerView.ViewHolder(webView) {
internal fun bind(position: Int) {
webView.loadUrl("file://" + items[position])
}
}
Related
My navigation in app is based on ViewPager and custom Page content in it.
I can initialize screens and then navigate through them with switchScreen function.
I have upper main navigation with tabs where you can navigate by clicking tabs and each tab has its designated screen to show.
Each of those main screens can have inner ViewPager with separate navigation and screens.
Issue is that sometimes under some unknown circumstances (I'm not sure why it is happening or what is causing this issue) inner ViewPager will inject its screen into main navigation flow. And really weird thing is, that if I debug it in code, it seems like ViewPager is indeed switching and showing right screen, but I see wrong screen on my display.
It seems like that those main navigation tabs are taking control of inner navigation view pager inside Screen C and displaying its screens inside outer ViewPager (but in code it looks fine - If I switch to Tab B, it will indeed show in logs that Screen B1 is set and displayed, but I see Screen C2 instead)
MainNavigation is initialized inside MainActivity and it has indeed visible tabs which user can click.
Inner navigation inside each Tab does not have any tabLayout visible, it is switching only in code by calling function switchScreen
Example:
MainNavigationActivity (same module handling screen selection is initialized inside each main Pages handling inner navigation):
protected fun createCustomTabScreenFlow(){
mainPagerAdapter = FlowViewPagerAdapter(mainFlowList)
mainViewPager.adapter = mainPagerAdapter
mainViewPager.offscreenPageLimit = mainFlowList.size
mainViewPager.addOnPageChangeListener(object: ViewPager.OnPageChangeListener{
override fun onPageScrollStateChanged(state: Int) {}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}
override fun onPageSelected(position: Int) {
backButtonStatus(isEnabled = true)
App.log("MainTabActivity: -------")
App.log("MainTabActivity: onPageSelected: $position")
App.log("MainTabActivity: hasVisibleTabs: $hasVisibleTabs")
val page = mainFlowList[position]
App.log("MainTabActivity: onPageSelected: new: ${page::class.java} current: ${mainCurrentPage?.let { it::class.java }?:"null"}")
setTabsEnabled(false)
if (hasVisibleTabs){
if (page != mainCurrentPage){
//switch automatically
App.log("MainTabActivity: onPageSelected: switch to page: ${page::class.java}")
mainCurrentPage?.onScreenRemoved()
mainCurrentPage?.setScreenVisibleState(false)
switchScreen(page::class.java)
} else {
App.log("MainTabActivity: onPageSelected: switch to page: same page")
setTabsEnabled(true)
}
}
}
})
mainTabLayout.setupWithViewPager(mainViewPager)
(0..mainTabLayout.tabCount).forEach { tab->
mainFlowList.getOrNull(tab)?.icon?.let {
mainTabLayout.getTabAt(tab)?.let { tabItem->
val tabItemView = CustomResources.inflateLayout(layoutInflater, R.layout.custom_tab_view, null, false) as ViewGroup
tabItemView.findViewById<ImageView>(R.id.icon).setImageResource(it)
tabItem.customView = tabItemView
tabItem.customView?.requestLayout()
}
}
}
onScreenFlowIntialized()
}
fun switchScreen(
screen: Class<out FlowScreen>,
payload: List<ScreenPayload>? = null,
action: (() -> Unit)? = null,
useAnim: Boolean = true,
){
App.log("MainTabActivity: switchScreen: $screen")
try {
val index = mainFlowList.indexOfFirst { item -> item::class.java == screen }
App.log("MainTabActivity: switchScreen: index: $index")
if (index >= 0){
delayedScreenSelection {
mainTabLayout.getTabAt(index)?.select()
mainViewPager.setCurrentItem(index, useAnim)
mainPagerAdapter.getPageAtPos(index)?.apply {
App.log("MainTabActivity: switchScreen: page: ${this::class.java}")
mainCurrentPage?.apply {
setScreenVisibleState(false)
resetToDefault()
removeBottomSheet(false)
this#MainTabActivity.removeBottomSheet(false)
}
mainCurrentPage = this
mainCurrentPage?.apply {
setScreenVisibleState(true)
clearPayload()
}
payload?.let { mainCurrentPage?.sendPayload(payload) }
onScreenSwitched()
onPageChanged()
}?:kotlin.run {
setTabsEnabled(true)
}
}
} else {
setTabsEnabled(true)
}
}catch (e: IndexOutOfBoundsException){
App.log("MainTabActivity: switchScreen: $e")
setTabsEnabled(true)
}
}
FlowViewPagerAdapter:
class FlowViewPagerAdapter(private var pageList: List<FlowScreen>): PagerAdapter() {
override fun isViewFromObject(view: View, `object`: Any): Boolean {
return view == `object`
}
override fun getCount(): Int {
return pageList.size
}
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
container.removeView(`object` as View)
}
override fun instantiateItem(container: ViewGroup, position: Int): View {
val layout = pageList[position].getScreen(container)
container.addView(layout)
return layout
}
var mCurrentView: View? = null
override fun setPrimaryItem(container: ViewGroup, position: Int, `object`: Any) {
mCurrentView = `object` as View
val pager = container as? CustomizableViewPager
mCurrentView?.let { v->
pager?.measureCurrentView(v)
}
}
fun getPageAtPos(pos: Int): FlowScreen?{
return pageList.getOrNull(pos)
}
override fun getPageTitle(position: Int): CharSequence? {
return getPageAtPos(position)?.name
}
}
I have a case where a view within RecyclerView items needs to be animated using the Lottie library. Each recycler view item is clickable and contains a liking Lottie animation.
I defined a custom RecyclerView.ItemAnimator like this:
class SampleItemAnimator : DefaultItemAnimator() {
override fun animateChange(
oldHolder: RecyclerView.ViewHolder,
newHolder: RecyclerView.ViewHolder,
preInfo: ItemHolderInfo,
postInfo: ItemHolderInfo
): Boolean {
val holder = newHolder as BindingViewHolder<ItemSampleBinding>
val animator = lottieAnimatorListener {
dispatchAnimationFinished(holder)
holder.binding.sampleAnimation.removeAllAnimatorListeners()
}
holder.binding.sampleAnimation.addAnimatorListener(animator)
if (preInfo is SampleItemHolderInfo) {
if (preInfo.isItemLicked) {
holder.binding.sampleAnimation.playAnimation()
} else {
resetAnimation(holder.binding.sampleAnimation)
}
return true
}
return super.animateChange(oldHolder, newHolder, preInfo, postInfo)
}
private fun resetAnimation(lottieAnimationView: LottieAnimationView) {
lottieAnimationView.progress = 0f
lottieAnimationView.cancelAnimation()
}
override fun recordPreLayoutInformation(
state: RecyclerView.State,
viewHolder: RecyclerView.ViewHolder,
changeFlags: Int,
payloads: MutableList<Any>
): ItemHolderInfo {
if (changeFlags == FLAG_CHANGED) {
return produceItemHolderInfoOrElse(payloads.firstOrNull() as? Int) {
super.recordPreLayoutInformation(state, viewHolder, changeFlags, payloads)
}
}
return super.recordPreLayoutInformation(state, viewHolder, changeFlags, payloads)
}
private fun produceItemHolderInfoOrElse(value: Int?, action: () -> ItemHolderInfo) =
when (value) {
LIKE_ITEM -> SampleItemHolderInfo(true)
UNLIKE_ITEM -> SampleItemHolderInfo(false)
else -> action()
}
override fun canReuseUpdatedViewHolder(viewHolder: RecyclerView.ViewHolder) = true
override fun canReuseUpdatedViewHolder(
viewHolder: RecyclerView.ViewHolder,
payloads: MutableList<Any>
) = true
}
lottieAnimatorListener is just a function that creates Animator.AnimatorListener to tell RecyclerView when the animation is canceled or ended by calling dispatchAnimationFinished(holder).
Everything works except that sometimes the liking animation can randomly play on items with no likes, especially while scrolling RecyclerView too fast.
As far as I understand, it happens because the ItemAnimator re-uses the same view holders and either uses outdated ItemHolderInfo or does not notify the RecyclerView about the end of the animation correctly.
That is how I pass a payload to the adapter to tell what has changed using DiffUtil.Callback.
class SampleListDiffCallback : DiffCallback<SampleItem> {
override fun areContentsTheSame(oldItem: SampleItem, newItem: SampleItem) =
oldItem.markableItem == newItem.markableItem
override fun areItemsTheSame(oldItem: SampleItem, newItem: SampleItem) =
oldItem.identifier == newItem.identifier
override fun getChangePayload(
oldItem: SampleItem,
oldItemPosition: Int,
newItem: SampleItem,
newItemPosition: Int
): Any? = createPayload(oldItem.markableItem, newItem.markableItem)
private fun createPayload(
oldItem: MarkableItem,
newItem: MarkableItem
) = when {
! oldItem.isLiked && newItem.isLiked -> LIKE_ITEM
oldItem.isLiked && ! newItem.isLiked -> UNLIKE_ITEM
else -> null
}
}
That is how I define a ViewHolder using the FastAdapter library:
class SampleItem(
val markableItem: MarkableItem,
private val onClickItem: (Item) -> Unit
) : AbstractBindingItem<ItemSampleBinding>() {
override val type: Int = R.layout.item
override var identifier: Long = markableItem.item.hashCode().toLong()
override fun createBinding(inflater: LayoutInflater, parent: ViewGroup?) =
ItemSampleBinding.inflate(inflater, parent, false)
override fun bindView(binding: ItemSampleBinding, payloads: List<Any>) {
super.bindView(binding, payloads)
with(binding) {
itemName.text = markableItem.item.name
itemImage.setContent(markableItem.item, IMAGE_SIZE)
likeAnimation.progress = if(markableItem.isClicked) 1f else 0f
root.setThrottleClickListener { onClickItem(markableItem.item) }
}
}
}
UPD: The liking animation's duration is 2 seconds.
Does anybody know if there is any way to fix it?
So, I am creating a cocktail app, based on the https://www.thecocktaildb.com/ api. Thus far, I have only created a screen to display options based on the ingredient I put in the search bar (search bar is not done yet). Yet when I run the app, only the first entry is displayed
By putting Log.e("TAG", "$position") inside of my onBindViewHolder, of the adapter, I saw that the position variable never increases from 0
class CocktailsAdapter: RecyclerView.Adapter<CocktailsAdapter.CocktailsViewHolder>() {
inner class CocktailsViewHolder(val binding: ItemCocktailPreviewBinding) :
RecyclerView.ViewHolder(binding.root)
private val differCallback = object : DiffUtil.ItemCallback<CocktailsByBaseDto>() {
override fun areItemsTheSame( oldItem: CocktailsByBaseDto, newItem: CocktailsByBaseDto): Boolean {
return oldItem.drinks[0].idDrink == newItem.drinks[0].idDrink
}
override fun areContentsTheSame(oldItem: CocktailsByBaseDto, newItem: CocktailsByBaseDto): Boolean {
return oldItem.drinks[0] == newItem.drinks[0]
}
}
val differ = AsyncListDiffer(this, differCallback)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CocktailsViewHolder {
return CocktailsViewHolder(
ItemCocktailPreviewBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: CocktailsViewHolder, position: Int) {
val binding = holder.binding
val cocktail = differ.currentList[position]
holder.itemView.apply {
Glide.with(this).load(cocktail.drinks[position].strDrinkThumb).into(binding.imgCocktailsMainRecyclerViewImage)
binding.tvCocktailsMainRecyclerViewTitle.text = cocktail.drinks[position].strDrink
Log.e("TAG", "$position")
setOnClickListener {
onItemClickListener?.let { it(cocktail) }
}
}
}
override fun getItemCount(): Int {
return differ.currentList.size
}
private var onItemClickListener: ((CocktailsByBaseDto) -> Unit)? = null
fun setOnItemClickListener(listener: (CocktailsByBaseDto) -> Unit) {
onItemClickListener = listener
}
I have tried both position and 0 (which makes more sense) inside val cocktail = differ.currentList[position], but neither gave me a different result
Fixed it by changing the class I had passed to DiffUtil.ItemCallback
I am building an app to be able to drag an item from one recycle view to another and I still have to keep the option to re-order inside a single recycler view.
So I have defined a Reorder Callback already
class ReorderHelperCallback(val adapter : ItemTouchHelperAdapter): ItemTouchHelper.Callback() {
override fun getMovementFlags(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
): Int {
val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN or
ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
return makeMovementFlags( dragFlags, 0)//swipeFlags )
}
override fun onMove(
recyclerView: RecyclerView,
source: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
adapter.onItemMove(source.getAdapterPosition(),
target.adapterPosition)
return true
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
//Not use for Drag N Drop
}
}
also an interface:
interface OnStartDragListener {
fun onStartDrag(viewHolder: RecyclerView.ViewHolder?)
}
and a touchhelper:
interface ItemTouchHelperAdapter {
fun onItemMove(fromPosition: Int, toPosition: Int): Boolean
fun onItemDismiss(position: Int)
}
to allow the reorder to work, I had to update the Recycler view adapter as below:
class Adapter(
private var context: Context?,
val dragStartListener : OnStartDragListener
): RecyclerView.Adapter<Adapter.ViewHolder>(), ItemTouchHelperAdapter {
var arrayItems : ArrayList<Data?> = ArrayList()
fun setData(array : MutableList<Data?>){
array.toCollection(arrayItems)
notifyDataSetChanged()
}
override fun getItemCount(): Int {
return arrayItems.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Adapter.ViewHolder {
val binding = DashboardTileLayoutBinding
.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding, dragStartListener)
}
override fun onBindViewHolder(holder: Adapter.ViewHolder, position: Int) {
holder.setData(arrayItems[position])
}
inner class ViewHolder(val binding: LayoutBinding,
val dragStartListener : OnStartDragListener? = null)
: RecyclerView.ViewHolder(binding.root) {
val tileLayout = binding.tileLayout
fun setData(data: Data?) {
....
tileLayout.setOnDragListener { view, dragEvent ->
when(dragEvent.action) {
ACTION_DRAG_STARTED -> {
dragStartListener?.onStartDrag(this)
true
}
else -> false
}
}
}
}
override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
Collections.swap(arrayItems, fromPosition, toPosition)
notifyItemMoved(fromPosition, toPosition)
return true
}
override fun onItemDismiss(position: Int) {
TODO("Not yet implemented")
}
}
and the fragment which contain the rv, I have updated the adapter init:
list1adapter?.let { adapter ->
adapter.setData(list)
val callback: ItemTouchHelper.Callback = ReorderHelperCallback(adapter)
mItemTouchHelperSelected = ItemTouchHelper(callback)
mItemTouchHelperSelected?.attachToRecyclerView(selectedLayout)
}
---
override fun onStartDrag(viewHolder: RecyclerView.ViewHolder?) {
viewHolder?.let {
mItemTouchHelperSelected?.startDrag(it)
}
}
But my fragment contain 2 recycler views. list1 is working fine to user drag and drop to re-order the item but now, I would like to also be able to move an item from my rv list1 to the list2 and vice versa
Any idea, how to make it Kotlin ? I tried an sample code, byt I am losing the re-ordering.
Thanks
So am having this recyclerview which will contain holders of multiple types one of which could be a scrollable horizontal list of edge to edge images, that are being scrolled automatically and have a current item indicator. so for this i used a viewholder which will itself contain another recyclerview and a dots indicator( which itself is another recycler view, so basically recyclerview = a list of vh , where one of the vh = 2 horizontal recyclerview).
title
[A,B,C,D...]
[+ ---]
title
[A,B,C,D...]
[+ --]
title
[A,B,C,D...]
[+ --]
title
[A,B,C,D...]
[+ --]
My innermost recylerview of horizontal images is created something like this:
class ImageAdapter : RecyclerView.Adapter<ImageVH>() {
var imageResList = mutableListOf<Int>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ImageVH(parent, viewType)
override fun onBindViewHolder(holder: ImageVH, pos: Int)
= holder.bindData(imageResList[pos % imageResList.size])
override fun getItemCount() = Int.MAX_VALUE
}
class ImageVH(v: View) : RecyclerView.ViewHolder(v) {
constructor(parent: ViewGroup, viewtype: Int) : this(
LayoutInflater.from(parent.context).inflate(R.layout.item_image, parent, false)
)
fun bindData(imageRes: Int) {
Glide.with(itemView.context).load("").error(imageRes).into(itemView.ivImage)
}
}
it is basically fooling the adapter to think as if i have a million images but will actually have just a few images. this creates an impression of circular scroll.
Next i will need something to change the dots indicator of the second recyclerview. for this i went into the parent of this recyclerview and attached an onScrollListener . The onScrollListener gives me 2 function: onScrolled and onScrollStateChanged.
with onScrolled , i determine when to change the next dots recyclerview's state to show the new dot. i do this via linear layout manager. when it gives findFirstCompletelyVisibleItemPosition as positive number .
with onScrollStateChanged(), i run a kind of recursion, where whenever i get the state as SCROLL_STATE_IDLE, I post a handler to scroll the recyclerview to next item after 2 seconds. after 2 seconds, it will automatically smooth scroll and again fire the same event, causing the handler to fire the same action again.
so the code looks something like this:
data class Rails(val title: String, val images: MutableList<Int>,val autoscroll:Boolean =false)
class RailsAdapter : RecyclerView.Adapter<RailVH>() {
var railsList = mutableListOf<Rails>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = RailVH(parent, viewType)
override fun onBindViewHolder(holder: RailVH, pos: Int) = holder.bindData(railsList[pos])
override fun getItemCount() = railsList.size
}
class RailVH(v: View) : RecyclerView.ViewHolder(v) {
constructor(parent: ViewGroup, viewtype: Int) : this(
LayoutInflater.from(parent.context).inflate(R.layout.item_rails, parent, false)
)
private var autoscrollImages = false
fun bindData(rails: Rails) {
autoscrollImages = rails.autoscroll
with(itemView) {
tvTitle?.text = rails.title
rvImagers?.apply {
adapter = ImageAdapter().also {
it.imageResList = rails.images
it.notifyDataSetChanged()
}
PagerSnapHelper().attachToRecyclerView(this)
isNestedScrollingEnabled = false
onFlingListener = null
addOnScrollListener(onScrollListener)
}
}
if(autoscrollImages){
bannerChangerHandler.postDelayed(bannerChangerRunnable,bannerChangerDelayMilllis)
}
}
private val onScrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
//super.onScrolled(recyclerView, dx, dy)
val bannerLLManager = itemView.rvImagers?.layoutManager as? LinearLayoutManager
bannerLLManager?.let { linearLayoutManager ->
val bannerCurrentPos = linearLayoutManager.findFirstCompletelyVisibleItemPosition()
if (bannerCurrentPos >= 0) {
val rvDotsDataListSize = 5
val positionInRange = bannerCurrentPos % rvDotsDataListSize
Toast.makeText(
itemView.context,
"highlight dot #$positionInRange",
Toast.LENGTH_SHORT
).show()
}
}
}
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
//super.onScrollStateChanged(recyclerView, newState)
when (newState) {
RecyclerView.SCROLL_STATE_IDLE -> {
if(autoscrollImages){
Log.e(">>a>>", "RecyclerView.SCROLL_STATE_IDLE!")
bannerChangerHandler.postDelayed(bannerChangerRunnable, bannerChangerDelayMilllis
)
}
}
RecyclerView.SCROLL_STATE_DRAGGING -> {
Log.e(">>a>>", "RecyclerView.SCROLL_STATE_DRAGGING!")
bannerChangerHandler.removeCallbacks(bannerChangerRunnable)
}
else -> {
}
}
}
}
private val bannerChangerHandler: Handler = Handler()
private val bannerChangerRunnable = Runnable {
itemView.rvImagers?.apply {
val bannerManager = layoutManager as? LinearLayoutManager
bannerManager?.let {
val bannerCurrentPos = it.findFirstCompletelyVisibleItemPosition()
smoothScrollToPosition(bannerCurrentPos + 1)
}
}
}
private var bannerChangerDelayMilllis = 2000L
}
for brevity, assume whenever the toast is occuring, its going to scroll the 2nd dots indicator recyclerview .
This all seems to work in principle, but after sometimes the handler seems to fire twice or thrice , causing bad ux. sometimes it even goes berserks and stops showing any logs or anything and just makes the rails run infinetely very fast, like handler firing an autoscroll runner every millisecond.
handlers firing 2-3 times
So any help with this? i am assuming something is wrong at the implementation level, like firing handler events could be handled better?
Update:
thanks to #ADM , I got this working. I tweaked it as per my requirements, and had to forgo of circular scroll support in the reverse direction, but the given solution was enough to answer my query. thanks!
Handler is not an issue here its the Runnable. you are using and posting same Runnable each time thats why its getting piled up . You can not remove the previous call because you do not have a Tag or token to this delayed call . take a look at some of Handler's method like sendMessageDelayed these might help .
After giving it some thought i think you can move the Auto scroll part to SnapHelper. Not a full prove solution but i think it will work. You might have to put few checks in SnapHelper . Give it a try and let me know . i haven't tested it.
class AutoPagedSnapHelper(private var autoScrollInterval: Long) : PagerSnapHelper() {
private var recyclerView: RecyclerView? = null
private var currentPage = 0
private var isHold = false
private val autoScrollRunnable = Runnable {
recyclerView?.let {
if (recyclerView?.scrollState != RecyclerView.SCROLL_STATE_DRAGGING && !isHold) {
if (it.adapter != null) {
val lastPageIndex = (recyclerView?.adapter!!.itemCount - 1)
var nextIndex: Int
nextIndex = currentPage + 1
if (currentPage == lastPageIndex) {
nextIndex = 0
}
it.post {
val linearSmoothScroller = object : LinearSmoothScroller(recyclerView?.context) {
override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics): Float {
return MILLISECONDS_PER_INCH / displayMetrics.densityDpi
}
}
linearSmoothScroller.targetPosition = nextIndex
(recyclerView?.layoutManager as LinearLayoutManager).startSmoothScroll(linearSmoothScroller)
}
}
} else {
postNextPage()
}
}
}
override fun attachToRecyclerView(recyclerView: RecyclerView?) {
super.attachToRecyclerView(recyclerView)
if (this.recyclerView === recyclerView) {
return
}
if (autoScrollInterval != 0L) {
this.recyclerView = recyclerView
this.recyclerView?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == RecyclerView.SCROLL_STATE_IDLE || newState == RecyclerView.SCROLL_STATE_SETTLING) {
val itemPosition = (recyclerView.layoutManager as LinearLayoutManager).findFirstCompletelyVisibleItemPosition()
if (itemPosition != -1) {
currentPage = itemPosition
postNextPage()
}
}
}
})
postNextPage()
recyclerView?.addOnItemTouchListener(object : RecyclerView.OnItemTouchListener {
override fun onInterceptTouchEvent(rv: RecyclerView, event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
isHold = true
}
MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> {
isHold = false
}
}
return false
}
override fun onTouchEvent(rv: RecyclerView, event: MotionEvent) {}
override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {}
})
}
}
fun postNextPage() {
recyclerView?.handler?.removeCallbacks(autoScrollRunnable)
recyclerView?.postDelayed(autoScrollRunnable, autoScrollInterval)
}
companion object {
private const val MILLISECONDS_PER_INCH = 75f //default is 25f (bigger = slower)
}
}
This should take care of auto change page. You do not have to use scrollListener in Adapter. Give it a try.