I know that there are plenty of similiar posts about similar issues as mine, I even followed this and this and other posts as well (I won't list every single post here though) and tried different things out, but I still can't make my recyclerview show anything on my Fragment.
I write an app mostly for learning purposes - something similar to this, just to get sense about how things work together. If you like to see the project, (at least what I've done so far), you can do this here.
I just want to show items on recyclerview. Currently, nothing from the recyclerview is shown in the Fragment a white background only :(.
I don't even get any errors sort of "No adapter attached; skipping layout" or "No LayoutManager attached; skipping layout". And I am sure this could be a really small issue, so could you please explain me what am I missing or doing wrong.
Thank you so much for your efforts.
Here is what I do:
The Adapter code:
class ActivitiesAdapter internal constructor(
context: Context
) : RecyclerView.Adapter<ActivitiesAdapter.ActivitiesViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
private var activities = emptyList<DoItAgainEntity>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ActivitiesViewHolder {
val itemView = inflater.inflate(R.layout.recyclerview_item, parent, false)
return ActivitiesViewHolder(itemView)
}
override fun onBindViewHolder(holder: ActivitiesViewHolder, position: Int) {
val current = activities[position]
holder.activityItemView.text = current.engagement
}
internal fun setActivities(activities: List<DoItAgainEntity>) {
this.activities = activities
notifyDataSetChanged()
}
override fun getItemCount() = activities.size
inner class ActivitiesViewHolder(itemview: View) : RecyclerView.ViewHolder(itemview) {
val activityItemView: TextView = itemview.findViewById(R.id.textView)
}
}
The Fragment code:
class ShowDBEntriesFragment : Fragment() {
private lateinit var viewModel: ShowDBEntriesViewModel
private lateinit var layout: View
private lateinit var recyclerView: RecyclerView
private lateinit var adapter: ActivitiesAdapter
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
layout = inflater.inflate(R.layout.show_dbentries_fragment, container, false)
adapter = ActivitiesAdapter(context!!)
recyclerView = layout.findViewById(R.id.recyclerview)
recyclerView.addItemDecoration(
DividerItemDecoration(
context!!,
LinearLayoutManager.VERTICAL
)
)
recyclerView.layoutManager =
LinearLayoutManager(context!!, LinearLayoutManager.VERTICAL, false)
recyclerView.adapter = adapter
return layout
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(this).get(ShowDBEntriesViewModel::class.java)
fab.setOnClickListener {
val fragmentManager = (activity as MainActivity).supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()
val fragment = InsertNewEngagementFragment()
fragmentTransaction.replace(R.id.fragment_newEngagement, fragment)
fragmentTransaction.commit()
}
}
}
The xml code from recyclerview_item.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="#+id/textView"
style="#style/activity_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#android:color/holo_orange_light" />
</LinearLayout>
You are not calling setActivities().
Your code will look like this:
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
layout = inflater.inflate(R.layout.show_dbentries_fragment, container, false)
adapter = ActivitiesAdapter(context!!)
recyclerView = layout.findViewById(R.id.recyclerview)
recyclerView.addItemDecoration(
DividerItemDecoration(
context!!,
LinearLayoutManager.VERTICAL
)
)
recyclerView.layoutManager =
LinearLayoutManager(context!!, LinearLayoutManager.VERTICAL, false)
recyclerView.adapter = adapter
// Create activities variable
adapter.setActivities(activities)
return layout
}
In recyclerview_item.xml
Change
android:layout_height="match_parent"
to
android:layout_height="wrap_content"
The item layout is probably taking entire layout space.
This is the answer of this issue. I don't OWN this code. This code was written from a genious with huge heart (and also a friend of mine) who understands how things work and writes beautiful code. It's a great honor of mine to learn from you, bro - thank you so much for your ultimate kindness and god-like Android knowledge you have! Keep it up like this.
And here it is - the right way of doing things:
The Adapter code:
class ActivitiesAdapter : RecyclerView.Adapter<ActivitiesViewHolder>() {
private var activities = emptyList<DoItAgainEntity>()
internal fun setActivities(activities: List<DoItAgainEntity>) {
this.activities = activities
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ActivitiesViewHolder =
ActivitiesViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.recyclerview_item,
parent,
false
)
)
override fun onBindViewHolder(holder: ActivitiesViewHolder, position: Int) {
holder.bind(activities[position])
}
override fun getItemCount() = activities.size
}
class ActivitiesViewHolder(
override val containerView: View
) : RecyclerView.ViewHolder(containerView), LayoutContainer {
fun bind(vo: DoItAgainEntity) {
itemView.textView.text = vo.engagement
}
}
The Fragment code:
class ShowDBEntriesFragment : Fragment() {
private lateinit var viewModel: ShowDBEntriesViewModel
private lateinit var activitiesAdapter: ActivitiesAdapter
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View = inflater.inflate(R.layout.show_dbentries_fragment, container, false)
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(this).get(ShowDBEntriesViewModel::class.java)
activitiesAdapter = ActivitiesAdapter()
recyclerview.apply {
addItemDecoration(
DividerItemDecoration(
context!!,
LinearLayoutManager.VERTICAL
)
)
layoutManager = LinearLayoutManager(context!!, LinearLayoutManager.VERTICAL, false)
adapter = activitiesAdapter
}
// for testing purposes. could be deleted easily
activitiesAdapter.setActivities(
listOf(
DoItAgainEntity(1, "play guitar", 100),
DoItAgainEntity(2, "make breakfast", 2),
DoItAgainEntity(2, "go out with friends", 20)
)
)
fab.setOnClickListener {
val fragmentManager = (activity as MainActivity).supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()
val fragment = InsertNewEngagementFragment()
fragmentTransaction.replace(R.id.fragment_newEngagement, fragment)
fragmentTransaction.commit()
}
}
}
The xml code from recyclerview_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="#+id/textView"
style="#style/activity_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#android:color/holo_orange_light" />
</LinearLayout>
Related
I'm trying to implement recycler view inside some of my fragments, and i tried to do so on the first one. No issues are displayed in the IDE on compilation time, but on runtime I get this message on the console: E/RecyclerView: No layout manager attached; skipping layout. Also, data is not showing in my application.
Here is my Fragment:
var sandwiches = listOf<Sandwich>()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding = DataBindingUtil.inflate<FragmentSandwichesBinding>(
inflater,
R.layout.fragment_sandwiches, container, false
)
val application = requireNotNull(this.activity).application
val dataSource = NbaCafeDB.getInstance(application).sandwichDao
val viewModelFactory = SandwichViewModelFactory(dataSource, application)
val sandwichViewModel =
ViewModelProvider(this, viewModelFactory).get(SandwichViewModel::class.java)
sandwiches = sandwichViewModel.getAll()
val adapter = SandwichAdapter(sandwiches)
binding.sandwichRecycler.adapter = adapter
binding.setLifecycleOwner(this)
return binding.root
}
}
And here is my Adapter:
class SandwichAdapter (val sandwich: List<Sandwich>) : RecyclerView.Adapter<SandwichAdapter.SandwichHolder>() {
override fun getItemCount() = sandwich.size
class SandwichHolder(val view: View) : RecyclerView.ViewHolder(view) {
fun bind(sandwich: Sandwich) {
view.findViewById<TextView>(R.id.sandwichNom).text = sandwich.nomSandwich
view.findViewById<TextView>(R.id.sandwichDesc).text = sandwich.descSandwich
view.findViewById<TextView>(R.id.sandwichPreu).text = (sandwich.preuSandwich.toString()+" €")
}
companion object {
fun from(parent: ViewGroup): SandwichHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val view = layoutInflater
.inflate(R.layout.sandwich_cell_layout, parent, false)
return SandwichHolder(view)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SandwichHolder {
return SandwichHolder.from(parent)
}
override fun onBindViewHolder(holder: SandwichHolder, position: Int) {
holder.bind(sandwich[position])
}
}
Also, I'm retrieving data from a room database and using viewModel and viewModelFactory, in case that changes anything.
Thanks!
You don't need to call RecyclerView.setLayoutManager(layoutManager).
Just add app:layoutManager to RecyclerView in your xml.
The default orientation of LinearLayoutManager is VERTICAL, but if you want to change orientation as HORIZONTAL, you can just add android:orientation="horizontal".
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical|horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
I'm having trouble with Android RecyclerView in Kotlin inside a fragment who is in Home activity.
Here's my code:
myFragmentLayout:
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rvChapterList"
android:layout_width="match_parent"
android:layout_height="match_parent" />
myFragmentCode:
val chaptersList: ArrayList<String> = ArrayList()
private lateinit var layoutManager: RecyclerView.LayoutManager
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.courses_layout, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
chaptersList.add("Android")
chaptersList.add("Kotlin")
chaptersList.add("RecyclerView")
layoutManager = LinearLayoutManager(context)
rvChapterList.layoutManager = layoutManager
rvChapterList.adapter = ChapterAdapter(Home(), chaptersList)
}
and myChapterAdapter:
class ChapterAdapter(private val context: Home, private val chaptersList: ArrayList<String>) : RecyclerView.Adapter<ChapterAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(context).inflate(R.layout.list_item, parent, false))
}
override fun getItemCount(): Int {
return chaptersList.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.chapterName?.text = chaptersList.get(position)
holder.itemView.setOnClickListener {
Toast.makeText(context, chaptersList.get(position), Toast.LENGTH_LONG).show()
}
}
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val chapterName = view.tvChapterName
}
}
It got this code from this tutorial, it's working inside an activity but not iside a fragment.
rvChapterList.adapter = ChapterAdapter(Home(), chaptersList)
Never create an Activity or other type of Context yourself. You always obtain them from the framework.
Replace that with:
rvChapterList.adapter = ChapterAdapter(requireActivity(), chaptersList)
and change the context property in ChapterAdapter to be a Context or Activity, not Home.
i am trying to code my app in kotlin but i am getting null cannot be casted to non-null type and it force my app to close. i tried to reference to other stackoverflow where they shift the init but i do not have any init portion in my current code. any help will be appreciated thank you.
reminder_fragment.kt
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val v = inflater.inflate(R.layout.reminder_fragment, container, false)
//getting recyclerview from xml
val recyclerView = v.findViewById(R.id.reminderrecycler) as RecyclerView
//adding a layoutmanager
recyclerView.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL ,false)
//crating an arraylist to store users using the data class user
val reminderlist = ArrayList<reminders>()
//adding some dummy data to the list
reminderlist.add(reminders("Belal Khan"))
reminderlist.add(reminders("Ramiz Khan"))
reminderlist.add(reminders("Faiz Khan"))
reminderlist.add(reminders("Yashar Khan"))
//creating our adapter
val adapter = CustomAdapter(reminderlist)
//now adding the adapter to recyclerview
recyclerView.adapter = adapter
return v;
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
val manager = supportFragmentManager
private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.navigation_reminders -> {
createReminderFragment()
return#OnNavigationItemSelectedListener true
}
}
false
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
bottom_navigation.setItemIconTintList(null);
createReminderFragment()
bottom_navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener)
}
/*private fun replaceFragment(fragment: Fragment) {
val fragmentTransaction = supportFragmentManager.beginTransaction()
fragmentTransaction.replace(R.id.fragmentplaceholder, fragment)
fragmentTransaction.commit();
}*/
fun createReminderFragment() {
val transaction = manager.beginTransaction()
val fragment = reminder_fragment()
transaction.replace(R.id.fragmentplaceholder, fragment)
transaction.addToBackStack(null)
transaction.commit()
}
}
reminder_fragment.xml
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".reminder_fragment"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/reminderrecycler"/>
</LinearLayout>
CustomAdapter.kt
class CustomAdapter(val reminderlist: ArrayList<reminders>) :
RecyclerView.Adapter<CustomAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.reminder_fragment, parent, false)
return ViewHolder(v);
}
override fun getItemCount(): Int {
return reminderlist.size }
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bindItems(reminderlist[position])
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bindItems(reminders: reminders) {
val medicineText = itemView.findViewById(R.id.medicineText) as TextView
medicineText.text = reminders.medicineN
}
}
}
You does not implement your RecyclerView well.
You must have an seperated layout for RecyclerView items .
You set your RecyclerView items layout , your layout which your RecylerView decalared in it now .
Therefor android can not find your TextView with Id medicineText
I recommend you to do these:
1) Create a seperate layout for your RecyclerView items and inflate it to your holder as below :
val v = LayoutInflater.from(parent.context).inflate(R.layout.recylerView_item, parent, false)
2) put in an TextView in RecylerView item layout with id of medicineText
3)Use ? symbol to handle possible NullPonterException when you declaring your TextView
val medicineText = itemView.findViewById(R.id.medicineText) as? TextView
WHY NOT ANY PEOPLE HELP TO ME???
In my application I used BottomNavBar and NavigationGraph for show some fragments!
In one of my fragments I have many views (fragment layout has 1069 lines xml codes) and when select this fragment from BottomNavBar, after some second show me this fragment.
In the other words show me this fragment with delay!
Fragment codes:
class HomeDashboardFragment : Fragment(), HomeDashboardContracts.View {
#NonNull
private var pageTitle: TextView? = null
#NonNull
private var menuIcon: TextView? = null
private lateinit var token: String
private lateinit var presenter: HomeDashboardPresenterImpl
private var giftExpireSplit: List<String> = emptyList()
private var giftExpireDate: List<String> = emptyList()
private var timeUtils: TimeUtils? = null
#NonNull
private var disposable: Disposable? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_home_dashboard, container, false)
}
#SuppressLint("WrongConstant")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//Initialize
presenter = HomeDashboardPresenterImpl(requireContext(), this)
//Initialize views from activity
activity?.let { itActivity ->
pageTitle = itActivity.findViewById(R.id.toolbarMain_title)
menuIcon = itActivity.findViewById(R.id.toolbarMain_menuIcon)
//Set title
pageTitle?.let { itTitle ->
itTitle.text = getString(R.string.menuHomeDashboard)
}
//Open menu
menuIcon?.let { itMenu ->
itMenu.setOnClickListener {
itActivity.findViewById<AwesomeDrawerLayout>(R.id.homePage_drawerLayout).openDrawer(Gravity.END)
}
}
//Get token
token = GoodPrefs.getInstance().getString(PrefsKey.USER_JWT_TOKEN.name, "")
//User registered
if (GoodPrefs.getInstance().isKeyExists(PrefsKey.USER_JWT_TOKEN.name)) {
menuIcon?.visibility = View.VISIBLE
}
//Set layout
presenter.checkRegisterUser(token)
//Load profile data
if (!isEmptyString(token)) {
presenter.getProfile(token, USER_NOTIF_ID)
}
}
}
MainActivity codes for set fragments into BottomNavBar with NavigationGraph :
private fun setupNavigation() {
val navController = Navigation.findNavController(this, R.id.homePage_fragmentNavHost)
NavigationUI.setupWithNavController(homePage_bottomNavBar, navController)
}
override fun onSupportNavigateUp() = Navigation.findNavController(this, R.id.homePage_fragmentNavHost).navigateUp()
How can i fix this issue?
You can add a layout dummy consisting of a single viewGroup. Then in the onViewCreated method inflate the real layout. For example:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?) =
inflater.inflate(R.layout.layout_dummy, container, false);
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Handler().post {
val layout = flContainer
val child = layoutInflater.inflate(R.layout.fragment_real, null)
layout.removeAllViews()
layout.addView(child)
setupToolbar()
setupWebView()
}
}
layout_dummy.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
android:id="#+id/flContainer"
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
You issue is almost assuredly caused by your massive XML file. Loading all of those views takes time. You have A LOT of nesting which slows things down.
You have to optimize your layout. Remove things you don't need. Replace as many nested LinearLayouts as you can with ConstraintLayout. Maybe use a RecyclerView if this is intended to be a long, scrolling view.
Hope that helps.
I have a pretty straightforward case. I want to implement shared element transition between an item in recyclerView and fragment. I'm using android navigation component in my app.
There is an article about shared transition on developer.android and topic on stackoverflow but this solution works only for view that located in fragment layout that starts transition and doesn't work for items from RecyclerView. Also there is a lib on github but i don't want to rely on 3rd party libs and do it by myself.
Is there some solution for this? Maybe it should work and this is just a bug? But I haven't found any information about it.
code sample:
transition start
class TransitionStartFragment: Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_transition_start, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val testData = listOf("one", "two", "three")
val adapter = TestAdapter(testData, View.OnClickListener { transitionWithTextViewInRecyclerViewItem(it) })
val recyclerView = view.findViewById<RecyclerView>(R.id.test_list)
recyclerView.adapter = adapter
val button = view.findViewById<Button>(R.id.open_transition_end_fragment)
button.setOnClickListener { transitionWithTextViewInFragment() }
}
private fun transitionWithTextViewInFragment(){
val destination = TransitionStartFragmentDirections.openTransitionEndFragment()
val extras = FragmentNavigatorExtras(transition_start_text to "transitionTextEnd")
findNavController().navigate(destination, extras)
}
private fun transitionWithTextViewInRecyclerViewItem(view: View){
val destination = TransitionStartFragmentDirections.openTransitionEndFragment()
val extras = FragmentNavigatorExtras(view to "transitionTextEnd")
findNavController().navigate(destination, extras)
}
}
layout
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="#+id/transition_start_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="transition"
android:transitionName="transitionTextStart"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="#+id/open_transition_end_fragment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="#id/transition_start_text"
android:text="open transition end fragment" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/test_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="#id/open_transition_end_fragment"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
adapter for recyclerView
class TestAdapter(
private val items: List<String>,
private val onItemClickListener: View.OnClickListener
) : RecyclerView.Adapter<TestAdapter.ViewHodler>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHodler {
return ViewHodler(LayoutInflater.from(parent.context).inflate(R.layout.item_test, parent, false))
}
override fun getItemCount(): Int {
return items.size
}
override fun onBindViewHolder(holder: ViewHodler, position: Int) {
val item = items[position]
holder.transitionText.text = item
holder.itemView.setOnClickListener { onItemClickListener.onClick(holder.transitionText) }
}
class ViewHodler(itemView: View) : RecyclerView.ViewHolder(itemView) {
val transitionText = itemView.findViewById<TextView>(R.id.item_test_text)
}
}
in onItemClick I pass the textView form item in recyclerView for transition
transition end
class TransitionEndFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
setUpTransition()
return inflater.inflate(R.layout.fragment_transition_end, container, false)
}
private fun setUpTransition(){
sharedElementEnterTransition = TransitionInflater.from(context).inflateTransition(android.R.transition.move)
}
}
layout
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="#+id/transition_end_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="transition"
android:transitionName="transitionTextEnd"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
fun transitionWithTextViewInFragment() - has transition.
fun transitionWithTextViewInRecyclerViewItem(view: View) - no transition.
To solve the return transition problem you need to add this lines on the Source Fragment (the fragment with the recycler view) where you initialize your recycler view
// your recyclerView
recyclerView.apply {
...
adapter = myAdapter
postponeEnterTransition()
viewTreeObserver
.addOnPreDrawListener {
startPostponedEnterTransition()
true
}
}
Here is my example with RecyclerView that have fragment shared transition.
In my adapter i am setting different transition name for each item based on position(In my example it is ImageView).
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = items[position]
holder.itemView.txtView.text=item
ViewCompat.setTransitionName(holder.itemView.imgViewIcon, "Test_$position")
holder.setClickListener(object : ViewHolder.ClickListener {
override fun onClick(v: View, position: Int) {
when (v.id) {
R.id.linearLayout -> listener.onClick(item, holder.itemView.imgViewIcon, position)
}
}
})
}
And when clicking on item, my interface that implemented in source fragment:
override fun onClick(text: String, img: ImageView, position: Int) {
val action = MainFragmentDirections.actionMainFragmentToSecondFragment(text, position)
val extras = FragmentNavigator.Extras.Builder()
.addSharedElement(img, ViewCompat.getTransitionName(img)!!)
.build()
NavHostFragment.findNavController(this#MainFragment).navigate(action, extras)
}
And in my destination fragment:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
info("onCreate")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
sharedElementEnterTransition = TransitionInflater.from(context).inflateTransition(android.R.transition.move)
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
info("onCreateView")
return inflater.inflate(R.layout.fragment_second, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
info("onViewCreated")
val name=SecondFragmentArgs.fromBundle(arguments).name
val position=SecondFragmentArgs.fromBundle(arguments).position
txtViewName.text=name
ViewCompat.setTransitionName(imgViewSecond, "Test_$position")
}
Faced the same issue as many on SO with the return transition but for me the root cause of the problem was that Navigation currently only uses replace for fragment transactions and it caused my recycler in the start fragment to reload every time you hit back which was a problem by itself.
So by solving the second (root) problem the return transition started to work without delayed animations. For those of you who are looking to keep the initial state when hitting back here is what I did :
just adding a simple check in onCreateView as so
private lateinit var binding: FragmentSearchResultsBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return if (::binding.isInitialized) {
binding.root
} else {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_search_results, container, false)
with(binding) {
//doing some stuff here
root
}
}
So triple win here: recycler is not redrawn, no refetching from server and also return transitions are working as expected.
I have managed return transitions to work.
Actually this is not a bug in Android and not a problem with setReorderingAllowed = true. What happens here is the original fragment (to which we return) trying to start transition before its views/data are settled up.
To fix this we have to use postponeEnterTransition() and startPostponedEnterTransition().
For example:
Original fragment:
class FragmentOne : Fragment(R.layout.f1) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
postponeEnterTransition()
val items = listOf("one", "two", "three", "four", "five")
.zip(listOf(Color.RED, Color.GRAY, Color.GREEN, Color.BLUE, Color.YELLOW))
.map { Item(it.first, it.second) }
val rv = view.findViewById<RecyclerView>(R.id.rvItems)
rv.adapter = ItemsAdapter(items) { item, view -> navigateOn(item, view) }
view.doOnPreDraw { startPostponedEnterTransition() }
}
private fun navigateOn(item: Item, view: View) {
val extras = FragmentNavigatorExtras(view to "yura")
findNavController().navigate(FragmentOneDirections.toTwo(item), extras)
}
}
Next fragment:
class FragmentTwo : Fragment(R.layout.f2) {
val item: Item by lazy { arguments?.getSerializable("item") as Item }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedElementEnterTransition =
TransitionInflater.from(context).inflateTransition(android.R.transition.move)
val tv = view.findViewById<TextView>(R.id.tvItemId)
with(tv) {
text = item.id
transitionName = "yura"
setBackgroundColor(item.color)
}
}
}
For more details and deeper explanation see:
https://issuetracker.google.com/issues/118475573
and
https://chris.banes.dev/2018/02/18/fragmented-transitions/
Android material design library contains MaterialContainerTransform class which allows to easily implement container transitions including transitions on recycler-view items. See container transform section for more details.
Here's an example of such a transition:
// FooListFragment.kt
class FooListFragment : Fragment() {
...
private val itemListener = object : FooListener {
override fun onClick(item: Foo, itemView: View) {
...
val transitionName = getString(R.string.foo_details_transition_name)
val extras = FragmentNavigatorExtras(itemView to transitionName)
navController.navigate(directions, extras)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Postpone enter transitions to allow shared element transitions to run.
// https://github.com/googlesamples/android-architecture-components/issues/495
postponeEnterTransition()
view.doOnPreDraw { startPostponedEnterTransition() }
...
}
// FooDetailsFragment.kt
class FooDetailsFragment : Fragment() {
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sharedElementEnterTransition = MaterialContainerTransform().apply {
duration = 1000
}
}
}
And don't forget to add unique transition names to the views:
<!-- foo_list_item.xml -->
<LinearLayout ...
android:transitionName="#{#string/foo_item_transition_name(foo.id)}">...</LinearLayout>
<!-- fragment_foo_details.xml -->
<LinearLayout ...
android:transitionName="#string/foo_details_transition_name">...</LinearLayout>
<!-- strings.xml -->
<resources>
...
<string name="foo_item_transition_name" translatable="false">foo_item_transition_%1$s</string>
<string name="foo_details_transition_name" translatable="false">foo_details_transition</string>
</resources>
The full sample is available on GitHub.
You can also take a look at Reply - an official android material sample app where a similar transition is implemented, see HomeFragment.kt & EmailFragment.kt. There's a codelab describing the process of implementing transitions in the app, and a video tutorial.