Get Center Visible Item of Recycleview When Scrolling

Get center visible item of RecycleView when scrolling

I made something just like this. I can do exactly what you need.
First of all, this is how is my alogrithm work
Sample Image

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:
Sample Image

Look at this video link, this is my app demo

How to get the center item after RecyclerView snapped it to center?

if you need the View, you can call

 View view =  snapHelper.findSnapView(layoutManagaer);

once you have the View, you should be able to get the position on the dataset for that View. For instance using

   mainMenu.getChildAdapterPosition(view)

Get the center visible item value of Infinite RecyclerView

By using center indicator(textview) in the layout and addOnScrollListner we can achieve this
please refer the following example

In xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:layout_width="wrap_content"
android:text="↓"
android:id="@+id/centerIndicator"
android:textSize="24sp"
android:textStyle="bold"
android:visibility="visible"
android:textColor="@color/theme_yellow"
android:layout_centerHorizontal="true"
android:layout_height="wrap_content"
android:layout_marginTop="27dp"
android:background="@android:color/transparent"
/>
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:id="@+id/list"
android:clipToPadding="false"
android:divider="@android:color/transparent"
android:layout_height="wrap_content"/>

</RelativeLayout>

In Activity/Fragment:

public class Sample extends Fragment {
RecyclerView listView;
ArrayList<String>mWeekDaysList=new ArrayList<>();
LinearLayoutManager mlinearLayoutManagerForDateList;
DateAdapter mDateAdapter;
TimeListAdapter mtimeAdapter;
private int mCenterPivot;
private boolean mAutoSet = true;
Activity mactivity;
public NigaichiNiralFrag() {
// Required empty public constructor
}
@Override
public View onCreateView(final LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view= inflater.inflate(R.layout.fragment_nigaichi_niral, container, false);
mactivity=getActivity();
mWeekDaysList.add("Sunday");
mWeekDaysList.add("Monday");
mWeekDaysList.add("Tuesday");
mWeekDaysList.add("Wednesday");
mWeekDaysList.add("Thursday");
mWeekDaysList.add("Friday");
mWeekDaysList.add("Saturday");

listView = (RecyclerView) view.findViewById(R.id.list);
mlinearLayoutManagerForDateList = new LinearLayoutManager(mactivity);
mlinearLayoutManagerForDateList.setOrientation(LinearLayoutManager.HORIZONTAL);
listView.setLayoutManager(mlinearLayoutManagerForDateList);
final TextView mCenterIndicator = (TextView) view.findViewById(R.id.centerIndicator);
final int itemWidth = (int) getResources().getDimension(R.dimen.flexible_space_image_height) ;
mlinearLayoutManagerForDateList.scrollToPosition(Integer.MAX_VALUE / 2);
mDateAdapter=new DateAdapter(mWeekDaysList);
listView.setAdapter(mDateAdapter);
mCenterIndicator.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {

int center = ( mCenterIndicator.getLeft() + mCenterIndicator.getRight() ) / 2 ;
int padding = center - itemWidth / 2; //Assuming both left and right padding needed are the same
listView.setPadding(5,0,5,0);
mCenterPivot = center;
listView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
LinearLayoutManager lm = (LinearLayoutManager) recyclerView.getLayoutManager();
if( mCenterPivot == 0 ) {

// Default pivot , Its a bit inaccurate .
// Better pass the center pivot as your Center Indicator view's
// calculated center on it OnGlobalLayoutListener event
mCenterPivot = lm.getOrientation() == LinearLayoutManager.HORIZONTAL ? ( recyclerView.getLeft() + recyclerView.getRight() ) : ( recyclerView.getTop() + recyclerView.getBottom() );
}
if( !mAutoSet ) {

if( newState == RecyclerView.SCROLL_STATE_IDLE ) {
//ScrollStoppped

View view = findCenterView(lm);//get the view nearest to center
//view.setBackgroundColor(Color.RED);

int position = recyclerView.getChildAdapterPosition(view) % mWeekDaysList.size();
Log.d("isideScroll",mWeekDaysList.get(position));
mDateAdapter.setSelecteditem(position);
int viewCenter = lm.getOrientation() == LinearLayoutManager.HORIZONTAL ? ( view.getLeft() + view.getRight() )/2 :( view.getTop() + view.getBottom() )/2;
//compute scroll from center
int scrollNeeded = viewCenter - mCenterPivot; // Add or subtract any offsets you need here

if( lm.getOrientation() == LinearLayoutManager.HORIZONTAL ) {

recyclerView.smoothScrollBy(scrollNeeded, 0);
}
else
{
recyclerView.smoothScrollBy(0, (int) (scrollNeeded));

}
mAutoSet =true;
}
}
if( newState == RecyclerView.SCROLL_STATE_DRAGGING || newState == RecyclerView.SCROLL_STATE_SETTLING ){

mAutoSet =false;
}
}

@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
}
});

}
});
return returnView;
}
private void scrollToCenter(View v) {
int itemToScroll = listView.getChildAdapterPosition(v);
int centerOfScreen = listView.getWidth() / 2 - v.getWidth() / 2;
//v.setBackgroundColor(Color.RED);
mlinearLayoutManagerForDateList.scrollToPositionWithOffset(itemToScroll, centerOfScreen);
}
private View findCenterView(LinearLayoutManager lm) {

int minDistance = 0;
View view = null;
View returnView = null;
boolean notFound = true;

for(int i = lm.findFirstVisibleItemPosition(); i <= lm.findLastVisibleItemPosition() && notFound ; i++ ) {

view=lm.findViewByPosition(i);

int center = lm.getOrientation() == LinearLayoutManager.HORIZONTAL ? ( view.getLeft() + view.getRight() )/ 2 : ( view.getTop() + view.getBottom() )/ 2;
int leastDifference = Math.abs(mCenterPivot - center);

if( leastDifference <= minDistance || i == lm.findFirstVisibleItemPosition())
{
minDistance = leastDifference;
returnView=view;
}
else
{
notFound=false;

}
}
return returnView;
}
}

Adapter:

public class DateAdapter extends  RecyclerView.Adapter<DateAdapter.ReviewHolder>  {
ArrayList<String> mData;
private int selectedItem = -1;
int pos=0;
public DateAdapter(ArrayList<String> data){
mData=data;
}

@Override
public ReviewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Context context = parent.getContext();
View v= LayoutInflater.from(context).inflate(R.layout.item_horz,parent,false);
return new DateAdapter.ReviewHolder(v);
}

@Override
public void onBindViewHolder(ReviewHolder holder, int position) {
pos=position;
position = position % mData.size();
holder.tvName.setText(mData.get(position));
holder.tvName.setGravity(Gravity.CENTER);

if (position == selectedItem) {
Log.d("CenterPosition", "center" + position);
holder.tvName.setTextColor(Color.RED);
holder.tvName.setTextSize(20);
holder.tvName.setBackgroundColor(Color.parseColor("#fccd00"));

} else {
holder.tvName.setTextColor(Color.WHITE);
holder.tvName.setTextSize(16);
holder.tvName.setBackgroundColor(Color.BLACK);
}
}

@Override
public int getItemCount() {
// return mData.size();
return Integer.MAX_VALUE;
}

public class ReviewHolder extends RecyclerView.ViewHolder {

protected TextView tvName;
View container;

public ReviewHolder(View itemView) {
super(itemView);
container=itemView;
tvName= (TextView) itemView.findViewById(R.id.text);
}
}
public void setSelecteditem(int selecteditem) {
Log.d("POSITION",String.valueOf(selecteditem));
this.selectedItem = selecteditem;
notifyDataSetChanged();
}

}

item_horz.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="151dp"
android:id="@+id/wrapper"
android:background="@color/white"
android:orientation="horizontal"
android:layout_height="50dp">
<LinearLayout
android:layout_width="150dp"
android:layout_height="50dp"
android:background="@color/black">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textSize="20sp"
android:id="@+id/text"
android:textColor="@color/white"
android:text="21"
android:gravity="center"
/>
</LinearLayout>
</LinearLayout>

Hope this will help you guys..

RecyclerView - How highlight central visible item during scroll

Hope this helps -

Note that this is a hacky solution, especially the highlighting on scroll.
Also, I have not profiled any performance. You should probably check it.
I also changed timeLapseButton to TextView since changing background on Button will collapse other layout properties.

If you don't like hacky solution, you could check out some chart libraries.

ex:) https://github.com/PhilJay/MPAndroidChart

Adapter class

public interface TimeLapseButtonClickListener {
void onClick(int position);
}

private TimeLapseButtonClickListener timeLapseButtonListener;

public void setTimeLapseButtonClickListener(TimeLapseButtonClickListener listener) {
this.timeLapseButtonListener = listener;
}

private void setTimeLapseButton(Button button, String text, final int position) {
button.setText(text);

button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
this.timeLapseButtonListener.onClick(position);
}
});
}

Activity or Fragment where you have recyclerView & adapter

...

private int prevCenterPos; // Keep track the previous pos to dehighlight

...

// Click and smooth scroll to get clicked item in the middle
adapter.setListener(new RvTrendsGraphAdapter.TimeLapseButtonClickListener() {
@Override
public void onClick(int position) {
View view = mRecyclerView.getLayoutManager().findViewByPosition(position);
int middle = mRecyclerView.getWidth() / 2;
mRecyclerView.smoothScrollBy(view.getLeft() - middle, 0, new AccelerateDecelerateInterpolator());
}
});

// Highlight view in the middle on scroll
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {

@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);

int center = mRecyclerView.getWidth() / 2;
View centerView = mRecyclerView.findChildViewUnder(center, mRecyclerView.getTop());
int centerPos = mRecyclerView.getChildAdapterPosition(centerView);

if (prevCenterPos != centerPos) {
// dehighlight the previously highlighted view
View prevView = mRecyclerView.getLayoutManager().findViewByPosition(prevCenterPos);
if (prevView != null) {
View button = prevView.findViewById(R.id.rv_trends_graph_button);
int white = ContextCompat.getColor(context, R.color.white);
button.setBackgroundColor(white);
}

// highlight view in the middle
if (centerView != null) {
View button = centerView.findViewById(R.id.rv_trends_graph_button);
int highlightColor = ContextCompat.getColor(context, R.color.colorAccent);
button.setBackgroundColor(highlightColor);
}

prevCenterPos = centerPos;
}
}
});

Get visible items in RecyclerView

First / last visible child depends on the LayoutManager.
If you are using LinearLayoutManager or GridLayoutManager, you can use

int findFirstVisibleItemPosition();
int findFirstCompletelyVisibleItemPosition();
int findLastVisibleItemPosition();
int findLastCompletelyVisibleItemPosition();

For example:

GridLayoutManager layoutManager = ((GridLayoutManager)mRecyclerView.getLayoutManager());
int firstVisiblePosition = layoutManager.findFirstVisibleItemPosition();

For LinearLayoutManager, first/last depends on the adapter ordering. Don't query children from RecyclerView; LayoutManager may prefer to layout more items than visible for caching.



Related Topics



Leave a reply



Submit