Shared element transition in Jetpack Navigation from RecyclerView to Detail Fragment - android

I'm trying to make a transition with simple animation of shared element between Fragments. In the first fragment I have elements in RecyclerView, in second - exactly the same element (defined in separate xml layout, in the list elements are also of this type) on top and details in the rest of the view. I'm giving various transitionNames for all elements in bindViewHolder and in onCreateView of target fragment I'm reading them and set them to element I want make transition. Anyway animation is not happening and I don't have any other ideas. Here below I'm putting my code snippets from source and target fragments and list adapter:
ListAdapter:
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = list[position]
ViewCompat.setTransitionName(holder.view, item.id)
holder.view.setOnClickListener {
listener?.onItemSelected(item, holder.view)
}
...
}
interface interactionListener {
fun onItemSelected(item: ItemData, view: View)
}
ListFragment (Source):
override fun onItemSelected(item: ItemData, view: View) {
val action = ListFragmentDirections.itemDetailAction(item.id)
val extras = FragmentNavigatorExtras(view to view.transitionName)
val data = Bundle()
data.putString("itemId", item.id)
findNavController().navigate(action.actionId, data, null, extras)
}
SourceFragmentLayout:
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="#+id/pullToRefresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="#layout/item_overview_row" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
DetailFragment (Target):
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val rootView = inflater.inflate(R.layout.fragment_detail, container, false)
val itemId = ItemDetailFragmentArgs.fromBundle(arguments).itemId
(rootView.findViewById(R.id.includeDetails) as View).transitionName = itemId
sharedElementEnterTransition = ChangeBounds().apply {
duration = 750
}
sharedElementReturnTransition= ChangeBounds().apply {
duration = 750
}
return rootView
}
DetailFragmentLayout:
<include
android:id="#+id/includeDetails"
layout="#layout/item_overview_row"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
ItemOverviewRowLayout (this one included as item in recyclerView and in target fragment as header):
<androidx.cardview.widget.CardView 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="wrap_content"
android:clickable="true"
android:focusable="true"
android:foreground="?android:attr/selectableItemBackground"
android:orientation="vertical" >
I made also another application using Jetpack navigation, shared elements and elements described by the same layout.xml and it's working since I'm not making transition from recyclerView to target fragment. Maybe I'm wrong here, setting the transitionName to found view in target fragment? I don't know how to make it another way, because the IDs of target included layout should be unique because of recyclerView items.

Okay, I found that how should it looks like to have enter animation with shared element:
In DetailFragment (Target) you should run postponeEnterTransition() on start onViewCreated (my code from onCreateView can be moved to onViewCreated). Now you have time to sign target view element with transitionName. After you end with loading data and view, you HAVE TO run startPostponedEnterTransition(). If you don't do it, ui would freeze, so you can't do time consuming operations between postponeEnterTransition and startPostponedEnterTransition.
Anyway, now the problem is with return transition. Because of course it's the same situation - you have to reload recyclerView before you release animation. Of course you can also use postponeEnterTransition (even if it's return transition). In my case, I have list wrapped by LiveData. In source fragment lifecycle observer is checking data. There is another challenge - how to determine if data is loaded. Theoretically with recyclerView you can use helpful inline function:
inline fun <T : View> T.afterMeasure(crossinline f: T.() -> Unit) {
viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
if (measuredWidth > 0 && measuredHeight > 0) {
viewTreeObserver.removeOnGlobalLayoutListener(this)
f()
}
}
})
}
...and in code where you are applying your layout manager and adapter you can use it like this:
recyclerView.afterMeasure { startPostponedEnterTransition() }
it should do the work with determine time when return animation should start (you have to be sure if transitionNames are correct in recyclerView items so transition can have target view item)

From the answer that using ViewTreeObserver is quite consume resources a lot. and also have a lot of processes to do. so I do suggest you use doOnPreDraw instead of waiting after recyclerView was measured. the code implement will like this below.
recyclerView.doOnPreDraw {
startPostponedEnterTransition()
}

Related

Android Studio: autoSize of EditText doesn't work

in my current project there's an EditText with fixed layout_width and layout_height, called exercise that is expanded downwards programmatically: One line of text (String) + "\n" is added to the EditText.
Sometimes the line added to the EditText, let's call it element, is too long to fit inside the full width of the object so it's splitted into a new line.
The thing is I would either like the lines' text size in exercise to be resized to fit the EditText's width or have a clear visible distance between each line (element), but just not inside the newline due to not fitting inside the exercise's width.
Therefore I googled as much as I could and tried out every possible solution I could find today.
What I tried:
Using either EditText as the object and android:autoSizeTextType="uniform" & android:inputType="textMultiLine|textCapSentences"as attributes or androidx.appcompat.widget.AppCompatEditText, accompanied by the attributes app:autoSizeMaxTextSize="28sp", app:autoSizeMinTextSize="8sp"and app:autoSizeStepGranularity="1sp"
(with a device that supports just exactly API 26)
using other types of text objects
using lineSpacingExtra to insert some spacing. This unfortunately also inserted the spacing between the wrapped/ splitted line so the original element's line that got splitted by wrapping inside the EditText had the spacing aswell.
That's where I am now. I can't get the text size be reduced automatically when the line would be too wide for the EditText's width.
I could supply the full XML, if needed.
I'm grateful for any hint that could help here. Thanks in advance!
Here's a really basic RecyclerView implementation (using view binding, let me know if you're not familiar with that - you can just findViewById all the things instead):
class MainFragment : Fragment(R.layout.fragment_main) {
lateinit var binding: FragmentMainBinding
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = FragmentMainBinding.bind(view)
with(binding) {
val adapter = MyAdapter()
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(requireContext())
addButton.setOnClickListener {
val item = textEntry.text.toString()
if (item.isNotBlank()) {
adapter.addItem(item)
textEntry.text.clear()
}
}
}
}
}
class MyAdapter : RecyclerView.Adapter<MyAdapter.ViewHolder>() {
private var data: List<String> = emptyList()
fun addItem(item: String) {
data = data + item
notifyItemInserted(data.lastIndex)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = ItemViewBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.binding.textView.text = data[position]
}
override fun getItemCount(): Int = data.size
class ViewHolder(val binding: ItemViewBinding) : RecyclerView.ViewHolder(binding.root)
}
fragment_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:gravity="center"
android:padding="10dp">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="8dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="#id/textEntry"
/>
<EditText
android:id="#+id/textEntry"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:singleLine="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="#id/addButton"
/>
<Button
android:id="#+id/addButton"
android:text="ADD"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
item_view.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
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="wrap_content"
android:paddingHorizontal="16dp"
>
<TextView
android:id="#+id/textView"
app:autoSizeTextType="uniform"
android:layout_width="match_parent"
android:layout_height="48sp"
android:maxLines="1"
android:gravity="center_vertical"
/>
</FrameLayout>
It's pretty simple - you have a text entry field and a button to add the contents as a new line. The button passes the contents to addItem on the adapter, which appends it to the list of lines in data. The RecyclerView just displays all the items in data, using a ViewHolder layout that has an auto-sizing TextView to scale each item.
Ideally you'd want to persist data somehow (e.g. the Add button passes the new data to a ViewModel, stores it somehow, updates the current list which the adapter has observed so it updates whenever there's a change) - I just left it as a basic proof of concept. Also, it's easier to store separate items if they're kept separate - you can always serialise it by joining them into a single string if you really want! But generally you wouldn't want to do that
edit - since you're having trouble with the setTypeface thing, this is all it is:
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
with(holder.binding.textView) {
val styled = position % 2 == 0
text = data[position]
setTypeface(typeface, if (styled) Typeface.BOLD else Typeface.NORMAL)
setTextColor(if (styled) Color.RED else Color.BLACK)
}
}
The logic is just styling alternate items differently, but hopefully you get the idea. You decide how to style a given item depending on what it is, and then you apply that style by setting attributes as appropriate. It's always an "if this is true do A, otherwise do B" situation, so you're always setting the attribute one way or the other. You never only set it for one case, because then you're leaving old state displayed if it's not that case.
It's more complicated, but you also have the option of creating different ViewHolders (with their own XML layouts) for different kinds of item. So instead of having a single ViewHolder that has to work with everything, where you have to reconfigure things like all the styling in onBindViewHolder depending on which type of item is displayed, you can just have different ViewHolders with different styling, different layouts etc:
// creating a sealed class so we can say our adapter handles a MyViewHolder type,
// and we can have a specific set of possible subclasses of that
sealed class MyViewHolder(view: View) : RecyclerView.ViewHolder(view)
class HeaderViewHolder(val binding: HeaderItemBinding) : MyViewHolder(binding.root)
class ItemViewHolder(val binding: ItemViewBinding) : MyViewHolder(binding.root)
// the Adapter now uses the MyViewHolder type (which as above, covers a couple of different
// ViewHolder classes we're using)
class MyAdapter : RecyclerView.Adapter<MyViewHolder>() {
private var data: List<String> = emptyList()
fun addItem(item: String) {
data = data + item
notifyItemInserted(data.lastIndex)
}
// some identifiers for the different ViewHolder types we're using
private val HEADER_TYPE = 0
private val ITEM_TYPE = 1
override fun getItemViewType(position: Int): Int {
// Work out what kind of ViewHolder the item in this position should display in.
// This gets passed to onCreateViewHolder, where you create the appropriate type,
// and that type of ViewHolder is what gets passed into onBindViewHolder for this position
return if (data[position].startsWith("Section")) HEADER_TYPE else ITEM_TYPE
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val inflater = LayoutInflater.from(parent.context)
// creating the appropriate ViewHolder instance depending on the type requested
return when(viewType) {
HEADER_TYPE -> HeaderViewHolder(HeaderItemBinding.inflate(inflater, parent, false))
ITEM_TYPE -> ItemViewHolder(ItemViewBinding.inflate(inflater, parent, false))
else -> throw RuntimeException("Unhanded view type!")
}
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
// The type of MyViewHolder passed in here depends on what getItemViewType returns
// for this position - binding is a different type in each case,
// because it's been generated from different layouts
when(holder) {
is HeaderViewHolder -> holder.binding.textView.text = data[position]
is ItemViewHolder -> holder.binding.textView.text = data[position]
}
}
override fun getItemCount(): Int = data.size
}
(You could be a bit more clever than this, but just to illustrate the general idea!)
That's using the same item_view.xml as before, and a header_item.xml variation on that (but you could have literally anything, they're completely separate layouts, completely separate ViewHolders):
header_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
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="wrap_content"
android:paddingHorizontal="16dp"
>
<TextView
android:id="#+id/textView"
app:autoSizeTextType="uniform"
android:layout_width="match_parent"
android:layout_height="48sp"
android:layout_weight="1"
android:textStyle="bold"
android:textColor="#DD1100"
android:maxLines="1"
android:gravity="center_vertical"
/>
</LinearLayout>
So instead of having to "redesign" one ViewHolder in code to go back and forth between different item types and their styling, You can just use two differently-styled layouts. It's a bit more work to set up, but it can be neater and much more flexible when you have completely independent things - especially if you want to give them different functionality. It depends whether it's worth it to you, or if you're happy to just have a single ViewHolder and restyle things in code, hide or show elements etc.
you can try something like this
if(et.getText().length()>10) {
et.setTextSize(newValue)

Exposed Dropdown Menu not showing items

Exposed Dropdown Menu doesn't show items after user selection and fragment transition.
Following is the basic xml declaration:
<com.google.android.material.textfield.TextInputLayout
...
style="#style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
<AutoCompleteTextView
....
android:id="#+id/dropdown"
android:dropDownHeight="300dp"
android:inputType="none" />
</com.google.android.material.textfield.TextInputLayout>
And, the declared code on the fragment (inside onViewCreated()):
val items = listOf("Material", "Design", "Components", "Android")
val adapter = ArrayAdapter(requireContext(), R.layout.item_menu, items)
dropdown.setAdapter(adapter)
dropdown.setText(items[0], false)
As mentioned here, it was set on AutoCompleteTextView's setText method (dropdown.setText("", false)) the filter parameter as false. However, after navigating to a next fragment and coming back to it only the pre-selected text is shown on the dropdown.
Fragments are changed using navigation component (v. 2.3.2).
The fragment's view gets destroyed when using the navigation component. (maybe not always, but it will certainly happen some of the time as you experienced)
I think you might be able to make it work simply by adding a condition:
if (savedInstanceState == null) {
dropdown.setText(items[0], false)
}
So that the default is only set when not restoring the view state.
Otherwise it's just a matter saving the state as usual. Here's a documentation article about it if you're unsure what I'm talking about. It will essentially amount to adding the following code to your fragment:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val item = savedInstanceState?.getInt("selectedPos", 0) ?: 0
dropdown.setText(items[item], false)
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt("selectedPos", dropdown.getListSelection())
}
If you're using the MVVM architecture, you can save the selected position using SavedStateHandle in your ViewModel, when it gets changed.
I had the same problem. I searched for issues on github page. I found this https://github.com/material-components/material-components-android/issues/2012#issuecomment-808853621 work around for now. It works.
Create an extension like below
fun AutoCompleteTextView.showDropdown(adapter: ArrayAdapter<String>?) {
if(!TextUtils.isEmpty(this.text.toString())){
adapter?.filter?.filter(null)
}
}
Then on click of dropdown
binding.quaters.setOnClickListener {
binding.quaters.showDropdown(arrayAdapter)
}
That's all it should work. This seems to be a bug which should be fixed hopefully.
This is a temprorary solution that is working for me -
https://github.com/material-components/material-components-android/issues/2012#issuecomment-868181589
Write the setup code for ExposedDropdownMenu in onResume() of a fragment,
instead of onCreateView()/onViewCreated()
override fun onResume() {
super.onResume()
val sortingArtist = resources.getStringArray(R.array.sortingArtist)
val arrayAdapterArtist = ArrayAdapter(requireContext(), R.layout.dropdown_items_artist, sortingArtist)
binding?.autoCompleteTextViewArtist?.setAdapter(arrayAdapterArtist)
binding?.autoCompleteTextViewArtist?.setText(sortingArtist[0], false)
}
For reference - https://material.io/components/menus/android#exposed-dropdown-menus

Nested Recyclerviews with Complex Room LiveData

I have a collection of parent objects each having a collection of child objects. Call these ParentModels and ChildModels.
On screen I want to display a RecyclerView of rendered ParentModels, each containing inter alia a RecyclerView of rendered ChildModels.
Wishing to avoid having a god LiveData that redraws everything just because one property of one ChildModel changes, I intend to separate these.
I can't figure out how to structure this with Recyclerview Adapters and Holders plus whatever Fragments and ViewModels I need. Right now I have
class MyFragment: Fragment() {
private lateinit val mViewModel: FragmentViewModel
// ...
fun onViewCreated(/*...*/) {
val parentAdapter = ParentAdapter()
view.findViewById<RecyclerView>(/*...*/).apply {
adapter = parentAdapter
//...
}
viewModel.getParents().observe(this, Observer {
parentAdapter.setParents(it)
}
}
}
class FragmentViewModel #Inject constructor(repository: RoomRepo): ViewModel() {
mParents: LiveData<List<ParentModel>> = repository.getParents()
fun getParents() = mParents
//...
}
class ParentAdapter: RecyclerView.Adapter<ParentHolder>() {
private lateinit var mParents: List<ParentModel>
fun setParents(list: List<ParentModel>) {
mParents = list
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, /*...*/) {
return ParentHolder(LayoutInflater.from(parent.context).inflate(R.layout.parent, parent, false))
}
override fun onBindViewHolder(holder: ParentHolder, position: Int) {
holder.bind(/*UNKNOWN*/)
}
// ...
inner class ParentHolder(private val mView: View): RecyclerView.ViewHolder(mView) {
fun bind(/*UNKNOWN*/) {
// WHAT TO DO HERE???
}
}
}
Plus my R.layout.parent (I've omitted other irrelevant stuff like a View that just draws a horizontal line, but that's why I have my RecyclerView nested inside a LinearLayout):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:orientation="vertical"
android:layout_height="wrap_content"
android:layout_width="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
I have written a ChildAdapter, ChildHolder, and a few other things unthinkingly because I thought this would be trivial to implement, but at this point something's gunked up my brain and I'm likely not seeing the obvious thing.
I've got the first RecyclerView loading correctly based on underlying data. But this parent recyclerview also needs to:
fetch children based on a single parent.id
create a child recyclerview for a single parent recyclerview item that displays the children
Room returns a LiveData> from function repository.getChildrenByParentId(id: Long). That's the data I'm working from.
But where do I fetch this, how do I hook it into the relevant child recyclerview that belongs to the parent recyclerview?
I don't want to have a God fragment that does
viewModel.getParents().observe(...) { parentAdapter.update(it) } and also have to do some kind of viewModel.getChildren().observe(...) { parentAdapter.updateChildren(it) }
because that destroys separation of concerns. Seems to me each item in the parent recyclerview should have a viewmodel that fetches the children that would belong to it, then creates a recyclerview and uses a ChildAdapter to display these children, but I can't seem to figure out where to plug in the ChildFragment and ChildViewModel (with repository.getChildrenByParentId in it) to get this all working.
All examples I find online don't seem to help as they use contrived examples with no LiveData and a God fragment/activity that puts everything inside a single adapter.
I would literally have 1 adapter that can render everything, using the DiffUtil (or its async version) class to ensure I don't (and I quote) "redraw everything just because one property of one ChildModel changes".
I would move this complex responsibility of constructing (and providing) the data, to your repository (or, if you prefer to have it closer, to your ViewModel acting as a coordinator between 1 or more (I don't know how your model looks, so I am only imagining) repositories providing data.
This would allow you to offer to the ui a much more curated immutable list of ParentsAndChildren together and your RecyclerView/Adapter's responsibility is suddenly much simpler, display this, and bind the correct view for each row. Your UI is suddenly faster, spends much less time doing things on the main thread and you can even unit test the logic to create this list, completely independent of your Activity/Fragment.
I imagine ParentsAndChildren to be something like:
class ParentChildren(parent: Parent?, children: Children?)
Your bind could then inflate one view when parent is not null and children is. When children is not null, you know it's a children (you could include the parent as well, depends on how you construct this data). Problem solved here, your adapter would look like
class YourAdapter : ListAdapter<ParentChildren, RecyclerView.ViewHolder>(DiffUtilCallback()) {
...
You'd need to implement your DiffUtilCallback():
internal class DiffUtilCallback : DiffUtil.ItemCallback<ParentChildren>() {
and its two methods (areContentsTheSame, areItemsTheSame).
And your adapter's two methods:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
viewTypeParent -> YourParentViewHolder(inflater.inflate(R.layout.your_layout_for_parent), parent, false))
viewTypeChildren -> YourChildrenViewHolder(inflater.inflate(R.layout.your_layout_for_children), parent, false))
else -> throw IllegalArgumentException("You must supply a valid type for this adapter")
}
}
I would have an abstract base to simplify the adapter even further:
internal abstract class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
abstract fun bind(data: ParentChildren)
}
This allows you to have your
// I'm writing pseudo code here... keep it in mind
internal class ParentViewHolder(itemView: View) : BaseViewHolder(itemView) {
private val name: TextView = itemView.findViewById(R.id.item_text)
override fun bind(data: ParentChildren) {
name.text = parentChildren.parent?.name
}
}
internal class ChildrenViewHolder(itemView: View) : BaseViewHolder(itemView) {
private val name: TextView = itemView.findViewById(R.id.item_text)
override fun bind(data: ParentChildren) {
name.text = parentChildren.children?.name
}
}
You get the idea.
Now... ListAdapter<> has a method called submitList(T) where T is the Type of the adapter ParentChildren in the above pseudo-example.
This is as far as I go, and now you have to provide this Activity or Fragment hosting this adapter, the list via either LiveData or whatever is that you prefer for the architecture you have.
It can be a repository passing it to a MutableLiveData inside the viewModel and the ViewModel exposing a LiveData<List<ParentChildren> or similar to the UI.
The sky is the limit.
This shifts the complexity of putting this data together, closer to where the data is, and where the power of SQL/Room can leverage how you combine and process this, regardless of what the UI needs or wants to do with it.
This is my suggestion, but based upon the very limited knowledge I have about your project.
Good luck! :)

Android expandable cardView for existing recyclerView adapter

Alrighty, So I have an existing custom recycler view adapter that populates a recycler view using provided items, and sets attributes in a layout i have as follows, ill try to remove irrelevant items from code
class TransactionAdapter(val context: Context, var transactions: List<Transaction>) :
androidx.recyclerview.widget.RecyclerView.Adapter<TransactionAdapter.CustomViewHolder>() {
override fun onCreateViewHolder(p0: ViewGroup, p1: Int): CustomViewHolder {
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val view = inflater.inflate(R.layout.transaction_list_inner_view, p0, false)
return CustomViewHolder(view)
}
override fun onBindViewHolder(p0: CustomViewHolder, p1: Int) {
p0.transactionNameTextView?.text = transactions[p1].title
p0.transactionAmountTextView?.text = transactions[p1].amount
if (!transactions[p1].location.isNullOrEmpty()) {
p0.transactionLocationTextView?.text = transactions[p1].location
} else {
p0.transactionLocationTextView?.text = "N/A"
}
p0.transactionTimeTextView?.text = transactions[p1].createdAt
p0.transactionDeleteButton?.setOnClickListener { println("working delete") }
}
class CustomViewHolder(v: View) : androidx.recyclerview.widget.RecyclerView.ViewHolder(v) {
val transactionNameTextView: TextView? = v.findViewById(R.id.transactionNameTextView) as TextView?
val transactionAmountTextView: TextView? = v.findViewById(R.id.transactionAmountTextView) as TextView?
val transactionLocationTextView: TextView? = v.findViewById(R.id.transactionLocationTextView) as TextView?
val transactionTimeTextView: TextView? = v.findViewById(R.id.transactionTimeTextView) as TextView?
val transactionDeleteButton: AppCompatButton? = v.findViewById(R.id.transactionDeleteButton) as AppCompatButton?
}
}
Now, I want to implement this library to use instead expandable card views so that I can only show for example the name and amount of transactions, and have the rest be expanded, this library can be found here https://github.com/AleSpero/ExpandableCardView , the library asks me to make a new layout with an inner_view attribute, I have done so, my layout is called transaction_list_expandable_view.xml and it looks like this
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:ignore="ExtraText">
<com.alespero.expandablecardview.ExpandableCardView
android:id="#+id/transaction"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:title="testt"
app:inner_view="#layout/transaction_list_inner_view"
app:expandOnClick="true"
app:animationDuration="300"
app:startExpanded="false"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
Now comes the problem, my custom TransactionAdapter only handles one layout, which is the one I called transaction_list_inner_view, how can I have this adapter instead handle both inner and expandable views and get the desired result? (a list of cards with relevant titles that expands to reveal the rest of the details belonging to them)
Sorry for the long question and code, thanks in advance for any help.
After checking the code of the library you're using, i think that you shouldn't be inflating the inner view manually (in your adapter) as that's the responsibility of the ExpandableCardView you should inflate the transaction_list_expandable_view.xml in your adapter.
It would look like :
val view = inflater.inflate(R.layout.transaction_list_expandable_view, p0, false)
For populating your inner view, i'm not sure whether the inner view is already inflated in the expandable one at the time of instantiating the CustomViewHolder.
if it's the case just the switching of inner => expandable views above should do the trick.

ViewPager not swiping on touching child recyclerview, fine otherwise

Having an odd issue with a RecyclerView within in a ViewPager. Adapter is a PagerAdapter that has fixed size of two and returns pre-inflated ViewGroups. One of which has a RecyclerView in it.
Cannot get my head around it. Any help would be much appreciated.
On inflating the view do I ened to set the root? or possibly attachToRoot?
Sliding works fine on all but touching the RecyclerView.
Support version 23.3.0 but was still an issue on previous versions, so don't believe that is it.
xml that contains the RecyclerView. The InterceptingLayout is just a RelativeLayout in this case, with option of intercepting touch events, but isn't the issue here.
<xxxx.layouts.InterceptingRelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/list_interceptor"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.malinskiy.superrecyclerview.SuperRecyclerView
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="200dp"
app:layout_empty="#layout/layout_empty"
app:layout_moreProgress="#layout/layout_empty"
app:mainLayoutId="#layout/recyclerview_list"
app:recyclerPaddingTop="#dimen/status_bar_toolbar_height"/>
</xxx.layouts.InterceptingRelativeLayout>
Inflation of ViewGroup that contains RecyclerView
listInterceptor = (getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater).inflate(R.layout.stub_list_content, null) as InterceptingRelativeLayout
And relevant methods in Pager Adapter
override fun instantiateItem(container: ViewGroup, position: Int): Any {
if (position == 0) {
container.addView(daypicker)
return daypicker
} else {
container.addView(listInterceptor)
return listInterceptor
}
override fun getItemPosition(obj: Any?) = POSITION_NONE
override fun getCount() = 2
override fun isViewFromObject(arg0: View, arg1: Any): Boolean {
return arg0 === arg1
}

Categories

Resources