I have a layout like that:
<NestedScrollView>
<RecyclerView> // vertical recycler view
<RecyclerView/> // horizontal recycler view
<RecyclerView/>
<RecyclerView/>
...
<RecyclerView>
</NestedScrollView>
The result looks like Google play store:
And I disabled NestedScrolling in horizontal Recycler view:
horizontalRecyclerView.setHasFixedSize(true);
horizontalRecyclerView.setNestedScrollingEnabled(false);
My problem:
The vertical recyclerview does not scroll fling, whenever ACTION_UP happen, the vertical recyclerview also stop scrolling.
How can I nest vertical recyclerview inside nestedscrollview, and horizontal recyclerview inside vertical recyclerview like Playstore and keep the scroll smooth.
Solved:
Using custom nested scroll view of #vrund purohit (code below), and disabled nestedscroll both vertical and horizontal recyclerview:
verticalRecyclerView.setNestedScrollingEnabled(false);
... add each horizontal recyclerviews:
horizontalRecyclerView.setNestedScrollingEnabled(false);
Use below code for smooth scroll:
ViewCompat.setNestedScrollingEnabled(recyclerView, false);
Add this in your RecyclerView xml:
android:nestedScrollingEnabled="false"
I had this same problem and I solved this issue by customizing NeatedScrollView.
Here is the class for that.
MyNestedScrollView
public class MyNestedScrollView extends NestedScrollView {
#SuppressWarnings("unused")
private int slop;
#SuppressWarnings("unused")
private float mInitialMotionX;
#SuppressWarnings("unused")
private float mInitialMotionY;
public MyNestedScrollView(Context context) {
super(context);
init(context);
}
private void init(Context context) {
ViewConfiguration config = ViewConfiguration.get(context);
slop = config.getScaledEdgeSlop();
}
public MyNestedScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public MyNestedScrollView(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private float xDistance, yDistance, lastX, lastY;
#SuppressWarnings("unused")
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final float x = ev.getX();
final float y = ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
xDistance = yDistance = 0f;
lastX = ev.getX();
lastY = ev.getY();
// This is very important line that fixes
computeScroll();
break;
case MotionEvent.ACTION_MOVE:
final float curX = ev.getX();
final float curY = ev.getY();
xDistance += Math.abs(curX - lastX);
yDistance += Math.abs(curY - lastY);
lastX = curX;
lastY = curY;
if (xDistance > yDistance) {
return false;
}
}
return super.onInterceptTouchEvent(ev);
}
public interface OnScrollChangedListener {
void onScrollChanged(NestedScrollView who, int l, int t, int oldl,
int oldt);
}
private OnScrollChangedListener mOnScrollChangedListener;
public void setOnScrollChangedListener(OnScrollChangedListener listener) {
mOnScrollChangedListener = listener;
}
#Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (mOnScrollChangedListener != null) {
mOnScrollChangedListener.onScrollChanged(this, l, t, oldl, oldt);
}
}
}
Happy coding.
[RESOLVED] I have same issue with Horizontal recycleview. Change Gradle repo for recycleview
compile 'com.android.support:recyclerview-v7:23.2.1'
Write this: linearLayoutManager.setAutoMeasureEnabled(true);
Fixed bugs related to various measure-spec methods in update
Check http://developer.android.com/intl/es/tools/support-library/features.html#v7-recyclerview
I have found issue with 23.2.1 library: When item is match_parent recycle view fill full item to view, please always go with min height or "wrap_content".
Thanks
I've solved the issue by using below code:
myRecyclerView.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false){
#Override
public boolean canScrollHorizontally() {
return true;
}
#Override
public boolean canScrollVertically() {
return true;
}
});
Related
There are texts on my static layout, the layout is an item in a Recyclerview. The touch event of the Recyclerview class controls the pinch zoom to the text with ScaleGestureDetector. The zooming senario is, when the user action move the screen of Recyclerview, getting the screenshot of the recyclerview and displaying the image over the Recyclerview, and the user zooming to the image. When the action up, applying new text size that coming from scaling to the items. The new text size is should be same with when the zooming to image displaying text size. For this I use RelativeSizeSpan and float scaler value. I want to limit the total text size changing but it just doesn't happen.
The real problem is, the pinch zoom can be done more than once and it is necessary to collect the scaling that each of them because the pinch zoom reseting each action pointer up. (mScaleFactor = 1.0f) And the all of scaling shouldn't cross the specified limit. (MAX_ZOOM and MIN_ZOOM)
Recyclerview:
private ScaleListener mScaleListener;
private ScaleGestureDetector mScaleGestureDetector;
#Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction() & MotionEvent.ACTION_MASK;
if(event.getPointerCount() == 2 && (action == MotionEvent.ACTION_MOVE || action == MotionEvent.ACTION_POINTER_UP)) {
if(mScaleGestureDetector == null){
mScaleListener = new ScaleListener(mRecyclerview, mContext);
mScaleGestureDetector = new ScaleGestureDetector(mContext, mScaleListener);
} return mScaleGestureDetector.onTouchEvent(event);
}
}
Adapter:
private void changeTextSize(float mScaleFactor){
...
float newFontSize = (relativeSizeSpan.getSizeChange() * mScaleFactor);
...
}
ScaleListener:
public class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
private static final float MAX_ZOOM = 2.5f;
private static final float MIN_ZOOM = 0.5f;
private float mScaleFactor = 1.0f;
private ImageView mScreenShotView;
private Context mContext;
private View mView;
public ScaleListener(View mView, Context mContext) {
this.mView = mView;
this.mContext = mContext;
init();
}
private void init(){
int mWidth = mView.getWidth();
int mHeight = mView.getHeight();
if(mWidth == 0 || mHeight == 0) return;
mScreenShotView = new ImageView(mContext);
mScreenShotView.setLayoutParams(new ViewGroup.LayoutParams(mWidth, mHeight));
ViewGroup mPhysicalParentLayout = (ViewGroup) mView.getParent();
mPhysicalParentLayout.addView(mScreenShotView, mPhysicalParentLayout.indexOfChild(mView));
}
#Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
mScreenShotView.setBackgroundDrawable(new BitmapDrawable(Kit.getScreenshot(mView)));
mScreenShotView.setAlpha(1f); mView.setAlpha(0f);
return true;
}
#Override
public boolean onScale(ScaleGestureDetector scaleGestureDetector){
mScaleFactor *= scaleGestureDetector.getScaleFactor();
mScaleFactor = Math.max(MIN_ZOOM, Math.min(mScaleFactor, MAX_ZOOM));
mScreenShotView.setScaleX(mScaleFactor);
mScreenShotView.setScaleY(mScaleFactor);
return true;
}
#Override
public void onScaleEnd(ScaleGestureDetector detector) {
((ReadBookRcAdapter)Objects.requireNonNull(((RecyclerView)mView).getAdapter())).changeTextSize(mScaleFactor);
mScreenShotView.animate().alpha(0f).setDuration(300).setListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationStart(Animator animation) {
mView.animate().alpha(1f).setDuration(300).setListener(null);
}
#Override
public void onAnimationEnd(Animator animation) {
mScreenShotView.setScaleX(1.0f);
mScreenShotView.setScaleY(1.0f);
mScaleFactor = 1.0f;
}
});
}
}
Solved the issue with restricting value of TextView.setTextSize(). It's my adapter class:
public class ReadBookRcAdapter extends RecyclerView.Adapter<ReadBookRcAdapter.ReadBookViewHolder> {
private Context mContext;
private ArrayList<ReadBookTextBlockModel> mTextViewBlocks;
private float mTextSize;
public ReadBookRcAdapter(Context context) {
this.mContext = context;
this.mTextViewBlocks = new ReadBookTextBlockModel(context).getTextBlocks();
mTextSize = 15.0f;
}
class ReadBookViewHolder extends RecyclerView.ViewHolder {
TextView mTextViewItem;
ReadBookViewHolder(#NonNull View itemView) {
super(itemView);
this.mTextViewItem = itemView.findViewById(R.id.readBookTextRow);
}
void bind(ReadBookTextBlockModel dataList){
if (mTextSize != 15.0f) mTextViewItem.setTextSize(mTextSize);
mTextViewItem.setText(dataList.getBlockText());
}
}
#NonNull
#Override
public ReadBookViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.rc_item_read_book, parent, false);
return new ReadBookViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull ReadBookViewHolder holder, int position) {
ReadBookTextBlockModel model = mTextViewBlocks.get(position);
holder.bind(model);
}
public void setTextSize(float scale){
float maxTextSize = 37.5f;
float minTextSize = 7.5f;
if((mTextSize == minTextSize && scale < 1.0f) || (mTextSize == maxTextSize && scale >= 1.0f))
return;
float newTextSize = mTextSize * scale;
newTextSize = Math.max(minTextSize, Math.min(newTextSize, maxTextSize));
mTextSize = newTextSize;
Log.e("mTextSize*scale", String.valueOf(mTextSize));
notifyDataSetChanged();
}
}
I'm creating an android app that displays a recyclerview of cards. I have implemented an auto-scroll feature as part of my linear layout manager that will smoothscroll to the last element in the recyclerview.
I would like the list to snap back to the top of the recyclerview once it has detected that the last element is visible.
Here is the code for my custom layout manager.
public class ScrollLayoutManager extends LinearLayoutManager {
private static final float MILLISECONDS_PER_INCH = 10000f; //default is 25f (bigger = slower)
public ScrollLayoutManager(Context context) {
super(context);
}
public ScrollLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
public ScrollLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
#Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
final LinearSmoothScroller linearSmoothScroller = new LinearSmoothScroller(recyclerView.getContext()) {
#Override
public PointF computeScrollVectorForPosition(int targetPosition) {
return super.computeScrollVectorForPosition(targetPosition);
}
#Override
protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
}
};
linearSmoothScroller.setTargetPosition(position);
startSmoothScroll(linearSmoothScroller);
}
}
You might want attach a RecyclerView.OnScrollListener (docs) to your RecyclerView and override onScrolled() and use either findLastCompletelyVisibleItemPosition() (docs) or findLastVisibleItemPosition() (docs), depending on your needs, in combination with getItemCount() (docs) to check if you hit the last item and then scroll back to the top.
I worked on android swipe gestures, i applied swipe gesture on Item inside recycler view and recycler view is inside fragment and then fragment is inside activity and activity has Scroll View, The problem was that parent activity was intercepting swipe gesture of item inside child recycler view, After so much hard i found solution and this code is working fine, Is there any other better solution to this problem? if yes then post it in answer otherwise vote my code so that i can use it in my app without any fear of failure. Here is my code
listingView.setOnTouchListener(new OnSwipeTouchListener(myContext)
{
public void onClick() {
ViewListing();
}
// #Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getActionMasked())
{
case MotionEvent.ACTION_DOWN:
x1 = event.getX();
break;
case MotionEvent.ACTION_UP:
LockableScrollView.setScrollingEnabled(true);
break;
case MotionEvent.ACTION_MOVE:
x2 = event.getX();
float deltaX = x2 - x1;
if (Math.abs(deltaX) > 20)
{
LockableScrollView.setScrollingEnabled(false);
// Left to Right swipe action
if (x2 > x1)
{
hideSidePanel();
LockableScrollView.setScrollingEnabled(true);
}
else
//MH: Right to Left Swipe
{
showSidePanel();
LockableScrollView.setScrollingEnabled(true);
}
}
break;
}
return super.onTouch(v, event);
}
});
Lockable Scroll View code
public class LockableScrollView extends ScrollView {
#Override
public void setClipChildren(boolean clipChildren) {
clipChildren=false;
super.setClipChildren(clipChildren);
}
#Override
public void setFocusable(int focusable) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
focusable=View.NOT_FOCUSABLE;
}
super.setFocusable(focusable);
}
// true if we can scroll (not locked)
// false if we cannot scroll (locked)
private static boolean mScrollable = true;
public LockableScrollView(Context context) {
super(context);
}
public LockableScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public LockableScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public LockableScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public static void setScrollingEnabled(boolean enabled) {
mScrollable = enabled;
}
public static boolean isScrollable() {
return mScrollable;
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
//MH: if we can scroll pass the event to the superclass
if (mScrollable) return super.onTouchEvent(ev);
//MH: only continue to handle the touch event if scrolling enabled
return mScrollable; // mScrollable is always false at this point
default:
return super.onTouchEvent(ev);
}
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//MH: Don't do anything with intercepted touch events if
//MH: we are not scrollable
if (!mScrollable) return false;
else return super.onInterceptTouchEvent(ev);
}
}
I've created a vertical ViewPager, using ViewPage.PageTransformer and swapping X and Y coordinates. (I use this approach)
Now, what I want it to do is to stop scrolling at a certain point (I want the last view to take only 65% of the screen's height, but the full width).
Usually, I would override getPageWidth() in this case, but since my width and height are kind of mixed up right now, when I do that, my view takes 65% of both the height and width of the screen.
So how should I fix this?
Thank you!
MyViewPager.java
public class MyViewPager extends ViewPager {
public MyViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
setPageTransformer(true, new VerticalPageTransformer());
setOverScrollMode(OVER_SCROLL_NEVER);
}
private class VerticalPageTransformer implements ViewPager.PageTransformer {
#Override
public void transformPage(View view, float position) {
if (position < -1) {
view.setAlpha(0);
} else if (position <= 1) {
view.setAlpha(1);
view.setTranslationX(view.getWidth() * -position);
float yPosition = position * view.getHeight();
view.setTranslationY(yPosition);
} else {
view.setAlpha(0);
}
}
}
private MotionEvent swapXY(MotionEvent ev) {
float width = getWidth();
float height = getHeight();
float newX = (ev.getY() / height) * width;
float newY = (ev.getX() / width) * height;
ev.setLocation(newX, newY);
return ev;
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
return super.onTouchEvent(swapXY(ev));
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev){
boolean intercepted = super.onInterceptTouchEvent(swapXY(ev));
swapXY(ev);
return intercepted;
}
}
MyPagerAdapter.java
public class MyPagerAdapter extends PagerAdapter {
private Context context;
private LayoutInflater layoutInflater;
public MyPagerAdapter(Context context) {
this.context = context;
layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public int getCount() {
return 2;
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
#Override
public Object instantiateItem(ViewGroup container, final int position) {
if (position == 0) {
View view = layoutInflater.inflate(R.layout.item_profile_picture, container, false);
container.addView(view);
return view;
}
else {
View view = layoutInflater.inflate(R.layout.item_profile_info, container, false);
container.addView(view);
return view;
}
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((FrameLayout) object);
}
#Override
public float getPageWidth(int position) {
if (position == 1) return (0.65f);
return super.getPageWidth(position);
}
}
Hi as far as I understand you are trying to stop at certain point without having the overscroll. Add overscroll(never) method to the viewpager that you want. And manage your margins on the objects. Wrap content as much as possible.
Solved this problem by using this library.
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.