i have created a circular recyclerview by making adapter count into Integer.MAX.Now, i need to highlight the center recycler item like in the image.Kindly help me!!
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..
if you want to use horizontalscrollview (create items dynamically)instead recyclerview.
create your parent layout
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true">
<LinearLayout
android:layout_alignParentBottom="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"/>
</HorizontalScrollView>
inflate your childs items to parent layout.
LayoutInflater inflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
for (int i = 0; i < list.size(); i++) {
// inflate child element
final LinearLayout titleRow = (LinearLayout) inflater.inflate(R.layout.item_gallery, fragmentGalleryScrollLl, false);
final CircleImageView shapeImageView = (CircleImageView) titleRow.findViewById(R.id.item_gallery_iv);
shapeImageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
scrollItem(titleRow);
}
});
fragmentGalleryScrollLl.addView(titleRow);
}
use this method when your click the items.
private void scrollItem(LinearLayout titleRow) {
int scrollX = (titleRow.getLeft() - (fragmentGalleryScrollSv.getWidth() / 2)) + (titleRow.getWidth() / 2);
fragmentGalleryScrollSv.smoothScrollTo(scrollX, 0);
}
Inside BindView, you can check middle position of the adapter and apply logic for image for that specific holder.
onBindViewHolder(View holder, int postion){
if(position == getItemCount() / 2)
{ //Write image logic for holder
}}
Callback to get center item position when scrolling stopped
import android.content.Context
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
class RecyclerCenterItemFinder(
private val context: Context,
private val layoutManager: LinearLayoutManager,
private val callback: (Int) -> Unit,
private val controlState: Int = RecyclerView.SCROLL_STATE_IDLE
) :
RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
if (controlState == ALL_STATES || newState == controlState) {
val firstVisible = layoutManager.findFirstVisibleItemPosition()
val lastVisible = layoutManager.findLastVisibleItemPosition()
val itemsCount = lastVisible - firstVisible + 1
val screenCenter: Int = context.resources.displayMetrics.widthPixels / 2
var minCenterOffset = Int.MAX_VALUE
var middleItemIndex = 0
for (index in 0 until itemsCount) {
val listItem = layoutManager.getChildAt(index) ?: return
val topOffset = listItem.top
val bottomOffset = listItem.bottom
val centerOffset =
Math.abs(topOffset - screenCenter) + Math.abs(bottomOffset - screenCenter)
if (minCenterOffset > centerOffset) {
minCenterOffset = centerOffset
middleItemIndex = index + firstVisible
}
}
callback(middleItemIndex)
}
}
companion object {
const val ALL_STATES = 10
}
}
recycler.addOnScrollListener(RecyclerCenterItemFinder(requireContext(),
recycler.layoutManager,
{ centerItemPosition ->
// do something
}
))
Related
How can I snap to particular position for LinearSnapHelper() in horizontal RecyclerView? There is a function scrolltoposition for RecyclerView which scroll to that position but did not keep it in center for this snaphelper.
I am looking for something like below image. So when I set to particular position, it will keep it in center. I dont find anything related to select position for SnapHelper
i find this , but this doesn't help me.
Any help would be appreciated.
If I understand your question, you are looking for a way to jump to a position and have that position centered in the RecyclerView.
Maybe you have tried RecyclerView.scrollToPosition() but that doesn't snap to the view. You may also have tried RecyclerView.smoothScrollToPosition() and that works better but you may want to avoid all the movement if you have a lot of items and are scrolling a long way.
The reason that scrollToPosition() doesn't work is that it doesn't trigger the LinearSnapHelper which uses a scroll listener to detect when to snap. Since smoothScrollToPosition() does trigger the LinearSnapHelper, we will use scrollToPosition() to get us in the area of the target view then use smoothScrollToPosition() to get the view centered as follows:
private RecyclerView mRecycler;
private void newScrollTo(final int pos) {
RecyclerView.ViewHolder vh = mRecycler.findViewHolderForLayoutPosition(pos);
if (vh != null) {
// Target view is available, so just scroll to it.
mRecycler.smoothScrollToPosition(pos);
} else {
// Target view is not available. Scroll to it.
mRecycler.addOnScrollListener(new RecyclerView.OnScrollListener() {
// From the documentation:
// This callback will also be called if visible item range changes after a layout
// calculation. In that case, dx and dy will be 0.This callback will also be called
// if visible item range changes after a layout calculation. In that case,
// dx and dy will be 0.
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
mRecycler.removeOnScrollListener(this);
if (dx == 0) {
newScrollTo(pos);
}
}
});
mRecycler.scrollToPosition(pos);
}
}
Sample app
MainActivity.java
public class MainActivity extends AppCompatActivity {
private final LinearLayoutManager mLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
private final List<String> mItems = new ArrayList<>();
private RecyclerView mRecycler;
private final int mItemCount = 2000;
private final Handler mHandler = new Handler();
private final LinearSnapHelper mLinearSnapHelper = new LinearSnapHelper();
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
for (int i = 0; i < mItemCount; i++) {
mItems.add(i + "");
}
mRecycler = findViewById(R.id.recyclerView);
final RecyclerViewAdapter adapter = new RecyclerViewAdapter(null);
adapter.setItems(mItems);
mRecycler.setLayoutManager(mLayoutManager);
mRecycler.setAdapter(adapter);
mLinearSnapHelper.attachToRecyclerView(mRecycler);
newScrollTo(1);
// fireScrollTo();
}
private int maxScrolls = mItemCount;
private void fireScrollTo() {
if (--maxScrolls > 0) {
int pos = (int) (Math.random() * mItemCount);
newScrollTo(pos);
mHandler.postDelayed(new Runnable() {
#Override
public void run() {
fireScrollTo();
}
}, 2000);
}
}
private void newScrollTo(final int pos) {
mRecycler.smoothScrollToPosition(pos);
RecyclerView.ViewHolder vh = mRecycler.findViewHolderForLayoutPosition(pos);
if (vh != null) {
// Target view is available, so just scroll to it.
mRecycler.smoothScrollToPosition(pos);
} else {
// Target view is not available. Scroll to it.
mRecycler.addOnScrollListener(new RecyclerView.OnScrollListener() {
// From the documentation:
// This callback will also be called if visible item range changes after a layout
// calculation. In that case, dx and dy will be 0.This callback will also be called
// if visible item range changes after a layout calculation. In that case,
// dx and dy will be 0.
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
mRecycler.removeOnScrollListener(this);
if (dx == 0) {
newScrollTo(pos);
}
}
});
mRecycler.scrollToPosition(pos);
}
}
}
activity_main.xml
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:layout_width="3px"
android:layout_height="match_parent"
android:background="#android:color/holo_red_light"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingStart="660px"
android:paddingEnd="660px"/>
</android.support.constraint.ConstraintLayout>
RecyclerViewAdapter.java
class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<String> mItems;
RecyclerViewAdapter(List<String> items) {
mItems = items;
}
#Override
public #NonNull
RecyclerView.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(android.R.layout.simple_list_item_1, parent, false);
view.getLayoutParams().width = 220;
view.getLayoutParams().height = 220;
// view.setPadding(220 * 3, 0, 220 * 3, 0);
((TextView) view).setGravity(Gravity.CENTER);
return new ItemViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder holder, int position) {
ItemViewHolder vh = (ItemViewHolder) holder;
String itemText = mItems.get(position);
vh.mItemTextView.setText(itemText);
int bgColor = (position % 2 == 0)
? android.R.color.holo_blue_light
: android.R.color.holo_green_light;
holder.itemView.setBackgroundColor(
holder.itemView.getContext().getResources().getColor(bgColor));
}
#Override
public int getItemCount() {
return (mItems == null) ? 0 : mItems.size();
}
#Override
public int getItemViewType(int position) {
return TYPE_ITEM;
}
static class ItemViewHolder extends RecyclerView.ViewHolder {
private TextView mItemTextView;
ItemViewHolder(View item) {
super(item);
mItemTextView = item.findViewById(android.R.id.text1);
}
}
public void setItems(List<String> items) {
mItems = items;
}
#SuppressWarnings("unused")
private final static String TAG = "RecyclerViewAdapter";
private final static int TYPE_ITEM = 1;
}
Add this where ever you want to scroll your recycler view
recyclerView.scrollToPosition(position)
recyclerView.post {
var view = recyclerView.layoutManager?.findViewByPosition(position);
if (view == null) {
// do nothing
}
var snapDistance = snapHelper.calculateDistanceToFinalSnap(recyclerView.layoutManager!!, view!!)
if (snapDistance?.get(0) != 0 || snapDistance[1] != 0) {
recyclerView.scrollBy(snapDistance?.get(0)!!, snapDistance?.get(1));
}
}
By using this LinearSnap Helper which is attached to your recycler view
var snapHelper = LinearSnapHelper()
snapHelper.attachToRecyclerView(recyclerView)
For horizontal RecyclerView, you should use PagerSnapHelper() instead of LinearSnapHelper().
Just did some research and trace the SnapHelper's source code, it turns out the solution could be very simple:
class MyPagerSnapHelper: PagerSnapHelper() {
fun smoothScrollToPosition(layoutManager: RecyclerView.LayoutManager, position: Int) {
val smoothScroller = createScroller(layoutManager) ?: return
smoothScroller.targetPosition = position
layoutManager.startSmoothScroll(smoothScroller)
}
}
And then you can pass RecyclerView's LayoutManager and target position here
snapHelper.smoothScrollToPosition(recyclerView.layoutManager!!, index)
I have a recyclerView in coordinatorLayout and I want to get last visible Item from it:
<?xml version="1.0" encoding="utf-8"?><android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.design.widget.AppBarLayout
android:id="#+id/appbarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/colorPrimary"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar"
>
<android.support.design.widget.CollapsingToolbarLayout
android:id="#+id/main.collapsing"
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#f00"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:scrollbars="none"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/lastVisibleItem"
android:text="last position"
/>
</android.support.design.widget.CoordinatorLayout>
and
findViewById(R.id.lastVisibleItem).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int position=((LinearLayoutManager) mRecyclerView.getLayoutManager()).findLastCompletelyVisibleItemPosition();
Log.d(TAG, "onClick: position="+position);
Toast.makeText(RecyclerViewActivity.this, "position="+position, Toast.LENGTH_SHORT).show();
}
});
the problem is in start findLastCompletelyVisibleItemPosition() method return, for example, 14 which is wrong and 12 is right and after I scroll down to item 14 findLastCompletelyVisibleItemPosition() return 14 again.
I know that's because of AppBarLayout and CoordinatorLayout but I cannot find proper way to find out what's right last complete visible item position is.
I've managed to find a workaround for this issue
You should use custom LayoutManager for your RecyclerView and override methods for finding first and last positions
public final class FixedForAppBarLayoutManager extends LinearLayoutManager {
public FixedForAppBarLayoutManager(final Context context, final AttributeSet attrs, final int defStyleAttr, final int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public FixedForAppBarLayoutManager(final Context context, final int spanCount) {
super(context, spanCount);
}
public FixedForAppBarLayoutManager(final Context context, final int spanCount, final int orientation, final boolean reverseLayout) {
super(context, spanCount, orientation, reverseLayout);
}
#Override
public int findFirstVisibleItemPosition() {
final View item = findVisibleItem(0, getChildCount(), false);
return item == null ? -1 : getPosition(item);
}
#Override
public int findFirstCompletelyVisibleItemPosition() {
final View item = findVisibleItem(0, getChildCount(), true);
return item == null ? -1 : getPosition(item);
}
#Override
public int findLastVisibleItemPosition() {
final View item = findVisibleItem(getChildCount() - 1, -1, false);
return item == null ? -1 : getPosition(item);
}
#Override
public int findLastCompletelyVisibleItemPosition() {
final View item = findVisibleItem(getChildCount() - 1, -1, true);
return item == null ? -1 : getPosition(item);
}
private View findVisibleItem(final int fromIndex, final int toIndex, final boolean isCompletely) {
final int next = toIndex > fromIndex ? 1 : -1;
for (int i = fromIndex; i != toIndex; i += next) {
final View child = getChildAt(i);
if (checkIsVisible(child, isCompletely)) {
return child;
}
}
return null;
}
private boolean checkIsVisible(final View child, final boolean isCompletely) {
final int[] location = new int[2];
child.getLocationOnScreen(location);
final View parent = (View) child.getParent();
final Rect parentRect = new Rect();
parent.getGlobalVisibleRect(parentRect);
if (getOrientation() == HORIZONTAL) {
final int childLeft = location[0];
final int childRight = location[0] + child.getWidth();
return isCompletely
? childLeft >= parentRect.left && childRight <= parentRect.right
: childLeft <= parentRect.right && childRight >= parentRect.left;
} else {
final int childTop = location[1];
final int childBottom = location[1] + child.getHeight();
return isCompletely
? childTop >= parentRect.top && childBottom <= parentRect.bottom
: childTop <= parentRect.bottom && childBottom >= parentRect.top;
}
}
}
Answer below works like a charm! Maybe for someone kotlin variant can be useful:
import android.content.Context
import android.graphics.Rect
import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
class FixedForAppBarLayoutManager(context: Context?) : LinearLayoutManager(context) {
override fun findFirstVisibleItemPosition(): Int {
val item: View? = findVisibleItem(0, childCount, false)
return if (item == null) -1 else getPosition(item)
}
override fun findFirstCompletelyVisibleItemPosition(): Int {
val item: View? = findVisibleItem(0, childCount, true)
return if (item == null) -1 else getPosition(item)
}
override fun findLastVisibleItemPosition(): Int {
val item: View? = findVisibleItem(childCount - 1, -1, false)
return if (item == null) -1 else getPosition(item)
}
override fun findLastCompletelyVisibleItemPosition(): Int {
val item: View? = findVisibleItem(childCount - 1, -1, true)
return if (item == null) -1 else getPosition(item)
}
private fun findVisibleItem(
fromIndex: Int,
toIndex: Int,
isCompletely: Boolean
): View? {
val next = if (toIndex > fromIndex) 1 else -1
var i = fromIndex
while (i != toIndex) {
val child: View? = getChildAt(i)
child?.let {
if (checkIsVisible(child, isCompletely)) {
return child
}
i += next
}
}
return null
}
private fun checkIsVisible(child: View, isCompletely: Boolean): Boolean {
val location = IntArray(2)
child.getLocationOnScreen(location)
val parent: View = child.parent as View
val parentRect = Rect()
parent.getGlobalVisibleRect(parentRect)
return if (orientation == HORIZONTAL) {
val childLeft = location[0]
val childRight: Int = location[0] + child.width
if (isCompletely) {
childLeft >= parentRect.left && childRight <= parentRect.right
} else {
childLeft <= parentRect.right && childRight >= parentRect.left
}
} else {
val childTop = location[1]
val childBottom: Int = location[1] + child.height
if (isCompletely) {
childTop >= parentRect.top && childBottom <= parentRect.bottom
} else {
childTop <= parentRect.bottom && childBottom >= parentRect.top
}
}
}
}
I have to create following layout
So far, I have successfully created the layout and populated all views. However, I am facing problem in the making ReyclerView Endless on first fragment.
Consider the RecyclerView has 10 items on first load, now on scroll I am adding another 10 items and so on. However, the RecyclerView isn't displaying those items, it's height gets fixed at the end of 10th element. I know that the elements are loaded correctly in RecyclerView and if I try to scroll with two fingers on emulator (GenyMotion), the RecyclerView scrolls just fine.
Update :-
Code for RecyclerView's Fragment -
public class CheckInFragmentRecyclerAdapter extends RecyclerView.Adapter<CheckInFragmentRecyclerAdapter.ViewHolder> {
final List<StoreNew> stores;
public CheckInFragmentRecyclerAdapter(final List<StoreNew> stores) {
this.stores = stores;
}
#Override
public CheckInFragmentRecyclerAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.child_check_in_fragment, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(CheckInFragmentRecyclerAdapter.ViewHolder holder, int position) {
// Setting data
}
#Override
public int getItemCount() {
return stores.size();
}
/**
* Function to clear existing data from list
* #param stores StoreNew instance containing store information
*/
public void update(final List<StoreNew> stores) {
this.stores.clear();
this.stores.addAll(stores);
notifyDataSetChanged();
}
/**
* Function to add more data to list
* #param stores StoreNew instance containing store information
*/
public void addNewList(final List<StoreNew> stores) {
this.stores.addAll(stores);
notifyDataSetChanged();
}
public class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(View itemView) {
super(itemView);
// Initializing component
}
}
}
Update :-
Adding layouts for used screens.
main_screen.xml - This is the home screen
<android.support.v4.widget.NestedScrollView
android:id="#+id/scrollView1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_above="#+id/home_footer"
android:layout_below="#+id/toolbar"
android:fillViewport="true">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- Sliding Tab for showing images -->
<com.example.slidingtab.SlidingTabLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/white" />
<!-- ViewPager for Images -->
<android.support.v4.view.ViewPager
android:id="#+id/vpOffers"
android:layout_width="match_parent"
android:layout_height="150dp"
android:layout_marginTop="8dp" />
<!-- Segmented Control for fragments -->
<info.hoang8f.android.segmented.SegmentedGroup xmlns:segmentedgroup="http://schemas.android.com/apk/res-auto"
android:id="#+id/segmented2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="15dp"
android:layout_marginEnd="10dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
android:orientation="horizontal"
segmentedgroup:sc_border_width="1dp"
segmentedgroup:sc_corner_radius="4dp"
segmentedgroup:sc_tint_color="#color/black">
<RadioButton
android:id="#+id/rbTab1"
style="#style/segmented_radio_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:checked="true"
android:textSize="#dimen/normal_text"
android:text="#string/check_in" />
<RadioButton
android:id="#+id/rbTab2"
style="#style/segmented_radio_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="#dimen/normal_text"
android:text="#string/upload_bill" />
<RadioButton
android:id="#+id/rbTab3"
style="#style/segmented_radio_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="#dimen/normal_text"
android:text="#string/redeem" />
</info.hoang8f.android.segmented.SegmentedGroup>
<!-- Custom wrap content ViewPager containing fragments -->
<!-- This will make sure that the height of ViewPager is equal to height of Fragment -->
<com.example.ui.custom.WrapContentHeightViewPager
android:id="#+id/vpFragments"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="-7dp" />
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
WrapContentHeightViewPager.java
public class WrapContentHeightViewPager extends ViewPager {
private static final String TAG = WrapContentHeightViewPager.class.getSimpleName();
private int height = 0;
private int decorHeight = 0;
private int widthMeasuredSpec;
private boolean animateHeight;
private int rightHeight;
private int leftHeight;
private int scrollingPosition = -1;
private boolean enabled;
public WrapContentHeightViewPager(Context context) {
super(context);
init();
}
public WrapContentHeightViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
this.enabled = true;
init();
}
private void init() {
addOnPageChangeListener(new OnPageChangeListener() {
public int state;
#Override
public void onPageScrolled(int position, float offset, int positionOffsetPixels) {}
#Override
public void onPageSelected(int position) {
if (state == SCROLL_STATE_IDLE) {
height = 0; // measure the selected page in-case it's a change without scrolling
Log.d(TAG, "onPageSelected:" + position);
}
}
#Override
public void onPageScrollStateChanged(int state) {
this.state = state;
}
});
}
#Override
public boolean onTouchEvent(MotionEvent event) {
return this.enabled && super.onTouchEvent(event);
}
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return this.enabled && super.onInterceptTouchEvent(event);
}
public void setPagingEnabled(boolean enabled) {
this.enabled = enabled;
}
#Override
public void setAdapter(PagerAdapter adapter) {
height = 0; // so we measure the new content in onMeasure
super.setAdapter(new PagerAdapterWrapper(adapter));
}
/**
* Allows to redraw the view size to wrap the content of the bigger child.
*
* #param widthMeasureSpec with measured
* #param heightMeasureSpec height measured
*/
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
widthMeasuredSpec = widthMeasureSpec;
int mode = MeasureSpec.getMode(heightMeasureSpec);
if (mode == MeasureSpec.UNSPECIFIED || mode == MeasureSpec.AT_MOST) {
if(height == 0) {
// measure vertical decor (i.e. PagerTitleStrip) based on ViewPager implementation
decorHeight = 0;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
if(lp != null && lp.isDecor) {
int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
boolean consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM;
if(consumeVertical) {
decorHeight += child.getMeasuredHeight() ;
}
}
}
// make sure that we have an height (not sure if this is necessary because it seems that onPageScrolled is called right after
int position = getCurrentItem();
View child = getViewAtPosition(position);
if (child != null) {
height = measureViewHeight(child);
}
//Log.d(TAG, "onMeasure height:" + height + " decor:" + decorHeight);
}
int totalHeight = height + decorHeight + getPaddingBottom() + getPaddingTop();
heightMeasureSpec = MeasureSpec.makeMeasureSpec(totalHeight, MeasureSpec.EXACTLY);
//Log.d(TAG, "onMeasure total height:" + totalHeight);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
#Override
public void onPageScrolled(int position, float offset, int positionOffsetPixels) {
super.onPageScrolled(position, offset, positionOffsetPixels);
// cache scrolled view heights
if (scrollingPosition != position) {
scrollingPosition = position;
// scrolled position is always the left scrolled page
View leftView = getViewAtPosition(position);
View rightView = getViewAtPosition(position + 1);
if (leftView != null && rightView != null) {
leftHeight = measureViewHeight(leftView);
rightHeight = measureViewHeight(rightView);
animateHeight = true;
//Log.d(TAG, "onPageScrolled heights left:" + leftHeight + " right:" + rightHeight);
} else {
animateHeight = false;
}
}
if (animateHeight) {
int newHeight = (int) (leftHeight * (1 - offset) + rightHeight * (offset));
if (height != newHeight) {
//Log.d(TAG, "onPageScrolled height change:" + newHeight);
height = newHeight;
requestLayout();
invalidate();
}
}
}
private int measureViewHeight(View view) {
view.measure(getChildMeasureSpec(widthMeasuredSpec, getPaddingLeft() + getPaddingRight(), view.getLayoutParams().width), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
return view.getMeasuredHeight();
}
protected View getViewAtPosition(int position) {
if(getAdapter() != null) {
Object objectAtPosition = ((PagerAdapterWrapper) getAdapter()).getObjectAtPosition(position);
if (objectAtPosition != null) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child != null && getAdapter().isViewFromObject(child, objectAtPosition)) {
return child;
}
}
}
}
return null;
}
/**
* Wrapper for PagerAdapter so we can ask for Object at index
*/
private class PagerAdapterWrapper extends PagerAdapter {
private final PagerAdapter innerAdapter;
private SparseArray<Object> objects;
public PagerAdapterWrapper(PagerAdapter adapter) {
this.innerAdapter = adapter;
this.objects = new SparseArray<>(adapter.getCount());
}
#Override
public void startUpdate(ViewGroup container) {
innerAdapter.startUpdate(container);
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
Object object = innerAdapter.instantiateItem(container, position);
objects.put(position, object);
return object;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
innerAdapter.destroyItem(container, position, object);
objects.remove(position);
}
#Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
innerAdapter.setPrimaryItem(container, position, object);
}
#Override
public void finishUpdate(ViewGroup container) {
innerAdapter.finishUpdate(container);
}
#Override
public Parcelable saveState() {
return innerAdapter.saveState();
}
#Override
public void restoreState(Parcelable state, ClassLoader loader) {
innerAdapter.restoreState(state, loader);
}
#Override
public int getItemPosition(Object object) {
return innerAdapter.getItemPosition(object);
}
#Override
public void notifyDataSetChanged() {
innerAdapter.notifyDataSetChanged();
}
#Override
public void registerDataSetObserver(DataSetObserver observer) {
innerAdapter.registerDataSetObserver(observer);
}
#Override
public void unregisterDataSetObserver(DataSetObserver observer) {
innerAdapter.unregisterDataSetObserver(observer);
}
#Override
public float getPageWidth(int position) {
return innerAdapter.getPageWidth(position);
}
#Override
public CharSequence getPageTitle(int position) {
return innerAdapter.getPageTitle(position);
}
#Override
public int getCount() {
return innerAdapter.getCount();
}
#Override
public boolean isViewFromObject(View view, Object object) {
return innerAdapter.isViewFromObject(view, object);
}
public Object getObjectAtPosition(int position) {
return objects.get(position);
}
}
}
first_fragment.xml
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/lvCheckIn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:requiresFadingEdge="none"
android:fadingEdgeLength="0dp"
android:orientation="vertical" />
Adding more data to the RecyclerView on scrolling -
private RecyclerView.OnScrollListener scrollListener = new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
visibleItemCount = recyclerView.getChildCount();
totalItemCount = adapter.getItemCount();
firstVisibleItem = ((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition();
if (loading) {
if (totalItemCount > previousTotal) {
loading = false;
previousTotal = totalItemCount;
}
}
if (!loading && (totalItemCount - visibleItemCount)
<= (firstVisibleItem + visibleThreshold) && current_page < totalPages) {
// End has been reached
// Do something
current_page++;
// Sending request to server
loading = true;
}
}
};
When data is received via API (adapter already added above)-
adapter.addNewList(homePageNew.checkin_stores.stores);
There seems to be a similar discussion here: ViewPager in a NestedScrollView
Maybe the sample of the Naruto guy (https://github.com/TheLittleNaruto/SupportDesignExample/) could solve your situation.
Edit
I'm not sure if I'm getting the point of your question. Anyway here you can a possible solution to your situation.
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/app_bar_layout"
>
<android.support.design.widget.CollapsingToolbarLayout
android:id="#+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
>
---- include here everything before the pager ----
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
---- this is your pager
<include layout="#layout/fragment_pager"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="#+id/app_bar_layout"
/>
</android.support.design.widget.CoordinatorLayout>
Then you can just listen to the RecycleView in the pager to add items as the RecycleView scrolls to bottom.
I hope it helped
You can see my implementation of the endless RecyclerView scroll:
GalleryActivity.java
public class GalleryActivity extends AppCompatActivity {
private StaggeredGridLayoutManager layoutManager;
private RecyclerView recyclerView;
private ImageRecyclerViewAdapter imageAdapter;
private List<ImageItem> images;
private ImageSearchClient imageSearchClient;
private String query;
private int currentStartPosition = 1;
private boolean loading = true;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_gallery);
getSupportActionBar().setHomeButtonEnabled(true);
query = getIntent().getStringExtra(Utils.QUERY_TAG);
if (query == null)
return;
imageSearchClient = new ImageSearchClient(query);
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
recyclerView.setHasFixedSize(true);
layoutManager = new StaggeredGridLayoutManager(2, 1);
recyclerView.setLayoutManager(layoutManager);
images = new ArrayList<>();
imageAdapter = new ImageRecyclerViewAdapter(this, images);
recyclerView.setAdapter(imageAdapter);
recyclerView.addOnScrollListener(new EndlessRecyclerScrollListener());
loadMoreData();
}
private void loadMoreData() {
loading = true;
imageSearchClient.getService().customSearch(Utils.API_KEY, Utils.CX_KEY, query,
Utils.IMAGE_SEARCH_TYPE,
currentStartPosition,
Utils.ITEMS_COUNT,
new Callback<ImageResponse>() {
#Override
public void success(ImageResponse imageResponse, Response response) {
List<ImageItem> items = imageResponse.getItems();
for (ImageItem item : items) {
images.add(item);
}
imageAdapter.notifyDataSetChanged();
currentStartPosition += items.size();
loading = false;
}
#Override
public void failure(RetrofitError error) {
Log.e(GalleryActivity.class.getSimpleName(),
error.getResponse().getReason());
}
});
}
private class EndlessRecyclerScrollListener extends RecyclerView.OnScrollListener {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int[] visibleItems = layoutManager.findLastVisibleItemPositions(null);
int lastItem = 0;
for (int i : visibleItems) {
lastItem = Math.max(lastItem, i);
}
if (lastItem > 0 && lastItem > images.size() - Utils.ITEMS_COUNT && !loading) {
if (NetworkUtils.hasConnection(GalleryActivity.this)) {
loadMoreData();
} else {
Toast.makeText(GalleryActivity.this, R.string.network_error,
Toast.LENGTH_SHORT).show();
}
}
}
}
}
ImageRecyclerViewAdapter.java (nothing is extraordinary here):
public class ImageRecyclerViewAdapter extends RecyclerView.Adapter<ImageViewHolder> {
private List<ImageItem> itemList;
private Context context;
private int parentWidth = 0;
public ImageRecyclerViewAdapter(Context context, List<ImageItem> itemList) {
this.context = context;
this.itemList = itemList;
}
#Override
public ImageViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View layoutView =
LayoutInflater.from(parent.getContext()).inflate(R.layout.gallery_card_item, null);
ImageViewHolder imageViewHolder = new ImageViewHolder(layoutView);
parentWidth = parent.getWidth();
return imageViewHolder;
}
#Override
public void onBindViewHolder(final ImageViewHolder holder, int position) {
Picasso.with(context)
.load(itemList.get(position).getLink())
.error(R.drawable.error_image)
.placeholder(R.drawable.progress_animation)
.into(new Target() {
#Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
int targetWidth = parentWidth / 2;
float ratio = (float) bitmap.getHeight() / (float) bitmap.getWidth();
float heightFloat = ((float) targetWidth) * ratio;
final android.view.ViewGroup.MarginLayoutParams layoutParams
= (ViewGroup.MarginLayoutParams) holder.image.getLayoutParams();
layoutParams.height = (int) heightFloat;
layoutParams.width = (int) targetWidth;
holder.image.setLayoutParams(layoutParams);
holder.image.setImageBitmap(bitmap);
}
#Override
public void onBitmapFailed(Drawable errorDrawable) {
}
#Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
}
});
}
#Override
public int getItemCount() {
return this.itemList.size();
}
}
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;
}
In my android application I have a recyclerview and LinearLayout. LinearLayout lies on the top and recyclerview lies down to the linearlayout. what my requirement is when recyclerview starts scrolls the last item, the linearlayout should start hide in propotion to scroll amount.
I have done a little and it works. but it is not proportional to the scroll amount. here is my code
ScrollListener
public class MyScrollListener extends OnScrollListener {
private LinearLayoutManager mLayoutManager;
public MyScrollListener(LinearLayoutManager manager) {
this.mLayoutManager = manager;
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
int visibleItemCount = mLayoutManager.getChildCount();
int totalItemCount = mLayoutManager.getItemCount();
int firstVisibleItem = mLayoutManager.findFirstVisibleItemPosition();
if (visibleItemCount + firstVisibleItem >= totalItemCount) {
scrollTheShit(dy * -1, ((View) recyclerView.getChildAt(visibleItemCount - 1)).getWidth());
}
}
public void scrollTheShit(int dy, int widthOfLastChild) {}
}
MainActivity
public class MainActivity extends AppCompatActivity {
RecyclerView recyclerView;
LinearLayout linearLayout;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
linearLayout = (LinearLayout) findViewById(R.id.container);
recyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
recyclerView.setHasFixedSize(true);
final LinearLayoutManager mgr = new LinearLayoutManager(this);
recyclerView.setLayoutManager(mgr);
recyclerView.setAdapter(new RecyclerAdapter());
recyclerView.addOnScrollListener(new MyScrollListener(mgr) {
#Override
public void scrollTheShit(int dy, int widthOfLastChild) {
linearLayout.setY(linearLayout.getY() + dy );
}
});
}
private class RecyclerAdapter extends Adapter<RecyclerAdapter.ViewHolder> {
#Override
public int getItemCount() {
return 10;
}
#Override
public void onBindViewHolder(ViewHolder viewHolder,
int position) {
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int position) {
View v = LayoutInflater.from(MainActivity.this)
.inflate(R.layout.child, parent, false);
ViewHolder holder = new ViewHolder(v);
return holder;
}
public class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(View arg0) {
super(arg0);
}
}
}
}
I have to hide the top section when the below RecyclerView scrolls
how do I correct this ? anyone please help me
this may help you
QuickReturnRecyclerView.java
import android.content.Context;
import android.os.Build;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.TranslateAnimation;
import android.widget.FrameLayout;
public class QuickReturnRecyclerView extends RecyclerView {
private static final String TAG = QuickReturnRecyclerView.class.getName();
private View mReturningView;
private static final int STATE_ONSCREEN = 0;
private static final int STATE_OFFSCREEN = 1;
private static final int STATE_RETURNING = 2;
private int mState = STATE_ONSCREEN;
private int mMinRawY = 0;
private int mReturningViewHeight;
private int mGravity = Gravity.BOTTOM;
public QuickReturnRecyclerView(Context context) {
super(context);
init();
}
public QuickReturnRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public QuickReturnRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init(){
}
/**
* The view that should be showed/hidden when scrolling the content.
* Make sure to set the gravity on the this view to either Gravity.Bottom or
* Gravity.TOP and to put it preferable in a FrameLayout.
* #param view Any kind of view
*/
public void setReturningView(View view) {
mReturningView = view;
try {
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) mReturningView.getLayoutParams();
mGravity = params.gravity;
} catch (ClassCastException e) {
throw new RuntimeException("The return view need to be put in a FrameLayout");
}
measureView(mReturningView);
mReturningViewHeight = mReturningView.getMeasuredHeight();
addOnScrollListener(new RecyclerScrollListener());
}
private void measureView(View child) {
ViewGroup.LayoutParams p = child.getLayoutParams();
if (p == null) {
p = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0, p.width);
int lpHeight = p.height;
int childHeightSpec;
if (lpHeight > 0) {
childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
} else {
childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
}
child.measure(childWidthSpec, childHeightSpec);
}
private class RecyclerScrollListener extends OnScrollListener {
private int mScrolledY;
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
if(mGravity == Gravity.BOTTOM)
mScrolledY += dy;
else if(mGravity == Gravity.TOP)
mScrolledY -= dy;
if(mReturningView == null)
return;
int translationY = 0;
int rawY = mScrolledY;
switch (mState) {
case STATE_OFFSCREEN:
if(mGravity == Gravity.BOTTOM) {
if (rawY >= mMinRawY) {
mMinRawY = rawY;
} else {
mState = STATE_RETURNING;
}
} else if(mGravity == Gravity.TOP) {
if (rawY <= mMinRawY) {
mMinRawY = rawY;
} else {
mState = STATE_RETURNING;
}
}
translationY = rawY;
break;
case STATE_ONSCREEN:
if(mGravity == Gravity.BOTTOM) {
if (rawY > mReturningViewHeight) {
mState = STATE_OFFSCREEN;
mMinRawY = rawY;
}
} else if(mGravity == Gravity.TOP) {
if (rawY < -mReturningViewHeight) {
mState = STATE_OFFSCREEN;
mMinRawY = rawY;
}
}
translationY = rawY;
break;
case STATE_RETURNING:
if(mGravity == Gravity.BOTTOM) {
translationY = (rawY - mMinRawY) + mReturningViewHeight;
if (translationY < 0) {
translationY = 0;
mMinRawY = rawY + mReturningViewHeight;
}
if (rawY == 0) {
mState = STATE_ONSCREEN;
translationY = 0;
}
if (translationY > mReturningViewHeight) {
mState = STATE_OFFSCREEN;
mMinRawY = rawY;
}
} else if(mGravity == Gravity.TOP) {
translationY = (rawY + Math.abs(mMinRawY)) - mReturningViewHeight;
if (translationY > 0) {
translationY = 0;
mMinRawY = rawY - mReturningViewHeight;
}
if (rawY == 0) {
mState = STATE_ONSCREEN;
translationY = 0;
}
if (translationY < -mReturningViewHeight) {
mState = STATE_OFFSCREEN;
mMinRawY = rawY;
}
}
break;
}
/** this can be used if the build is below honeycomb **/
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.HONEYCOMB) {
TranslateAnimation anim = new TranslateAnimation(0, 0, translationY, translationY);
anim.setFillAfter(true);
anim.setDuration(0);
mReturningView.startAnimation(anim);
} else {
mReturningView.setTranslationY(translationY);
}
}
}
}
activity_main.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<yourpackagename.QuickReturnRecyclerView
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical" />
<LinearLayout
android:id="#+id/linear"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:background="#android:color/darker_gray"
android:orientation="horizontal"
android:padding="15dp">
<Button
android:id="#+id/button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="left|top"
android:layout_weight="1"
android:text="New Button" />
<Button
android:id="#+id/button2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|top"
android:layout_weight="1"
android:text="New Button" />
<Button
android:id="#+id/button3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="right|top"
android:layout_weight="1"
android:text="New Button" />
</LinearLayout>
</FrameLayout>
in MainActivity.java just add below lines
mRecyclerView = (QuickReturnRecyclerView) findViewById(R.id.recycler_view);
linear = (LinearLayout) findViewById(R.id.linear);
// use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView
mRecyclerView.setHasFixedSize(true);
// use a linear layout manager
mLayoutManager = new LinearLayoutManager(getApplicationContext());
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setReturningView(linear);