I have used Vertical Viewpager
in my project.
But there are some issues,
when the page has lots of onclick() events, the scrolling is too hard
fling event doesn't change the page
I tried to use gesture detector, but it changed pages too fast without transitions (without invoking transformPage())
when I scroll the page, Sometimes onclick() events also get triggered
So I decided to use Recyclerview as Viewpager
with the help of PagerSnapHelper
.
It works fine. But the problem is ,
how to do the transition or animation when the item is changed (like I did in ViewPager)
For example, Zoomout transition or stack transition in Viewpager.
I tried stackLayoutManager but it takes more time to scroll and tried this related link .It doesn't work.
I researched for both issues How to reduce scroll speed for viewpager and how to do animations in recyclerview. I didn't get solution for it.
Can anyone help me!!! is it possible or I need to use any other widgets.
Edit
I tried #ADM's suggestion, it works fine but doesn't support PagerSnapHelper.
I have changed stacklayout manager in the above link but it doesn't support scrollToPosition() and PagerSnapHelper.
Code:
public class StackLayoutManager extends LinearLayoutManager {
private static final String TAG = "StackLayoutManager";
//the space unit for the stacked item
private int mSpace = 0;
/**
* the offset unit,deciding current position(the sum of {@link #mItemWidth} and {@link #mSpace})
*/
private int mUnit;
//item width
private int mItemWidth;
private int mItemHeight;
//the counting variable ,record the total offset including parallex
private int mTotalOffset;
//record the total offset without parallex
private int mRealOffset;
private ObjectAnimator animator;
private int animateValue;
private RecyclerView.Recycler recycler;
private int lastAnimateValue;
private int initialOffset;
private boolean initial;
private int mMinVelocityX;
private VelocityTracker mVelocityTracker = VelocityTracker.obtain();
private int pointerId;
private Method sSetScrollState;
RecyclerView recyclerView;
public StackLayoutManager(Context context) {
super(context, VERTICAL, false);
setAutoMeasureEnabled(true);
}
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
this.recycler = recycler;
detachAndScrapAttachedViews(recycler);
//got the mUnit basing on the first child,of course we assume that all the item has the same size
View anchorView = recycler.getViewForPosition(0);
measureChildWithMargins(anchorView, View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
mItemWidth = anchorView.getMeasuredWidth();
mItemHeight = getDecoratedMeasuredHeight(anchorView);
mUnit = mItemHeight;
//because this method will be called twice
initialOffset = mUnit;
mMinVelocityX = ViewConfiguration.get(anchorView.getContext()).getScaledMinimumFlingVelocity();
fill(recycler, 0);
}
@Override
public void onLayoutCompleted(RecyclerView.State state) {
super.onLayoutCompleted(state);
if (!initial) {
fill(recycler, initialOffset, false);
initial = true;
}
}
@Override
public void onAdapterChanged(RecyclerView.Adapter oldAdapter, RecyclerView.Adapter newAdapter) {
initial = false;
mTotalOffset = mRealOffset = 0;
}
private int fill(RecyclerView.Recycler recycler, int dy, boolean apply) {
return fillFromTop(recycler, dy);
}
public int fill(RecyclerView.Recycler recycler, int dy) {
return fill(recycler, dy, true);
}
private int fillFromTop(RecyclerView.Recycler recycler, int dy) {
if (mTotalOffset + dy < 0 || (mTotalOffset + dy + 0f) / mUnit > getItemCount() - 1)
return 0;
detachAndScrapAttachedViews(recycler);
mTotalOffset += dy;
int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (recycleVertically(child, dy))
removeAndRecycleView(child, recycler);
}
int curPos = mTotalOffset / mUnit;
int leavingSpace = getHeight() - (left(curPos) + mUnit);
int itemCountAfterBaseItem = leavingSpace / mUnit + 2;
int e = curPos + itemCountAfterBaseItem;
int start = curPos - 1 >= 0 ? curPos - 1 : 0;
int end = e >= getItemCount() ? getItemCount() - 1 : e;
int left = getWidth() / 2 - mItemWidth / 2;
//layout views
for (int i = start; i <= end; i++) {
View view = recycler.getViewForPosition(i);
float alpha = alpha(i);
addView(view);
measureChildWithMargins(view, 0, 0);
int top = (left(i) /* - ( 1 - scale ) * view.getMeasuredHeight() */ );
int right = view.getMeasuredWidth() + left;
int bottom = view.getMeasuredHeight() + top;
layoutDecoratedWithMargins(view, left, top, right, bottom);
view.setAlpha(alpha);
view.setScaleY(1);
view.setScaleX(1);
}
return dy;
}
private View.OnTouchListener mTouchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
mVelocityTracker.addMovement(event);
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (animator != null && animator.isRunning())
animator.cancel();
pointerId = event.getPointerId(0);
}
if (event.getAction() == MotionEvent.ACTION_UP) {
if (v.isPressed())
v.performClick();
mVelocityTracker.computeCurrentVelocity(1000, 14000);
float xVelocity = mVelocityTracker.getYVelocity(pointerId);
int o = mTotalOffset % mUnit;
int scrollX;
if (Math.abs(xVelocity) < mMinVelocityX && o != 0) {
if (o >= mUnit / 2)
scrollX = mUnit - o;
else
scrollX = -o;
Log.d("scrollx","from scroll");
Log.d("scrollx", "" + scrollX);
brewAndStartAnimator(300, (int) (scrollX));
}
}
return false;
}
};
private RecyclerView.OnFlingListener mOnFlingListener = new RecyclerView.OnFlingListener() {
@Override
public boolean onFling(int velocityX, int velocityY) {
int o = mTotalOffset % mUnit;
int s = mUnit - o;
int scrollX;
int vel = absMax(velocityX, velocityY);
if (vel > 0) {
scrollX = s;
} else
scrollX = -o;
Log.d("scrollx","from fling");
Log.d("scrollx",""+scrollX);
brewAndStartAnimator(100, scrollX);
// setScrollStateIdle();
return true;
}
};
private int absMax(int a, int b) {
if (Math.abs(a) > Math.abs(b))
return a;
else return b;
}
@Override
public void onAttachedToWindow(RecyclerView view) {
super.onAttachedToWindow(view);
this.recyclerView=view;
view.setOnTouchListener(mTouchListener);
view.setOnFlingListener(mOnFlingListener);
}
private void brewAndStartAnimator(int dur, int finalX) {
animator = ObjectAnimator.ofInt(StackLayoutManager.this, "animateValue", 0, finalX);
animator.setDuration(dur);
animator.start();
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
lastAnimateValue = 0;
recyclerView.scrollToPosition(findFirstCompletelyVisibleItemPosition());
}
@Override
public void onAnimationCancel(Animator animation) {
lastAnimateValue = 0;
recyclerView.smoothScrollToPosition(findFirstCompletelyVisibleItemPosition());
}
});
}
private float alpha(int position) {
float alpha;
int curPos = mTotalOffset / mUnit;
float n = (mTotalOffset + .0f) / mUnit;
if (position > curPos)
alpha = 1.0f;
else {
alpha = 1 - (n - position) / 1;
}
return alpha <= 0.001f ? 0 : alpha;
}
@Override
public void scrollToPosition(int pos)
{
scrollToPositionWithOffset(pos, 0);
}
private int left(int position) {
int curPos = mTotalOffset / mUnit;
int tail = mTotalOffset % mUnit;
float n = (mTotalOffset + .0f) / mUnit;
float x = n - curPos;
return ltr(position, curPos, tail, x);
}
private int ltr(int position, int curPos, int tail, float x) {
int left;
if (position <= curPos) {
if (position == curPos) {
left = (int) (mSpace * (1 - x));
} else {
left = (int) (mSpace * (1 - x - (curPos - position)));
}
} else {
if (position == curPos + 1)
left = mSpace + mUnit - tail;
else {
int baseStart = (int) (mUnit + mUnit);
left = (int) (baseStart + (position - curPos - 2) * mUnit - (position - curPos - 2) * (mUnit));
if (BuildConfig.DEBUG)
Log.i(TAG, "ltr: curPos " + curPos + " pos:" + position + " left:" + left + " baseStart" + baseStart + " curPos+1:" + left(curPos + 1));
}
left = left <= 0 ? 0 : left;
}
return left;
}
@SuppressWarnings("unused")
public void setAnimateValue(int animateValue) {
this.animateValue = animateValue;
int dy = this.animateValue - lastAnimateValue;
fill(recycler, dy, false);
lastAnimateValue = animateValue;
}
@SuppressWarnings("unused")
public int getAnimateValue() {
return animateValue;
}
private boolean recycleVertically(View view, int dy) {
return view != null && (view.getTop() - dy < 0 || view.getBottom() - dy > getHeight());
}
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
return fill(recycler, dy);
}
@Override
public boolean canScrollHorizontally() {
return false;
}
@Override
public boolean canScrollVertically() {
return true;
}
@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
return new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.MATCH_PARENT);
}
@SuppressWarnings("unused")
public interface CallBack {
float scale(int totalOffset, int position);
float alpha(int totalOffset, int position);
float left(int totalOffset, int position);
}
}
And If it is not possible with recyclerview, Please help me what to do with that VerticalViewPager. I have tried setting the field values, But it doesn't work.
try {
Class cls = this.getClass().getSuperclass();
Field distanceField = cls.getDeclaredField("mFlingDistance");
distanceField.setAccessible(true);
distanceField.setInt(this, distanceField.getInt(this)/2);
}catch (Exception ignored) {
Log.d("error", ignored.toString());
}
try {
Class cls = this.getClass().getSuperclass();
Field minVelocityField = cls.getDeclaredField("mMinimumVelocity");
minVelocityField.setAccessible(true);
minVelocityField.setInt(this, minVelocityField.getInt(this) /2);
} catch (Exception ignored) {
Log.d("error", ignored.toString());
}
try {
Class cls = this.getClass().getSuperclass();
Field maxVelocityField = cls.getDeclaredField("mMaximumVelocity");
maxVelocityField.setAccessible(true);
maxVelocityField.setInt(this, maxVelocityField.getInt(this)/2);
}catch (Exception ignored) {
Log.d("error", ignored.toString());
}