Decorate only the absolute first item of RecyclerView - android

I need to add a separator only to the absolute first item of my recycle view.
I have already read How to selectively decorate RecyclerView items , and i understand that
The (onDraw) method loops over all the child views currently in the RecyclerView visible on the screen.
and my problem is exactly that. since it executes every time the views in the RecycleView change, even if i am able to locate and decorate only the first item, as soon as i scroll down, the decoration shifts to the current first item.
In that link the selection is done by the method isDecorated which looks at the instance of the current child's ViewHolder. My guess is that the guy wanted to decorate with respect to the ViewHolder type, which is not my problem since i have only one type of element in my RecyclerView
This is my DividerItemDecoration.java
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private Drawable mDivider;
public DividerItemDecoration(Drawable divider) {
mDivider = divider;
}
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
RecyclerView.ViewHolder holder = parent.getChildViewHolder(view);
if (parent.getChildAdapterPosition(view) == 1) {
outRect.top = outRect.top + mDivider.getIntrinsicHeight();
}
return;
}
#Override
public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
int dividerLeft = parent.getPaddingLeft();
int dividerRight = parent.getWidth() - parent.getPaddingRight();
int childCount = parent.getChildCount();
for (int i = 0; i < childCount - 1; i++) {
if(i==0 ){
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
int dividerTop = child.getBottom() + params.bottomMargin;
int dividerBottom = dividerTop + mDivider.getIntrinsicHeight();
mDivider.setBounds(dividerLeft, dividerTop, dividerRight, dividerBottom);
mDivider.draw(canvas);
}
}
}
}
Please consider that i have searched all day and could only find examples and gists to add decorator to every item, to selectively add it based on the type, but nothing really tackling with the absolute position.
Also, please no libraries like https://github.com/yqritc/RecyclerView-FlexibleDivider, i want to learn , not to copy.

You can check if it's the first element,
Here's an example:
Just implement getItemViewType(), and take care of the viewType parameter in onCreateViewHolder().
So you do something like:
#Override
public int getItemViewType(int position) {
if (position == 0)
return 1;
else
return 2;
}
then in onCreateViewHolder inflate your different layout according to your viewType.
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == 1) {
// inflate your first item layout & return that viewHolder
} else {
// inflate your other item layout & return that viewHolder
}
}

Related

eliminate divider on top of the first item in the RecyclerView

I have a RecyclerView with an header on top( namely a title, a TextView that describe the content of the RecyclerView)
Now I combined two different ViewHolders with some logic into the Adapter to obtain this effect, but I have an unexpected result.
The recyclerView hava to have dividers, but I have a line I want to eliminate between the TextViewand the first Item of the `RecyclerView:
In other words I need to eliminate only the top divider of the RecyclerView,
the first item, because I want that between the TextView on top and the list below there is not separation, the other items instead I expect they are separated as I obtained
This post shows how to eliminate the last row divider of a RV, but i need the first top line and I have no idea how I can adapt this snippet to my use case, or if I should create a new class.
In the RecyclerView.ItemDecoration I want to identify the first view in the RecyclerView and not draw a decoration for it. I will also want to not reserve any space for the decoration since it is not drawn. This necessitates an override of getItemOffsets().
Here is some code that applies a decoration to the bottom of all RecyclerView items except the first and the last.
public class DividerItemDecorator extends RecyclerView.ItemDecoration {
private Drawable mDivider;
public DividerItemDecorator(Drawable divider) {
mDivider = divider;
}
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int pos = parent.getChildAdapterPosition(view);
if (pos != 0 &&
pos != parent.getLayoutManager().getItemCount() - 1) {
outRect.bottom = mDivider.getIntrinsicHeight();
}
}
#Override
public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
int dividerLeft = parent.getPaddingLeft();
int dividerRight = parent.getWidth() - parent.getPaddingRight();
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
int pos = parent.getChildAdapterPosition(child);
if (pos != 0 &&
pos != parent.getLayoutManager().getItemCount() - 1) {
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
int dividerTop = child.getBottom() + params.bottomMargin;
int dividerBottom = dividerTop + mDivider.getIntrinsicHeight();
mDivider.setBounds(dividerLeft, dividerTop, dividerRight, dividerBottom);
mDivider.draw(canvas);
}
}
}
}
Here is what this looks like. I have exaggerated the dividers so they would stand out.

espresso checking text in a recycler view header added as item decoration

so I have a recycler view, and I needed to add items to it as sections or headers. I'm achieving this using recycler views ItemDecorations onDrawOver method, so writing it to a canvas and inserting it like this
private void drawHeader(Canvas c, View child, View headerView) {
c.save();
if (sticky) {
c.translate(0, Math.max(0, child.getTop() - headerView.getHeight()));
} else {
c.translate(0, child.getTop() - headerView.getHeight());
}
headerView.draw(c);
c.restore();
}
#Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
if (headerView == null) {
headerView = inflateHeaderView(parent);
header = (TextView) headerView.findViewById(R.id.title);
fixLayoutSize(headerView, parent);
}
CharSequence previousHeader = "";
for (int i = 0; i < parent.getChildCount(); i++) {
View child = parent.getChildAt(i);
final int position = parent.getChildAdapterPosition(child);
String title = sectionCallback.getSectionHeader(position);
header.setText(title);
if (!previousHeader.equals(title) || sectionCallback.isSection(position)) {
drawHeader(c, child, headerView);
previousHeader = title;
}
}
}
private View inflateHeaderView(RecyclerView parent) {
return LayoutInflater.from(parent.getContext())
.inflate(R.layout.request_sticky_header, parent, false);
}
I'm now writing tests for this but it crashes with 'No views in hierarchy found matching: with text:' I can't see the text in the heirarchy and I'm wondering if its because the layout is drawn to the canvas, can anyone help me with a way to check if the view has been added?
btw the view is there i can see it i just want a test thats proves it, if its not possible ill rely on manual checking and screenshots many thanks

Modify selected item in Numberpicker Android

I'm trying to figure some way to achieve the next kind of view. At the moment I have tried to create a Listview and just make bigger the selected item. But I cannot make the selected item always be in the middle of my view. So now I'm trying to get this with a numberpicker.
But I didn't find any way to hide the divider bar, and make different the selected item and the rest of the view. The idea is get something like in the bottom image.
I think that the ListView may be more configurable than the NumberPicker.
What you can do is use different row layouts dependind if it is the middle one or the others, so your getView(...) method would look like this:
public View getView(int position, View convertView, ViewGroup parent) {
if (position == 1) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.focused_layout, parent, false);
// Do whatever with this view
} else {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.not_focused_layout, parent, false);
// Do whatever with this view
}
return convertView;
}
This way you can customize both layouts both in XML and code. Yo can change the condition if you want the "special" item any other way.
Following is a number picker with custom display values:
final NumberPicker aNumberPicker = new NumberPicker(context);
List<Integer> ids = getIds();
aNumberPicker.setMaxValue(ids.size()-1);
aNumberPicker.setMinValue(0);
mDisplayedIds = new String[ids.size()];
for (int i = 0; i < ids.size(); i++) {
mDisplayedIds[i] = "Nombre"+String.valueOf(ids.get(i)) ;
}
aNumberPicker.setDisplayedValues(mDisplayedIds);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(50, 50);
RelativeLayout.LayoutParams numPickerParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
numPickerParams.addRule(RelativeLayout.CENTER_HORIZONTAL);
relativeLayout.setLayoutParams(params);
relativeLayout.addView(aNumberPicker, numPickerParams);
Also, you can check out some open source library like this one AndroidPicker
You can implement this using RecyclerView with one Holder for Normal Item and one Holder for Selected Item.
Inside your RecyclerView Adapter
private static int SELECTED_ITEM_POSITION = 2;
private static int NORMAL_ITEM = 1;
private static int SELECTED_ITEM = 2;
#Override
public int getItemViewType(int position)
{
if(position == SELECTED_ITEM_POSITION)
return SELECTED_ITEM;
else
return NORMAL_ITEM;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
if(viewType == SELECTED_ITEM)
{
YourSelectedViewHolder selectedViewHolder = (YourSelectedViewHolder)layoutInflater.inflate(R.layout.selected_item_layout, parent, false);
return selectedViewHolder;
}
else //viewType == NORMAL_ITEM
{
YourNormalViewHolder normalViewHolder = (YourNormalViewHolder)layoutInflater.inflate(R.layout.normal_item_layout, parent, false);
return normalViewHolder;
}
}
I wanted to achieve a pretty similar effect on one of my project, where I wanted the middle item of my recycler view to be more prominent.
In my case, that said item is only z-translated to give an impression of focus, but the result is pretty similar to what you're describing.
I'll post my code here, in case it could help you go in the right direction :
//We're on the onCreateView in a fragment here
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
//First I find the first visible element
int firstVisiblePosition = mLayoutManager.findFirstVisibleItemPosition();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (firstVisiblePosition != -1) {
int lastVisiblePosition = mLayoutManager.findLastVisibleItemPosition();
int itemHeight = mLayoutManager.getChildAt(0).getMeasuredHeight();
int itemTop = mLayoutManager.getChildAt(0).getTop();
//We use a '+' as itemTop will be negative
int delta = itemHeight + itemTop;
int currentItemToBeFocused = (delta < (itemHeight / 2)) ? 1 : 0;
//Reset the z-translation of other items to 0
for (int i = 0, last = (lastVisiblePosition - firstVisiblePosition); i <= last; ++i) {
if (mLayoutManager.getChildAt(i) != null) {
mLayoutManager.getChildAt(i).setTranslationZ(0);
}
}
//And set the z-translation of the current "centered" item
if (mLayoutManager.getChildAt(currentItemToBeFocused) != null) {
mLayoutManager.getChildAt(currentItemToBeFocused).setTranslationZ(10);
}
}
}
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
});

Android RecyclerView finding out first and last view on ItemDecoration

Here is my scenario
I have to add space on first and last item of Recycler View
I am doing so using ItemDecoration
recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int totalHeight = parent.getHeight();
int viewHeight = getResources().getDimensionPixelOffset(R.dimen.breadcrumb_btn_height);
int padding = (totalHeight - viewHeight) / 2;
//to position view on middle
if (view.getTag() instanceof Integer) {
int position = (int) view.getTag();
if (position == 0) {
//first position
outRect.set(padding, padding, 0, padding);
} else if (position == linearLayoutManager.getItemCount() - 1) {
//last position
outRect.set(0, padding, padding, padding);
} else {
outRect.set(0, padding, 0, padding);
}
}
}
});
Here I have
LinearLayoutManager with Horizontal Orientation
I am adding position tag on RecyclerView Adapter in the following manner
#Override
public void onBindViewHolder(BreadCrumbViewHolder holder, int position) {
holder.setPosition(position);
}
Is there any other way to access RecyclerView first and last child on ItemDecoration so that later on removing and adding items won't have any problem.
Is there any other way to access RecyclerView first and last child on
ItemDecoration so that later on removing and adding items won't have
any problem.
Yes. You can achieve what you want only by implementing your own ItemDecoration.
To get the current item/child position:
int position = parent.getChildAdapterPosition(view);
To get the number of items/childs:
int itemCount = state.getItemCount();
With this you can add a space on the first and last child of your RecyclerView.
Now changing your code for this approach you get:
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
//your padding...
final int itemPosition = parent.getChildAdapterPosition(view);
if (itemPosition == RecyclerView.NO_POSITION) {
return;
}
final int itemCount = state.getItemCount();
/** first position */
if (itemPosition == 0) {
outRect.set(padding, padding, 0, padding);
}
/** last position */
else if (itemCount > 0 && itemPosition == itemCount - 1) {
outRect.set(0, padding, padding, padding);
}
/** positions between first and last */
else {
outRect.set(0, padding, 0, padding);
}
}
Bonus: If you are looking to give padding to all items of the RecyclerView take a look at the Gist I've created: https://gist.github.com/ryanamaral/93b5bd95412baf85a81a

Listview - Footer at the bottom of screen

I have a ListView with a footer added with listview.addFooterView(footerView);
All works as expected excepted in one case: when my listview's items doesn't fill the whole screen, I would like the footer to be at the bottom of the screen, instead of being in the middle. Is there a way to do this easily? Or should I change my layout?
Thanks
EDIT: that might help (this is what I want)
If you want it to always be at the bottom of the screen, no matter how long your ListView is, then get rid of listview.addFooterView(footerView); and use a RelativeLayout. Give yourListView` the property
android:layout_alignParentTop="true"
and give the property to your footer
android:layout_alignParentBottom="true"
If this doesn't solve your problem then please be a little more specific about what you want and provide a picture of what you want if possible.
Edit
After reading the comments this might work. There might be an easier way but you could do something like
listView.post(new Runnable()
{
public void run()
{
int numItemsVisible = listView.getLastVisiblePosition() -
listView.getFirstVisiblePosition();
if (itemsAdapter.getCount() - 1 > numItemsVisible)
{
// set your footer on the ListView
}
else
{
footerView.setVisibility(View.VISIBLE);
}
}
footerView would be a custom layout that you would create with the properties I referenced above. This should set that to visible if the items aren't more than can fit on the screen. If they are more than can fit then you apply the footer view on the ListView as you are now. This might not be the best way but its the first thing that comes to mind. You would run this code just before you set the Adapter.
You cannot use ListView footer as footer for the whole layout.
You're better off with RelativeLayout as root element for your layout, and then a direct child of it containing the footer view with the attribute:
android:layout_alignParentBottom="true"
In addition to #codeMagic response, you could add a listener to check when your adapter gets updated and then update the footer
registerDataSetObserver(new DataSetObserver() {
#Override
public void onChanged() {
super.onChanged();
updateSmartFooter();
}
});
where updateSmartFooter is the function he described
private void updateSmartFooter {
listView.post(new Runnable()
{
public void run()
{
int numItemsVisible = listView.getLastVisiblePosition() -
listView.getFirstVisiblePosition();
if (itemsAdapter.getCount() - 1 > numItemsVisible)
{
// set your footer on the ListView
}
else
{
footerView.setVisibility(View.VISIBLE);
}
}
}
}
After spent a lot of time to research, I found the best solution for it.
Please have a look at: https://stackoverflow.com/a/38890559/6166660
and https://github.com/JohnKuper/recyclerview-sticky-footer
For details:
Create a StickyFooterItemDecoration extends RecyclerView.ItemDecoration like the example code below.
After that, set ItemDecoration to your recyclerView:
recyclerListView.addItemDecoration(new StickyFooterItemDecoration());
---------------------------------------
public class StickyFooterItemDecoration extends RecyclerView.ItemDecoration {
private static final int OFF_SCREEN_OFFSET = 5000;
#Override
public void getItemOffsets(Rect outRect, final View view, final RecyclerView parent, RecyclerView.State state) {
int adapterItemCount = parent.getAdapter().getItemCount();
if (isFooter(parent, view, adapterItemCount)) {
if (view.getHeight() == 0 && state.didStructureChange()) {
hideFooterAndUpdate(outRect, view, parent);
} else {
outRect.set(0, calculateTopOffset(parent, view, adapterItemCount), 0, 0);
}
}
}
private void hideFooterAndUpdate(Rect outRect, final View footerView, final RecyclerView parent) {
outRect.set(0, OFF_SCREEN_OFFSET, 0, 0);
footerView.post(new Runnable() {
#Override
public void run() {
parent.getAdapter().notifyDataSetChanged();
}
});
}
private int calculateTopOffset(RecyclerView parent, View footerView, int itemCount) {
int topOffset = parent.getHeight() - visibleChildsHeightWithFooter(parent, footerView, itemCount);
return topOffset < 0 ? 0 : topOffset;
}
private int visibleChildsHeightWithFooter(RecyclerView parent, View footerView, int itemCount) {
int totalHeight = 0;
int onScreenItemCount = Math.min(parent.getChildCount(), itemCount);
for (int i = 0; i < onScreenItemCount - 1; i++) {
totalHeight += parent.getChildAt(i).getHeight();
}
return totalHeight + footerView.getHeight();
}
private boolean isFooter(RecyclerView parent, View view, int itemCount) {
return parent.getChildAdapterPosition(view) == itemCount - 1;
}
}

Categories

Resources