How to add RecyclerView in ScrollView - android

I need to add a RecyclerView in ScrollView.
The RecyclerView should wrap its contents and shouldn't be be scrollable, but the entire scroll view should be scrollable.
Can this be done an how?

If you look at the Recycler view adpater class
you will be having getITemViewType function
from that you can manage to put you scrollview content in one type of item
#Override
public int getItemViewType(int position) {
if (position == 0) {
return VIEW_TYPE_HEADER1;
} else if (position == 1) {
return VIEW_TYPE_HEADER2;
} else {
return VIEWTYEPITEM;
}
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(
final ViewGroup viewGroup, int viewType) {
if (viewType == VIEWTYEPITEM) {
//return item view
} else if (viewType == VIEW_TYPE_HEADER2) {
//return item view 2
} else {
//return item view 1
}
}

Main purpose of RecyclerView (as well as of old ListView and GridView) is performance optimization in cases when you have so many views in the list, that they do not fit on the screen and therefore are scrollable.
If your RecyclerView is so small that there are not enough views to fill it or there is some constant (and small) amount of views - then you don't need to use it at all. You won't win anything. Moreover, when you have one scrollable view inside of another scrollable view - how do you expect it to work? Which view should scroll when? It is ambiguous that's why it is not possible to do.
On the other hand, if you have a lot of views, then you better of using just RecyclerView without a ScrollView. In such situation it's common to add some sort of header or footer views which are arbitrarily big. Since RecyclerView is already scrollable, they will work as you want it to work. #SHASHIDHAR MANCHUKONDA exlained this idea in his answer to your question.

Stop the scrolling of the ScrollView. Is is perfect upon 5.0.
public class UnScrollView extends ScrollView{
private int downX;
private int downY;
private int mTouchSlop;
public UnScrollView(Context context) {
super(context);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
public UnScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
public UnScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
#Override
public boolean onInterceptTouchEvent(MotionEvent e) {
int action = e.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
downX = (int) e.getRawX();
downY = (int) e.getRawY();
break;
case MotionEvent.ACTION_MOVE:
int moveY = (int) e.getRawY();
if (Math.abs(moveY - downY) > mTouchSlop) {
return true;
}
}
return super.onInterceptTouchEvent(e);
}
}

Related

ItemTouchHelper with RecyclerView in NestedScrollView: Drag scrolling not work

I have implemented ItemTouchHelper like descriped in this articel:
https://medium.com/#ipaulpro/drag-and-swipe-with-recyclerview-b9456d2b1aaf#.k7xm7amxi
All works fine if the RecyclerView is a child of the CoordinatorLayout.
But if the RecyclerView is a child of NestedScrollView in CoordinatorLayout, the drag scrolling not working anymore.
Draging an item and move it to the top or bottom of the screen, the RecyclerView not scrolling like it do if its not a child of NestedScrollView.
Any ideas?
You have to disable the nestedScrolling for the recyclerView:
recyclerView.setIsNestedScrollingEnabled(false);
I have run into this same problem and I spent nearly a whole day to solve it.
Precondition:
First of all, my xml layout looks like this:
<CoordinatorLayout>
<com.google.android.material.appbar.AppBarLayout
...
</com.google.android.material.appbar.AppBarLayout>
<NestedScrollView>
<RecyclerView/>
</NestedScrollView>
</CoordinatorLayout>
And to make the scrolling behavior normal, I also let the nestedScrolling for the RecyclerView disabled by: RecyclerView.setIsNestedScrollingEnabled(false);
Reason:
But with ItemTouchHelper I still cannot make the Recyclerview auto scroll as expected when I drag the item in it. The reason why IT CANNOT SCROLL is in the method scrollIfNecessary() of ItemTouchHelper:
boolean scrollIfNecessary() {
RecyclerView.LayoutManager lm = mRecyclerView.getLayoutManager();
if (mTmpRect == null) {
mTmpRect = new Rect();
}
int scrollY = 0;
lm.calculateItemDecorationsForChild(mSelected.itemView, mTmpRect);
if (lm.canScrollVertically()) {
int curY = (int) (mSelectedStartY + mDy);
final int topDiff = curY - mTmpRect.top - mRecyclerView.getPaddingTop();
if (mDy < 0 && topDiff < 0) {
scrollY = topDiff;
} else if (mDy > 0) {
final int bottomDiff = curY + mSelected.itemView.getHeight() + mTmpRect.bottom
- (mRecyclerView.getHeight() - mRecyclerView.getPaddingBottom());
if (bottomDiff > 0) {
scrollY = bottomDiff;
}
}
}
if (scrollY != 0) {
scrollY = mCallback.interpolateOutOfBoundsScroll(mRecyclerView,
mSelected.itemView.getHeight(), scrollY,
mRecyclerView.getHeight(), scrollDuration);
}
if (scrollY != 0) {
mRecyclerView.scrollBy(scrollX, scrollY);
return true;
}
return false;
}
Reason 1: when nestedScrolling for the RecyclerView is set to false, actually the effective scrolling object is the NestedScrollView, which is the parent of RecyclerView. So RecyclerView.scrollBy(x, y) here does not work at all!
Reason 2: mRecyclerView.getHeight() is much bigger than NestedScrollView.getHeight(). So when I drag the item in RecyclerView to bottom, the result of scrollIfNecessary() is also false.
Reason 3: mSelectedStartY does not seem like the expected value when in our case. Because we need to calculate the scrollY of NestedScrollView in our case.
Therefore, we need to override this method to fullfill our expectation. Here comes the solution:
Solution:
Step 1:
In order to override this scrollIfNecessary()(This method is not public), you need to new a class under a package named the same as ItemTouchHelper's. Like this:
Step 2:
Besides overriding scrollIfNecessary(), we also need to override select() in order to get the value of mSelectedStartY and the scrollY of NestedScrollView when starting draging.
public override fun select(selected: RecyclerView.ViewHolder?, actionState: Int) {
super.select(selected, actionState)
if (selected != null) {
mSelectedStartY = selected.itemView.top
mSelectedStartScrollY = (mRecyclerView.parent as NestedScrollView).scrollY.toFloat()
}
}
Notice: mSelectedStartY and mSelectedStartScrollY are both very important for scrolling the NestedScrollView up or down.
Step 3:
Now we can override scrollIfNecessary(), and you need to pay attention to the comments below:
public override fun scrollIfNecessary(): Boolean {
...
val lm = mRecyclerView.layoutManager
if (mTmpRect == null) {
mTmpRect = Rect()
}
var scrollY = 0
val currentScrollY = (mRecyclerView.parent as NestedScrollView).scrollY
// We need to use the height of NestedScrollView, not RecyclerView's!
val actualShowingHeight = (mRecyclerView.parent as NestedScrollView).height
lm!!.calculateItemDecorationsForChild(mSelected.itemView, mTmpRect!!)
if (lm.canScrollVertically()) {
// The true current Y of the item in NestedScrollView, not in RecyclerView!
val curY = (mSelectedStartY + mDy - currentScrollY).toInt()
// The true mDy should plus the initial scrollY and minus current scrollY of NestedScrollView
val checkDy = (mDy + mSelectedStartScrollY - currentScrollY).toInt()
val topDiff = curY - mTmpRect!!.top - mRecyclerView.paddingTop
if (checkDy < 0 && topDiff < 0) {// User is draging the item out of the top edge.
scrollY = topDiff
} else if (checkDy > 0) { // User is draging the item out of the bottom edge.
val bottomDiff = (curY + mSelected.itemView.height + mTmpRect!!.bottom
- (actualShowingHeight - mRecyclerView.paddingBottom))
if (bottomDiff > 0) {
scrollY = bottomDiff
}
}
}
if (scrollY != 0) {
scrollY = mCallback.interpolateOutOfBoundsScroll(
mRecyclerView,
mSelected.itemView.height, scrollY, actualShowingHeight, scrollDuration
)
}
if (scrollY != 0) {
...
// The scrolling behavior should be assigned to NestedScrollView!
(mRecyclerView.parent as NestedScrollView).scrollBy(0, scrollY)
return true
}
...
return false
}
Result:
I can just show you my work through the Gif below:
This is the solution that works for me.
Create 2 custom classes
1> LockableScrollView
public class LockableScrollView extends NestedScrollView {
// true if we can scroll (not locked)
// false if we cannot scroll (locked)
private boolean mScrollable = true;
public LockableScrollView(#NonNull Context context) {
super(context);
}
public LockableScrollView(#NonNull Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
}
public LockableScrollView(#NonNull Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setScrollingEnabled(boolean enabled) {
mScrollable = enabled;
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// Don't do anything with intercepted touch events if
// we are not scrollable
if (ev.getAction() == MotionEvent.ACTION_MOVE) {// if we can scroll pass the event to the superclass
return mScrollable && super.onInterceptTouchEvent(ev);
}
return super.onInterceptTouchEvent(ev);
}
}
2>LockableRecyclerView extends RecyclerView
public class LockableRecyclerView extends RecyclerView {
private LockableScrollView scrollview;
public LockableRecyclerView(#NonNull Context context) {
super(context);
}
public LockableRecyclerView(#NonNull Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
}
public LockableRecyclerView(#NonNull Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setScrollview(LockableScrollView lockedscrollview) {
this.scrollview = lockedscrollview;
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_MOVE) {
scrollview.setScrollingEnabled(false);
return super.onInterceptTouchEvent(ev);
}
scrollview.setScrollingEnabled(true);
return super.onInterceptTouchEvent(ev);
}
#Override
public boolean onTouchEvent(MotionEvent e) {
if (e.getAction() == MotionEvent.ACTION_MOVE) {
scrollview.setScrollingEnabled(false);
return super.onTouchEvent(e);
}
scrollview.setScrollingEnabled(true);
return super.onTouchEvent(e);
}
}
Use this views instead of NestedScrollView and RecyclerView in xml
in kotlin file set
recyclerView.setScrollview(binding.scrollView)
recyclerView.isNestedScrollingEnabled = false
ItemTouchHelper(object :
ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.UP) {
override fun onMove(
#NonNull recyclerView: RecyclerView,
#NonNull viewHolder: RecyclerView.ViewHolder,
#NonNull target: RecyclerView.ViewHolder
): Boolean {
return false
}
override fun onSwiped(#NonNull viewHolder: RecyclerView.ViewHolder, direction: Int) {
// when user swipe thr recyclerview item to right remove item from favorite list
if (direction == ItemTouchHelper.UP) {
val itemToRemove = favList[viewHolder.absoluteAdapterPosition]
}
}
}).attachToRecyclerView(binding.recyclerView)
android:descendantFocusability="blocksDescendants"
add in NestedScrollView and add
android:focusableInTouchMode="true"
in child layout it look like below
<androidx.core.widget.NestedScrollView
android:descendantFocusability="blocksDescendants">
<androidx.constraintlayout.widget.ConstraintLayout
android:focusableInTouchMode="true">
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
check this github repo
https://github.com/khambhaytajaydip/Drag-Drop-recyclerview

ScrollView is ignoring setScrollX()at high scrolling speeds

So, I was going to create a custom view in order to get a photo album like tinder, a scrollview with autoadjust at photos positions. I thought an horizontalscrollview with a couple of tweaks would do the job. Added a 5*LayoutWidth horizontal LinerLayout to the scrollView sw and defined its onTouchListener like this:
sw.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
Log.d("MotionEvent", String.valueOf( event.getAction()));
if(event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL){
int scrollX=sw.getScrollX();
Log.d("scrollX", String.valueOf( scrollX));
//Log.d("layoutWidth", String.valueOf( layoutWidth));
int aux = Math.round((float)scrollX/layoutWidth)*layoutWidth;
Log.d("correction", String.valueOf( aux));
sw.setScrollX(aux );
}
return false;
}
});
It is working just as expected, focusing on one of the 5 photos when releasing the touch input , but when I scroll and relese by fast gestures the scrollview is just ignoring sw.setScrollX(aux) and keeps scrolling. Can I do something to avoid this automatic scroll after release¿ Can I somehow manipulate the data of the input event¿
Thanks!
Finally I got the solution. I extended the HorizontalcrollView like that:
public class stepScrollView extends HorizontalScrollView {
public stepScrollView(Context context) {
super(context);
}
public stepScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public stepScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_UP:
Display d = ((Activity) getContext()).getWindowManager().getDefaultDisplay();
Point size = new Point();
d.getSize(size);
int layoutWidth = size.x;
int aux = Math.round((float)this.getScrollX()/layoutWidth)*layoutWidth;
this.setScrollX(aux );
return false;
default:
return super.onTouchEvent(ev);
}
}
}
it works with all the images you want to put in a Horizontal LinearLayout inner this custom view. You also have to resize your images to the stepScrollView horizontal dimension. In my case layoutWidth.

RecyclerView childs, how to connect 2 or more views

Hello I have a RecyclerView, and I use HorizontalScrollView in children of theRecyclerView`. I need to scroll all of them when I scrolling one. Anyone can tell me How to make that,thanks!
I'm going to assume you're doing something similar to what I did where you have some sort of tabular view and the HorizontalScrollViews are all the same width.
This is how I did it:
First I made a customization to the HorizontalScrollView so I could get event notifications when the view was swiped:
public class HorizontalScrollView extends android.widget.HorizontalScrollView {
private OnScrollListener mListener;
public HorizontalScrollView(Context context) {
super(context);
}
public HorizontalScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public HorizontalScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setOnScrollListener(OnScrollListener listener) {
mListener = listener;
}
#Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (mListener != null) {
mListener.onScrollChanged(this, l);
}
}
public interface OnScrollListener {
public void onScrollChanged(View view, int scrollX);
}
}
Then when I create the ViewHolders I add a listener that will set all the views to the same scrollX:
view.setOnScrollListener(new OnScrollListener() {
#Override
public void onScrollChanged(View scrollView, int scrollX) {
for (int i = 0; i < recyclerView.getChildCount(); i++) {
View child = recyclerView.getChildAt(i);
if (child instanceof HorizontalScrollView && child != scrollView) {
HorizontalScrollView scrollView2 = (HorizontalScrollView) child;
if (scrollView2.getScrollX() != scrollX) {
scrollView2.setScrollX(scrollX);
}
}
}
}
});
This code is just for illustration purposes; don't expect to copy/paste this and have it work.
I'm assuming that your ViewHolder can get a reference to your RecyclerView to access all the current list items.
This code had some problems, when you swiped one view then swiped another view while everything was still moving from the first swipe, things could get out of sync. But this is a basic idea to get you started in a positive direction.

Custom ViewPager rendering issue on certain devices

I have developed a kind of rolling mechanism for choosing staff in the application. It's Custom View Pager that allows to present more then one item on screen at each time (3 in my case) and surrounded with shadow from both sides.
Here is how it should look and works like this on devices like the Nexus 5, Nexus 4, Galaxy S3:
But on some devices like (Sony Xperia, and different kinds of Motorola) the rendering looks bad, here is the result:
Regarding the code I refereed to this blog post by #Commonsware:
http://commonsware.com/blog/2012/08/20/multiple-view-viewpager-options.html
And the third option there which code you could find here.
Here is my relevant code:
PagerContainer:
public class PagerContainer extends FrameLayout implements ViewPager.OnPageChangeListener {
private ViewPager mPager;
boolean mNeedsRedraw = false;
public PagerContainer(Context context) {
super(context);
init();
}
public PagerContainer(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public PagerContainer(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
//Disable clipping of children so non-selected pages are visible
setClipChildren(false);
//Child clipping doesn't work with hardware acceleration in Android 3.x/4.x
//You need to set this value here if using hardware acceleration in an
// application targeted at these releases.
if (Build.VERSION.SDK_INT >= 11 && Build.VERSION.SDK_INT < 19)
{
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
}
#Override
protected void onFinishInflate() {
try {
mPager = (ViewPager) getChildAt(0);
mPager.setOnPageChangeListener(this);
} catch (Exception e) {
throw new IllegalStateException("The root child of PagerContainer must be a ViewPager");
}
}
public ViewPager getViewPager() {
return mPager;
}
private Point mCenter = new Point();
private Point mInitialTouch = new Point();
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
mCenter.x = w / 2;
mCenter.y = h / 2;
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
//We capture any touches not already handled by the ViewPager
// to implement scrolling from a touch outside the pager bounds.
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mInitialTouch.x = (int)ev.getX();
mInitialTouch.y = (int)ev.getY();
default:
ev.offsetLocation(mCenter.x - mInitialTouch.x, mCenter.y - mInitialTouch.y);
break;
}
return mPager.dispatchTouchEvent(ev);
}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
//Force the container to redraw on scrolling.
//Without this the outer pages render initially and then stay static
if (mNeedsRedraw) invalidate();
}
#Override
public void onPageSelected(int position) {
invalidate();
}
#Override
public void onPageScrollStateChanged(int state) {
mNeedsRedraw = (state != ViewPager.SCROLL_STATE_IDLE);
}
}
Init part:
//Size View Pager:
//========================================
pagerSize = mContainerSize.getViewPager();
adapter = new MySizePagerAdapter();
pagerSize.setAdapter(adapter);
//Necessary or the pager will only have one extra page to show make this at least however many pages you can see
pagerSize.setOffscreenPageLimit(adapter.getCount());
//A little space between pages
pagerSize.setPageMargin(15);
//If hardware acceleration is enabled, you should also remove clipping on the pager for its children.
pagerSize.setClipChildren(false);
More research brought me to understand that this problem has something to do with the Hardware acceleration or the lack of it in some devices. But disabling it via code didn't helped me either.
I would try setting the layerType of the ViewPager and it's children to software render, instead of the parent frame layout.
You also might want to check out this blog post: http://udinic.wordpress.com/2013/09/16/viewpager-and-hardware-acceleration/
I have ended up using another implementation of a ViewPager that gave me the same result but the rendering problem was no where to be seen there, this is the code:
private class MyTypePagerAdapter extends PagerAdapter {
#Override
public Object instantiateItem(ViewGroup container, int position) {
TextView view = new TextView(getActivity());
view.setText(mTempBeverageList.get(position).getName().toUpperCase());
if (!wasTypeChanged && (!isLocaleHebrew && position == 1))
{
view.setTypeface(null, Typeface.BOLD);
view.setTextSize(19);
}
else
{
view.setTextSize(16);
}
view.setSingleLine();
view.setGravity(Gravity.CENTER);
view.setTextColor(getResources().getColor(R.color.cups_black));
view.setBackgroundColor(getResources().getColor(R.color.cups_cyan));
container.addView(view);
return view;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View)object);
}
#Override
public int getCount() {
return mTempBeverageList.size();
}
#Override
public float getPageWidth(int position) {
return (0.33333f);
}
#Override
public boolean isViewFromObject(View view, Object object) {
return (view == object);
}
}
And the initialization part:
pagerType= (ViewPager) view.findViewById(R.id.pagerType);
pagerType.setAdapter(new MyTypePagerAdapter());
pagerType.setOffscreenPageLimit(6);

Scroll offset of GridView

Is there a way to determine the current scroll offset or scroll position of a GridView?
View.getScrollY() // Underlying var is not updated during a scroll.
I have tried setting an OnScrollListener but the onScroll callback is not fine grained enough for my purposes.
Here is the how I'm attempting to determine the scroll offset using an OnScrollListener.
private int getScrollY() {
int top = 0;
if (mGridView.getChildCount() > 0) {
final View firstView = mGridView.getChildAt(0);
top = firstView.getTop();
}
return top;
}
The issue with this code is that the returned y offset is inaccurate when scrolling upwards; the top view is recycled and hence, the y offset seems to jump;
Is there a nice way of calculating the scroll offset of a GridView? I can't seem to find a good solution.
Use this.
public class CustomGridView extends GridView {
public CustomGridView(Context context) {
super(context);
}
public CustomGridView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomGridView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
/* ADD THIS */
#Override
public int computeVerticalScrollOffset() {
return super.computeVerticalScrollOffset();
}
}
Returns an int value when called
You can use GridView.getFirstVisiblePosition().
http://developer.android.com/reference/android/widget/AdapterView.html#getFirstVisiblePosition()

Categories

Resources