Seems like position in recylerview changes while scrolling.
What I want to do is like this.
Adapter.java
#Override
public void onBindViewHolder(aViewHolder holder, int position) {
if (position == 0) {
holder.zeroIcon.setVisibility(View.VISIBLE);
} else if (position == 1) {
holder.oneIcon.setVisiblity(View.VISIBLE);
} else {
holder.otherIcon.setVisiblity(View.VISIBLE);
}
// Set text on each item
...
}
#Override
public int getItemCount() { return models.size(); }
public class aViewHolder extends RecyclerView.ViewHolder {
private ImageView zeroIcon;
private ImageView oneIcon;
private ImageView otherIcon;
public aViewHolder(View itemView) {
super(itemView);
zeroIcon = itemview.findViewById(...);
...
}
}
I set these icon's visibility GONE as default in xml file.
When I see the recylerview at first, the icons show up as I expected depending on its position.
However, when I scroll down and scroll up, incorrect icons also show up on incorrect position.
Like otherIcon shows up on the first and second item while scrolling down and up. While scrolling down, zeroIcon and oneIcon show up on some other items.
How can I fix this?
list_item.xml is like this.
<RelativeLayout ...>
<ImageView
android:id="#+id/zero"
android:visiblity="gone"
android:background="#drawable/zero" />
<ImageView
android:id="#id/one"
android:visiblity="gone"
android:background="#drawable/one" />
<ImageView
android:id="#id/other"
android:visiblity="gone"
android:background="#drawable/other" />
Modify it in this way,
if (position == 0) {
holder.zeroIcon.setVisibility(View.VISIBLE);
holder.otherIcon.setVisiblity(View.GONE);
holder.oneIcon.setVisiblity(View.GONE);
} else if (position == 1) {
holder.oneIcon.setVisiblity(View.VISIBLE);
holder.zeroIcon.setVisibility(View.GONE);
holder.otherIcon.setVisiblity(View.GONE);
} else {
holder.otherIcon.setVisiblity(View.VISIBLE);
holder.oneIcon.setVisiblity(View.GONE);
holder.zeroIcon.setVisibility(View.GONE);
}
In RecyclerView you should manage other views also while changing an item.
Related
I'm trying to reference a TextView from a layout. It works for all other views but for this specific one it returns null. Doing it the same way for all the views, so I can't see why it wouldn't work on this one.
Important to mention is that the layout file showed here is included into b_user_list. All views referenced from within the include works, except for one.
b_user_list.xml:
<include
android:id="#+id/showUserLay"
layout="#layout/c_show_front_layout"
... />
c_show_front_layout.xml:
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/volunDbListLay"
... >
<TextView
android:id="#+id/textView932"
... />
<View
android:id="#+id/divider522"
... />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/volunDbRecycler"
... />
<TextView
android:id="#+id/noVolunOrgsFound"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:text="No organizations found."
app:layout_constraintBottom_toBottomOf="#+id/volunDbRecycler"
app:layout_constraintEnd_toEndOf="#+id/volunDbRecycler"
app:layout_constraintStart_toStartOf="#+id/volunDbRecycler"
app:layout_constraintTop_toTopOf="#+id/volunDbRecycler" />
</androidx.constraintlayout.widget.ConstraintLayout>
ActivityUser.java:
private ConstraintLayout settErrorLay;
private TextView errorTxt;
private TextView errorClose;
private TextView noVolunOrgsFound;
private RecyclerView volunDbRecycler;
private ConstraintLayout volunDbListLay, notVsAccWarning;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.b_user_list);
...
settErrorLay = findViewById(R.id.userIntegrErrorLay);
errorClose = findViewById(R.id.closeUserIntegrErrorTxt);
...
if (errorClose != null && settErrorLay != null) {
// Checks if includeView show_front_layout.xml worked
volunDbRecycler = findViewById(R.id.volunDbRecycler);
volunDbListLay = findViewById(R.id.volunDbListLay);
notVsAccWarning = findViewById(R.id.notVsAccWarning);
noVolunOrgsFound = findViewById(R.id.noVolunOrgsFound);
...
}
...
}
...
I found a way to reference the view, but obviously not the proper way. I ran through all the children of the parent view, and when the id match I add it as the view. Then it didn't return null:
if (noVolunOrgsFound == null) {
for (int i = 0; i < volunDbListLay.getChildCount(); i++) {
if (volunDbListLay.getChildAt(i).getId() == R.id.noVolunOrgsFound) {
noVolunOrgsFound = (TextView) volunDbListLay.getChildAt(i);
}
}
}
So I have ran out of ideas for why this doesn't work. It can't be the <include> since the other sibling views works, the id is the same both in the layout and java, and it works with by finding it within the children of the parent, just not with findViewById().
EDIT: I posted an answer that seemed to be solving the problem, but now I just have the same problem with a RecyclerView. This time, it didn't work to manually loop through the views of the parent and checking when the child view id match the RecyclerView's id..
Is there a limit to how many views can be loaded or referenced in Java or something?
Found a solution, I left out something in the question that I didn't think would have a say in the matter (And I'm still not sure why it does).
Before the findViewById(R.id.noVolunOrgsFound); was a function, and in that function was a button assigned to use the TextView (the one that was null). Not sure how the button makes the TextView null, but apparently it did.
initializeDbSettingsLay();
volunDbRecycler = findViewById(R.id.volunOrgRecycler);
volunDbListLay = findViewById(R.id.volunOrgListLay);
notVsAccWarning = findViewById(R.id.notVsAccWarning);
noVolunOrgsFound = findViewById(R.id.noVolunOrgsFound);
Moving the function after the findViewById(R.id.noVolunOrgsFound);, it now wasn't null anymore. Not sure why the button either made it null or not properly find the view, since it worked on the other layouts that also is used in that button function. Only the TextView became null. I'd like to say that the TextView can't be assigned after the ClickListener has been created, but generally values can be changed later, and so did the other views. So I'm not sure why it failed with the TextView.
Here is the function:
private void initializeDbSettingsLay() {
...
vsSettingsBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
userSettingsBtn.setBackgroundTintList(ColorStateList.valueOf(Color.WHITE));
dbSettingsBtn.setBackgroundTintList(ColorStateList.valueOf(Color.WHITE));
vsSettingsBtn.setBackgroundTintList(ColorStateList.valueOf(ActivityUser.this.getColor(R.color.slight_gray)));
userSettingsLay.setVisibility(View.GONE);
dbSettingsLay.setVisibility(View.GONE);
vsSettingsLay.setVisibility(View.VISIBLE);
learnMoreVsLay.setVisibility(View.GONE);
learnMoreVsBtn.setText("Learn more");
if (currentUserModel == null || currentUserModel.getVolunAccount() == null) {
volunDbListLay.setVisibility(View.GONE);
notVsAccWarning.setVisibility(View.VISIBLE);
} else {
volunDbListLay.setVisibility(View.VISIBLE);
notVsAccWarning.setVisibility(View.GONE);
loadVolunOrgList();
}
}
});
...
}
private void loadVolunOrgList() {
if (currentUserModel == null || currentUserModel.getVolunAccount() == null || currentUserModel.getOrgsList() == null || currentUserModel.getOrgsList().isEmpty()) {
return;
}
List<OrganisationModel> orgList = currentUserModel.getOrgDataList();
if (orgList.size() == 0) {
noVolunOrgsFound.setVisibility(View.VISIBLE);
return;
}
noVolunOrgsFound.setVisibility(View.GONE);
//Removing both noVolunOrgsFound.setVisibility() above from this function, the rest of the code then worked \/
UserIntegrAdapter orgAdapter = new UserIntegrAdapter(orgList, this, getSupportFragmentManager());
volunDbRecycler.setAdapter(orgAdapter);
volunDbRecycler.setLayoutManager(new LinearLayoutManager(ActivityUser.this));
}
I am using Floating Action Button. I want to disable Recyclerview Items from Clicking when i press FAB button. I tried this method but not working setClickable(true);
My Layout
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:fab="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="#fff"
tools:context="com.hartwintech.socialchat.activity.IconTabsActivity">
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler_view"
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
>
</android.support.v7.widget.RecyclerView>
<com.github.clans.fab.FloatingActionMenu
android:id="#+id/floatmenu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_marginBottom="60dp"
android:layout_marginRight="16dp"
fab:fab_showAnimation="#anim/show_from_bottom"
fab:fab_hideAnimation="#anim/hide_to_bottom"
fab:menu_labels_style="#style/MenuLabelsStyle"
fab:menu_shadowColor="#444"
fab:menu_colorNormal="#FFB805"
fab:menu_colorPressed="#F2AB00"
fab:menu_colorRipple="#D99200"/>
</RelativeLayout>
Java Class
floatMenu.setOnMenuToggleListener(new FloatingActionMenu.OnMenuToggleListener() {
#Override
public void onMenuToggle(boolean opened) {
if (opened) {
final int color = R.color.transp;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mrecyclerview.setClickable(false);
mrecyclerview.setEnabled(false);
mrecyclerview.setForeground(new ColorDrawable(ContextCompat.getColor(getContext(), color)));
}
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mrecyclerview.setClickable(true);
mrecyclerview.setEnabled(true);
mrecyclerview.setForeground(null);
}
}
}
});
You can add a simple boolean to your adapter like this:
public boolean isClickable = true;
and set it in your fab-click:
mAdapter.isClickable = true/false;
And within your OnClickListener in the Adapter, only act when it is clickable:
public void onClick(View view) {
if(!isClickable)
return;
// do your click stuff
}
To disable RecyclerView, follow below steps:
1. Add following view into your layout file,
<View
android:id="#+id/viewDisableLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#40000000"
android:clickable="true"
android:focusable="true"
android:visibility="gone"/>
2. Set View Visibility `View.VISIBLE when you want to disable RecyclerView else
You can simply use recursion to disable/enable clicks on view
public static void setClickable(View view, boolean clickable) {
if (view != null) {
if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) view;
for (int i = 0; i < viewGroup.getChildCount(); i++) {
setClickable(viewGroup.getChildAt(i), clickable);
}
}
view.setClickable(clickable);
}
}
Björn Kechel's answer helps me. As he said I just added Boolean. When i click the fab menu the boolean is activated. Then have to write the condition on mrecyclerview.addOnItemTouchListenerJava Class
public Boolean fabClick = false;
floatMenu.setOnMenuToggleListener(new FloatingActionMenu.OnMenuToggleListener() {
#Override
public void onMenuToggle(boolean opened) {
if (opened) {
final int color = R.color.transp;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
fabClick = true;
mrecyclerview.setClickable(false);
mrecyclerview.setEnabled(false);
mrecyclerview.setForeground(new ColorDrawable(ContextCompat.getColor(getContext(), color)));
}
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
fabClick = false;
mrecyclerview.setClickable(true);
mrecyclerview.setEnabled(true);
mrecyclerview.setForeground(null);
}
}
}
});
mrecyclerview.addOnItemTouchListener(new RecyclerTouchListener(getActivity(), mrecyclerview, new RecyclerTouchListener.ClickListener() {
#Override
public void onClick(View view, int position) {
if(!fabClick) {
android.support.v4.app.FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.setCustomAnimations(R.anim.fragment_anim_start, R.anim.fragment_anim_stop);
Intent i = new Intent(getActivity(), Group_Chat_Screen.class);
startActivity(i);
}
}
You need to set the click listener to every FloatingActionButton.
see this issue on library
Working solution with RecyclerView.OnItemTouchListener:
#SuppressLint("ClickableViewAccessibility")
#BindingAdapter("itemsClickable")
fun setRecyclerViewClickable(view: RecyclerView, clickable: Boolean) {
view.isEnabled = clickable
if (!clickable) {
val itemTouchListener = object : RecyclerView.OnItemTouchListener {
override fun onTouchEvent(rv: RecyclerView?, e: MotionEvent?) {
}
override fun onInterceptTouchEvent(rv: RecyclerView?, e: MotionEvent?): Boolean {
return rv?.isEnabled == false
}
override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {
}
}
view.addOnItemTouchListener(itemTouchListener)
view.tag = itemTouchListener
} else {
(view.tag as? RecyclerView.OnItemTouchListener)?.let {
view.requestDisallowInterceptTouchEvent(true)
view.removeOnItemTouchListener(it)
}
}
}
Java
recyclerView.setOnTouchListener((view1, motionEvent) -> true );
Kotlin
reyclerView.setOnTouchListener { v, event -> true }
this solution disables all touch events
You can disable the touch of recyclerview
recyclerView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
return true;
}
});
In the xml file, set the layout_width and layout_height for FloatingActionMenu as match_parent and set clickable as false :
android:layout_width="match_parent "
android:layout_height="match_parent "
android:clickable="false"
In your java class,
floatMenu.setOnMenuToggleListener(new FloatingActionMenu.OnMenuToggleListener() {
#Override
public void onMenuToggle(boolean opened) {
if (opened) {
floatMenu.setClickable(true);
} else {
floatMenu.setClickable(false);
}
}
});
This should work.
I solved this problem with very simple logic. This will prevent double click on single item and multiple items of RecyclerView as well.
Declare a Variable in your Activity.
private long mLastClickTime = 0;
Then use in any OnClickListener.
#Override
public void onClick(View v) {
if(SystemClock.elapsedRealtime() - mLastClickTime < 1000){//You can reclick after 1 second
return;//Before 1 seconds from first click this onclick will return from here
}
mLastClickTime = SystemClock.elapsedRealtime();
//Do stuff here
}
Actually I found this solution in stackover flow when I was searching for preventing double click on Button. I'm writing this line to acknowledge that actual answer is posted by someone ( Unfortunatly I'm unable to find that answer to link his/her answer here.)
Hope this will solve your problem.:)
I am trying to change background color in specific item(s) in a RecycleView.
Because I need to set text too, I have the following code that works fine:
protected void populateViewHolder(RankingViewHolder viewHolder, final Ranking model, int position)
{
final Context mContext = getActivity().getApplicationContext();
viewHolder.txt_name.setText(model.getUserName());
viewHolder.txt_score.setText(String.valueOf(model.getScore()));
viewHolder.txt_class.setText(model.getUser_class());
Picasso.with(mContext).load(model.getAvatarUrl()).error(R.drawable.ic_people_black_24dp).into(viewHolder.personPhoto);
int totalRanking = adapter.getItemCount();
int realRank = totalRanking - viewHolder.getAdapterPosition();
viewHolder.ranknumber.setText("# "+String.valueOf(realRank));
}
This works as I want and realRanktakes the correct values, and the viewHolder.ranknumber.setText("# "+String.valueOf(realRank));
Sets the right text with no problem.
Now I am trying (as I got a number/text result correct, to make an if statement like this:
if(adapter.getItemCount() -viewHolder.getAdapterPosition() == 0)
{
viewHolder.itemView.setBackgroundColor(Color.GREEN);
}
if(adapter.getItemCount() -viewHolder.getAdapterPosition() == 1)
{
viewHolder.itemView.setBackgroundColor(Color.YELLOW);
}
if(adapter.getItemCount() -viewHolder.getAdapterPosition() == 2)
{
viewHolder.itemView.setBackgroundColor(Color.BLUE);
}
(I tried with String.valueOf(realRank)equality, with realRankequality too)
In all cases I have the same result. The color changes as its should at positions 1,2,3 BUT it changes at positions 7,8,9 and 14,15,16 and 21,22,23 etc.
What am I missing here?
public class RankingViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener
{
private ItemClickListener itemClickListener;
public TextView txt_name, txt_score, txt_class, ranknumber;
public ImageView personPhoto;
public RankingViewHolder(View itemView)
{
super(itemView);
txt_name = itemView.findViewById(R.id.txt_name);
txt_score = itemView.findViewById(R.id.txt_score);
personPhoto = itemView.findViewById(R.id.person_photo);
txt_class = itemView.findViewById(R.id.txt_class);
ranknumber = itemView.findViewById(R.id.ranknumber);
itemView.setOnClickListener(this);
}
public void setItemClickListener(ItemClickListener itemClickListener) {
this.itemClickListener = itemClickListener;
}
#Override
public void onClick(View view) {
itemClickListener.onClick(view , getAdapterPosition(),false);
}
}
The adapter:
adapter = new FirebaseRecyclerAdapter<Ranking, RankingViewHolder>(
Ranking.class,
R.layout.layout_ranking,
RankingViewHolder.class,
rankingTbl.orderByChild("score").limitToFirst(100)
)
This line of code int realRank = totalRanking - viewHolder.getAdapterPosition();gives a number (1,2,3,4,5,6 etc.) Why i cannot use this number to check equality?
Notice
Keeping this code for NOT working solution, just for future reference:
if(position == 0){
viewHolder.itemView.setBackgroundColor(Color.GREEN);
}
else if(position == 1){
viewHolder.itemView.setBackgroundColor(Color.YELLOW);
}
else if(position == 2){
viewHolder.itemView.setBackgroundColor(Color.BLUE);
}
else{
viewHolder.itemView.setBackgroundColor(Color.WHITE);
}
This changes the color BUT not for only 3 first items. As you scroll down, changes the color for every 3 first viewable items like before, meaning 1,2,3, 7,8,9, etc.
Update:
I dont use a custom adapter, i use FirebaseRecyclerAdapter.
Thanks to #Muhammad Haroon comment i checked that has getItemViewType. So now i m trying with it like
position =adapter.getItemViewType( 0);
if(position == 0){
viewHolder.itemView.setBackgroundColor(Color.GREEN);
}
Not working for now, but i think its the correct direction...
Update 2
With position its not possible as RecycleView recycles the views so i have the same result. The working code is
if (linearLayoutManager.findFirstVisibleItemPosition() > 0) {
viewHolder.itemView.setBackgroundResource(R.drawable.blackframe);
}
else{
viewHolder.itemView.setBackgroundResource(R.drawable.goldframe);
}
Works fine except that after scrolling loosing the change of background.
So as we want and need the perfection, any idea for keeping even after scroll?
hi try add this in your Adapater it may solve your problem.
#Override
public int getItemViewType(int position) {
return position;
}
Please give this a try
override in your custom adapter
#Override
public long getItemId(int position) {
return position;
}
and in in your adapter object:
myAdapter.setHasStableIds(true);
In populateViewHolder add these line of code
if(position == 0){
viewHolder.itemView.setBackgroundColor(Color.GREEN);
}
else if(position == 1){
viewHolder.itemView.setBackgroundColor(Color.YELLOW);
}
else if(position == 2){
viewHolder.itemView.setBackgroundColor(Color.BLUE);
}
else{
viewHolder.itemView.setBackgroundColor(Color.WHITE);
}
position is a parameter in populateViewHolder.
I've read a lot of StackOverflow posts about such problem but most of them were not using cursor as their data source for RecyclerView. But my app does.
In my app, I query the database which gives me results as shown in the following image. Results are ordered by _id column.
I want my RecyclerView to represents the data something like below:
White Blood Cell(WBC)
52 10^9/L 2018-08-15
52 10^9/L 2018-08-15
52 10^9/L 2018-08-15
Red Blood Cell - Male
52 10^9/L 2018-08-15
52 10^9/L 2018-08-15
52 10^9/L 2018-08-15
As you can see it forms groups with headings and relevant items go under each heading.
I have created two different layouts.
Heading with an item below it layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout>
<TextView
android:id="#+id/heading"" />
<LinearLayout>
<TextView
android:id="#+id/value" />
<TextView
android:id="#+id/date" />
</LinearLayout>
</LinearLayout>
Item layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout>
<TextView
android:id="#+id/value" />
<TextView
android:id="#+id/date"/>
</LinearLayout>
Ignore missing details in layout files
On the other hand, my adapter does the following.
public class BloodTestRecordAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
// it holds the _id of the last group
private int lastGroupId = -1;
private final int VIEW_TYPE_HEADING = 0, VIEW_TYPE_ITEM = 1;
#Override
public int getItemViewType(int position) {
int returnItemViewType;
// get _id value for the row at given position
int currentGroupId = getCursorAtPosition(position).getInt(0);
if (currentGroupId != lastGroupId) {
// return VIEW_TYPE_HEADING
lastGroupId = currentGroupId;
returnItemViewType = VIEW_TYPE_HEADING;
} else {
returnItemViewType = VIEW_TYPE_ITEM;
}
return returnItemViewType;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
RecyclerView.ViewHolder viewHolder;
View view;
if (viewType == VIEW_TYPE_HEADING) {
// inflate HEADING LAYOUT
} else {
// inflate item layout
}
return viewHolder;
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, Cursor cursor, int position) {
if (viewHolder.getItemViewType() == VIEW_TYPE_HEADING) {
// bind values to heading layout
} else {
// bind values to item layout
}
}
public class HeadingViewHolder extends RecyclerView.ViewHolder {
}
public class ItemViewHolder extends RecyclerView.ViewHolder {
}
}
what I am doing in getItemViewType is I check to see whether the _id column matches the lastGroupId by using position argument that is passed to the method. If it matches I just return the VIEW_TYPE_ITEM, which is the normal item layout.
And if it doesn't match, then it gives us an indication that it is the beginning of another group, so for this case, I return VIEW_TYPE_HEADING.
It works fine but as I get more items the heading and placement of items under groups don't seem to be correct. Sometimes the heading layout goes vanish for some group of items as shown below in the image.
I know for sure that the way i implemented the getViewItemType is not
correct, I also know as my viewholders get recycled it messes with my getViewItem method because of that lastGroupId variable.
Can you give me any suggestions how should I do this or what can I do to achieve such things in RecyclerView with Cursor or what are my mistakes?
The problem appear when you scrolling up the recyclerView and so that lastGroupId doesn't full fill correctly.
Use this logic in place of your current code in getItemViewType:
if (position == 0) {
return VIEW_TYPE_HEADING;
} else {
int currentGroupId = getCursorAtPosition(position).getInt(0);
int previousGroupId= getCursorAtPosition(position - 1).getInt(0);
return currentGroupId != previousGroupId ? VIEW_TYPE_HEADING : VIEW_TYPE_ITEM;
}
I have a basic layout setup: DrawerLayout that consists of the main layout for the activity and the layout for the navigation view. The main layout has a CoordinatorLayout and all the necessary ones to implement the collapsing toolbar functionality, as well as a ViewPager which I populate with fragments later on in the code. The fragment's layout mainly consists of the RecyclerView and other minor views.
The problem is the following: whenever I scroll either of the two tabs down a little and swipe to a new tab and return back to the original one, the RecyclerView receives the onScrolled callback (which it shouldn't since I haven't scrolled the RV itself, but the ViewPager) with the negative deltaY parameter (which means the direction is upwards) which causes the next item from the top to come into visibility as a result.
A couple of notes:
The deltaY parameter seems to be always equal to the next item's height from the top (which gives the impression that the RV scrolls one item upwards whenever this whole process happens)
I don't set the OnPageChangeListener to the ViewPager to get notified
whenever the page is scrolled, selected or the state was changed.
I supply the RecyclerViewScrollListener to the fragment's RecyclerView to get notified when the RV has been scrolled to do some logic (like propagating this event to the activity to show or hide the floating action button)
I've made a screenshot of the call stack when the onScrolled is called. Perhaps somebody might find it useful. Take a look.
I've also made a short video representing this behavior. Take a look.
I've checked a lot of resources on the Internet, but none of them helped me to resolve this issue. Feel free to ask any questions. Any help would be much appreciated.
dashboard_activity_layout.xml:
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/coordinatorLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="#+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="#dimen/toolbar_shadow_height">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="#dimen/toolbar_height"
android:paddingTop="#dimen/toolbar_padding_top"
app:layout_scrollFlags="scroll|enterAlwaysCollapsed"
app:titleTextAppearance="#style/TitleTextView"
tools:background="#color/colorPrimary"/>
<android.support.design.widget.TabLayout
android:id="#+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="#dimen/dashboard_activity_content_tab_layout_height"
app:tabMode="fixed"
app:tabGravity="fill"
tools:background="#color/colorPrimary"/>
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="#+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
tools:background="#color/colorPrimaryDark"/>
<android.support.design.widget.FloatingActionButton
android:id="#+id/actionButtonFab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="#dimen/dashboard_activity_content_fab_margin"
android:layout_gravity="bottom|end"
android:src="#mipmap/ic_search_white_24dp"
app:fabSize="normal"
app:useCompatPadding="true"
tools:backgroundTint="#color/colorAccent"/>
DashboardActivity.java (relevant parts):
private void initViewPager() {
mViewPager = findViewById(R.id.viewPager);
mAdapter = new DashboardViewPagerAdapter(getSupportFragmentManager());
mAdapter.setViewPagerId(mViewPager.getId());
mAdapter.setRecyclerViewStateListener(mRecyclerViewStateListener);
populateAdapter();
mViewPager.setAdapter(mAdapter);
mViewPager.setOffscreenPageLimit(mAdapter.getCount());
}
private void populateAdapter() {
BaseFragment baseFragment = mAdapter.getFragmentForPosition(TAB_TAB_1);
if(baseFragment == null) {
baseFragment = SomeFragment.newInstance(Common.SOME_TYPE_1);
}
mAdapter.addFragment(baseFragment);
baseFragment = mAdapter.getFragmentForPosition(TAB_TAB_2);
if(baseFragment == null) {
baseFragment = SomeFragment.newInstance(Common.SOME_TYPE_2);
}
mAdapter.addFragment(baseFragment);
baseFragment = mAdapter.getFragmentForPosition(TAB_TAB_3);
if(baseFragment == null) {
baseFragment = SomeOtherFragment.init();
}
mAdapter.addFragment(baseFragment);
}
private RecyclerViewStateListener mRecyclerViewStateListener = new RecyclerViewStateListener() {
#Override
public void onScrolledDownwards(RecyclerView recyclerView, int deltaY) {
// Hiding the FAB by animating it
DashboardCommon.hideActionButton(mActionButtonFab, mViewAnimator, true);
}
#Override
public void onScrolledUpwards(RecyclerView recyclerView, int deltaY) {
// Showing the FAB by animating it
DashboardCommon.showActionButton(mActionButtonFab, mViewAnimator, true);
}
};
SomeFragment.java (relevant parts):
private void initRecyclerView() {
mRecyclerView = findViewById(R.id.recyclerView);
mRecyclerView.addOnScrollListener(new RecyclerViewScrollListener(this));
mLayoutManager = new LinearLayoutManager(
getContext(),
LinearLayoutManager.VERTICAL,
false
);
mRecyclerView.setLayoutManager(mLayoutManager);
mAdapter = new SomeRecyclerViewAdapter(getContext(), mItems);
mRecyclerView.setAdapter(mAdapter);
}
// mRecyclerViewStateListener is the listener passed from the
// DashboardActivity
#CallSuper
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
if(mRecyclerViewStateListener != null) {
mRecyclerViewStateListener.onScrollStateChanged(recyclerView, newState);
}
}
#Override
public void onScrolled(RecyclerView recyclerView, int deltaX, int deltaY) {
if(mRecyclerViewStateListener != null) {
mRecyclerViewStateListener.onScrolled(recyclerView, deltaX, deltaY);
}
}
#Override
public void onScrolledDownwards(RecyclerView recyclerView, int deltaY) {
if(mRecyclerViewStateListener != null) {
mRecyclerViewStateListener.onScrolledDownwards(recyclerView, deltaY);
}
}
#Override
public void onScrolledUpwards(RecyclerView recyclerView, int deltaY) {
if(mRecyclerViewStateListener != null) {
mRecyclerViewStateListener.onScrolledUpwards(recyclerView, deltaY);
}
}
#Override
public void onBottomReached() {
if(mRecyclerViewStateListener != null) {
mRecyclerViewStateListener.onBottomReached();
}
}
#Override
public void onMidpointReached(int direction) {
if(mRecyclerViewStateListener != null) {
mRecyclerViewStateListener.onMidpointReached(direction);
}
}
#Override
public void onTopReached() {
if(mRecyclerViewStateListener != null) {
mRecyclerViewStateListener.onTopReached();
}
}
RecyclerViewScrollListener.java (relevant parts):
public class RecyclerViewScrollListener extends RecyclerView.OnScrollListener {
public static final int DIRECTION_UNSPECIFIED = -1;
public static final int DIRECTION_UPWARDS = 0;
public static final int DIRECTION_DOWNWARDS = 1;
// Omitted...
private StateListener mStateListener;
public RecyclerViewScrollListener(StateListener stateListener) {
// Omitted..
mStateListener = stateListener;
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
if(mStateListener != null) {
mStateListener.onScrollStateChanged(recyclerView, newState);
}
}
#Override
public void onScrolled(RecyclerView recyclerView, int deltaX, int deltaY) {
if(mStateListener != null) {
mStateListener.onScrolled(recyclerView, deltaX, deltaY);
}
f(deltaY > 0) {
// Recycler view's contents are moving downwards
// Notifying about the downwards scroll
if(mStateListener != null) {
mStateListener.onScrolledDownwards(recyclerView, deltaY);
}
// Omitted...
if(someConditionIsTrue) {
// Omitted...
// Notifying the listener
mStateListener.onBottomReached();
} else if((someOtherConditionIsTrue) {
mStateListener.onMidpointReached(DIRECTION_DOWNWARDS);
}
} else if(deltaY < 0) {
// Recycler view's contents are moving upwards
// Notifying about upwards scroll
if(mStateListener != null) {
mStateListener.onScrolledUpwards(recyclerView, deltaY);
}
// Omitted...
if(someConditionIsTrue) {
// Omitted..
// Notifying the listener
mStateListener.onTopReached();
} else if(someOtherConditionIsTrue) {
mStateListener.onMidpointReached(DIRECTION_UPWARDS);
}
}
}
public interface StateListener {
void onScrollStateChanged(RecyclerView recyclerView, int newState);
void onScrolled(RecyclerView recyclerView, int deltaX, int deltaY);
void onScrolledDownwards(RecyclerView recyclerView, int deltaY);
void onScrolledUpwards(RecyclerView recyclerView, int deltaY);
void onBottomReached();
void onMidpointReached(int direction);
void onTopReached();
}
}
Using This Scroll one item at a time automatically
RecyclerView my_recycler_view= (RecyclerView) findViewById(R.id.my_recycler_view);
SnapHelper snapHelper = new PagerSnapHelper();
snapHelper.attachToRecyclerView(my_recycler_view);
Happy Coding Cheers
Hope will help
RecyclerView my_recycler_view= (RecyclerView) findViewById(R.id.my_recycler_view);
SnapHelper snapHelper = new PagerSnapHelper();
snapHelper.attachToRecyclerView(my_recycler_view);
Even though I posted this question more than 2 years ago, I've decided to provide an answer. Better late than never, right?
As far as I remember, the problem was in the fact that the layout of the RecyclerView items had the TextView, which contained android:textIsSelectable="true" attribute. Turned out that by removing that attribute, the problem went away. How did I implement text selection then, you may ask? To be honest, I do not recall since it's been so long ago.
Anyway, if you have a problem like this, try removing the android:textIsSelectable="true" attribute and it may very well fix the issue for you.