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();
}
}
});
Related
I have a recyclerView set up to work as a carousel view. As the items move they expand as they get close to the centre of the screen. The method to scale the views is called by overriding onScrolled(). My problem is when deleting an item the view is scrolled but onScrolled() is not called and the views do not scale. Is there a way to know when a view is removed and the animation is finished?
I can not override onItemRangeRemoved() because that is only called at the start of the delete.
#Override
public void onChanged() {
super.onChanged();
initEmptyView();
addOnScrollListener(new OnScrollListener() {
#Override
public void onScrolled(#NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
onScrollChanged();
}
});
}
public void onScrollChanged(){
for (int i = 0; i < getChildCount(); i++){
View child = getChildAt(i);
int childCenterX = (child.getLeft() + child.getRight()) / 2;
double spreadFactor = 150;
float scaleValue = getGaussianScale(childCenterX, 1f, 0.1f, spreadFactor);
child.setScaleX(scaleValue);
child.setScaleY(scaleValue);
}
int currentPosition = myLayoutManager.findFirstCompletelyVisibleItemPosition();
((TopUp) context).setCurrentPosition(currentPosition);
}
Found it.
recyclerView.setItemAnimator(new DefaultItemAnimator(){
#Override
public void onRemoveFinished(RecyclerView.ViewHolder item) {
super.onRemoveFinished(item);
}
});
when fragment is rendering first time its calling scroll listener of recyclerview causing bugs, here is my code
viewPagerDetail.setOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
int tab= linearVertical.findFirstCompletelyVisibleItemPosition();
if (tab>=0 && tab < tabLayoutDetail.getTabCount()) {
tabLayoutDetail.getTabAt(tab).select();
}
viewPagerDetail.getAdapter().notifyDataSetChanged();
}
});
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.
I have implemented a RecyclerView with inbox style swipe view. When swiped, I removed the list item using the method below:
public void removeItem(int position) {
countries.remove(position);
notifyItemRemoved(position);
}
Similarly when the FAB is pressed I add data using the method,
public void addItem(String country) {
countries.add(country);
notifyItemInserted(countries.size());
}
However, when I remove a data item by swiping, it is removed from the ArrayList and RecyclerView list, but when I add data by FAB the removed data is still displayed in the list. I checked the ArrayList data set. It is as intended.
In the above screenshot you can see the String Test is the newly added data. The data in last two row I already deleted. It gets randomly displayed.
The complete code of my Adapter and Activity.
public class DataAdapter extends RecyclerView.Adapter<DataAdapter.ViewHolder> {
private ArrayList<String> countries;
private TextView tv_country;
public DataAdapter(ArrayList<String> countries) {
this.countries = countries;
}
#Override
public DataAdapter.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.row_layout, viewGroup, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(DataAdapter.ViewHolder viewHolder, int i) {
tv_country.setText(countries.get(i));
}
#Override
public int getItemCount() {
return countries.size();
}
public void addItem(String country) {
countries.add(country);
notifyItemInserted(countries.size());
}
public void removeItem(int position) {
countries.remove(position);
notifyItemRemoved(position);
}
public class ViewHolder extends RecyclerView.ViewHolder{
public ViewHolder(View view) {
super(view);
tv_country = (TextView)view.findViewById(R.id.tv_country);
}
}
}
MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private ArrayList<String> countries = new ArrayList<>();
private DataAdapter adapter;
private RecyclerView recyclerView;
private RecyclerView.LayoutManager layoutManager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
}
private void initViews(){
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(this);
recyclerView = (RecyclerView)findViewById(R.id.card_recycler_view);
recyclerView.setHasFixedSize(true);
layoutManager = new LinearLayoutManager(getApplicationContext());
recyclerView.setLayoutManager(layoutManager);
adapter = new DataAdapter(countries);
recyclerView.setAdapter(adapter);
countries.add("Australia");
countries.add("India");
countries.add("United States of America");
countries.add("Germany");
countries.add("Russia");
adapter.notifyDataSetChanged();
ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
#Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return false;
}
#Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition();
if (direction == ItemTouchHelper.LEFT){
adapter.removeItem(position);
}
}
#Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
Paint p = new Paint();
Bitmap icon;
if(actionState == ItemTouchHelper.ACTION_STATE_SWIPE){
View itemView = viewHolder.itemView;
if(dX > 0){
p.setColor(Color.parseColor("#388E3C"));
c.drawRect((float) itemView.getLeft(), (float) itemView.getTop(), dX,(float) itemView.getBottom(), p);
icon = BitmapFactory.decodeResource(
getResources(), R.drawable.ic_edit_white);
float height = (float) itemView.getBottom() - (float) itemView.getTop();
float width = height / 3;
RectF dest = new RectF((float) itemView.getLeft() + width ,(float) itemView.getTop() + width,(float) itemView.getLeft()+ width+width,(float)itemView.getBottom() - width);
c.drawBitmap(icon,null,dest,p);
} else {
p.setColor(Color.parseColor("#D32F2F"));
c.drawRect((float) itemView.getRight() + dX, (float) itemView.getTop(),(float) itemView.getRight(), (float) itemView.getBottom(), p);
icon = BitmapFactory.decodeResource(getResources(), R.drawable.ic_delete_white);
float height = (float) itemView.getBottom() - (float) itemView.getTop();
float width = height / 3;
RectF dest = new RectF((float) itemView.getRight() - width - width ,(float) itemView.getTop() + width,(float) itemView.getRight() - width,(float)itemView.getBottom() - width);
c.drawBitmap(icon,null,dest,p);
}
}
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
};
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleItemTouchCallback);
itemTouchHelper.attachToRecyclerView(recyclerView);
}
#Override
public void onClick(View v) {
switch (v.getId()){
case R.id.fab:
adapter.addItem("Test");
Log.d("Raj",countries.toString());
break;
}
}
}
What I have tried
I have tried using notifyItemRangeChanged() like this:
public void removeItem(int position) {
countries.remove(position);
notifyItemRemoved(position);
notifyItemRangeChanged(position, countries.size());
}
I can't be sure that this will solve your problem, but one thing that is incorrect is how you are binding your data. tv_country should not be a part of the DataAdapter; it should be a part of each individual ViewHolder. One of the reasons we use the ViewHolder pattern is to maintain an easy reference to the views in each row.
Your bind method should end up similar to:
public void onBindViewHolder(DataAdapter.ViewHolder viewHolder, int i) {
viewHolder.tv_country.setText(countries.get(i));
}
And be sure to make tv_country a public field of your inner ViewHolder class.
public class ViewHolder extends RecyclerView.ViewHolder{
public TextView tv_country;
public ViewHolder(View view) {
super(view);
tv_country = (TextView) view.findViewById(R.id.tv_country);
}
}
Not sure I understood what is happening a 100%, but why are you actually trying to animate yourself? If you implement getItemId() and hasStableIds() if I remember correctly, you just should tell the recyclerview which element was removed or added (like you actually do).
Inside the onSwiped method, add viewHolder.setIsRecyclable(false);
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);
}
}