I have a RecyclerView which uses a simple line separator Decoration.
The decoration is fine except when a NEW item is added to the bottom of the recycler and scrolled into view, the decoration is initially drawn in it's final location and then the view underneath scrolls into place, but the decoration doesn't scroll.
The desired behavior would be for the decoration to be drawn in it's starting location and then scroll into position along with the new item.
Here's the relevant portions of the Decoration:
class NormalDecoration extends RecyclerView.ItemDecoration {
private int spacing;
private Drawable drawable;
NormalDecoration(Context context) {
spacing = context.getResources().getDimensionPixelOffset(R.dimen.chat_separator_height);
drawable = ContextCompat.getDrawable(context, R.drawable.chat_divider);
}
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
if (applySpacing(view, parent)) {
outRect.top += spacing;
}
}
boolean applySpacing(View view, RecyclerView parent) {
int position = parent.getChildAdapterPosition(view);
return position != -1 && position < mItems.size();
}
#Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
int dividerLeft = parent.getPaddingLeft();
int dividerRight = parent.getWidth() - parent.getPaddingRight();
for(int index = parent.getChildCount() - 1 ; index >= 0 ; --index) {
View view = parent.getChildAt(index);
if(applySpacing(view, parent)) {
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
int dividerTop = view.getBottom() + params.bottomMargin;
int dividerBottom = dividerTop + spacing;
drawable.setBounds(dividerLeft, dividerTop, dividerRight, dividerBottom);
drawable.draw(c);
}
}
}
}
My Adapter contains the following helper:
class Adapter {
public void add(final int index, final IChatMessageItem item) {
mItems.add(index, item);
notifyItemInserted(index);
}
}
And here's how I add the item to the recycler that results in unacceptable scrolling behavior:
...
adapter.add(index, item);
layout.scrollToPosition(index);
...
You need to use view.getTranslationX(), getTranslationY(), and getAlpha() respectively to animate the decoration along with the view moving.
If you just draw a divider, you might want to use the official support decoration which also does the above mentioned.
I also wrote an article about this in detail here.
Related
I have a validate button inside each item of a recyclerView in order to change the color to green of the current item. When a new item is added in the recyclerview , I want to set the default background (no background color).
I've tried inside the BindView(position) function so by default when a new item is added in the itemsList, the color of the current element (item 0) is green whether I clicked on validate or not and that's not what I want.
I've also tried in the onBindViewHolder function but it doesnt work.
How can I change the color of this item in this recyclerview and this color remains the same whithout considering the index changing in the List if a new item is added?
I want that each new item of this recyclerview to be in the default color (background color white or no background color) and the item +n to be in the color of the corresponding status (validated = green , reschedulded = grey)
Once the validated button has been clicked I want the item to remains in read only mode.
Here is the code :
The adapter:
private Context context;
private List<RideObject> itemList;
public TestListeAdapter(List<RideObject> itemList, Context context) {
this.itemList = itemList;
this.context = context;
}
#NotNull
#Override
public TestListeAdapter.TestListeViewHolders onCreateViewHolder(ViewGroup parent, int viewType) {
View layoutView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_testliste, null, false);
RecyclerView.LayoutParams lp = new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
layoutView.setLayoutParams(lp);
return new TestListeAdapter.TestListeViewHolders(layoutView);
}
#RequiresApi(api = Build.VERSION_CODES.M)
#Override
public void onBindViewHolder(#NotNull TestListeAdapter.TestListeViewHolders holder, final int position) {
holder.bindView(position);
if (holder.validated){
holder.mCard.setCardBackgroundColor(ContextCompat.getColor(context.getApplicationContext(), R.color.teal_700));
}
the viewholder Class :
class TestListeViewHolders extends RecyclerView.ViewHolder {
//MyClickListener listener;
TextView rideId;
ImageView mCheck;
ImageView mreschedulded;
CardView mCard;
TestListeViewHolders(View itemView) {
super(itemView);
mCheck = itemView.findViewById(R.id.validate);
mreschedulded= itemView.findViewById(R.id.reschedulded);
mCard = itemView.findViewById(R.id.card_view);
}
private void bindView(int pos) {
RideObject item = itemList.get(pos);
mCheck.setOnClickListener(view -> {
item.setRDVHour(timePicker.getHour());
item.setRDVMinute(timePicker.getMinute());
validated = true;
item.setValider(true);
item.confirmDriver();
//if (itemList.size() == 1){
//mCard.setCardBackgroundColor(ContextCompat.getColor(context.getApplicationContext(), R.color.teal_700));
//itemView.setBackgroundColor(ContextCompat.getColor(context.getApplicationContext(),R.color.teal_700));
});
mreschedulded.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
timePicker.setEnabled(true);
itemView.setBackgroundColor(ContextCompat.getColor(context.getApplicationContext(),R.color.grey));
}
});
}
}
In the MainActivity:
resultsTestList.add(0, mCurrentRide);
mTestListAdapter.notifyDataSetChanged()
Ive tried the code in the answer but it doesnt work . Currently, the setbackgroundcolor applies just to the first element of the list. I would want that if the first element (item0) goes to second element (item1) the item keep his color background which is not the case with the given answer code
When binding RecyclerView items, you have to think of it as that you need to change the ViewHolder completely disregarding the previous state.
The view holder should not hold key information like validated.
Whether the view holder stays in the validated state or not should depend on the item it is being bind to.
#RequiresApi(api = Build.VERSION_CODES.M)
#Override
public void onBindViewHolder(#NotNull TestListeAdapter.TestListeViewHolders holder, final int position) {
holder.bindView(position);
}
...
private void bindView(int pos) {
RideObject item = itemList.get(pos);
if (item.validated) {
mCard.setCardBackgroundColor(...R.color.teal_700));
else {
mCard.setCardBackgroundColor(...R.color.grey));
mCheck.setOnClickListener(view -> {
item.setRDVHour(timePicker.getHour());
item.setRDVMinute(timePicker.getMinute());
item.validated = true;
item.setValider(true);
item.confirmDriver();
});
mReschedulded.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
timePicker.setEnabled(true);
itemView.setBackgroundColor(ContextCompat.getColor(context.getApplicationContext(),R.color.grey));
}
});
}
}
Can anyone please please help me
I want to create a recycler view with 1st row scrolling horizontally and the rest should be scrolling vertically just like instagram home page.
I have looked up almost everything but no luck.
Please can anyone help.
In your adapter class
private static final int POSTER = 1; //for sliding item
private static final int CHILDGROUP = 2; //normal items
In getItemViewType()
#Override
public int getItemViewType(int position) {
if (position == 0 )
return POSTER;
else
return CHILDGROUP;
}
In onCreateViewHolder() check which item & inflate layout as per item
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == POSTER) {
View view = LayoutInflater.from(mContext).inflate(R.layout.listrow_auto_viewpager, parent, false);
return new PosterSliderHolder(view);
} else {
View view = LayoutInflater.from(mContext).inflate(R.layout.listrow_sub_category, parent, false);
return new GroupViewHolder(view);
}
}
In onBindViewHolder() check the item type & load the data
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder.getItemViewType() == POSTER) {
final PosterSliderHolder posterViewHolder = (PosterSliderHolder) holder;
//your logic for sliding item (horizontal recyclerview )
}
else{
final GroupViewHolder groupViewHolder = (GroupViewHolder) holder;
//normal list item
}
}
I have set the negative margin for items like this:-
public class ItemDecorator extends RecyclerView.ItemDecoration {
private final int mSpace;
public ItemDecorator(int space) {
this.mSpace = space;
}
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int position = parent.getChildAdapterPosition(view);
if (position != 0)
outRect.top = mSpace;
}
}
This causes top item to stack lowest and next item after the top item overlaps it. I want top item to overlap its next item and so on.
I need recyclerview to scroll it's last item to the middle of itself. I mean that i need las element of recyclerview with LinearLayoutManager not to stop at the bottom of recyclerview but scroll to the middle of it.
Is it a simple way to achieve this?
You can use a RecyclerView.ItemDecoration to put an extra bottom-margin on the last item. It'll end up looking something like this:
public class TopMarginDecoration
extends RecyclerView.ItemDecoration {
#Override
public void getItemOffsets(outRect, view, parent, state) {
int position = parent.getChildAdapterPosition(view);
if (position + 1 == parent.getAdapter().getItemCount()) {
// get height of recyclerview
// get height of child view
// calculate required space to center the child view
outRect.bottom += calculatedSpace;
}
}
}
You can use RecyclerView.addItemDecoration(…) in your Activity or Fragment to tie this together.
final LinearLayoutManager llm = new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false);
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (recyclerView.getAdapter().getItemCount() == (llm.findLastCompletelyVisibleItemPosition() + 1)) {
Toast.makeText(MainActivity.this, "Sholled", Toast.LENGTH_SHORT).show();
}
}
});
I'm trying to use ItemDecorator to add some separators into a RecyclerView. This is the fragment of the code that does it.
//...
public abstract C onInflateViewHolder(Context ctx);
public abstract void onBindViewHolder(C holder, int index);
#Override
public void onDraw(Canvas c, RecyclerView parent) {
if (mOrientation == VERTICAL_LIST) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
}
public void drawVertical(Canvas c, RecyclerView parent) {
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
for (int i = 0; i < parent.getChildCount(); i++) {
C view = onInflateViewHolder(mContext);
onBindViewHolder(view, i);
RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
//parent.addView(view.itemView, i, p);
view.itemView.draw(c);
}
}
//...
As you can see I tried using parent.addView(view.itemView, i, p);, parent.addView(view.itemView, i); and finally view.itemView.draw(c);.
This is the implementations:
mItemDecorator =
new GenericDecorator<ChatDateSeparatorViewHolder>(getActivity(), GenericDecorator.VERTICAL_LIST) {
#Override
public ChatDateSeparatorViewHolder onInflateViewHolder(Context ctx) {
View v = LayoutInflater.from(ctx).inflate(R.layout.chat_message_separator, null);
return new ChatDateSeparatorViewHolder(v);
}
#Override
public void onBindViewHolder(ChatDateSeparatorViewHolder holder, int index) {
if (index < mAdapter.getItemCount()-1) {
ChatMessage previous = mAdapter.getMessage(index);
ChatMessage current = mAdapter.getMessage(index+1);
long startTime = previous.getSendDate().getTime();
long endTime = current.getSendDate().getTime();
long diffTime = endTime - startTime;
long diffDays = diffTime / (1000 * 60 * 60 * 24);
if (diffDays > 0) {
holder.text.setText(Converters.format(current.getSendDate(), getActivity()));
} else {
holder.root.setVisibility(View.GONE);
}
} else {
holder.root.setVisibility(View.GONE);
}
}
};
By the way, C is a Recycler.ViewHolder, as you can see in the implementation.
All seems to be fine but It crashes when tries to add the views. The exceptions that I did get was NullpointerException when I used the addView with the LayoutParams ad at android.support.v7.widget.RecyclerView$LayoutParams.getViewPosition(RecyclerView.java:6957)
I just need to know how add programatically a View into a RecyclerView without using an adapter.
EDIT:
I'm trying to add separators in a RecyclerView, this separators will be views that I get from a ViewHolder, it's everything done, the only thing that I need to know is how add, programatically, the view (that I get from my ViewHolder) into the RecyclerView
Here is a screenshot of the mockup of the app, so you will have a better idea:
The line that says "Hoy" (today in spanish) is one of the separators.
You can do this by using your item View's tag
In your Adapter's onBindViewHolder, you can determine whether or not this was the last message for the day. If it was the last message of the day, then you can call something like holder.itemView.setTag("isLastForDay").
Then in your decorator, while looping through your RecyclerView's children, read each child's tag. If the tag contains "isLastForDay", then execute your "hoy" drawing logic.
This is how i frequently do this
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
recyclerView.setLayoutManager(layoutManager);
RecycleMarginDecoration recycleMarginDecoration = new RecycleMarginDecoration(this);
recyclerView.addItemDecoration(recycleMarginDecoration);
Add Decorator Class
public class RecycleMarginDecoration extends RecyclerView.ItemDecoration {
private int margin;
public RecycleMarginDecoration(Context context) {
/* Assign value from xml whatever you want to make as margin*/
margin = context.getResources().getDimensionPixelSize(R.dimen.padding_four);
}
#Override
public void getItemOffsets(
Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
outRect.set(margin, margin / 2, margin / 2, 0);
}
}