Is there a way to create preview slider in android - android

I'm trying to create interface like
.
Central element is ViewPager with full-size image, top element is RecyclerView with preview images. But I couldn't find the way to magnet elements to center. Is there way to override behavior of RecyclerView?

I found the solution exactly what you need. I created a small example on github.com: AndroidPreviewSlider
The thing is that top view is ViewPager and center view is ViewPager too.
Detailed code is below:
Adapter for top preview
public class PhotoPreviewAdapter extends FragmentPagerAdapter {
private static final int DEFAULT_SIDE_PREVIEW_COUNT = 3;
private final int sidePreviewCount;
private final List<PhotoInfo> photoInfos;
public PhotoPreviewAdapter(FragmentManager fm, List<PhotoInfo> photoInfos) {
this(fm, DEFAULT_SIDE_PREVIEW_COUNT, photoInfos);
}
public PhotoPreviewAdapter(FragmentManager fm, int sidePreviewCount, List<PhotoInfo> photoInfos) {
super(fm);
this.sidePreviewCount = sidePreviewCount;
this.photoInfos = photoInfos;
}
public int getSidePreviewCount() {
return sidePreviewCount;
}
#Override
public Fragment getItem(int position) {
if (isDummy(position)) {
return DummyPreviewFragment.newInstance();
} else {
return PhotoPreviewFragment.newInstance(photoInfos.get(getRealPosition(position)));
}
}
private boolean isDummy(int position) {
return position < sidePreviewCount || position > photoInfos.size() - 1 + sidePreviewCount;
}
private int getRealPosition(int position) {
return position - sidePreviewCount;
}
#Override
public int getCount() {
return photoInfos.size() + (sidePreviewCount * 2);
}
#Override
public float getPageWidth(int position) {
return 1.0f / getElementsPerPage();
}
private int getElementsPerPage() {
return (sidePreviewCount * 2) + 1;
}
}
Adapter for central view is simple
public class PhotoViewAdapter extends FragmentPagerAdapter {
private final List<PhotoInfo> photoInfos;
public PhotoViewAdapter(FragmentManager fm, List<PhotoInfo> photoInfos) {
super(fm);
this.photoInfos = photoInfos;
}
#Override
public Fragment getItem(int position) {
return PhotoViewFragment.newInstance(photoInfos.get(position));
}
#Override
public int getCount() {
return photoInfos.size();
}
}
And one of the main is sync listener
public class OnSyncPageChangeListener implements ViewPager.OnPageChangeListener {
private int scrollState = ViewPager.SCROLL_STATE_IDLE;
private final ViewPager syncToViewPager;
private final ViewPager syncWithViewPager;
public OnSyncPageChangeListener(ViewPager syncToViewPager, ViewPager syncWithViewPager) {
this.syncToViewPager = syncToViewPager;
this.syncWithViewPager = syncWithViewPager;
}
#Override
public void onPageScrolled(final int position, final float positionOffset, final int positionOffsetPixels) {
if (scrollState != ViewPager.SCROLL_STATE_IDLE) {
final float ratio = calculateRatioForPosition(position);
final float scrollX = syncWithViewPager.getScrollX();
final float scrollY = syncWithViewPager.getScrollY();
syncToViewPager.scrollTo((int) (scrollX * ratio), (int) scrollY);
}
}
private float calculateRatioForPosition(int position) {
final float syncToViewPagerWidth = syncToViewPager.getWidth();
final float syncWithViewPagerWidth = syncWithViewPager.getWidth();
final float syncToViewPagerElementWeight = syncToViewPager.getAdapter().getPageWidth(position);
final float syncWithViewPagerElementWeight = syncWithViewPager.getAdapter().getPageWidth(position);
final float syncToViewPagerElementsCount = (1.0f / syncToViewPagerElementWeight);
final float syncWithViewPagerElementsCount = (1.0f / syncWithViewPagerElementWeight);
final float syncToViewPagerElementWidth = syncToViewPagerWidth / syncToViewPagerElementsCount;
final float syncWithViewPagerElementWidth = syncWithViewPagerWidth / syncWithViewPagerElementsCount;
return syncToViewPagerElementWidth / syncWithViewPagerElementWidth;
}
#Override
public void onPageSelected(final int position) {
}
#Override
public void onPageScrollStateChanged(final int state) {
scrollState = state;
if (state == ViewPager.SCROLL_STATE_IDLE) {
syncToViewPager.setCurrentItem(syncWithViewPager.getCurrentItem(), false);
}
}
}
And finally init ViewPager's
#Bind(R.id.photoPreviewPager)
ViewPager photoPreviewPager;
#Bind(R.id.photoViewPager)
ViewPager photoViewPager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
final List<PhotoInfo> photoInfos = PhotoInfosProvider.generate();
final PhotoPreviewAdapter photoPreviewAdapter = new PhotoPreviewAdapter(getSupportFragmentManager(), photoInfos);
final PhotoViewAdapter photoViewAdapter = new PhotoViewAdapter(getSupportFragmentManager(), photoInfos);
photoPreviewPager.setAdapter(photoPreviewAdapter);
photoPreviewPager.addOnPageChangeListener(new OnSyncPageChangeListener(photoViewPager, photoPreviewPager));
photoViewPager.setAdapter(photoViewAdapter);
photoViewPager.setOffscreenPageLimit(photoPreviewAdapter.getSidePreviewCount() * 2 + 1);
photoViewPager.addOnPageChangeListener(new OnSyncPageChangeListener(photoPreviewPager, photoViewPager));
}
More detailed code is in repo

The LinearLayoutManager have a scrollToPositionWithOffset method that takes both the position and also the offset of the start of the item from the start of the RecyclerView, which seems like it would accomplish what you need.
You can use this image slider(https://github.com/jjhesk/LoyalNativeSlider) instead of the ViewPager .

First of all, you can use this google official example to create visual effect of pulling this view to center. You'll have ImageView over ViewPager for displaying animated appearing. When animation will be finished, make make your ImageView Gone and show requested image in ViewPager.

Related

how to achieve on androidTV scrolling mechanism like Netflix with RecyclerView

I am trying to achieve horizontal scroll mechanism on AndroidTV like Netflix
I tried to use SnapHelper without success and also override LinearLayoutManager smoothScroll
it looks nice but is not perfect and also bit lag.
When I am perform long press, the movement doesn't smoothie and sometimes its stuck.
I am receiving focus callback from the next item while I am tapping left or right on the controller and then using recyclerview.smoothScroolToPostion.
anyone can help me with that?
recyclerView.setAdapter(new MyAdapter(new FocusListener() {
#Override
public void dispatchFocusChanged(View view, boolean gainFocus) {
if(gainFocus) {
int position = recyclerView.getChildAdapterPosition(view);
recyclerView.smoothScrollToPosition(position);
}
}
}));
LinearLayoutManager
#Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
LinearSmoothScroller smoothScroller =
new LinearSmoothScroller(recyclerView.getContext()) {
//This returns the milliseconds it takes to
//scroll one pixel.
#Override
protected float calculateSpeedPerPixel
(DisplayMetrics displayMetrics) {
return 500f/displayMetrics.densityDpi;
}
#Override
protected int getHorizontalSnapPreference() {
return SNAP_TO_END;
}
#Override
protected int calculateTimeForDeceleration(int dx) {
return (int) Math.ceil(calculateTimeForScrolling(dx) / .3356);
}
#Override
protected int calculateTimeForScrolling(int dx) {
return 200;//super.calculateTimeForScrolling(dx);
}
};
smoothScroller.setTargetPosition(position);
startSmoothScroll(smoothScroller);
}
SnapHelper
public class Snap extends LinearSnapHelper {
private Context context;
private OrientationHelper orientationHelper;
private Scroller scroller;
private int maxScrollDistance = 0;
private final static int MAX_SCROLL_ON_FLING_DURATION_MS = 1000;
#Override
public void attachToRecyclerView(#Nullable RecyclerView recyclerView) throws IllegalStateException {
if (recyclerView != null) {
context = recyclerView.getContext();
scroller = new Scroller(context, new DecelerateInterpolator());
} else {
scroller = null;
context = null;
}
super.attachToRecyclerView(recyclerView);
}
#Override
public View findSnapView(RecyclerView.LayoutManager layoutManager) {
return findFirstView(layoutManager, getHelper(layoutManager));
}
public View findFirstView(RecyclerView.LayoutManager layoutManager, OrientationHelper orientationHelper) {
if (layoutManager == null) return null;
int childCount = layoutManager.getChildCount();
if (childCount == 0) return null;
int absClosest = Integer.MAX_VALUE;
View closestView = null;
int start = orientationHelper.getStartAfterPadding();
for (int i = 0; i < childCount; i++) {
View child = layoutManager.getChildAt(i);
int childStart = orientationHelper.getDecoratedStart(child);
int absDistanceToStart = Math.abs(childStart - start);
if (absDistanceToStart < absClosest) {
absClosest = absDistanceToStart;
closestView = child;
}
}
return closestView;
}
private OrientationHelper getHelper(RecyclerView.LayoutManager layoutManager) {
if (orientationHelper == null) {
orientationHelper = OrientationHelper.createHorizontalHelper(layoutManager);
}
return orientationHelper;
}
#Override
public int[] calculateDistanceToFinalSnap(RecyclerView.LayoutManager layoutManager, View targetView) {
int[] out = new int[2];
out[0] = distanceToStart(targetView, getHelper(layoutManager));
return out;
}
#Override
public int[] calculateScrollDistance(int velocityX, int velocityY) {
int[] out = new int[2];
OrientationHelper helper = orientationHelper;
if (null == helper)
return out;
if (maxScrollDistance == 0) {
maxScrollDistance = (helper.getEndAfterPadding() - helper.getStartAfterPadding()) / 2;
}
scroller.fling(0, 0, velocityX, velocityY, -maxScrollDistance, maxScrollDistance, 0, 0);
out[0] = scroller.getFinalX();
out[1] = scroller.getFinalY();
return out;
}
private int distanceToStart(View targetView, OrientationHelper helper) {
int childStart = helper.getDecoratedStart(targetView) + convertDpToPixel(40);
int containerStart = helper.getStartAfterPadding();
return 0;//childStart - containerStart;
}
public int convertDpToPixel(float dp) {
DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
float px = dp * (metrics.densityDpi / 160f);
return (int) Math.round(px);
}
#Nullable
#Override
protected RecyclerView.SmoothScroller createScroller(final RecyclerView.LayoutManager layoutManager) {
if (layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)
return super.createScroller(layoutManager);
Context context = this.context;
return new LinearSmoothScroller(context) {
#Override
protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
int[] snapDistance = calculateDistanceToFinalSnap(layoutManager, targetView);
int dx = snapDistance[0];
int dy = snapDistance[1];
int dt = calculateTimeForDeceleration(Math.abs(dx));
int time = Math.max(1, Math.min(MAX_SCROLL_ON_FLING_DURATION_MS, dt));
action.update(dx, dy, time, mDecelerateInterpolator);
}
};
}}

How to show Admob Interstitial ad after 5 swipes in vertical viewpager?

I have made an app in which I have added functionality of vertical swipe using ViewPager class. Now I need help to show Admob Interstitial ad after 5 swipes. I have seen several other SO question but could not get clarity.
Can anyone help me how can I do this? Here is my code:
MainActivity
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mains);
mData = dbHelper.getAll(Data.class);
DATA_CARDS = mData.size();
ScreenSlidePagerAdapter screenSlidePagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
VerticalViewPager verticalViewPager = (VerticalViewPager) findViewById(R.id.pager);
verticalViewPager.setAdapter(screenSlidePagerAdapter);
}
ScreenSlidePagerAdapter
public class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
private static final int NUM_PAGES = MainActivity.DATA_CARDS;
public ScreenSlidePagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
SlidingFragment slidingFragment = new SlidingFragment();
Bundle bundle = new Bundle();
bundle.putInt("position", position);
slidingFragment.setArguments(bundle);
return slidingFragment;
}
#Override
public int getCount() {
return NUM_PAGES;
}
}
VerticalViewPager
public class VerticalViewPager extends ViewPager {
public VerticalViewPager(Context context) {
super(context);
init();
}
public VerticalViewPager(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) { // [-Infinity,-1)
// This page is way off-screen to the left.
view.setAlpha(0);
} else if (position <= 1) { // [-1,1]
view.setAlpha(1);
// Counteract the default slide transition
view.setTranslationX(view.getWidth() * -position);
//set Y position to swipe in from top
float yPosition = position * view.getHeight();
view.setTranslationY(yPosition);
} else { // (1,+Infinity]
// This page is way off-screen to the right.
view.setAlpha(0);
}
}
}
/**
* Swaps the X and Y coordinates of your touch event.
*/
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 onInterceptTouchEvent(MotionEvent ev){
boolean intercepted = super.onInterceptTouchEvent(swapXY(ev));
swapXY(ev); // return touch coordinates to original reference frame for any child views
return intercepted;
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
return super.onTouchEvent(swapXY(ev));
}
}
You can add the PageChangeListener to your view pager and call the onPageSelected() calls, for this you need to have class field called e.g. int mAdCounter = 0:
verticalViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
mAdCount++;
if (mAdCount % 5 == 0) {
//Display your add
//It's better if you load the ad before you need to display it, you can hide the adview and load it
} else {
//Hide the adView
}
}
#Override
public void onPageScrollStateChanged(int state) {
}
});

Custom ViewPager center current item with a custom getPageWidth in Adapter

I have a Custom ViewPager which act like a Carousel, the thing is I want to be able to show multiple page on one. To do so we have 2 solutions :
Using the margins setPageMargin() method
Or we can use Override the getPageWidth method of the adapter of my ViewPager.
The first solution is working great but I want to be able to precise exactly the percentage of my current page. The thing is with the getPageWidth solution my current page is shifting at the left. It's logical because she only takes 80% of her previous size. So I tried to override the scrollTo function of my custom ViewPager like this :
#Override
public void scrollTo(int x, int y) {
x = (int) (x - (getWidth() - getWidth()*getAdapter().getPageWidth(getCurrentItem()))/2);
super.scrollTo(x, y);
}
It's working but I can't scroll as before, it's just acting really weird, don't go in the good direction, don't follow the finger... Is there any solution which allow me to have a working scroll and a centered current page ?
Here is my adapter :
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import java.util.ArrayList;
public class CarouselAdapter extends FragmentPagerAdapter implements ViewPager.OnPageChangeListener {
private ArrayList<Entity> mData = new ArrayList<>();
private ScaledRelativeLayout cur = null, next = null;
private float scale;
private MainActivity context;
private FragmentManager fragmentManager;
public CarouselAdapter(MainActivity context, FragmentManager fragmentManager, ArrayList<Entity> mData) {
super(fragmentManager);
this.fragmentManager = fragmentManager;
this.context = context;
this.mData = mData;
}
#Override
public float getPageWidth(int position) {
return (0.85f);
}
#Override
public Fragment getItem(int position) {
if (position == MainActivity.FIRST_PAGE) {
scale = MainActivity.BIG_SCALE;
} else {
scale = MainActivity.SMALL_SCALE;
}
position = position % mData.size();
Fragment fragment = CarouselFragment.newInstance(context, mData.get(position), position, scale);
return fragment;
}
#Override
public int getCount() {
return mData.size();
}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if (positionOffset >= 0f && positionOffset <= 1f) {
cur = getRootView(position);
cur.setScaleBoth(MainActivity.BIG_SCALE - MainActivity.DIFF_SCALE * positionOffset);
if (position < mData.size()-1) {
next = getRootView(position +1);
next.setScaleBoth(MainActivity.SMALL_SCALE + MainActivity.DIFF_SCALE * positionOffset);
}
}
}
#Override
public void onPageSelected(int position) {}
#Override
public void onPageScrollStateChanged(int state) {}
private ScaledRelativeLayout getRootView(int position) {
return (ScaledRelativeLayout) fragmentManager.findFragmentByTag(this.getFragmentTag(position)).getView().findViewById(R.id.rootItem);
}
private String getFragmentTag(int position) {
return "android:switcher:" + context.carousel.getId() + ":" + position;
}
}
More useless but here is my MainActivity :
import android.support.v4.app.FragmentActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Button;
import java.util.ArrayList;
public class MainActivity extends FragmentActivity {
public static int FIRST_PAGE;
public final static float BIG_SCALE = 1.0f;
public final static float SMALL_SCALE = 0.85f;
public final static float DIFF_SCALE = BIG_SCALE - SMALL_SCALE;
public CarouselViewPager carousel;
private CarouselAdapter carouselAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FIRST_PAGE = mData.size()/2;
carouselAdapter = new CarouselAdapter(this, this.getSupportFragmentManager(), mData);
carousel = (CarouselViewPager) findViewById(R.id.carousel);
carousel.setAdapter(carouselAdapter);
carousel.addOnPageChangeListener(carouselAdapter);
carousel.setCurrentItem(Math.round(FIRST_PAGE));
carousel.setOffscreenPageLimit(3);
carousel.setClipToPadding(false);
carousel.setScrollDurationFactor(2.5f);
}
}
Edit :
root.post(new Runnable() {
#Override
public void run() {
int width = root.getWidth();
Log.w("Post", "Width : " + width + ", newWidth : " + (width * 0.8));
// root.setPadding((int) (width * 0.125), 0, (int) (width * 0.125), 0);
ViewPager.LayoutParams params = (ViewPager.LayoutParams) root.getLayoutParams();
params.width = (int) (width * 0.8);
root.setLayoutParams(params);
root.requestLayout();
root.forceLayout();
root.invalidate();
}
});
Regards
Instead of setPageMargin or getPageWidth You can just set padding to your ViewPager.
<android.support.v4.view.ViewPager
android:id="#+id/main_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:clipToPadding="false" />
Notice the clipToPadding part, it is important to show other pages.
If you want to show your width as x% of the screen, you can do this in Java
mRootView.post(new Runnable() {
#Override
public void run() {
int w = mRootView.getWidth();
mViewPager.setPadding(w * 0.25, 0, w * 0.25, 0);
// 25% padding on either side so pages takes exactly 50% space
}
});

Get center visible item of RecycleView when scrolling

This is what I want:
As image above, I want to draw a center line on RecycleView, then get the center item when scrolling (as well as move left or right)
Here is my try to draw a horizontal RecycleView:
HorizontalAdapter adapter = new HorizontalAdapter(data);
LinearLayoutManager layoutManager
= new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
recycleView.setLayoutManager(layoutManager);
recycleView.setAdapter(adapter);
Is there any way to know which item is moved to the center of RecycleView? And how can I scroll RecycleView to left or right just one position?
Update: I tried to use a scroll listener to get the middle position, but it doesn't work as an aspect.
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int firstPos = layoutManager.findFirstVisibleItemPosition();
int lastPos = layoutManager.findLastVisibleItemPosition();
int middle = Math.abs(lastPos - firstPos) / 2 + firstPos;
int selectedPos = -1;
for (int i = 0; i < adapter.getItemCount(); i++) {
if (i == middle) {
adapter.getItem(i).setSelected(true);
selectedPos = i;
} else {
adapter.getItem(i).setSelected(false);
}
}
adapter.notifyDataSetChanged();
}
And get the result:
I only want to change the selected item (make text to white color) when it is on the blue Rect
I made something just like this. I can do exactly what you need.
First of all, this is how is my alogrithm work
This is my recyclerView Adapter
public class DateAdapter extends RecyclerView.Adapter<DateAdapter.DateViewHolder> {
private ArrayList<LabelerDate> dateDataList;
private static final int VIEW_TYPE_PADDING = 1;
private static final int VIEW_TYPE_ITEM = 2;
private int paddingWidthDate = 0;
private int selectedItem = -1;
public DateAdapter(ArrayList<LabelerDate> dateData, int paddingWidthDate) {
this.dateDataList = dateData;
this.paddingWidthDate = paddingWidthDate;
}
#Override
public DateViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == VIEW_TYPE_ITEM) {
final View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_date,
parent, false);
return new DateViewHolder(view);
} else {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_padding,
parent, false);
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) view.getLayoutParams();
layoutParams.width = paddingWidthDate;
view.setLayoutParams(layoutParams);
return new DateViewHolder(view);
}
}
#Override
public void onBindViewHolder(DateViewHolder holder, int position) {
LabelerDate labelerDate = dateDataList.get(position);
if (getItemViewType(position) == VIEW_TYPE_ITEM) {
if(labelerDate.dateType.equals(BirthDayActivity.DateType.C31))
holder.tvDate.setText(String.valueOf(labelerDate.valueDate));
holder.tvDate.setVisibility(View.VISIBLE);
holder.imgSmall.setVisibility(View.VISIBLE);
if (position == selectedItem) {
holder.tvDate.setTextColor(Color.parseColor("#094673"));
holder.tvDate.setTextSize(35);
holder.imgSmall.setBackgroundResource(R.color.textviewbold);
} else {
holder.tvDate.setTextColor(Color.GRAY);
holder.tvDate.setTextSize(35);
holder.imgSmall.setBackgroundResource(R.color.gray);
}
}
}
public void setSelecteditem(int selecteditem) {
this.selectedItem = selecteditem;
notifyDataSetChanged();
}
#Override
public int getItemCount() {
return dateDataList.size();
}
#Override
public int getItemViewType(int position) {
LabelerDate labelerDate = dateDataList.get(position);
if (labelerDate.dateType.equals(BirthDayActivity.DateType.NONE)) {
return VIEW_TYPE_PADDING;
}
return VIEW_TYPE_ITEM;
}
public class DateViewHolder extends RecyclerView.ViewHolder {
public TextView tvDate;
public ImageView imgSmall;
public DateViewHolder(View itemView) {
super(itemView);
tvDate = (TextView) itemView.findViewById(R.id.tvNumberDate);
imgSmall = (ImageView) itemView.findViewById(R.id.small_marked_dob);
}
}}
This is most important alogrithm:
public void getRecyclerviewDate() {
recyclerViewDate = (RecyclerView) findViewById(R.id.recyclerViewDay);
ViewTreeObserver vtoDate = recyclerViewDate.getViewTreeObserver();
vtoDate.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
recyclerViewDate.getViewTreeObserver().removeOnPreDrawListener(this);
finalWidthDate = recyclerViewDate.getMeasuredWidth();
itemWidthDate = getResources().getDimension(R.dimen.item_dob_width);
paddingDate = (finalWidthDate - itemWidthDate) / 2;
firstItemWidthDate = paddingDate ;
allPixelsDate = 0;
final LinearLayoutManager dateLayoutManager = new LinearLayoutManager(getApplicationContext());
dateLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
recyclerViewDate.setLayoutManager(dateLayoutManager);
recyclerViewDate.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
synchronized (this) {
if(newState == RecyclerView.SCROLL_STATE_IDLE){
calculatePositionAndScrollDate(recyclerView);
}
}
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
allPixelsDate += dx;
}
});
if (labelerDates == null)
labelerDates = new ArrayList<>();
labelerDates.addAll(genLabelerDate(currentMonth, currentYear));
dateAdapter = new DateAdapter(labelerDates, (int) firstItemWidthDate);
recyclerViewDate.setAdapter(dateAdapter);
return true;
}
});
}
/* this if most important, if expectedPositionDate < 0 recyclerView will return to nearest item*/
private void calculatePositionAndScrollDate(RecyclerView recyclerView) {
int expectedPositionDate = Math.round((allPixelsDate + paddingDate - firstItemWidthDate) / itemWidthDate);
if (expectedPositionDate == -1) {
expectedPositionDate = 0;
} else if (expectedPositionDate >= recyclerView.getAdapter().getItemCount() - 2) {
expectedPositionDate--;
}
scrollListToPositionDate(recyclerView, expectedPositionDate);
}
/* this if most important, if expectedPositionDate < 0 recyclerView will return to nearest item*/
private void scrollListToPositionDate(RecyclerView recyclerView, int expectedPositionDate) {
float targetScrollPosDate = expectedPositionDate * itemWidthDate + firstItemWidthDate - paddingDate;
float missingPxDate = targetScrollPosDate - allPixelsDate;
if (missingPxDate != 0) {
recyclerView.smoothScrollBy((int) missingPxDate, 0);
}
}
private void setDateValue() {
int expectedPositionDateColor = Math.round((allPixelsDate + paddingDate - firstItemWidthDate) / itemWidthDate);
setColorDate = expectedPositionDateColor + 1;
//set color here
dateAdapter.setSelecteditem(setColorDate);
}
#Override
protected void onRestoreInstanceState(#NonNull Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
allPixelsDate = savedInstanceState.getFloat(BUNDLE_LIST_PIXELS_DATE);
allPixelsDateChanged = savedInstanceState.getFloat(BUNDLE_LIST_PIXELS_DATE_CHANGED);
}
#Override
protected void onSaveInstanceState(#NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putFloat(BUNDLE_LIST_PIXELS_DATE, allPixelsDate);
outState.putFloat(BUNDLE_LIST_PIXELS_DATE_CHANGED, allPixelsDateChanged);
}
And this is my result:
Look at this video link, this is my app demo
Sometimes is needed the entire example code block together, because we may miss something. Here is what I have, feel free to correct anything since I may be doing some little mistake somewhere. And Yes, this answer is an extension of #tranhieu answer. Thanks #tranhieu.
MainActivity.java
package com.test;
import android.app.Activity;
import android.graphics.Color;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.TextView;
import java.util.ArrayList;
public class MainActivity extends Activity {
private static final String TAG = MainActivity.class.getSimpleName();
public float firstItemWidthDate;
public float paddingDate;
public float itemWidthDate;
public int allPixelsDate;
public int finalWidthDate;
private DateAdapter dateAdapter;
private ArrayList<LabelerDate> labelerDates = new ArrayList<>();
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getRecyclerviewDate();
}
public void getRecyclerviewDate() {
final RecyclerView recyclerViewDate = (RecyclerView) findViewById(R.id.rv_tasks_date);
if (recyclerViewDate != null) {
recyclerViewDate.postDelayed(new Runnable() {
#Override
public void run() {
setDateValue();
}
}, 300);
recyclerViewDate.postDelayed(new Runnable() {
#Override
public void run() {
recyclerViewDate.smoothScrollToPosition(dateAdapter.getItemCount()-1);
setDateValue();
}
}, 5000);
}
ViewTreeObserver vtoDate = recyclerViewDate.getViewTreeObserver();
vtoDate.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
recyclerViewDate.getViewTreeObserver().removeOnPreDrawListener(this);
finalWidthDate = recyclerViewDate.getMeasuredWidth();
itemWidthDate = getResources().getDimension(R.dimen.item_dob_width);
paddingDate = (finalWidthDate - itemWidthDate) / 2;
firstItemWidthDate = paddingDate;
allPixelsDate = 0;
final LinearLayoutManager dateLayoutManager = new LinearLayoutManager(getApplicationContext());
dateLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
recyclerViewDate.setLayoutManager(dateLayoutManager);
recyclerViewDate.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
synchronized (this) {
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
calculatePositionAndScrollDate(recyclerView);
}
}
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
allPixelsDate += dx;
}
});
if (labelerDates == null) {
labelerDates = new ArrayList<>();
}
genLabelerDate();
dateAdapter = new DateAdapter(labelerDates, (int) firstItemWidthDate);
recyclerViewDate.setAdapter(dateAdapter);
dateAdapter.setSelecteditem(dateAdapter.getItemCount() - 1);
return true;
}
});
}
private void genLabelerDate() {
for (int i = 0; i < 32; i++) {
LabelerDate labelerDate = new LabelerDate();
labelerDate.setNumber(Integer.toString(i));
labelerDates.add(labelerDate);
if (i == 0 || i == 31) {
labelerDate.setType(DateAdapter.VIEW_TYPE_PADDING);
} else {
labelerDate.setType(DateAdapter.VIEW_TYPE_ITEM);
}
}
}
/* this if most important, if expectedPositionDate < 0 recyclerView will return to nearest item*/
private void calculatePositionAndScrollDate(RecyclerView recyclerView) {
int expectedPositionDate = Math.round((allPixelsDate + paddingDate - firstItemWidthDate) / itemWidthDate);
if (expectedPositionDate == -1) {
expectedPositionDate = 0;
} else if (expectedPositionDate >= recyclerView.getAdapter().getItemCount() - 2) {
expectedPositionDate--;
}
scrollListToPositionDate(recyclerView, expectedPositionDate);
}
/* this if most important, if expectedPositionDate < 0 recyclerView will return to nearest item*/
private void scrollListToPositionDate(RecyclerView recyclerView, int expectedPositionDate) {
float targetScrollPosDate = expectedPositionDate * itemWidthDate + firstItemWidthDate - paddingDate;
float missingPxDate = targetScrollPosDate - allPixelsDate;
if (missingPxDate != 0) {
recyclerView.smoothScrollBy((int) missingPxDate, 0);
}
setDateValue();
}
//
private void setDateValue() {
int expectedPositionDateColor = Math.round((allPixelsDate + paddingDate - firstItemWidthDate) / itemWidthDate);
int setColorDate = expectedPositionDateColor + 1;
// set color here
dateAdapter.setSelecteditem(setColorDate);
}
public class DateAdapter extends RecyclerView.Adapter<DateAdapter.DateViewHolder> {
private ArrayList<LabelerDate> dateDataList;
private static final int VIEW_TYPE_PADDING = 1;
private static final int VIEW_TYPE_ITEM = 2;
private int paddingWidthDate = 0;
private int selectedItem = -1;
public DateAdapter(ArrayList<LabelerDate> dateData, int paddingWidthDate) {
this.dateDataList = dateData;
this.paddingWidthDate = paddingWidthDate;
}
#Override
public DateViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == VIEW_TYPE_ITEM) {
final View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item,
parent, false);
return new DateViewHolder(view);
} else {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item,
parent, false);
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) view.getLayoutParams();
layoutParams.width = paddingWidthDate;
view.setLayoutParams(layoutParams);
return new DateViewHolder(view);
}
}
#Override
public void onBindViewHolder(DateViewHolder holder, int position) {
LabelerDate labelerDate = dateDataList.get(position);
if (getItemViewType(position) == VIEW_TYPE_ITEM) {
holder.tvDate.setText(labelerDate.getNumber());
holder.tvDate.setVisibility(View.VISIBLE);
Log.d(TAG, "default " + position + ", selected " + selectedItem);
if (position == selectedItem) {
Log.d(TAG, "center" + position);
holder.tvDate.setTextColor(Color.parseColor("#76FF03"));
holder.tvDate.setTextSize(35);
} else {
holder.tvDate.setTextColor(Color.WHITE);
holder.tvDate.setTextSize(18);
}
} else {
holder.tvDate.setVisibility(View.INVISIBLE);
}
}
public void setSelecteditem(int selecteditem) {
this.selectedItem = selecteditem;
notifyDataSetChanged();
}
#Override
public int getItemCount() {
return dateDataList.size();
}
#Override
public int getItemViewType(int position) {
LabelerDate labelerDate = dateDataList.get(position);
if (labelerDate.getType() == VIEW_TYPE_PADDING) {
return VIEW_TYPE_PADDING;
} else {
return VIEW_TYPE_ITEM;
}
}
public class DateViewHolder extends RecyclerView.ViewHolder {
public TextView tvDate;
public DateViewHolder(View itemView) {
super(itemView);
tvDate = (TextView) itemView.findViewById(R.id.txt_date);
}
}
}
private class LabelerDate {
private int type;
private String number;
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.RecyclerView
android:id="#+id/rv_tasks_date"
android:layout_width="match_parent"
android:layout_height="48dp" />
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center"
android:layout_marginTop="48dp"
android:src="#android:drawable/ic_dialog_info" />
</FrameLayout>
</LinearLayout>
item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="wrap_content"
android:layout_height="match_parent">
<TextView
android:id="#+id/txt_date"
android:layout_width="#dimen/item_dob_width"
android:layout_height="48dp"
android:text="32"
android:textColor="#android:color/white"
android:background="#android:color/darker_gray"
android:textSize="28sp"
android:gravity="center"/>
</LinearLayout>
dimens.xml
<resources>
<dimen name="item_dob_width">100dp</dimen>
</resources>
Oh boy. I've been searching for this answer for almost a week and then found out the solution. Custom LayoutManagers? No. ItemDecorator? Nope.
Here's the easiest way to do it:
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:paddingStart="150dp"
android:paddingEnd="150dp"
android:clipToPadding="false" />
The critical part is:
android:paddingStart="150dp"
android:paddingEnd="150dp"
android:clipToPadding="false"
And then just assign SnapHelper to your RecylcerView:
val snapHelper = LinearSnapHelper()
snapHelper.attachToRecyclerView(recyclerView)
This is it. The easiest and most perfect solution to the problem
I'm used the SnapHelper right here:
// init snaphelper
SnapHelper snapHelper = new LinearSnapHelper();
snapHelper.attachToRecyclerView(recyclerView)
// init layout manager
LinearLayoutManager layoutManager = new LinearLayoutManager(mainActivity);
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
recyclerView.setLayoutManager(layoutManager);
// init adapter
adatper.setSnapHelper(snapHelper);
adatper.setLayoutManager(layoutManager);
adatper.initAdapter(new Float((DisplayHelper.getDisplayWidth(mainActivity) / 2) - (fooViewWidth / 2)).intValue());
recyclerView.setAdapter(adatper);
As said by TranHieu the solution of inserting 2 item for padding (at start and at end positions) is good.
I don't like the use of ViewTreeObserver because of poor readability of code. With this technique then you must also manage redrawing of the items if they are recycled.
If you are using customview classes you can set its width directly into these classes.
For example this is my padding class
/**
* Created by firegloves on 25/09/15.
*/
#EViewGroup(R.layout.view_padding)
public class PaddingView extends FooView {
Context mCtx;
public PaddingView(Context context) {
super(context);
mCtx = context;
}
public void setWidth(int width) {
setLayoutParams(new LayoutParams(width, ViewGroup.LayoutParams.WRAP_CONTENT));
}
}
In my adapter I store the desired padding item width, that is equal to (displayWidth / 2) - (realItemWidth / 2)
This is my adapter, don't look at methods not matching RecyclerView.Adapter, pay attention to the initAdapter method and to the onCreateItemView method
#EBean
public class FooAdapterRecycler extends RecyclerViewAdapterBase<Foo, FooView> {
private final int TYPE_PADDING_VIEW = 0;
private final int TYPE_REAL_VIEW = 1;
#RootContext
Context ctx;
#Bean(Finder.class)
IFinder finder;
SnapHelper snapHelper;
RecyclerView.LayoutManager layoutManager;
private int paddingWidth = 0;
/**
* preleva i dati dal finder
*/
public void initAdapter(int paddingWidth) {
/*******************************
* THIS CODE IS THE IMPORTANT ONE
******************************/
this.paddingWidth = paddingWidth;
// add 1 item for initial space
mItems = new ArrayList<>();
Foo foo = new Foo();
mItems.add(foo);
// get real items from finder
mItems.addAll(finder.findAll());
// add 1 item for final space
mItems = new ArrayList<>();
Foo foo2 = new Foo();
mItems.add(foo2);
}
#Override
public int getItemViewType(int position) {
if (position == 0 || position == getItemCount()-1) {
return TYPE_PADDING_VIEW;
} else {
return TYPE_REAL_VIEW;
}
}
#Override
protected FooView onCreateItemView(ViewGroup parent, int viewType) {
/*******************************
* THIS CODE IS THE IMPORTANT ONE
******************************/
if (viewType == TYPE_PADDING_VIEW) {
PaddingView view = PaddingView_.build(ctx);
view.setWidth(paddingWidth);
return view;
} else {
return FooView_.build(ctx);
}
}
public void setSnapHelper(SnapHelper snapHelper) {
this.snapHelper = snapHelper;
}
public void setLayoutManager(RecyclerView.LayoutManager layoutManager) {
this.layoutManager = layoutManager;
}
}
I'm using AndroidAnnotations library but it's not required
Hope that helps
USING SNAPHELPER - A SMOOTHER SOLUTION
Here it is another solution using SnapHelper. Starting from the answer of #TranHieu here:
https://stackoverflow.com/a/34647005/3944251
and the compressed by #sector11 here:
https://stackoverflow.com/a/38411582/3944251
I wrote the following code which is also based in both answers above, but it's simpler and offers a smoother solution using SnapHelper presented in android support library 24.2.0.
Here you have the MainActivity class. The rest is the same with #sector11's answer.
import android.graphics.Color;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.LinearSnapHelper;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.TextView;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
public float firstItemWidthDate;
public float itemWidthDate;
public int allPixelsDate;
public int finalWidthDate;
private DateAdapter dateAdapter;
private ArrayList<LabelerDate> labelerDates;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
labelerDates = new ArrayList<>();
getRecyclerviewDate();
}
public void getRecyclerviewDate() {
final RecyclerView recyclerViewDate = (RecyclerView) findViewById(R.id.rv_tasks_date);
recyclerViewDate.postDelayed(new Runnable() {
#Override
public void run() {
//recyclerViewDate.smoothScrollToPosition(dateAdapter.getItemCount()-1);
setDateValue();
}
}, 300);
ViewTreeObserver vtoDate = recyclerViewDate.getViewTreeObserver();
vtoDate.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
recyclerViewDate.getViewTreeObserver().removeOnPreDrawListener(this);
finalWidthDate = recyclerViewDate.getMeasuredWidth();
itemWidthDate = getResources().getDimension(R.dimen.item_dob_width);
firstItemWidthDate = (finalWidthDate - itemWidthDate) / 2;
allPixelsDate = 0;
final LinearLayoutManager dateLayoutManager = new LinearLayoutManager(getApplicationContext());
dateLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
recyclerViewDate.setLayoutManager(dateLayoutManager);
/* Create a LinearSnapHelper and attach the recyclerView to it. */
final LinearSnapHelper snapHelper = new LinearSnapHelper();
snapHelper.attachToRecyclerView(recyclerViewDate);
recyclerViewDate.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
allPixelsDate += dx;
recyclerView.post(new Runnable() {
public void run() {
setDateValue();
}
});
}
});
genLabelerDate();
dateAdapter = new DateAdapter(labelerDates, (int) firstItemWidthDate);
recyclerViewDate.setAdapter(dateAdapter);
dateAdapter.setSelecteditem(dateAdapter.getItemCount() - 1);
return true;
}
});
}
private void genLabelerDate() {
for (int i = 0; i < 32; i++) {
LabelerDate labelerDate = new LabelerDate();
labelerDate.setNumber(Integer.toString(i));
labelerDates.add(labelerDate);
if (i == 0 || i == 31) {
labelerDate.setType(DateAdapter.VIEW_TYPE_PADDING);
} else {
labelerDate.setType(DateAdapter.VIEW_TYPE_ITEM);
}
}
}
//
private void setDateValue() {
int expectedPositionDateColor = Math.round(allPixelsDate / itemWidthDate);
int setColorDate = expectedPositionDateColor + 1;
// set color here
dateAdapter.setSelecteditem(setColorDate);
}
public class DateAdapter extends RecyclerView.Adapter<DateAdapter.DateViewHolder> {
private ArrayList<LabelerDate> dateDataList;
private static final int VIEW_TYPE_PADDING = 1;
private static final int VIEW_TYPE_ITEM = 2;
private int paddingWidthDate = 0;
private int selectedItem = -1;
public DateAdapter(ArrayList<LabelerDate> dateData, int paddingWidthDate) {
this.dateDataList = dateData;
this.paddingWidthDate = paddingWidthDate;
}
#Override
public DateAdapter.DateViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
if (viewType == VIEW_TYPE_PADDING) {
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) view.getLayoutParams();
layoutParams.width = paddingWidthDate;
view.setLayoutParams(layoutParams);
}
return new DateViewHolder(view);
}
#Override
public void onBindViewHolder(DateAdapter.DateViewHolder holder, int position) {
LabelerDate labelerDate = dateDataList.get(position);
if (getItemViewType(position) == VIEW_TYPE_ITEM) {
holder.tvDate.setText(labelerDate.getNumber());
holder.tvDate.setVisibility(View.VISIBLE);
Log.d(TAG, "default " + position + ", selected " + selectedItem);
if (position == selectedItem) {
Log.d(TAG, "center" + position);
holder.tvDate.setTextColor(Color.parseColor("#76FF03"));
holder.tvDate.setTextSize(35);
} else {
holder.tvDate.setTextColor(Color.WHITE);
holder.tvDate.setTextSize(18);
}
} else {
holder.tvDate.setVisibility(View.INVISIBLE);
}
}
public void setSelecteditem(int selecteditem) {
this.selectedItem = selecteditem;
notifyDataSetChanged();
}
#Override
public int getItemCount() {
return dateDataList.size();
}
#Override
public int getItemViewType(int position) {
LabelerDate labelerDate = dateDataList.get(position);
if (labelerDate.getType() == VIEW_TYPE_PADDING) {
return VIEW_TYPE_PADDING;
} else {
return VIEW_TYPE_ITEM;
}
}
public class DateViewHolder extends RecyclerView.ViewHolder {
public TextView tvDate;
public DateViewHolder(View itemView) {
super(itemView);
tvDate = (TextView) itemView.findViewById(R.id.txt_date);
}
}
}
private class LabelerDate {
private int type;
private String number;
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
}
}
You can use a LinearSnapHelper
Attach to your recyclerView like
val snapHelper = LinearSnapHelper()
snapHelper.attachToRecyclerView(this)
Then, to get the center view, use
snapHelper.findSnapView(horizontalScrollView.layoutManager)?
As mentioned in the other answer, there is no direct way to do this.
This is probably how you can achieve what you described in the question.
Know the number of items visible on the screen.
Select the middle item programmatically every time the view is scrolled.
Keep a partially transparent image as an overlay on the middle item on the recyclerview. (You'll need to compute the coordinates based on the width of the recycler view or width of the screen and the width of the overlay image you choose to put.
Refresh the selected value in a text view below the recycler view every time there is a scroll.
The image overlays have to be placed in a way they appear connected and as one single control.
For this feature use EcoGallery library:
https://github.com/falnatsheh/EcoGallery
At first, I needed something similar, not this. But I was able to adapt #TranHieu solution to my needs, so I voted up his solution.
I wanted to create full-screen horizontal recyclerview that after user sroll updates scrollPosition to mostVisibleItem.
setup:
private void setUpScrolling() {
mRecyclerVIew.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
mRecyclerVIew.getViewTreeObserver().removeOnPreDrawListener(this);
CustomScrollListener listener = (CustomScrollListener) mScrollListener;
listener.width = mRecyclerVIew.getMeasuredWidth();
listener.dx = 0;
return true;
}
});
}
listener:
private class CustomScrollListener extends OnScrollListener {
private int mLastDx = 0;
int width = 0;
int dx = 0;
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
if (mLastDx != dx) {
scrollToMostVisibleItem();
} else {
dx = 0;
mLastDx = 0;
}
}
super.onScrollStateChanged(recyclerView, newState);
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
this.dx += dx;
}
private void scrollToMostVisibleItem() {
int direction = (dx > 0) ? 1 : -1;
dx = Math.abs(dx);
int shiftCount = Math.round(dx / width);
int pixelShift = dx % width;
if (pixelShift > width / 2) {
shiftCount++;
}
float targetScrollPixels = shiftCount * width;
float finalScrollPixels = (targetScrollPixels - dx) * direction;
if (finalScrollPixels != 0) {
mRecyclerVIew.smoothScrollBy((int) finalScrollPixels, 0);
mLastDx = (int) finalScrollPixels;
dx = 0;
}
}
}
I used another approach in my case.
you can find the deatils here: RecyclerView - How highlight central visible item during scroll1
In my opinion, my solution is more easy than the others.
If someone is looking for a more generic implementation, here is my code based on the answers of this thread:
Add the CenterLinearSnapHelper
public class CenterLinearSnapHelper extends LinearSnapHelper {
//Constants
public static final String TAG = CenterLinearSnapHelper.class.getSimpleName();
//Attributes
private Context context;
private float itemWidth;
private OnPaddingComputationListener listener;
//Constructors
/**
* A linear snap helper which helps centering the items in a recyclerview.
*
* #param itemWidth The (fixed) width of a child view in pixels.
*/
public CenterLinearSnapHelper(float itemWidth) {
this.itemWidth = itemWidth;
}
public void attachToRecyclerView(#Nullable RecyclerView recyclerView,
#NonNull OnPaddingComputationListener listener) throws IllegalStateException {
this.listener = listener;
//Calculates the padding for the first and end item
calculatePadding(recyclerView);
//Create a LinearSnapHelper and attach the recyclerView to it.
attachToRecyclerView(recyclerView);
}
public float getItemWidth() {
return itemWidth;
}
private void calculatePadding(RecyclerView recyclerView) {
if (recyclerView == null)
return;
ViewTreeObserver observer = recyclerView.getViewTreeObserver();
observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
recyclerView.getViewTreeObserver().removeOnPreDrawListener(this);
int finalWidth = recyclerView.getMeasuredWidth();
float padding = (finalWidth - itemWidth) / 2;
listener.onPadding(padding, finalWidth);
return true;
}
});
}
public interface OnPaddingComputationListener {
void onPadding(float padding, int finalWidth);
}
}
In your Activity/Fragment where you create your RecyclerView:
float itemWidth = getResources().getDimension(R.dimen.favorite_room_width);
CenterLinearSnapHelper snapHelper = new CenterLinearSnapHelper(itemWidth);
snapHelper.attachToRecyclerView(binding.listFavorites, (padding, finalWidth) -> {
//Set the adapter
roomAdapter = new RoomAdapter(requireContext(), rooms);
roomAdapter.addPaddingItems((int) padding);
roomAdapter.setOnToggleClickListener(FavoritesFragment.this);
binding.listFavorites.setAdapter(roomAdapter);
});
In your adapter:
public void addPaddingItems(int padding) {
if (padding < 0)
throw new IllegalStateException("Padding cannot be smaller than 0");
this.padding = padding;
//Add 2 new items as the first and last
//NOTE: If you update your existing dataset (e.g add new items), you should redo the calculation!
rooms.add(0, new Room("First"));
rooms.add(rooms.size(), new Room("Last"));
}
#Override
public int getItemViewType(int position) {
if (padding >= 0 && (position == 0 || position == rooms.size() - 1)) {
return VIEW_TYPE_PADDING;
}
return VIEW_TYPE_ITEM;
}
#NonNull
#Override
public RoomViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
ViewFavoriteRoomBinding binding = DataBindingUtil.inflate(inflater, R.layout.view_favorite_room, parent, false);
if (viewType == VIEW_TYPE_PADDING) {
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) binding.getRoot().getLayoutParams();
layoutParams.width = padding;
binding.getRoot().setLayoutParams(layoutParams);
}
RoomViewHolder viewHolder = new RoomViewHolder(context, binding, onToggleClickListener);
viewHolder.getRecyclerView().setRecycledViewPool(viewPool);
return viewHolder;
}

RecyclerView does not scroll as expected

I have a project where I use a horizontal recycler view and I want to center one element. My implementation works, but not in every case check this GIF here:
As you may note it scrolls correctly if I come from the left. If I come from the right it overscrolls a lot and I have no idea how to stop nor how to fix that.
I striped my code to this example here:
public class DemoActivity extends ActionBarActivity implements View.OnClickListener {
private static final int JUMP_TO_LEFT = MyAdapter.NON_VISIBLE_ITEMS + MyAdapter.VISIBLE_ITEMS - 1;
private static final int JUMP_TO_RIGHT = MyAdapter.NON_VISIBLE_ITEMS;
private LinearLayoutManager mLayoutManager;
private RecyclerView mRecycler;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_demo);
findViewById(android.R.id.button1).setOnClickListener(this);
mRecycler = (RecyclerView)findViewById(R.id.recycler);
MyAdapter mAdapter = new MyAdapter();
mLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
mRecycler.setLayoutManager(mLayoutManager);
mRecycler.setHasFixedSize(true);
mRecycler.scrollToPosition(MyAdapter.NON_VISIBLE_ITEMS);
mRecycler.setAdapter(mAdapter);
}
#Override
public void onClick(View v) {
int pos = mLayoutManager.findFirstVisibleItemPosition();
int outer = (MyAdapter.VISIBLE_ITEMS - 1) / 2;
if(pos + outer >= MyAdapter.ITEM_IN_CENTER) {
mRecycler.smoothScrollToPosition(JUMP_TO_RIGHT);
} else {
mRecycler.smoothScrollToPosition(JUMP_TO_LEFT);
}
}
}
And here is my adapter:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.Holder> implements View.OnClickListener {
public static final int VISIBLE_ITEMS = 7;
public static final int NON_VISIBLE_ITEMS = 150;
private static final int TOTAL_ITEMS = VISIBLE_ITEMS + NON_VISIBLE_ITEMS * 2;
public static final int ITEM_IN_CENTER = (int)Math.ceil(VISIBLE_ITEMS / 2f) + NON_VISIBLE_ITEMS;
private Calendar mCalendar;
public MyAdapter() {
mCalendar = GregorianCalendar.getInstance();
setHasStableIds(true);
}
private int getToday() {
return (int)TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis());
}
#Override
public int getItemCount() {
return TOTAL_ITEMS;
}
#Override
public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
final TextView tv = new TextView(parent.getContext());
int width = parent.getWidth() / VISIBLE_ITEMS;
tv.setLayoutParams(new TableRow.LayoutParams(width, ViewGroup.LayoutParams.MATCH_PARENT, 1));
tv.setGravity(Gravity.CENTER);
tv.setBackgroundColor(Color.TRANSPARENT);
DisplayMetrics metrics = tv.getContext().getResources().getDisplayMetrics();
float padding = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, metrics);
tv.setLineSpacing(padding, 1f);
tv.setPadding(0, (int)padding, 0, 0);
tv.setOnClickListener(this);
return new Holder(tv);
}
#Override
public void onBindViewHolder(Holder holder, int position) {
int today = getToday();
mCalendar.setTimeInMillis(System.currentTimeMillis());
mCalendar.set(Calendar.HOUR_OF_DAY, 12); // set to noon to avoid energy saver time problems
mCalendar.add(Calendar.DAY_OF_YEAR, position - ITEM_IN_CENTER + 1);
DateFormat format = new SimpleDateFormat("E\nd");
String label = format.format(mCalendar.getTime()).replace(".\n", "\n");
int day = (int)TimeUnit.MILLISECONDS.toDays(mCalendar.getTimeInMillis());
holder.update(day, today, label);
}
#Override
public long getItemId(int position) {
mCalendar.setTimeInMillis(System.currentTimeMillis());
mCalendar.set(Calendar.HOUR_OF_DAY, 12); // set to noon to avoid energy saver time problems
mCalendar.add(Calendar.DAY_OF_YEAR, position - ITEM_IN_CENTER + 1);
DateFormat format = new SimpleDateFormat("dMMyyyy");
return Long.parseLong(format.format(mCalendar.getTime()));
}
#Override
public void onClick(View v) {
String day = ((TextView)v).getText().toString().replace("\n", " ");
Toast.makeText(v.getContext(), "You clicked on " + day, Toast.LENGTH_SHORT).show();
}
public class Holder extends RecyclerView.ViewHolder {
private final Typeface font;
private Holder(TextView v) {
super(v);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
font = Typeface.create("sans-serif-light", Typeface.NORMAL);
} else {
font = null;
}
}
public void update(int day, int today, String label) {
TextView tv = (TextView)itemView;
tv.setText(label);
if(day == today) {
tv.setTextSize(18);
tv.setTypeface(null, Typeface.BOLD);
} else {
tv.setTextSize(16);
tv.setTypeface(font, Typeface.NORMAL);
}
tv.setBackgroundColor(0xff8dc380);
}
}
}
Do you see a reason for that? To make it simpler for you I also put this code on GitHub. https://github.com/rekire/RecylcerViewBug
I found a surprising simple workaround:
#Override
public void onClick(View v) {
int pos = mLayoutManager.findFirstVisibleItemPosition();
int outer = (MyAdapter.VISIBLE_ITEMS + 1) / 2;
int delta = pos + outer - ForecastAdapter.ITEM_IN_CENTER;
//Log.d("Scroll", "delta=" + delta);
View firstChild = mForecast.getChildAt(0);
if(firstChild != null) {
mForecast.smoothScrollBy(firstChild.getWidth() * -delta, 0);
}
}
Here I calculate the width to jump myself, that does exactly what I want.
In case of LinearLayoutManager with vertical orientation you can create own SmoothScroller and override calculateDyToMakeVisible() method where you can set desired view position. For example, to make the target view always to be appeared at the top side of RecyclerView after smoothScroll() write this:
class CustomLinearSmoothScroller extends LinearSmoothScroller {
public CustomLinearSmoothScroller(Context context) {
super(context);
}
#Override
public int calculateDyToMakeVisible(View view, int snapPreference) {
final RecyclerView.LayoutManager layoutManager = getLayoutManager();
if (!layoutManager.canScrollVertically()) {
return 0;
}
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)view.getLayoutParams();
final int top = layoutManager.getDecoratedTop(view) - params.topMargin;
final int bottom = layoutManager.getDecoratedBottom(view) + params.bottomMargin;
final int viewHeight = bottom - top;
final int start = layoutManager.getPaddingTop();
final int end = start + viewHeight;
return calculateDtToFit(top, bottom, start, end, snapPreference);
}
"top" and "bottom" - bounds of the target view
"start" and "end" - points between which the view should be placed during smoothScroll
This worked for me:
itemsView.smoothScrollBy(-recyclerView.computeHorizontalScrollOffset(), 0)
To support smooth scrolling, you must override
smoothScrollToPosition(RecyclerView, State, int) and create a
RecyclerView.SmoothScroller.
RecyclerView.LayoutManager is responsible
for creating the actual scroll action. If you want to provide a custom
smooth scroll logic, override smoothScrollToPosition(RecyclerView,
State, int) in your LayoutManager.
https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html#smoothScrollToPosition(int)
In your case, use smoothScrollBy could be a workaround (doesn't need this override).

Categories

Resources