I'm having a horizontal RecyclerView.Adapter subclass with a simple LinearLayout row, having ?attr/selectableItemBackgroundBorderless as a background:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="60dp"
android:layout_height="match_parent"
android:gravity="center"
android:background="?attr/selectableItemBackgroundBorderless"
android:orientation="vertical"
>
<TextView/>
<TextView/>
</LinearLayout>
An interface for the view's OnClickListener is set from the RecyclerView - nothing fancy here.
The OnClickListener is handling the "selected" color of the view, and passing some data to another interface:
setAdapter(new CalendarViewAdapter(view -> {
int position = getChildLayoutPosition(view);
if (position == selectedDayPosition) return;
getAdapter().notifyItemChanged(selectedDayPosition);
selectedDayPosition = position;
getAdapter().notifyItemChanged(selectedDayPosition);
//view.setPressed(true);
//view.setPressed(false); <-- That didn't help as well (nor delaying in a handler etc.)
if (listener != null) {
listener.onDayPicked(CalendarPicker.this.days.get(selectedDayPosition).getPaginationInfo());
}
}));
My concern is after the notifyItemChanged() instead of the nice fade out of the ripple I end up with this weird result:
screenshot taken in the moment of the release of the touch event
My only explanation is this is some strange combination between the ?attr/selectableItemBackgroundBorderless and ?attr/selectableItemBackground. But I want it only in the borderless style.
There are a few questions on this subject, I tried all of the answers but unfortunately they are all using ?attr/selectableItemBackground and not the borderless variation.
Any help with my UI dilemma will be highly appreciated!
Related
I am new to android development. For my learning purpose, I am developing an android application to list the fibonacci numbers in a recycler view. The list gets appended with new numbers as the user scrolls down the recycler view.
The image shows the app displaying the index and respective fibonacci number for the index in the recycler view
This is the layout xml of single item in recycler view.
<?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="horizontal"
android:clickable="false"
android:focusable="false"
android:padding="5dp">
<TextView
android:id="#+id/info_index"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="3"
android:background="#CDD6D5"
android:gravity="center"
android:maxLines="1" />
<TextView
android:id="#+id/info_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="#CDD6D5"
android:gravity="center"
android:maxLines="1" />
</LinearLayout>
Excerpt from the adaptor class,
public class ViewHolder extends RecyclerView.ViewHolder {
final TextView index;
final TextView value;
ViewHolder(View itemView) {
super(itemView);
index = itemView.findViewById(R.id.info_index);
value = itemView.findViewById(R.id.info_value);
}
}
public void onBindViewHolder(#NonNull ViewHolder holder, final int position) {
final TextView value = holder.value;
final TextView index = holder.index;
value.setOnClickListener(new View.OnClickListener() {
#RequiresApi(api = Build.VERSION_CODES.KITKAT)
#Override
public void onClick(View v) {
if (value.getMaxLines() == 1000) {
value.setMaxLines(1);
notifyItemChanged(position);
} else {
value.setMaxLines(1000);
notifyItemChanged(position);
}
}
});
Log.d(TAG, "position-value:" + String.valueOf(position));
holder.index.setBackgroundColor(Color.WHITE);
holder.index.setText(String.valueOf(position + 1));
holder.value.setBackgroundColor(Color.WHITE);
holder.value.setText(mData.get(position));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
holder.index.setTextAlignment(View.TEXT_ALIGNMENT_TEXT_START);
holder.value.setTextAlignment(View.TEXT_ALIGNMENT_TEXT_START);
}
}
When I click on the value textview, the textview does not expand on the first click, instead I have to press two times to make it expand. The first time only the flickering happens. I have tried disabling the animator for recyclerview, tried re using the same view in the recycler view, but nothings helps.
My requirement is to expand fibonacci-value textview on click. By default it should be 1 line and when clicked it should show the whole content with multiple lines (as many as required).
currently this happens with two clicks. first time flickering and second time expands.
I believe this is a bug in android code. But just want to confirm here for any solutions that I might have missed.
The reason of the flickering is that, each time an item is clicked, just after setting maxLines, you are calling notifyItemChanged (which is the correct thing to do), but as a result, before redrawing the item onBindViewHolder is called again. So, when it is called again, there should be a way to know current max lines for that item.
Besides, if you try adding lots of items and scroll up and down, you'll see more bugs (since viewholders are reused) Thus, it is important to set/reset maxlines for each item inside onBindViewHolder (but outside click listener)
Secondly, DefaultItemAnimator of RecyclerView uses cross-fade animation when an item changes and by default, it creates two viewholders for that position for cross-fading between the two. So, above, you set a clicklistener on your "value" textview and interfere with the textview inside onClick callback. However, when you later click and inform adapter that the item is changed, it binds the second viewholder instance. So when you click, your click is consumed with the previous "value" instance, and right afterwards a new instance is bound and you set a new clicklistener to this second viewholder instance.
This is one of many reasons that interfering with viewholder items inside the click listener is error-prone. Sometimes people solve this kind of problems with setTag/getTag but I think it is similarly error-prone as well.
I think the easiest solution is to use a POJO (plain old java object) for each item and include the maxLine state in this POJO. Something like FibonacciItem with fields such as int: index, String: fibonacciNumber, boolean : expanded. Then you will provide the list as a list of FibonacciItems. And inside your click listener, you'll update maxlines of the clicked item. Something like this:
public void onClick(View v) {
if (mList.get(position).isExpanded()) {
mList.get(position).setExpanded(false);
notifyItemChanged(position);
} else {
mList.get(position).setExpanded(true);
notifyItemChanged(position);
}
}
And inside your onBindViewHolder, (but outside your click listener) you should set max lines for each item according to this value:
if(mList.get(position).isExpanded(){
holder.value.setMaxLines(1);
} else {
holder.value.setMaxLines(1000);
}
This will solve the issue. Besides, we usually use POJOs (or data classes) for each item in a list. It is easier to manage.
I was able to repeat your issue on my device, the issue you have is that your LinearLayout is consuming your click before it reaches your textview.
Include:
android:clickable="false"
android:focusableInTouchMode="false"
android:focusable="false"
Within your LinearLayout like so:
<?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="horizontal"
android:clickable="false"
android:focusableInTouchMode="false"
android:focusable="false"
android:padding="5dp">
<TextView
android:id="#+id/info_index"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="3"
android:background="#CDD6D5"
android:gravity="center"
android:maxLines="1" />
<TextView
android:id="#+id/info_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="#CDD6D5"
android:gravity="center"
android:maxLines="1" />
</LinearLayout>
Also include:
android:nestedScrollingEnabled="false"
Within your RecyclerView
I am trying to create a recyclerview that grows in size with additional items until a certain max height and then fades away. I understand that Constraint layout is the right way to go here and I swear this was already working a month or so ago and then the Recyclerview stopped caring about its constraint and being visible beyond 280dp (see picture). here is my code. I am certain this was already working, I dont know if google changed something to implementation "androidx.constraintlayout:constraintlayout:2.0.0-beta4" or if I am slowly going mad. Maybe someone knows how to fix this. maybe its down to the implementation being beta. any help will be much appreciated.
<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"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
tools:context=".MainActivity"
tools:showIn="#layout/activity_tilemap"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintHeight_default="wrap"
app:layout_constraintHeight_max="280dp"
app:layout_constrainedHeight="true"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:fadingEdge="horizontal"
android:fadingEdgeLength="30dp"
android:fillViewport="true"
android:requiresFadingEdge="vertical"
android:id="#+id/rv_comms"
android:padding="0dp"
android:clipToPadding="false" />
</androidx.constraintlayout.widget.ConstraintLayout>
I just found the culpable code. turns out the recording buttons in my adapter are to blame. they included the following method that enabled the button to overflow the views of other adapter items (when pressed the button will grow like in whatsapp, see image bellow)
public void setClip(View v) {
if (v.getParent() == null) {
return;
}
if (v instanceof ViewGroup) {
((ViewGroup) v).setClipChildren(false);
((ViewGroup) v).setClipToPadding(false);
}
if (v.getParent() instanceof View) {//this part is to blame
setClip((View) v.getParent());
}
}
the recursive nature of the method (seen already commented out in the last 4 lines of the method) basically set clipToPadding and clipChildren to false for the entire hierarchy of views from the record button upwards wich lead to the weird overflow. I ended up manually setting clipToPadding and clipChildren to false for only those views that were concerned with the adapter (this allowed me to retain the cool recording button overflow animation without having the fading edge issue) and now it looks pretty (see below).
I have a recyclerview under a nestedsrcollview. I want to implement the scrolling to a specific position for the recyclerview but I am having difficulty with it. The xml code is:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView 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"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".HomeFragment"
android:background="#ffffff"
android:fillViewport="true"
android:id="#+id/nestedscrollview"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="1dp"
android:orientation="vertical"
>
<<some other layouts>>
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/home_blog_list"
android:layout_marginBottom="52dp"
/>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
I want to implement the scrolltoposition for the home_blog_list recyclerview to a position (say 26). How to do it? P.S.- I have set nestedscrollingenabled to false for the home_blog_list.Please note that I want to scroll the nestedscrollview to a specific row of the recyclerview. I dont want the case where the recyclerview is scrolled only. Thanks in advance!
I stumbled into the same issue and I found an easy solution that does not require to refactor by using the library suggested by asif-ali.
On my current project, I have a NestedScrollView that holds a ConstraintLayout.
This ConstraintLayout contains a complex header made of multiple view, and then my RecyclerView.
Much like you, I needed the whole thing to be scrollable.
That said, when the user wishes to see an item from the particular RecyclerView, you would normally call:
RecyclerView#smoothScrollToPosition(int position)
But since the RecyclerView's height is set to wrap_content the full list is displayed, with as many ViewHolders as there are items in its adapter.
Granted, we do not benefit from recycling, but then why would we need a ScrollView ? Using #asif-ali solution might surely bring recycling optimizations but that's not the point here.
So, we have a fully laid-out RecyclerView. In order to scroll to a particular item (ViewHolder#itemView) position, you can do as followed:
final void smoothScrollToPosition(final int position) {
final ViewHolder itemViewHolder = this.recyclerView.findViewHolderForAdapterPosition(position);
// at this point, the ViewHolder should NOT be null ! Or else, position is incorrect !
final int scrollYTo = (int) itemViewHolder.itemView.getY();
// FYI: in case of a horizontal scrollview, you may use getX();
this.nestedScrollView.smoothScrollTo(
0, // x - for horizontal
scrollYTo
);
}
That's it !
It might be possible child is not fully visible after doing so (in my test case) so I'd suggest to add half the height of the itemView to the scrollYTo variable to make sure the nestedScrollView will scroll enough. If you do so, you might also want to check out in which direction the nestedScrollView must scroll to (either up, then remove half height, or down, then add half height.
[EDIT 1]
After further testing and research, based on this answer: https://stackoverflow.com/a/6831790/3535408 it is actually better and simpler to target the itemView.getBottom. On my app, it works flawlessly.
So the updated code looks as followed:
final void smoothScrollToPosition(final int position) {
final ViewHolder itemViewHolder = this.recyclerView.findViewHolderForAdapterPosition(position);
// at this point, the ViewHolder should NOT be null ! Or else, position is incorrect !
// FYI: in case of a horizontal scrollview, you may use getX();
this.nestedScrollView.smoothScrollTo(
0, // x - for horizontal
itemViewHolder.itemView.getBottom()
);
}
I think this is what you want, have a look: link
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
}
I am using a RecyclerView to show a list of videos.
Each item in the list holds Video and SeekBar (and more stuff actually but not relevant here) in a RelativeLayout, as follows:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/performance"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.my.company.VideoView
android:id="#+id/the_video"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:keepScreenOn="true"
android:scaleType="fitCenter" />
<SeekBar
android:id="#+id/the_seekbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:alpha="1.0"
android:maxHeight="#dimen/seekbar_height"
android:minHeight="#dimen/seekbar_height"
android:padding="0dp"
android:progressDrawable="#drawable/my_progressbar"
android:thumb="#drawable/my_progressbar_circle"
android:translationY="-5dp" />
</RelativeLayout>
As you can see I added a android:translationY property that brings the SeekBar up a little so it would be partially positioned on top of the previous cell, i.e. the previous Video.
However it remains partially hidden. I can only see the part that is in the RelativeLayout in which is it declared.
I tried calling bringToFront() on the seekbar and on the RelativeLayout (performance) itself - but that did not help.
Probably the question is not relevant to RecyclerView only. Being somewhat new in android dev I am not sure if I can place a view that is declared inside a RelativeLayout to show up outside of its borders.
Hope I was clear, need your help. Tx.
By default, every view is clipped to its parent size.
You could try to disable this clipping, by adding this in your RelativeLayout XML attributes:
android:clipChildren="false"
android:clipToPadding="false"
or in code
viewGroup.setClipChildren(false);
viewGroup.setClipToPadding(false);
In your case, it seems that either RecyclerView or LinearLayoutManager consider that previous items should be displayed over following ones. One way could be to use RecycleView decoration to overlap :
recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
private final static int vertOverlap = -10;// TODO : do not forget to convert dp in pixels
#Override
public void getItemOffsets (Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
outRect.set(0, vertOverlap, 0, 0);
}
});
So, you would not need to use translationY on your SeekBar, but rather to add some paddingTop to your VideoView :
android:paddingTop="5dp"
That way, I think you could hide the SeekBar if needed, and cell overlapping would not be visible.
Follow this answer with same case only the difference is it is overlapping next item of Recycleview https://stackoverflow.com/a/40603773/3839498