Я замечаю, что когда я прокручиваю вверх, библиотека подкачки использует ключ FirstVisibleItem в качестве initialKey после аннулирования данных, что хорошо для меня, но когда я прокручиваю вниз, она использует ключ LastVisibleItem в качестве initialKey, которыйвызывает у меня проблему.
Я использую этот запрос для получения сообщений чата из базы данных Firebase, и он прекрасно работает, пока initialKey является FirstVisibleItem:
.startAt (initialKey) .limitToFirst(размер);
Но использование того же запроса, когда initialKey является LastVisibleItem, заставляет переработчика перепрыгнуть (прокрутить вниз) к элементу, у которого есть initialKey, и сделать его сверху.
Пока единственное решение состоит в том, чтобы изменить запрос таким образом, когда пользователь прокручивает вниз:
.endAt (initialKey) .limitToLast (size);
Есть ли способ заставитьбиблиотека подкачки для использования только ключа FirstVisibleItem или способ сообщить хранилищу о том, что пользователь в данный момент прокручивает вверх или вниз для настройки запросасоответственно?
public void getMessages(String initialKey, final int size,
@NonNull final ItemKeyedDataSource.LoadInitialCallback<Message> callback) {
Log.i(TAG, "getMessages initiated. initialKey= " + initialKey);
this.initialKey = initialKey;
Query messagesQuery;
isInitialFirstLoaded = true;
ValueEventListener initialMessagesListener = new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
// [START_EXCLUDE]
Log.d(TAG, "start onDataChange. isInitialFirstLoaded = "+ isInitialFirstLoaded);
if (!isInitialFirstLoaded){
// Remove post value event listener
removeListeners();
Log.d(TAG, "usersChanged Invalidated removeEventListener");
//isInitialFirstLoaded = true;
Log.d(TAG, "onInvalidated(). isInitialFirstLoaded = "+ isInitialFirstLoaded);
invalidatedCallback.onInvalidated();
//UsersDataSource.InvalidatedCallback.class.getMethod("loadInitial", "LoadInitialParams");
//UsersDataSource.invalidate();
return;
}
if (dataSnapshot.exists()) {
final List<Message> messagesList = new ArrayList<>();
// loop throw users value
for (DataSnapshot snapshot: dataSnapshot.getChildren()){
Message message = snapshot.getValue(Message.class);
if (message != null) {
message.setKey(snapshot.getKey());
}
messagesList.add(message);
//Log.d(TAG, "getMessage = "+ message.getMessage()+" getSnapshotKey= " + snapshot.getKey());
}
if(messagesList.size() != 0){
callback.onResult(messagesList);
Log.d(TAG, "getMessages List.size= " + messagesList.size()+ " lastkey= "+messagesList.get(messagesList.size()-1).getKey() + " getInitialKey= "+ getInitialKey() );
}
} else {
// no data
Log.w(TAG, "getMessages no users exist");
}
getListeners();
isInitialFirstLoaded = false;
Log.d(TAG, "end isInitialFirstLoaded = "+ isInitialFirstLoaded);
}
@Override
public void onCancelled(DatabaseError databaseError) {
// Getting Post failed, log a message
Log.w(TAG, "loadPost:onCancelled", databaseError.toException());
}
};
if (initialKey == null) {// if it's loaded for the first time. Key is null
Log.d(TAG, "getMessages initialKey= " + initialKey);
messagesQuery = mMessagesRef.orderByKey()//limitToLast to start from the last (page size) items
.limitToLast(size);
} else {// not the first load. Key is the last or first seen key
Log.d(TAG, "getMessages initialKey= " + initialKey);// I need to adjust the Query according scrolling direction
if(user is scrolling up){
messagesQuery = mMessagesRef.orderByKey().startAt(initialKey).limitToFirst(size);
}else{
messagesQuery = mMessagesRef.orderByKey().endAt(initialKey).limitToLast(size);
}
}
messagesQuery.addValueEventListener(initialMessagesListener);
mListenersList.add(new FirebaseListeners(messagesQuery, initialMessagesListener));
Обновление:
Пока я нашел два решения, первое проще, а второе лучше:
Решения 1: Добавьте ScrollListener, чтобы выяснить, прокручивает ли пользователь вверх или вниз, прокручивает ли пользователь вверх, будет ли видимый элемент первым видимым элементом или выше, если прокрутить вниз, начальный ключ будет последним видимым элементом или ниже, все, что у меня естьдля этого нужно изменить запрос соответствующим образом.
public class PagingFragment extends Fragment {
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View fragView = inflater.inflate(R.layout.messages_fragment, container, false);
// Listen for scroll events
mMessagesRecycler.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
Log.d(TAG, "onScrollStateChanged newState= "+newState);
}
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
Log.d(TAG, "onScrolled dx= "+dx +" dy= "+dy);
int lastCompletelyVisibleItem = mLinearLayoutManager.findLastCompletelyVisibleItemPosition(); // the position of last displayed item
if(lastCompletelyVisibleItem >= (totalItemCount-1)){
// The position of last displayed item = total items, witch means we are at the bottom
mScrollDirection = REACHED_THE_BOTTOM;
Log.i(TAG, "List reached the bottom");
}else if(lastCompletelyVisibleItem <= visibleItemCount){
// The position of last displayed item is less than visibleItemCount, witch means we are at the top
mScrollDirection = REACHED_THE_TOP;
Log.i(TAG, "List reached the top");
}else{
if(dy < 0 ){
// dy is negative number, scrolling up
Log.i(TAG, "List scrolling up");
mScrollDirection = SCROLLING_UP;
}else{
// dy is positive number, scrolling down
Log.i(TAG, "List scrolling down");
mScrollDirection = SCROLLING_DOWN;
}
}
// set scrolling direction. it's needed for the initialkey
mMessagesViewModel.setScrollDirection(mScrollDirection);
return fragView;
}
public class MessagesViewModel extends ViewModel {
// Set scroll direction
public void setScrollDirection(int scrollDirection, int lastVisibleItem) {
MessagesListRepository.setScrollDirection(scrollDirection);
}
}
public class MessagesListRepository {
public static void setScrollDirection(int scrollDirection) {
Log.d(TAG, "mScrollDirection = " + scrollDirection);
mScrollDirection = scrollDirection;
}
// get initial data
public void getMessages(String initialKey, final int size,
@NonNull final ItemKeyedDataSource.LoadInitialCallback<Message> callback) {
Log.i(TAG, "getMessages initiated. initialKey= " + initialKey);
this.initialKey = initialKey;
Query messagesQuery;
isInitialFirstLoaded = true;
ValueEventListener initialMessagesListener = new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
// [START_EXCLUDE]
Log.d(TAG, "start onDataChange. isInitialFirstLoaded = "+ isInitialFirstLoaded);
if (!isInitialFirstLoaded){
// Remove post value event listener
removeListeners();
Log.d(TAG, "usersChanged Invalidated removeEventListener");
//isInitialFirstLoaded = true;
Log.d(TAG, "onInvalidated(). isInitialFirstLoaded = "+ isInitialFirstLoaded);
invalidatedCallback.onInvalidated();
//UsersDataSource.InvalidatedCallback.class.getMethod("loadInitial", "LoadInitialParams");
//UsersDataSource.invalidate();
return;
}
if (dataSnapshot.exists()) {
final List<Message> messagesList = new ArrayList<>();
// loop throw users value
for (DataSnapshot snapshot: dataSnapshot.getChildren()){
Message message = snapshot.getValue(Message.class);
if (message != null) {
message.setKey(snapshot.getKey());
}
messagesList.add(message);
}
if(messagesList.size() != 0){
callback.onResult(messagesList);
Log.d(TAG, "getMessages List.size= " + messagesList.size()+ " lastkey= "+messagesList.get(messagesList.size()-1).getKey() + " getInitialKey= "+ getInitialKey() );
}
} else {
// no data
Log.w(TAG, "getMessages no users exist");
}
isInitialFirstLoaded = false;
Log.d(TAG, "end isInitialFirstLoaded = "+ isInitialFirstLoaded);
}
@Override
public void onCancelled(DatabaseError databaseError) {
// Getting Post failed, log a message
Log.w(TAG, "loadPost:onCancelled", databaseError.toException());
}
};
if (initialKey == null) {// if it's loaded for the first time. Key is null
Log.d(TAG, "getMessages initialKey= " + initialKey);
messagesQuery = mMessagesRef.orderByKey()//limitToLast to start from the last (page size) items
.limitToLast(size);
} else {// not the first load. Key is the last seen key
Log.d(TAG, "getMessages initialKey= " + initialKey);
switch (mScrollDirection){
case REACHED_THE_BOTTOM:
Log.d(TAG, "messages query = REACHED_THE_BOTTOM");
messagesQuery = mMessagesRef.orderByKey()
.limitToLast(size);
break;
case REACHED_THE_TOP:
Log.d(TAG, "messages query = REACHED_THE_TOP");
messagesQuery = mMessagesRef.orderByKey()
.limitToFirst(size);
break;
case SCROLLING_UP:
messagesQuery = mMessagesRef.orderByKey()
.startAt(initialKey)
.limitToFirst(size);
break;
case SCROLLING_DOWN:
messagesQuery = mMessagesRef.orderByKey()
.endAt(initialKey)
.limitToLast(size);
break;
default:
messagesQuery = mMessagesRef.orderByKey()//limitToLast to start from the last (page size) items
.limitToLast(size);
break;
}
}
messagesQuery.addValueEventListener(initialMessagesListener);
mListenersList.add(new FirebaseListeners(messagesQuery, initialMessagesListener));
}
}
Решение 2: Добавьте ScrollListener для получения lastCompletelyVisibleItem, затем в репозитории я сравниваю lastCompletelyVisibleItem с позицией initialkey, циклически бросая, а массив содержит все отображаемыепредметы, чтобы узнать положение начального ключа.Если позиция initialkey равна или больше, чем lastCompletelyVisibleItem, это означает, что я должен запросить вышеуказанные (до) элементы, если позиция initialkey меньше, чем lastCompletelyVisibleItem, это значит, что я должен запросить нижеследующие (после) элементы.
public class PagingFragment extends Fragment {
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View fragView = inflater.inflate(R.layout.messages_fragment, container, false);
// Listen for scroll events
mMessagesRecycler.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
Log.d(TAG, "onScrollStateChanged newState= "+newState);
}
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
Log.d(TAG, "onScrolled dx= "+dx +" dy= "+dy);
int lastCompletelyVisibleItem = mLinearLayoutManager.findLastCompletelyVisibleItemPosition(); // the position of last displayed item
if(lastCompletelyVisibleItem >= (totalItemCount-1)){
// The position of last displayed item = total items, witch means we are at the bottom
mScrollDirection = REACHED_THE_BOTTOM;
Log.i(TAG, "List reached the bottom");
}else if(lastCompletelyVisibleItem <= visibleItemCount){
// The position of last displayed item is less than visibleItemCount, witch means we are at the top
mScrollDirection = REACHED_THE_TOP;
Log.i(TAG, "List reached the top");
}else{
if(dy < 0 ){
// dy is negative number, scrolling up
Log.i(TAG, "List scrolling up");
mScrollDirection = SCROLLING_UP;
}else{
// dy is positive number, scrolling down
Log.i(TAG, "List scrolling down");
mScrollDirection = SCROLLING_DOWN;
}
}
mMessagesViewModel.setScrollDirection(mScrollDirection, lastCompletelyVisibleItem);
return fragView;
}
public class MessagesViewModel extends ViewModel {
// Set scroll direction
public void setScrollDirection(int scrollDirection, int lastVisibleItem) {
MessagesListRepository.setScrollDirection(scrollDirection, lastVisibleItem);
}
}
public class MessagesListRepository {
private static List<Message> totalItemsList;// = new ArrayList<>();
private ValueEventListener afterMessagesListener = new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
// [START_EXCLUDE]
Log.d(TAG, "start onDataChange isAfterFirstLoaded = "+ isAfterFirstLoaded);
if (!isAfterFirstLoaded){
// Remove post value event listener
removeListeners();
Log.d(TAG, "getMessagesAfter Invalidated removeEventListener");
//isAfterFirstLoaded = true;
Log.d(TAG, "getMessagesAfter onInvalidated(). isAfterFirstLoaded = "+ isAfterFirstLoaded);
invalidatedCallback.onInvalidated();
return;
}
if (dataSnapshot.exists()) {
List<Message> messagesList = new ArrayList<>();
// loop throw users value
for (DataSnapshot snapshot: dataSnapshot.getChildren()){
if(!getLoadAfterKey().equals(snapshot.getKey())) { // if snapshot key = startAt key? don't add it again
Message message = snapshot.getValue(Message.class);
if (message != null) {
message.setKey(snapshot.getKey());
}
messagesList.add(message);
// Add messages to totalItemsList ArrayList to be used to get the initial key position
totalItemsList.add(message);
//Log.d(TAG, "getMessage = "+ message.getMessage()+" getSnapshotKey= " + snapshot.getKey());
}
}
if(messagesList.size() != 0){
//callback.onResult(messagesList);
getLoadAfterCallback().onResult(messagesList);
Log.d(TAG, "getMessagesAfter List.size= " + messagesList.size()+ " lastkey= "+messagesList.get(messagesList.size()-1).getKey());
}
} else {
// no data
Log.w(TAG, "getMessagesAfter no users exist");
}
isAfterFirstLoaded = false;
Log.d(TAG, "end isAfterFirstLoaded = "+ isAfterFirstLoaded);
}
@Override
public void onCancelled(DatabaseError databaseError) {
// Getting Post failed, log a message
Log.w(TAG, "getMessagesAfter loadPost:onCancelled", databaseError.toException());
}
};
private ValueEventListener beforeMessagesListener = new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
// [START_EXCLUDE]
Log.d(TAG, "start onDataChange isBeforeFirstLoaded = "+ isBeforeFirstLoaded);
if (!isBeforeFirstLoaded){
// Remove post value event listener
removeListeners();
Log.d(TAG, "getMessagesBefore Invalidated removeEventListener");
//isBeforeFirstLoaded = true;
Log.d(TAG, "getMessagesBefore onInvalidated(). isBeforeFirstLoaded = "+ isBeforeFirstLoaded);
invalidatedCallback.onInvalidated();
return;
}
if (dataSnapshot.exists()) {
List<Message> messagesList = new ArrayList<>();
// loop throw users value
for (DataSnapshot snapshot: dataSnapshot.getChildren()){
if(!getLoadBeforeKey().equals(snapshot.getKey())) { // if snapshot key = startAt key? don't add it again
Message message = snapshot.getValue(Message.class);
if (message != null) {
message.setKey(snapshot.getKey());
}
messagesList.add(message);
}
}
if(messagesList.size() != 0){
//callback.onResult(messagesList);
getLoadBeforeCallback().onResult(messagesList);
Log.d(TAG, "getMessagesBefore List.size= " + messagesList.size()+ " lastkey= "+messagesList.get(messagesList.size()-1).getKey());
// Create a reversed list to add messages to the beginning of totalItemsList
List<Message> reversedList = new ArrayList<>(messagesList);
Collections.reverse(reversedList);
for (int i = 0; i < reversedList.size(); i++) {
// Add messages to totalItemsList ArrayList to be used to get the initial key position
totalItemsList.add(0, reversedList.get(i));
}
}
} else {
// no data
Log.w(TAG, "getMessagesBefore no users exist");
}
getListeners();
isBeforeFirstLoaded = false;
Log.d(TAG, "end isBeforeFirstLoaded = "+ isBeforeFirstLoaded);
}
@Override
public void onCancelled(DatabaseError databaseError) {
// Getting Post failed, log a message
Log.w(TAG, "getMessagesBefore:onCancelled", databaseError.toException());
}
};
public static void setScrollDirection(int scrollDirection, int lastVisibleItem) {
Log.d(TAG, "mScrollDirection = " + scrollDirection+ " lastVisibleItem= "+ lastVisibleItem);
mScrollDirection = scrollDirection;
mlastVisibleItem = lastVisibleItem;
}
// get initial data
public void getMessages(String initialKey, final int size,
@NonNull final ItemKeyedDataSource.LoadInitialCallback<Message> callback) {
Log.i(TAG, "getMessages initiated. initialKey= " + initialKey);
this.initialKey = initialKey;
Query messagesQuery;
isInitialFirstLoaded = true;
ValueEventListener initialMessagesListener = new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
// [START_EXCLUDE]
Log.d(TAG, "start onDataChange. isInitialFirstLoaded = "+ isInitialFirstLoaded);
if (!isInitialFirstLoaded){
// Remove post value event listener
removeListeners();
Log.d(TAG, "usersChanged Invalidated removeEventListener");
//isInitialFirstLoaded = true;
Log.d(TAG, "onInvalidated(). isInitialFirstLoaded = "+ isInitialFirstLoaded);
invalidatedCallback.onInvalidated();
return;
}
if (dataSnapshot.exists()) {
final List<Message> messagesList = new ArrayList<>();
// loop throw users value
for (DataSnapshot snapshot: dataSnapshot.getChildren()){
Message message = snapshot.getValue(Message.class);
if (message != null) {
message.setKey(snapshot.getKey());
}
messagesList.add(message);
// Add messages to totalItemsList ArrayList to be used to get the initial key position
totalItemsList.add(message);
//Log.d(TAG, "getMessage = "+ message.getMessage()+" getSnapshotKey= " + snapshot.getKey());
}
if(messagesList.size() != 0){
callback.onResult(messagesList);
Log.d(TAG, "getMessages List.size= " + messagesList.size()+ " lastkey= "+messagesList.get(messagesList.size()-1).getKey() + " getInitialKey= "+ getInitialKey() );
}
} else {
// no data
Log.w(TAG, "getMessages no users exist");
}
isInitialFirstLoaded = false;
Log.d(TAG, "end isInitialFirstLoaded = "+ isInitialFirstLoaded);
}
@Override
public void onCancelled(DatabaseError databaseError) {
// Getting Post failed, log a message
Log.w(TAG, "loadPost:onCancelled", databaseError.toException());
}
};
if (initialKey == null) {// if it's loaded for the first time. Key is null
Log.d(TAG, "getMessages initialKey= " + initialKey);
messagesQuery = mMessagesRef.orderByKey()//limitToLast to start from the last (page size) items
.limitToLast(size);
} else {// not the first load. Key is the last seen key
Log.d(TAG, "getMessages initialKey= " + initialKey);
switch (mScrollDirection){
case REACHED_THE_BOTTOM:
Log.d(TAG, "messages query = REACHED_THE_BOTTOM");
messagesQuery = mMessagesRef.orderByKey()
.limitToLast(size);
break;
case REACHED_THE_TOP:
Log.d(TAG, "messages query = REACHED_THE_TOP");
messagesQuery = mMessagesRef.orderByKey()
.limitToFirst(size);
break;
default:
if(getInitialKeyPosition() >= mlastVisibleItem ){
// InitialKey is in the bottom, must load data from bottom to top
Log.d(TAG, "messages query = Load data from bottom to top");
messagesQuery = mMessagesRef.orderByKey()
.endAt(initialKey)
.limitToLast(size);
}else{
// InitialKey is in the top, must load data from top to bottom
Log.d(TAG, "messages query = Load data from top to bottom");
messagesQuery = mMessagesRef.orderByKey()
.startAt(initialKey)
.limitToFirst(size);
}
break;
}
}
// Clear the list of total items to start all over
totalItemsList.clear();
messagesQuery.addValueEventListener(initialMessagesListener);
mListenersList.add(new FirebaseListeners(messagesQuery, initialMessagesListener));
}
}