I have Implemented TabLayout. It contains three tabs. The first tab contains Listing of data. when User clicks on list item I want to show another Fragment. I have tried but not got success. Please help for find solution
ViewPager Adapter
inner class ViewPagerAdapter(manager: FragmentManager) : FragmentPagerAdapter(manager) {
val mFragmentList: ArrayList<Fragment> = ArrayList()
private val mFragmentTitleList: ArrayList<String> = ArrayList()
override fun getItem(position: Int): Fragment {
return mFragmentList.get(position)
}
override fun getCount(): Int {
return mFragmentList.size
}
fun addFrag(fragment: Fragment, title: String) {
mFragmentList.add(fragment)
mFragmentTitleList.add(title)
}
override fun getPageTitle(position: Int): CharSequence? {
return mFragmentTitleList.get(position)
}
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val createdFragment = super.instantiateItem(container, position) as Fragment
mFragmentList[position] = createdFragment
return createdFragment
}
}
Fragment Adapter
class MyAdapter(private val mActivity: FragmentActivity?, private var mMyModelList: ArrayList<MyModel>) : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {
private var mItemBinding: ItemBinding? = null
inner class MyViewHolder(val mBinding: ItemBinding) : RecyclerView.ViewHolder(mBinding.getRoot())
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val inflater = LayoutInflater.from(mActivity)
mItemBinding = DataBindingUtil.inflate(inflater, R.layout.item, null, false)
return MyViewHolder(mItemBinding!!)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val mMyModel: MyModel = mMyModelList[position]
holder.mBinding.txtTitle.setTextColor(mActivity!!.resources.getColor(R.color.light_green))
holder.mBinding.txtTitle.setText(mMyModel.lbl)
holder.mBinding.txtDateDay.setText(mMyModel.Day + " - " + mMyModel.date)
holder.mBinding.txtCircle.setBackgroundResource(R.drawable.green_circle)
holder.mBinding.constraintLayout.setOnClickListener {
var mSecondFragment: SecondFragment = SecondFragment()
//Below code is not working
mSecondFragment.onClickItem(mMyModel.id)
/*Here I want to show another Fragment*/
}
}
override fun getItemCount(): Int {
return mMyModelList.size
}
}
Fragment should open from Viewpager's Fragment.
there is no need for fragment adapter. just setup the view pager in activity.
then initiate your fragment in activity as well. There is no need on click listener in fragment adapter. Hope it helps you !
HistoryOnGoingFragment historyOnGoingFragment = new HistoryOnGoingFragment();
HistoryPastFragment historyPastFragment = new HistoryPastFragment();
private void setUpViewPager(int tabPosition) {
TabLayoutViewPagerAdapter adapter = new TabLayoutViewPagerAdapter(getFragmentManager());
adapter.addFragment(historyOnGoingFragment,"UPCOMING");
adapter.addFragment(historyPastFragment,"COMPLETED");
historyViewPager.setAdapter(adapter);
historyViewPager.setCurrentItem(tabPosition);
historyTabLayout.setupWithViewPager(historyViewPager);
}
After many tried I found custom Navigation Bar and applied changes which I needed for call nested fragment. It's working perfectly for me. Hope It'll help others too.
https://github.com/adib2149/BottomNavBar
Related
I'm trying to get bidirectional infinite scroll in my viewpager 2, but I can't seem to see how to reach the solution, so any help will be appreciated.
Adapter:
class LandingViewPagerAdapter(fa: FragmentActivity, val slides: List<DtoSlide>) :
FragmentStateAdapter(fa) {
override fun getItemCount(): Int {
return slides.size
}
override fun createFragment(position: Int): Fragment {
return LandingSlideFragment().newInstance(slides[position])
}
}
Activity:
private fun setupViewPager(slides: List<data.network.entities.DtoSlide>) {
pagerAdapter = LandingViewPagerAdapter(this, slides)
viewpager_landing.adapter = pagerAdapter
TabLayoutMediator(viewpager_dots, viewpager_landing) { tab, position -> }.attach()
}
I am getting exception:
E/MessageQueue-JNI: java.lang.IllegalArgumentException: No view found for id 0x7f0a0554 (ua.com.company.app:id/vpBanners) for fragment BannerItemFragment{30698be} (4c80b228-4303-4c80-b99d-a55b8359b8c2) id=0x7f0a0554}
My hierarchy looks like so:
My adapter for vpHome:
class HomeViewPagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
private val mFragmentList: MutableList<Fragment> = ArrayList()
private val mFragmentTitleList: MutableList<String> = ArrayList()
override fun getItem(position: Int): Fragment {
return mFragmentList[position]
}
override fun getCount(): Int {
return mFragmentList.size
}
override fun getPageTitle(position: Int): CharSequence? {
return mFragmentTitleList[position]
}
fun addFragment(fragment: Fragment, title: String) {
mFragmentList.add(fragment)
mFragmentTitleList.add(title)
}
}
And I apply it in this way:
private fun setupViewPager(viewPager: DisableSwipeViewPager) {
vpAdapter = HomeViewPagerAdapter(childFragmentManager).apply {
addFragment(ForYouFragment(), "for you")
addFragment(AnotherFragment1(), "a1")
addFragment(AnotherFragment2(), "a2")
}
viewPager.adapter = vpAdapter
}
Next, my SnapBannersCarouselViewHolder to handle items with ViewPager inside:
class SnapBannersCarouselViewHolder(
private val mBinding: HomeFragmentItemSnapBannersCarouselBinding
) : RecyclerView.ViewHolder(mBinding.root) {
companion object {
// ...
fun newInstance(
inflater: LayoutInflater,
parent: ViewGroup,
onMoreInteractionListener: ((infoBlockId: String) -> Unit)?
): SnapBannersCarouselViewHolder {
val binding =
HomeFragmentItemSnapBannersCarouselBinding.inflate(inflater, parent, false)
return SnapBannersCarouselViewHolder(
binding,
onMoreInteractionListener
)
}
}
fun bind(item: SnapBannersItem, fragmentManager: FragmentManager) {
// ...
val adapter = BannerPagesAdapter(fragmentManager, item.banners)
with(mBinding.vpBanners) {
// ...
this.adapter = adapter
offscreenPageLimit = 3
}
}
}
My RecyclerView adapter ForYouContentAdapter:
class ForYouContentAdapter(
var data: List<HomeBaseItem> = emptyList(),
var fragmentManagerRetriever: () -> FragmentManager
) : BaseRecyclerViewAdapter<HomeBaseItem>(data) {
enum class ViewType(val value: Int) {
// ...
SNAP_BANNERS(6)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
// ...
ViewType.SNAP_BANNERS.value -> SnapBannersCarouselViewHolder.newInstance(
mInflater!!,
parent,
onBannersMoreInteractionListener // todo possibly change it
)
else -> throw RuntimeException("Can not create view holder for undefined view type")
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (getItemViewType(position)) {
// ...
ViewType.SNAP_BANNERS.value -> {
val vHolder = holder as SnapBannersCarouselViewHolder
vHolder.bind(
getItem(position) as SnapBannersItem,
fragmentManagerRetriever.invoke()
)
}
}
}
}
And my fragmentManagerRetriever implementation in ForYouFragment looks like so:
private val fragmentManagerRetriever: () -> FragmentManager = {
childFragmentManager
}
My BannerItemFragment code:
class BannerItemFragment : Fragment() {
private lateinit var mBinding: HomeFragmentSnapBannerItemBinding
companion object {
// ...
fun newInstance(
item: RectWebViewItem
): BannerItemFragment {
return BannerItemFragment()
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
mBinding = HomeFragmentSnapBannerItemBinding.inflate(inflater, container, false)
return mBinding.root
}
// ...
}
In every place where I need to create fragments inside other fragments I am using childFragmentManager.
And when I open ForYouFragment first time, it works normally. My item with ViewPager works normally. But when I replace fragment in Activity's container (adding to back stack) being on ForYouFragment and then return back (back on HomeFragment because ForYouFragment inside HomeFragment), I am getting error.
To replace fragments I am using this method inside ForYouFragment:
private fun showAnotherFragment() {
ActivityUtils.replaceFragmentToActivity(
requireActivity(),
SomeFragment.newInstance(),
true
)
}
And ActivityUtils code:
object ActivityUtils {
fun replaceFragmentToActivity(
activity: FragmentActivity,
fragment: Fragment,
addToBackStack: Boolean
) {
replaceFragmentToActivity(activity, fragment, addToBackStack, containerId = R.id.fragmentContainer)
}
fun replaceFragmentToActivity(
activity: FragmentActivity,
fragment: Fragment,
addToBackStack: Boolean,
containerId: Int
) {
val fragmentManager = activity.supportFragmentManager
val transaction = fragmentManager.beginTransaction()
transaction.replace(containerId, fragment)
if (addToBackStack) {
transaction.addToBackStack(null)
}
transaction.commitAllowingStateLoss()
}
}
Please, help me understand why I am getting this exception?
UPD
Adapter for ViewPager inside RecyclerView:
class BannerPagesAdapter(
fragmentManager: FragmentManager,
var data: List<RectWebViewItem>
) : FragmentStatePagerAdapter(
fragmentManager,
BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
) {
private var fragments: MutableList<BannerItemFragment> = mutableListOf()
init {
// initial empty fragments
for (i in data.indices) {
fragments.add(BannerItemFragment())
}
}
override fun getItem(position: Int): Fragment {
return BannerItemFragment.newInstance(data[position])
}
override fun getCount(): Int {
return fragments.size
}
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val f = super.instantiateItem(container, position)
fragments[position] = f as BannerItemFragment
return f
}
}
Solved
The problem was that fragments want to be attached to the ViewPager before the ViewPager is attached to its parent. This question outlined here.
So, to solve this problem, I created custom ViewPager:
/**
* Use this ViewPager when you need to place ViewPager inside RecyclerView.
* [LazyViewPager] allows you to set [PagerAdapter] in lazy way. This prevents IllegalStateException
* in case when the fragments want to be attached to the viewpager before the viewpager is
* attached to its parent
*/
class LazyViewPager
#JvmOverloads
constructor(
context: Context,
attrs: AttributeSet? = null
) : ViewPager(context, attrs) {
private var mPagerAdapter: PagerAdapter? = null
override fun onAttachedToWindow() {
super.onAttachedToWindow()
if (mPagerAdapter != null) {
super.setAdapter(mPagerAdapter)
}
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
super.setAdapter(null)
}
#Deprecated("Do not use this method to set adapter. Use setAdapterLazy() instead.")
override fun setAdapter(adapter: PagerAdapter?) {}
fun setAdapterLazy(adapter: PagerAdapter?) {
mPagerAdapter = adapter
}
}
And then, instead of using setAdapter() I use setAdapterLazy().
Also, it is important to reset adapter to null in onDetachedFromWindow().
Ok, I got the following fragment with callback there witch I'm setting to ViewPager2 adapter:
class PremiumFragment : BaseFragment(), OnPageChanged {
override fun getLogTag(): String {
return TAG
}
private lateinit var pagerCounter: ImageView
private lateinit var viewPager: ViewPager2
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewPager = view.findViewById(R.id.view_pager) as ViewPager2
viewPager.adapter = ViewPagerAdapter(this)
pagerCounter = view.findViewById(R.id.counter)
}
companion object {
const val TAG = "PremiumFragment"
fun getInstance(): BaseFragment {
Log.d(TAG, "getInstance()")
val args = Bundle()
args.putInt(ARG_LAYOUT_ID, R.layout.premium_f)
val fragment =
PremiumFragment()
fragment.arguments = args
return fragment
}
}
override fun onPageChanged() {
when (viewPager.currentItem) {
0 -> pagerCounter.setImageResource(R.drawable.circle_1)
1 -> pagerCounter.setImageResource(R.drawable.circle_2)
2 -> pagerCounter.setImageResource(R.drawable.circle_3)
}
}
}
My adapter:
class ViewPagerAdapter constructor(val callback: OnPageChanged) : RecyclerView.Adapter<PagerVH>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PagerVH =
PagerVH(LayoutInflater.from(parent.context).inflate(R.layout.viewpager_f, parent, false))
override fun getItemCount(): Int = Images.values().size
override fun onBindViewHolder(holder: PagerVH, position: Int) = holder.itemView.run {
pager_container.findViewById<ImageView>(R.id.imageView)
.setImageResource(Images.values()[position].image)
}
override fun getItemViewType(position: Int): Int {
callback.onPageChanged()
return super.getItemViewType(position)
}
companion object {
enum class Images(private val value: Int, val image: Int) {
First(0, R.drawable.wow_girl),
Second(1, R.drawable.insights_girl_2),
Third(2, R.drawable.remind_girl);
}
}
}
It should set new drawable every time we swipe ViewPager, but it changes image in fragment only once an then it is not working, have completely no idea where in adapter I should call my callback for a proper behavior, please tell me where should I call this callback? Appreciate any advices!
So I found out that you should call callback from onViewDetachedFromWindow for it calls every time you swipe fragment, and it is quite logical
override fun onViewDetachedFromWindow(holder: PagerVH) {
callback.onPageChanged()
super.onViewDetachedFromWindow(holder)
}
I wanna ask if there any best way to implement view pager with only one fragment in Kotlin?
Like if you have a ViewPager Adapter like below
class ViewPagerAdapter(fragmentManager: FragmentManager) : FragmentPagerAdapter(fragmentManager) {
val fragments:MutableList<Fragment> = ArrayList()
val titles:MutableList<String> = ArrayList()
fun addFragment(fragment: Fragment, title:String){
fragments.add(fragment)
titles.add(title)
}
override fun getItem(p0: Int): Fragment = fragments[p0]
override fun getCount(): Int = fragments.size
override fun getPageTitle(position: Int): CharSequence? {
return titles[position]
}
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
super.destroyItem(container, position, `object`)
}
}
Now if you have fragment, SampleFragment (say)
Just call the method with whichever fragment you want,
adapter.addFragment(SampleFragment(),"Sample Fragment")
..
..
adapter.addFragment(SampleFragment(),"Sample Fragment 2")
Create model class FragmentConfig where you going to keep parameters for fragment.
Add array of configs fragmentConfigs to your FragmentPagerAdapter
and in adapter:
override fun getItem(position: Int): Fragment {
val fragmentConfig = fragmentConfigs[position]
return UniversalListFragment.newInstance(fragmentConfig)
}
Maybe this is a stupid question, but this is ruining my day...
I have a recyclerview in a fragment
override fun setUpRecyclerView(pics: List<Pictures>) {
recyclerView.setHasFixedSize(true)
layoutManager = LinearLayoutManager(context)
recyclerView.setLayoutManager(layoutManager)
mAdapter = NewsAdapter(pics, childFragmentManager)
recyclerView.setAdapter(mAdapter)
}
the NewsAdapter is:
class NewsAdapter
(private val mDataset: List<Pictures>, private val fragmentManager: FragmentManager)
: RecyclerView.Adapter<NewsAdapter.ViewHolder>() {
class ViewHolder(v: View) : RecyclerView.ViewHolder(v) {
var authorTextView: TextView
var viewPager: ViewPager
var indicator: CircleIndicator
init {
authorTextView = v.findViewById<View>(R.id.tv_author) as TextView
viewPager = v.findViewById<View>(R.id.viewPager) as ViewPager
indicator = v.findViewById<View>(R.id.indicator) as CircleIndicator
}
}
override fun onCreateViewHolder(parent: ViewGroup,
viewType: Int): NewsAdapter.ViewHolder {
val v = LayoutInflater.from(parent.context)
.inflate(R.layout.item_preview, parent, false)
return ViewHolder(v)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val pictureList = mDataset.get(position)
holder.titleTextView.text = pictureList.title
holder.authorTextView.text = pictureList.author
showContent(pictureList.content, holder.viewPager, holder.indicator)
}
fun showContent(contentModel: List<ContentModel>, viewPager: ViewPager, indicator: CircleIndicator) {
if (contentModel.size <= 1) {
indicator.hide()
}
viewPager.adapter = GalleryContentAdapter(fragmentManager, contentModel)
indicator.setViewPager(viewPager)
}
override fun getItemCount(): Int = mDataset.size
}
the GalleryContentAdapter is:
class GalleryContentAdapter(fm: FragmentManager, val contentModels: List<ContentModel>) : FragmentPagerAdapter(fm) {
val galleryContentFactory: GalleryContentFactory = GalleryContentFactory()
override fun getItem(position: Int): Fragment = galleryContentFactory.getFragment(contentModels[position])
override fun getCount(): Int = contentModels.size
}
From now I am only handling a kind of content and the factory is so easy, but this is the code:
class GalleryContentFactory {
fun getFragment(contentModel: ContentModel): Fragment {
when (contentModel.type) {
"PICTURE" -> return PictureFragment.newInstance(contentModel.value)
}
return PictureFragment.newInstance(contentModel.value)
}
}
and the last is the PictureFragment:
class PictureFragment : NewsContentFragment() {
companion object {
val KEY = "PictureFragment.URL"
fun newInstance(url: String): NewsContentFragment {
val fragment = PictureFragment()
val bundle = Bundle()
bundle.putString(KEY, url)
fragment.arguments = bundle
return fragment
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_news_gif, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
arguments?.getString(KEY).let {
view.image.loadUrl(it!!)
}
}
}
When I debug the code, I check that the line view.image.loadUrl(it!!) is being executed, but nothing is rendered.
The loadUrl method is a extended function of imageviews that loads an image using Glide like this
fun ImageView.loadUrl(url: String) {
GlideApp.with(context).load(url).into(this)
}
and is being called correctly.
I also tried with PagerStateAdapter and take into account that I am passing the ChildFragmentManager to the adapter of the ViewPager.
If you can help me, you will save my day. Thanks in advance
This doesn't work. The way to go is a recycler into a recycler. If you want simulate as a ViewPager, you may use https://github.com/rubensousa/RecyclerViewSnap with a MATCH_PARENT in the
child views.