I have a Recyclerview that contains CardViews. The CardViews contain a ViewPager2 and a few other views (text, etc). I want to capture when a user clicks anywhere on the CardView (the parent) and run some logic. The problem: even though the ViewPager is a child of the CardView, it still interferes with the CardView's onClickListener and if the user clicks on the portion of the CardView where the ViewPager is drawn the CardView's listener is never triggered.
This is interesting fundamentally because I would expect the parent view to supersede any click listeners in the children, but the ViewPager seems to be a unique layout in this regard.
I've looked into solutions here but i'm not sure if any of them are the best practice since this onClick logic resides in a RecyclerView in my case. Since i'm instantiating the listener in my ViewHolder's init method it should be avoiding unnecessary object instantiation but i'm cautious not to make an inefficient solution.
The requirements for my solution are:
If a user clicks anywhere on my CardView, run my onClickListener (which'll launch a fragment)
If a user swipes the ViewPager within my CardView, let the ViewPager handle the event as usual and don't run the CardView's onClick logic
What's the proper way to do this? With my code below, clicks outside of the ViewPager are working but clicks on top of the ViewPager don't trigger my fragment-launching logic.
ViewHolder
class RaffleViewHolder(cardView: CardView) : RecyclerView.ViewHolder(cardView), View.OnClickListener {
val itemName = cardView.itemName
val viewPager = cardView.viewPager
val featuredWedge = cardView.quarter_circle
val imageViewPagerAdapter = RaffleItemViewPagerAdapter()
init {
viewPager.adapter = imageViewPagerAdapter
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
viewPager.setPageTransformer(ViewPagerDepthPageTransformer())
}
cardView.setOnClickListener(this)
}
override fun onClick(view: View?) {
Timber.d("onclick fired in recyclerviewadapter")
//do stuff. This is only reached for clicks outside of the ViewPager
}
}
XML for CardView
<androidx.cardview.widget.CardView
android:id="#+id/itemCard"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="2dp"
android:elevation="2dp"
app:cardBackgroundColor="#color/white">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/viewPager"
android:layout_width="300dp"
android:layout_height="300dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/itemName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/viewPager" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
Related
I have a cardview, behind which there is a recyclerview of "Suggested Contacts", much like the default dialer in android. The CardView is placed to the bottom of the screen, and occupies about 70% of the screen, while the RecyclerView extends its height to match_parent. When the RecyclerView is scrolled, the CardView disappears and reveals the whole list.
The user can click on one of the RecyclerView's items to make a call directly. The issue is that items behind the CardView, which are not visible to the user, are also clickable.
How can I have the RecyclerView items which are visible only as clickable? (i.e. while the cardView is visible)
xml code:
<ScrollView
android:id="#+id/sv_suggested_contacts"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="expandSuggestions">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rv_suggested_contacts"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</ScrollView>
<androidx.cardview.widget.CardView
android:id="#+id/dialer_card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
app:cardElevation="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
[...]
I'm assuming your requirement is that the cardview should block all clicks, so that items below don't take the click right.
If that is the case and you are sure cardview is on top of the recyclerview you simply need to set an onClickListener on your cardview and do nothing inside it. Basically an empty click.
Or you can in xml for cardview add
android:onClick="nullClick"
and in the activity create the empty function
public void nullClick(View view) {
}
whichever way you like.
you can hide (setVisibility(View.GONE) item behind recycler, or set them not clickable
I am using a RecyclerView with ListAdapter and DiffCall to implement a chat view. As more data gets added in the middle for threads or replies, or at the end for new messages the RecyclerView scrolls up and down. When a new message gets add I scroll to the bottom and that is working. But if someone replies to message from middle or up, the RecyclerView scrolls down.
I tried all app:StackFromEnd, app:reverseLayout and different LinearLayoutManagers I am unable to stop the RecyclerViews increase in height and push down the last message.
override fun onResume() {
messageViewModel.preprocessMessageList().observe(viewLifecycleOwner, Observer {
handleUpdateAndScroll(it)
})
}
private fun handleUpdateAndScroll(messageListRows: List<MessagesListRow>) {
messageAdapter.submitList(messageListRows)
val messageRowListSize = messageListRows.size
getMessageListLayoutManager().scrollToPositionWithOffset(messageRowListSize, 0)
}
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/messageList"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="8dp"
android:scrollbars="vertical"
app:layout_constraintTop_toBottomOf="#id/oneView"
app:layout_constraintBottom_toTopOf="#id/twoView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
We have a recyclerView inside this recyclerView to render list of replies, that is the only thing I can think of that is different. And we have ConstraintLayout items for this RecyclerView and some of the nested RecyclerView. Plus the messageList RecyclerView itself is in a ConstraintLayout
Anyone any clue?
I have a RecyclerView that contains ListItems as follows:
pagerecycler.xml:
<com.tscriber.scriblett.views.PageRecyclerView
android:id="#+id/page_recycler_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
tools:listitem="#layout/infinity_page_list_item" />
infinity_page_list_item.xml
<ImageView
android:id="#+id/memo_page_background"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<ImageView
android:id="#+id/memo_page_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
Using Kotlin synthetics, from the parent fragment I can turn those ListItem ImageViews visible or invisible using import kotlinx.android.synthetic.main.infinity_page_list_item.* and memo_page_image?.visibility = View.INVISIBLE.
But that synthetic only turns the ImageView in the first ListItem invisible. Is it possible to turn the ImageView in the nth ListItem on and off using synthetics? Something like memo_page_image[2]?.visibility = View.INVISIBLE, but not that because that doesn't work.
I don't have to use synthetics, of course. If it's not possible with synthetics, how do I do it some other way?
I've mucked around with arrays of ListAdapter callbacks in the past, but that always felt like a kludge to me so I'm looking for a more elegant way to do it.
thanks in advance
John
in recyclerView ViewHolder, Using Kotlin synthetic, just try this:
itemView.memo_page_image?.visibility = View.INVISIBLE
Kotlin synthetics are for ViewBinding only . It make the ugly findViewById() goes away and cache the view to prevent multiple calls for finding the view(like in an Adapter View) . SO it does not play any role in RecyclerAdapter Configuration.
If you want to change n'th item in a RecyclerView you can not just use findViewByPosition() cause it will return null if nth item is not visible on screen.
So as a full proof Solution you just change in your dataset for this particular position(maybe a boolean whether to show or not true/false) and then just use notifyItemChanged(postion) and handle it inside onBindViewHolder(). Something like below:-
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
if(show){
memo_page_image?.visibility = View.VISIBLE
}else{
memo_page_image?.visibility = View.INVISIBLE
}
}
Problem:
It seems when I have a specific combination of views and setVisibility between Visible and Gone the RecyclerView does not update properly after initial load. I have a RelativeLayout->ConstraintLayout->Constraint Group(with visibility dynamically set). The view within the constraint group is not updating properly.
Example Use Case of Problem:
So the linked code below it will show a search view at the top. The initial state of empty shows the view properly(With the search icon showing). Then if you type "T" then the search icon will disappear(it shouldn't). So if you either delete the T or type the next letter "E" then it shows again. Also if you delete all search text and type T again it will show.
View Code:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="vm"
type="com.example.davidcorrado.myapplication.PlayerVM" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.constraint.ConstraintLayout
android:id="#+id/playerCell_main_layout"
android:layout_width="match_parent"
android:layout_height="84dp"
android:layout_alignParentTop="true">
<android.support.constraint.Group
android:id="#+id/playerCell_status_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="#{vm.showingStatusIcons}"
app:constraint_referenced_ids="playerCell_lineup_status_image" />
<ImageView
android:id="#+id/playerCell_lineup_status_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:src="#drawable/ic_search" />
</android.support.constraint.ConstraintLayout>
</RelativeLayout>
</layout>
Quirks that might help:
1) If I move the visibility line to the relativeLayout or the ConstraintLayout the things seem to work
2) If I remove the RelativeLayout view things seem to work.
See the below for the full code example:
https://github.com/DavidCorrado/LayoutIssue
As far as I can tell, the reason the search icon disappears is due to the call to ObservableList.clear() then ObservableList.addAll(*)
So the disappear happens on the clear, then the reappear happens on the addAll.
As the callbacks in the adapter kick in on both calls, my hunch is it animates the view to disappear, then animates to show when the addAll is triggered.
I have verified this by not actioning a 'List.clear()' and instead in your textChange listener simply adding more views to the list.
I'm not sure how you would clear and add items to the list without animating the clear, perhaps there are some settings you can toggle in the RecyclerAdapter to ignore the remove of Views or not animate the entry of Views?
My adjusted code in your callback for class PlayersListVM
class PlayersListVM {
val filteredVms: ObservableList<ViewModel> = ObservableArrayList<ViewModel>()
val showing = PlayerVM(isShowing = true)
val missing = PlayerVM()
init {
filteredVms.add(showing)
}
fun onQueryChanged(newText: String) {
if (filteredVms.size % 2 == 1) filteredVms.add(missing)
else filteredVms.add(showing)
}
}
What I'm trying to do
I have a RecyclerView with many items that are basically some CardView.
Those cards have a supporting text in the middle of their bodies, which has the visibility set to GONE by default, and it's made VISIBLE when I click the arrow on the right of the card.
I'm trying to animate the card while the text is revealed and while it's collapsed.
The picture below shows the expanded card and the collapsed one:
The CardView layout (I've removed some parts for readability):
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
card_view:cardCornerRadius="3dp"
card_view:cardElevation="4dp"
card_view:cardUseCompatPadding="true"
android:id="#+id/root">
<LinearLayout
android:id="#+id/item_ll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="#dimen/activity_vertical_margin">
<!-- The header with the title and the item -->
<TextView
android:id="#+id/body_content"
style="#style/TextAppearance.AppCompat.Medium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:layout_marginBottom="8dp"
android:text="#string/about_page_description"
android:textColor="#color/secondaryText"
android:visibility="gone"/>
<!-- The divider, and the footer with the timestamp -->
</LinearLayout>
</android.support.v7.widget.CardView>
The problem
The animations is working when the card is expanding and revealing the body TextView, however, when I try to collapse it back, the cards below the animated one overlaps the first one.
Example:
What I've tried so far
I've already asked a similar question about this behavior here before, but that solution is not working for a TextView in the middle of the card.
The code that's responsible for the animation part is inside the RecyclerView adapter. The arrow has a click listener that calls the method below:
private fun toggleVisibility() {
if (bodyContent.visibility == View.GONE || bodyContent.visibility == View.INVISIBLE) {
btSeeMore.animate().rotation(180f).start()
TransitionManager.beginDelayedTransition(root, AutoTransition())
bodyContent.visibility = View.VISIBLE
}
else {
btSeeMore.animate().rotation(0f).start()
TransitionManager.beginDelayedTransition(root, AutoTransition())
bodyContent.visibility = View.GONE
}
}
Where root is my CardView.
I've also tried to use the LinearLayout instead of the card itself for the delayed transition, but that didn't work either.
How can I achieve that behavior for my layout?
You will have to perform the transition on the RecyclerView, not on individual items. Otherwise, the RecyclerView layout changes aren't taken into account by the auto transition, because it will only look at what changes in that very child view, even though in fact, other ViewHolders are indirectly affected (layout parameters are changing).
So, instead of passing "root" (the item view) to TransitionManager#beginDelayedTransition, pass a reference to your RecyclerView
You have to apply TransitionManager.beginDelayedTransition on the root view where the cardview is contained
You have to remove android:animateLayoutChanges="true" from all over the layout
TransitionManager.beginDelayedTransition(the_root_view_where_card_view_exist, new AutoTransition());
RecyclerView does behave oddly if his items are resizing outside RecyclerViews callbacks. Try using adapter.notifyItemChanged(position, payload) and updating the item then:
Replace adapter's onclick with this:
adapter.notifyItemChanged(adapterPosition, true) // needs adapter reference, can use more meaningful payload
Then inside of your adapter:
override fun onBindViewHolder(holder: Holder, position: Int, payloads: List<Any>) {
if (payloads.isEmpty())
onBindViewHolder(holder, position)
else
holder.toggleVisibility()
}
You can also see what happens when running delayedTransition on LinearLayout instead of Card itself.
This won't be perfect, but it will trigger animation of following items instead of them jumping and clipping.
I would recomment you to use Animator framework and apply height animation to your TextView.
Here is a nice library you can use: https://github.com/cachapa/ExpandableLayout
I also suggest you to check it's source code, it uses the Animators
Maybe this is too late.
Inside onBindViewHolder() include this
holder.view.btSeeMore.setOnClickListener { view ->
val seeMore = (bodyContent.visibility != View.VISIBLE)
view.animate().rotation(if (seeMore) 180f else 0f).start()
bodyContent.visibility = if (seeMore) View.VISIBLE else View.GONE
}