Hi I am using recycler view and apply ITEMTOUCHHELPER its working.
I apply Logic (direction == ItemTouchHelper.LEFT) then delete item.
All of these things work correctly.
But when I swipe right side and then swipe left side. It give dX value >0.which means swiping done on right side.
If I delete an item no issue If I leave it as it is and swipe again then this strange behavior start.
When I swipe multiple times from left side then it gives dX<0 means then it starts again working.
Here is my Implementation
private void initSwipe(){
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){
Cursor cursor = ((BookRecyclerAdapter)recyclerView.getAdapter()).getCursor();
cursor.moveToPosition(position);
int pageNo = cursor.getInt(cursor.getColumnIndex(BookMarkContract.AddsEntry.COLUMN_NAME_PAGE_NO));
dbHelper.deletePageNo(pageNo);
bookRecyclerAdapter.swapCursor(dbHelper.getAllBookMarks());
bookRecyclerAdapter.notifyItemRemoved(position);
bookRecyclerAdapter.notifyDataSetChanged();
}
else
return;
}
#Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
Bitmap icon;
Log.d("dX",""+dX);
if(actionState == ItemTouchHelper.ACTION_STATE_SWIPE){
if(dX>0)
{
dX=0;
}
View itemView = viewHolder.itemView;
float height = (float) itemView.getBottom() - (float) itemView.getTop();
float width = height / 3;
if(dX>0)
return;
else {
p.setColor(Color.parseColor("#D32F2F"));
RectF background = new RectF((float) itemView.getRight() + dX, (float) itemView.getTop(), (float) itemView.getRight(), (float) itemView.getBottom());
c.drawRect(background, p);
icon = BitmapFactory.decodeResource(getResources(), R.drawable.ic_rub);
RectF icon_dest = new RectF((float) itemView.getRight() - 2 * width, (float) itemView.getTop() + width, (float) itemView.getRight() - width, (float) itemView.getBottom() - width);
c.drawBitmap(icon, null, icon_dest, p);
}
}
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
};
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleItemTouchCallback);
itemTouchHelper.attachToRecyclerView(recyclerView);
}
Here is my log cat Snippet of when i Swipping left but it gives me dX >0
PROBLEM IN ONE LINE
SWIPPING LEFT SIDE GIVES dX >0
QUESTION
Why I am getting this strange behavior ? it feels like an app is hanging
But it is not hanging it is swiping right side even When I swipe LEFT.
If you only want to allow leftswipe, this is your solution:
#Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
//int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
int swipeFlags = ItemTouchHelper.START;
return makeMovementFlags(dragFlags, swipeFlags);
}
Here is what I think it is a more cleaner and simpler solution.
When you extend ItemTouchHelper.SimpleCallback in order to get the custom behavior, you should pass the directions for your items to be swiped or dragged, you don't need to override getMovementFlags since this method uses the drag and swipe directions we are about to pass. For example:
public class RecyclerItemTouchHelper extends ItemTouchHelper.SimpleCallback {
public RecyclerItemTouchHelper(int dragDirs, int swipeDirs) {
super(dragDirs, swipeDirs);
}
// ...
}
Now, in your activity or fragment, when you setup your recycler view you can specify the swiping and dragging behavior like this:
// 0 means no dragging at all and the 2nd parameters means only swipe left is allowed
RecyclerItemTouchHelper helper = new RecyclerItemTouchHelper(0, ItemTouchHelper.LEFT);
new ItemTouchHelper(helper).attachToRecyclerView(yourRecyclerView);
Now you can configure your dragging and swiping however you want. I hope it helps!
For more information you can take a look at the official documentation, there's pretty much the exact example you need:
https://developer.android.com/reference/android/support/v7/widget/helper/ItemTouchHelper.SimpleCallback
Related
I have an imageView and a textView inside a cardView.
The alpha of the cardView is set to .5f.
The cardView is used in a vertical recyclerView.
What I'm trying to do here is as the user scrolls through the reyclerView the alpha of the completely visible cardView should always change to 1f and for the non-completely visible cardViews alpha stays .5f.
There is only one completely visible cardView at a time.
Here is what I tried but it doesn't work.
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int center = recyclerView.getHeight() / 2;
View centerView = recyclerView.findChildViewUnder( recyclerView.getTop(), center);
int centerPos = recyclerView.getChildAdapterPosition(centerView);
if (prevCenterPos != centerPos) {
// dehighlight the previously highlighted view
View prevView =
recyclerView.getLayoutManager().findViewByPosition(prevCenterPos);
if (prevView != null) {
prevView.setAlpha(.5f);
}
// highlight view in the middle
if (centerView != null) {
prevView.setAlpha(1f);
}
prevCenterPos = centerPos;
}
}
As you are finding center point with this
int center = recyclerView.getHeight() / 2;
which is not proper way you should use:
mLayoutManager.findFirstCompletelyVisibleItemPosition()
try this:
val firstCompelteVisible = mLayoutManager.findFirstCompletelyVisibleItemPosition()
val centerView =
recyclerView.layoutManager!!.findViewByPosition(firstCompelteVisible)
if (prevCenterPos != centerPos) {
// dehighlight the previously highlighted view
val prevView =
recyclerView.layoutManager!!.findViewByPosition(prevCenterPos)
if (prevView != null) {
prevView.alpha = .5f
}
// highlight view in the middle
if (centerView != null) {
prevView!!.alpha = 1f
}
prevCenterPos = centerPos
}
I hope this will work.
I know how it can be applied to the toolbar w.r.t. any scrollView in Coordinator layout.
I wanna do the same thing with the button w.r.t. RecycleView.
Just like the bottomNav in the app Pinterest.
Thanks!
A simple way that comes to mind is to use the TranslateAnimation provided by Android and to check if the RecyclerView is being scrolled. Here is what you need to do
Add a scroll listener to the RecyclerView and check if the scrolling direction is downward or upward
YourRecyclerView.addScrollListener(new ScrollListener() {
someScrollListenerMethod() {
if (scrolling up) {
showButton();
} else {
hideButton();
}
}
);
Create an animation for sliding the button up and down.
slideDownAnimation = TranslateAnimation(0f, 0f, 0f, ScreenHeight)
slideDownAnimation.duration = 1000f //1 second
slideUpAnimation = TranslateAnimation(0f, 0f, ScreenHeight, 0f)
slideUpAnimation.duration = 1000f //1 second
Create the show and hide button methods which will show and hide the button
private void showButton() {
yourButton.visibility = View.VISIBLE;
yourButton.startAnimation(slideUpAnimation);
}
private void hideButton() {
yourButton.visibility = View.INVISIBLE;
yourButton.startAnimation(slideDownAnimation);
}
Just a note, the code shared in step 1 is not the actual library code (I could not remember the exact code) but you can easily find it out.
Here's the applied code as explained by Bilal. Just in case anybody need it,
private void buttonSlideAnimation() {
addButton = findViewById(R.id.bottom_add_button);
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int height = displayMetrics.heightPixels;
final TranslateAnimation slideDownAnimation = new TranslateAnimation(0f, 0f, 0f, height);
final TranslateAnimation slideUpAnimation = new TranslateAnimation(0f, 0f, height, 0f);
slideDownAnimation.setDuration(1000);
slideUpAnimation.setDuration(1000);
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(#NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
#Override
public void onScrolled(#NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (dy>0){
addButton.setVisibility(View.INVISIBLE);
addButton.startAnimation(slideDownAnimation);
} else {
addButton.setVisibility(View.VISIBLE);
addButton.startAnimation(slideUpAnimation);
}
}
});
}
How can i calculate the height of the first visible item in my recyclerview ?
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
switch (newState) {
case RecyclerView.SCROLL_STATE_IDLE:
Log.d("scroll_stop","The RecyclerView is not scrolling : ");
View firstItemView = mGLM.findViewByPosition(mGLM.findFirstVisibleItemPosition());
View mVizibleView= mPlm.findViewByPosition(mGLM.findFirstVisibleItemPosition());
}
});
But this give me the full height of the view
Taking the Size of any RecyclerView Item is pointless, If you getHeight() or getWidth() of any item it's just gonna give a Null Error and your app is gonna crash.
What you can do, Is to Use your ViewHolder from recyclerView Adapter and First set a Specific Height to any Item you want and Then use that Height somewhere Else.
Look at the Code Below, Maybe helps:
class myViewHolder extends RecyclerView.ViewHolder {
RemaltiveLayout itemLayout;
public myViewHolder(View itemView) {
super(itemView);
itemLayout = (RemaltiveLayout) itemView.findViewById(R.id.myItemLayout);
itemLayout.getLayoutParams().height = res.getDimensionPixelSize(R.dimen.height_value);
}
You should be able to use item at a specific location position as well
This is not gonna work with Width though, cause items are always being stretched to fill columns of layoutManager
Steps to get first item height:
Get the layout manager from the recyclerview reference in onScrolled()
Get the first the visible item position using layoutManager.findFirstVisibleItemPosition()
If fist visible item position is 0 then get the 0th child height layoutManager.getChildAt(0)
Sample code:
RecyclerView.OnScrollListener scrollListener = new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(#NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
if (layoutManager != null) {
int child0Height = 0;
int currentFirstVisibleItem = layoutManager.findFirstVisibleItemPosition();
if (currentFirstVisibleItem == 0) {
final View childAt0 = layoutManager.getChildAt(0);
if (childAt0 != null) {
child0Height = childAt0.getHeight();
}
}
}
}
};
I want to add animations to the itemView, however they only run if the item is currently moving. For example the item starts to fadeOut until the alpha is 0, but if the item stops moving, the animation pauses and the item only fades out halfway. After that, the item needs to start moving again for the animation to complete.
I know onDraw typically only gets called when something changes, but I guess the OS doesn't realize the style of the View is changed as well, so it doesn't call it.
I tried adding invalidate() at the end so I can force an update but it doesn't do anything.
How can I fix this?
#Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
if (actionState == ACTION_STATE_SWIPE) {
if (optionsState == OptionState.UPVOTE) {
drawUpvoteOption(c, viewHolder.itemView, dX);
} else if (optionsState == OptionState.DOWNVOTE) {
if (!isRightSwipeMaxed(dX)) drawDownvoteOption(c, viewHolder.itemView, dX);
else drawDownvoteOption(c, viewHolder.itemView, MAX_RIGHT_SWIPE_DX);
}
setTouchListener(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
if (dX == 0) {
//This only works if the item is moving
viewHolder.itemView.animate().alpha(1f);
optionsState = OptionState.NONE;
}
if (!isRightSwipeMaxed(dX)) {
viewHolder.itemView.setTranslationX(dX);
} else {
viewHolder.itemView.setTranslationX(MAX_RIGHT_SWIPE_DX);
//This only works if the item is moving
viewHolder.itemView.animate().alpha(0);
}
//Doesn't do anything
viewHolder.itemView.invalidate();
}
I had the same issue, invalidate() on the itemView did nothing.
You have to do the invalidate() on the parent recyclerView for that to work :
viewHolder.animatedRecyclerView.invalidate()
I have a RecyclerView with Expandable Child Views, when the child ViewGroup is clicked it inflates an amount of views animating the ViewGroup height from 0 to the measured viewgroup height, like the following gif:
The problem is: I'm calling smoothScrollToPosition on recyclerView, it smooth scroll to the view position, but it considers the current view height, which is still not expanded, in the above gif i'm touching on the under view of the recyclerview, which dont scroll to position because the view is already visible, but when i touch again (calling the smoothscrolltoposition again) it scroll the view to the correct position, because the view is already expanded.
Is there any approach to scroll the view to the top of screen or just scroll to make content visible?
For references:
This is the method called to inflate the views:
collapsible_content.removeAllViews();
for(int i = 0; i < 5; i++) {
View link_view = getLayoutInflater().inflate(R.layout.list_item_timeline_step_link, collapsible_content, false);
TextView text = (TextView) link_view.findViewById(R.id.step_link_text);
text.setText("Test");
collapsible_content.addView(link_view);
}
And this is my method to expand:
public void toggle() {
collapsible_content.measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
Animation a;
if (mExpanded) {
a = new ExpandAnimation(collapsible_content.getLayoutParams().height, 0);
} else {
a = new ExpandAnimation(collapsible_content.getLayoutParams().height, getMeasuredHeight());
}
a.setDuration(mAnimationDuration);
collapsible_content.startAnimation(a);
mExpanded = !mExpanded;
}
And the animation:
private class ExpandAnimation extends Animation {
private final int mStartHeight;
private final int mDeltaHeight;
public ExpandAnimation(int startHeight, int endHeight) {
mStartHeight = startHeight;
mDeltaHeight = endHeight - startHeight;
}
#Override
protected void applyTransformation(float interpolatedTime,
Transformation t) {
final int newHeight = (int) (mStartHeight + mDeltaHeight *
interpolatedTime);
collapsible_content getLayoutParams().height = newHeight;
if (newHeight <= 0) {
collapsible_content setVisibility(View.GONE);
} else {
collapsible_content setVisibility(View.VISIBLE);
}
collapsible_content requestLayout();
}
#Override
public boolean willChangeBounds() {
return true;
}
}
My solution was to constant check for view bottom within applyTransformation method, and compare it with RecyclerView height, if the bottom get higher than the RV height, i scroll by the diff values:
final int bottom = collapsible_content.getBottom();
final int listViewHeight = mRecyclerView.getHeight();
if (bottom > listViewHeight) {
final int top = collapsible_content.getTop();
if (top > 0) {
mRecyclerView.smoothScrollBy(0, Math.min(bottom - listViewHeight + mRecyclerView.getPaddingBottom(), top));
}
}
The trick was to use Math.min to get the view top, so it don't scroll up making the top not visible.
Solution based on ListViewAnimations
Add an animationlistener and start the scrolling of the recyclerview after the expanding animation is finished.