notifyItemRemoved() scrolls the recycler view but does not call onScrolled() - java

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);
}
});

Related

Handling scroll for a horizontal recyclerview inside a vertical recyclerview that has a swipe action

Below is screenshot of a portion of an item in the primary recyclerview. The light green tiles are a secondary horizontal recycler view inside the primary recycler view. The primary recycler view has a swipe action to delete (indicated partially as well). The problem is that whenever the user scrolls the secondary recycler view the swipe action is triggered.
Expected result is that the swipe action is triggered whenever the user swipes anywhere outside of the secondary horizontal recycler view and the scroll action is triggered whenever the user scrolls any of the horizontal recycler view item.
This is where I trigger the swipe action for the recycler view:
SwipeToDeleteCallback swipeToDeleteCallback = new SwipeToDeleteCallback(context) {
#Override
public void onSwiped(#NonNull RecyclerView.ViewHolder viewHolder, int i) {
final int position = viewHolder.getAbsoluteAdapterPosition();
adapter.removePlan(position);
}
};
ItemTouchHelper itemTouchhelper = new ItemTouchHelper(swipeToDeleteCallback);
itemTouchhelper.attachToRecyclerView(recyclerview);
And this is the swipe touch helper callback:
Context mContext;
private final Paint mClearPaint;
private final ColorDrawable mBackground;
private final int backgroundColor;
private final Drawable deleteDrawable;
private final int intrinsicHeight;
SwipeToDeleteCallback(Context context) {
mContext = context;
mBackground = new ColorDrawable();
backgroundColor = ContextCompat.getColor(context, R.color.fullWhite);
mClearPaint = new Paint();
mClearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
deleteDrawable = ContextCompat.getDrawable(mContext, R.drawable.icon_delete);
intrinsicHeight = 240;
}
#Override
public int getMovementFlags(#NonNull RecyclerView recyclerView, #NonNull RecyclerView.ViewHolder viewHolder) {
return makeMovementFlags(0, ItemTouchHelper.LEFT);
}
#Override
public boolean onMove(#NonNull RecyclerView recyclerView, #NonNull RecyclerView.ViewHolder viewHolder, #NonNull RecyclerView.ViewHolder viewHolder1) {
return false;
}
#Override
public void onChildDraw(#NonNull Canvas c, #NonNull RecyclerView recyclerView, #NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
View itemView = viewHolder.itemView;
int itemHeight = itemView.getHeight();
boolean isCancelled = dX == 0 && !isCurrentlyActive;
if (isCancelled) {
clearCanvas(c, itemView.getRight() + dX, (float) itemView.getTop(), (float) itemView.getRight(), (float) itemView.getBottom());
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
return;
}
mBackground.setColor(backgroundColor);
mBackground.setBounds(itemView.getRight() + (int) dX, itemView.getTop(), itemView.getRight(), itemView.getBottom());
mBackground.draw(c);
deleteDrawable.setTint(Color.RED);
int deleteIconTop = itemView.getTop() + (itemHeight - intrinsicHeight) / 2;
int deleteIconMargin = (itemHeight - intrinsicHeight) / 2;
int deleteIconLeft = itemView.getRight() - deleteIconMargin / 3 ;
int deleteIconRight = itemView.getRight();
int deleteIconBottom = deleteIconTop + intrinsicHeight;
deleteDrawable.setBounds(deleteIconLeft, deleteIconTop, deleteIconRight, deleteIconBottom);
deleteDrawable.draw(c);
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
private void clearCanvas(Canvas c, Float left, Float top, Float right, Float bottom) {
c.drawRect(left, top, right, bottom, mClearPaint);
}
#Override
public float getSwipeThreshold(#NonNull RecyclerView.ViewHolder viewHolder) {
return 0.7f;
}

recyclerView scrollListener is calling triggering with oncreateView

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();
}
});

RecyclerView fling and item click

I have a RecyclerView that has a grid of items. Upon clicking on an item, it highlights.
I also want that when the user swipes right the a 'next' method is called, and when the user swipes left, a 'previous' method is called.
However, the two don't work out with each other, as each intercepts the other's events.
How do I get them to work together?
This is my code:
RecyclerView Adapter
public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
myHolder = holder as MyView;
myHolder.mMainView.SetOnClickListener(this);
if (selected_position == position)
{
holder.ItemView.SetBackgroundColor(Color.LightGray);
}
else
{
holder.ItemView.SetBackgroundColor(Color.Transparent);
}
}
public void OnClick(View v)
{
int position = mRecyclerView.GetChildLayoutPosition((View)sender);
// Updating old as well as new positions
NotifyItemChanged(selected_position);
selected_position = position;
NotifyItemChanged(selected_position);
}
Fragment that contains the RecyclerView
calendarRecyclerView.SetOnTouchListener(this);
public bool OnTouch(View v, MotionEvent e)
{
switch (e.Action)
{
case MotionEventActions.Down:
x1 = e.GetX();
break;
case MotionEventActions.Up:
x2 = e.GetX();
float deltaX = x2 - x1;
if (Math.Abs(deltaX) > MIN_DISTANCE)
{
// Left to Right swipe action
if (x2 > x1)
{
NextMonth();
}
// Right to left swipe action
else
{
PreviousMonth();
}
}
break;
}
return false;
}
Because I put return false in the OnTouch event, the item's click event is fired. However, the MouseDown event doesn't fire in OnTouch, preventing swiping back detection (beacuse x1 is always 0).
OnTouch event gets called on the first click, and the OnClick gets called only on the second click
Because MotionEventActions.Down and OnClickconflict. As a workaround I suggest you to change the background color at the MotionEventActions.Down event.
Create your own click listener
Call the listener when you touch down your items.
The listener will callback to MainActivity to notify the item changed.
At the same time the touch event will called.
I have set the OnTouchListener in the viewholder :
public class MyViewHolder:RecyclerView.ViewHolder,IOnTouchListener
{
private TextView textView;
private MyItemClickListener mListener;
private Context myContext;
float x1 = 0;
float x2 = 0;
public TextView TextView { get { return textView; } }
public MyViewHolder(View v, MyItemClickListener mItemClickListener) : base(v)
{
textView = v.FindViewById<TextView>(Resource.Id.itemText);
mListener = mItemClickListener;
v.SetOnTouchListener(this);
}
public MyViewHolder(View v, MyItemClickListener mItemClickListener, Context myContext) : this(v, mItemClickListener)
{
this.myContext = myContext;
}
public bool OnTouch(View v, MotionEvent e)
{
switch (e.Action)
{
case MotionEventActions.Down:
x1 = e.GetX();
if (mListener != null)
{
mListener.OnItemClick(v, Position);
}
break;
case MotionEventActions.Up:
x2 = e.GetX();
float deltaX = x2 - x1;
if (Math.Abs(deltaX) > 5)
{
// Left to Right swipe action
if (x2 > x1)
{
NextMonth(v);
}
// Right to left swipe action
else
{
PreviousMonth(v);
}
}
break;
}
return true;
}
public Boolean NextMonth(View v)
{
Toast.MakeText(myContext, "NextMonth called", ToastLength.Short).Show();
return true;
}
public Boolean PreviousMonth(View v)
{
Toast.MakeText(myContext, "PreviousMonth called", ToastLength.Short).Show();
return true;
}
}
Defined the click interface :
public interface MyItemClickListener
{
void OnItemClick(View view, int postion);
}
set the click callback in the MainActivity to change the background color:
public class MainActivity : Activity,MyItemClickListener
{
RecyclerView mRecyclerView;
RecyclerView.LayoutManager mLayoutManager;
CustomAdapter mAdapter;
string[] dataSet;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
InitDataSet();
SetContentView(Resource.Layout.Main);
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView = FindViewById<RecyclerView>(Resource.Id.recyclerView);
mRecyclerView.SetLayoutManager(mLayoutManager);
mAdapter = new CustomAdapter(dataSet,this);
mAdapter.setOnItemClickListener(this);
mRecyclerView.SetAdapter(mAdapter);
//mRecyclerView.SetOnTouchListener(this);
}
public void InitDataSet()
{
dataSet = new string[60];
for (int i = 0; i < 60; i++)
{
dataSet[i] = "This is element #" + i;
}
}
public void OnItemClick(View view, int postion)
{
mAdapter.NotifyItemChanged(CustomAdapter.selected_position);
CustomAdapter.selected_position = postion;
mAdapter.NotifyItemChanged(postion);
}
}
}
Note: Keep your finger move fast, if the speed is slow enough the MotionEventActions.Down will not be called.
Github souce code
Screen shot:
Try this in your onClick
public void OnClick(View v)
{
int position = mRecyclerView.GetChildLayoutPosition((View)sender);
int oldPosition = selectedPosition;
selected_position = position;
// Updating old as well as new positions
NotifyItemChanged(oldPosition);
NotifyItemChanged(selected_position);
}
Notice that you have to change the selected position before updating both items

View Drag Helper helper

I am trying to get the ViewDragHelper to work with a vertical LinearLayout, - Container - which has
A ---- List View
B ---- horizontal linear layout
C ---- Support Map Fragment
as children views.
A has layout_height="0dp". Pulling down on B, should proportionally increase the height of A, there by revealing the contents on the ListView, thereby automatically re positioning B and resizing C in the process.
The following is the code for the Container LinearLayout.
public class MyLinearLayout extends LinearLayout {
private LinearLayout headerLayout;
private ListView filtersListView;
private ViewDragHelper viewDragHelper;
private int MARGIN;
public MyLinearLayout (Context context) {
super(context);
init();
}
public MyLinearLayout (Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MyLinearLayout (Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
viewDragHelper = ViewDragHelper.create(this, 1, new DragHelperCallback());
float GESTURE_THRESHOLD_DP = 10.0f;
float scale = getResources().getDisplayMetrics().density;
MARGIN = (int) (GESTURE_THRESHOLD_DP * scale + 0.5f);
}
#Override
protected void onFinishInflate() {
Log.i("Places", "onFinishInflate");
super.onFinishInflate();
headerLayout = (LinearLayout) findViewById(R.id.header);
filtersListView = (ListView) findViewById(R.id.filters_list);
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.i("Places", "onInterceptTouchEvent");
return viewDragHelper.shouldInterceptTouchEvent(ev);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
Log.i("Places", "onTouchEvent");
viewDragHelper.processTouchEvent(event);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, (int) event.getRawY());
layoutParams.setMargins(MARGIN, MARGIN, MARGIN, MARGIN);
filtersListView.setLayoutParams(layoutParams);
return true;
}
private class DragHelperCallback extends ViewDragHelper.Callback {
#Override
public boolean tryCaptureView(View child, int pointerId) {
Log.i("Places", "tryCaptureView " + (child == headerLayout));
return child == headerLayout;
}
#Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
Log.i("Places", "onViewPositionChanged " + changedView.getClass().getName());
super.onViewPositionChanged(changedView, left, top, dx, dy);
}
#Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
Log.i("Places", "onViewReleased " + releasedChild.getClass().getName());
super.onViewReleased(releasedChild, xvel, yvel);
}
}
}
I have looked at the the following.
ViewDragHelper: how to use it?
Your ViewDragHelper is a bit off. Specifically you're missing the clampViewPositionVertical() override, which is required to enable dragging of the view at all. Right now, your ViewDragHelper is actually doing no work. You are probably getting some motion because you are manipulating the LayoutParams directly for every onTouchEvent(), which will also cause some problems.
You likely want your callback code to look more like this:
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return viewDragHelper.shouldInterceptTouchEvent(ev);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
viewDragHelper.processTouchEvent(event);
return true;
}
private class DragHelperCallback extends ViewDragHelper.Callback {
#Override
public int clampViewPositionVertical(View child, int top, int dy) {
//Never go below fully visible
final int bottomBound = getHeight() - child.getHeight();
//Never go above the top
final int topBound = 0;
return Math.max(Math.min(top, bottomBound), topBound);
}
#Override
public boolean tryCaptureView(View child, int pointerId) {
//Capture touches to the header view
return child == headerLayout;
}
#Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
if (dy == 0) return;
//React to the position change of the header by modifying layout
LinearLayout.LayoutParams layoutParams = (LayoutParams) filtersListView.getLayoutParams();
layoutParams.height += dy;
filtersListView.setLayoutParams(layoutParams);
}
}
This sets the drag bounds of your "header view" to move freely between the top of the container view and the bottom (clamping it so it doesn't go off-screen). By itself, this will only move the header view. Moving your LayoutParams code into onViewPositionChanged() lets your top/bottom views react to the exact drag distance.
Two Warnings:
Don't add margins to your LayoutParams. The way you are using them to constantly modify proportions will cause the dragging to jump. If you need to inset the ListView content, use padding or another nested container.
Changing the layout of an entire view continuously like this can be very expensive (layout passes are not cheap, and you trigger one every time you modify LayoutParams). This may work for a very simple layout, but if your view gets more complex you will likely see performance suffer on older hardware.

Scrolling area in recyclerview

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();
}
}
});

Categories

Resources