2017-02-28 23 views
13

我正在實現一個簡單的APOD應用:如何在使用Firebase和ChildEventListener的方向更改後保留RecyclerView的位置?

  • RecyclerView
  • CardView
  • Firebase
  • Picasso

該應用從FirebaseFirebase Storage抓住圖像和文字,將它們顯示在CardView中,並設置一個OnClickListener每個View。當用戶點擊圖片時,我通過Intent打開新的Activity。第二個Activity顯示原始點擊的圖像,以及更多關於它的信息。

我已經實現了這一切,使用GridLayoutManager,如果用戶的手機是VERTICAL,則爲1列,如果用戶的手機爲HORIZONTAL,則爲3列。

我遇到的問題是我似乎無法保存RecyclerView對方向更改的立場。我嘗試了所有可以找到的選項,但沒有一個可以工作。我能想到的唯一結論是,在輪換時,我正在銷燬FirebaseChildEventListener以避免內存泄漏,一旦定位完成,Firebase由於ChildEventListener的新實例而重新查詢數據庫。

有沒有什麼辦法可以保存我的RecyclerView對方向改變的立場?我不想android:configChanges,因爲它不會讓我改變我的佈局,並且我已經嘗試將其保存爲Parcelable,這是不成功的。我確定這件事很容易失蹤,但是,嘿,我是新手開發的。我非常感謝我的代碼的任何幫助或建議。謝謝!

下面是我的課程,我只縮短了必要的代碼。

MainActivity

public class MainActivity extends AppCompatActivity { 

private RecyclerAdapter mRecyclerAdapter; 
private DatabaseReference mDatabaseReference; 
private RecyclerView mRecyclerView; 
private Query query; 


@Override 
protected void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 

    PreferenceManager.setDefaultValues(this, R.xml.preferences, false); 
    setContentView(R.layout.activity_main); 
    getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); 

    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 
    setSupportActionBar(toolbar); 

    final int columns = getResources().getInteger(R.integer.gallery_columns); 

    mDatabaseReference = FirebaseDatabase.getInstance().getReference(); 
    query = mDatabaseReference.child("apod").orderByChild("date"); 

    mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view); 
    mRecyclerView.setHasFixedSize(true); 
    mRecyclerView.setLayoutManager(new GridLayoutManager(this, columns)); 


    mRecyclerAdapter = new RecyclerAdapter(this, query); 
    mRecyclerView.setAdapter(mRecyclerAdapter); 


    } 

@Override 
public void onDestroy() { 
    mRecyclerAdapter.cleanupListener(); 
    } 

} 

RecyclerAdapter

public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.ApodViewHolder> { 

private final Context mContext; 
private final ChildEventListener mChildEventListener; 
private final Query mDatabaseReference; 
private final List<String> apodListIds = new ArrayList<>(); 
private final List<Apod> apodList = new ArrayList<>(); 

public RecyclerAdapter(final Context context, Query ref) { 
    mContext = context; 
    mDatabaseReference = ref; 


    ChildEventListener childEventListener = new ChildEventListener() { 


     @Override 
     public void onChildAdded(DataSnapshot dataSnapshot, String s) { 
      int oldListSize = getItemCount(); 
      Apod apod = dataSnapshot.getValue(Apod.class); 

      //Add data and IDs to the list 
      apodListIds.add(dataSnapshot.getKey()); 
      apodList.add(apod); 

      //Update the RecyclerView 
      notifyItemInserted(oldListSize - getItemCount() - 1); 
     } 

     @Override 
     public void onChildChanged(DataSnapshot dataSnapshot, String s) { 

     } 

     @Override 
     public void onChildRemoved(DataSnapshot dataSnapshot) { 
      String apodKey = dataSnapshot.getKey(); 
      int apodIndex = apodListIds.indexOf(apodKey); 

      if (apodIndex > -1) { 

       // Remove data and IDs from the list 
       apodListIds.remove(apodIndex); 
       apodList.remove(apodIndex); 

       // Update the RecyclerView 
       notifyDataSetChanged(); 
      } 
     } 

     @Override 
     public void onChildMoved(DataSnapshot dataSnapshot, String s) { 

     } 

     @Override 
     public void onCancelled(DatabaseError databaseError) { 

     } 

    }; 
    ref.addChildEventListener(childEventListener); 
    mChildEventListener = childEventListener; 

} 

    @Override 
    public int getItemCount() { 
    return apodList.size(); 
} 
    public void cleanupListener() { 
    if (mChildEventListener != null) { 
     mDatabaseReference.removeEventListener(mChildEventListener); 
    } 
    } 

} 
+2

我認爲這個問題是一個配置更改後的新RV不在onCreate結束時瞭解其視圖內容的任何內容,因爲只有在所有子監聽器觸發後纔會進入該數據。所以它不知道如何完全恢復自己,因爲它認爲它當時是空的。我不知道解決方案,但確實想提供這些信息。你可以查詢RV的滾動偏移量,如果有幫助的話。 –

+0

@DougStevenson。非常感謝道格,我試着挽救和恢復職位,但無法實現。我認爲這篇文章會得到更多的關注,因爲我完全錯過了一些東西,或者Firebase很難實現這樣的事情。如果我找到其他東西,我會繼續嘗試並更新我的帖子。 –

回答

2

編輯:

我終於可以讓使用多因素這項工作。如果其中任何一個被遺漏了,它根本行不通。

在我的onCreate中的onPause創建新GridLayoutManager

gridLayoutManager = new GridLayoutManager(getApplicationContext(), columns); 

保存RecyclerView狀態

private final String KEY_RECYCLER_STATE = "recycler_state"; 
private static Bundle mBundleRecyclerViewState; 
private Parcelable mListState = null;` 


@Override 
protected void onPause() { 
    super.onPause(); 

    mBundleRecyclerViewState = new Bundle(); 
    mListState = mRecyclerView.getLayoutManager().onSaveInstanceState(); 
    mBundleRecyclerViewState.putParcelable(KEY_RECYCLER_STATE, mListState); 


} 

AndroidManifest.xml

包括 configChanges

保存並恢復RecyclerView狀態是不夠的。我也不得不去對糧食和改變我AndroidManifest.xml爲「機器人:configChanges =‘方向|屏幕尺寸’」

<activity 
     android:name=".MainActivity" 
     android:configChanges="orientation|screenSize" 
     > 

恢復RecyclerView狀態onConfigurationChanged使用Handler

的處理程序是一個最重要的部分,如果沒有包含它,它根本不會工作,並且RecyclerView的位置將重置爲0.我想這是幾年前的一個缺陷,並且從未修復過。然後,我根據方向更改了GridLayoutManagersetSpanCount

@Override 
public void onConfigurationChanged(Configuration newConfig) { 
    super.onConfigurationChanged(newConfig); 
    columns = getResources().getInteger(R.integer.gallery_columns); 

    if (mBundleRecyclerViewState != null) { 
     new Handler().postDelayed(new Runnable() { 

      @Override 
      public void run() { 
       mListState = mBundleRecyclerViewState.getParcelable(KEY_RECYCLER_STATE); 
       mRecyclerView.getLayoutManager().onRestoreInstanceState(mListState); 

      } 
     }, 50); 
    } 

    // Checks the orientation of the screen 
    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { 

     gridLayoutManager.setSpanCount(columns); 

    } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { 

     gridLayoutManager.setSpanCount(columns); 

    } 
    mRecyclerView.setLayoutManager(gridLayoutManager); 
} 

就像我說的,所有這些變化都需要包括在內,至少對我而言。我不知道這是否是最有效的方式,但花了很多時間後,它對我來說很有用。

1

每當方向發生變化時,活動都會重新創建。所以你將不得不在捆綁中保存位置,然後在onCreate()被第二次調用時重新使用它。

@Override 
public void onCreate(Bundle savedInstanceState) { 

    super.onCreate(savedInstanceState); 

    int recyclerViewPosition; 
    if(savedInstanceState != null){ 
    recyclerViewPosition = savedInstanceState.getInt(RECYCLER_VIEW_POSITION); 
    } 

    mRecyclerView.scrollToPosition(recyclerViewPosition); 

} 

@Override 
public void onSaveInstanceState(Bundle outState) { 
    super.onSaveInstanceState(outState); 

    //save the current recyclerview position 
    outState.putInt(RECYCLER_VIEW_POSITION, mRecyclerView.getAdapterPosition()); 
} 

你可能想看看RecyclerView.State()link關於如何恢復回收站視圖的狀態更多的選擇。

5

雖然旋轉活動被破壞並再次創建,這意味着所有的對象都被銷燬並重新創建,並且佈局也會重新繪製。

它你想停止重新創建活動,你可以使用android:configChanges但它是不好的做法,也不是正確的方式。

現在唯一剩下的事情就是在循環前保存recyclerview的位置並在重新創建活動後獲取它。

//保存recyclerview位置

@Override 
    public void onSaveInstanceState(Bundle savedInstanceState) { 
     // Save UI state changes to the savedInstanceState. 
     // This bundle will be passed to onCreate if the process is 
     // killed and restarted. 
     savedInstanceState.putInt("position", mRecyclerView.getAdapterPosition()); // get current recycle view position here. 
     super.onSaveInstanceState(savedInstanceState); 
    } 

//恢復值

@Override 
protected void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 

    PreferenceManager.setDefaultValues(this, R.xml.preferences, false); 
    setContentView(R.layout.activity_main); 
    getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); 

    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 
    setSupportActionBar(toolbar); 

    final int columns = getResources().getInteger(R.integer.gallery_columns); 

    mDatabaseReference = FirebaseDatabase.getInstance().getReference(); 
    query = mDatabaseReference.child("apod").orderByChild("date"); 

    mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view); 
    mRecyclerView.setHasFixedSize(true); 
    mRecyclerView.setLayoutManager(new GridLayoutManager(this, columns)); 


    mRecyclerAdapter = new RecyclerAdapter(this, query); 
    mRecyclerView.setAdapter(mRecyclerAdapter); 
    if(savedInstanceState != null){ 
    // scroll to existing position which exist before rotation. 
    mRecyclerView.scrollToPosition(savedInstanceState.getInt("position")); 
    } 

    } 

希望這些幫助你。

+0

謝謝@jitesh,我以前嘗試過,但這還不夠。一個原因是由於我不得不添加一個「處理程序」。當我終於能夠使它工作時,它刷新整個屏幕仍然有一個可怕的延遲,我猜是因爲'notifyItemInserted(oldListSize - getItemCount() - 1);'在新的'Activity' 。解決這個問題的唯一方法就是使用'android:configChanges',我知道這是令人不悅的,但是在方向改變時它看起來不那麼醜陋。 –

0

實際上,當活動重新創建時,有更好的方法來恢復回收站視圖位置,這是通過使用回收視圖佈局管理器選項來存儲和恢復最後一個位置(最後一個回收站視圖佈局狀態)。看看這個tutorial

用幾句話,您需要在相應的活動方法內調用佈局管理器方法onSaveInstanceState()和onRestoreInstanceState(Parcelable狀態)。最後,您必須在用適配器填充數據後立即恢復佈局管理器狀態。

如果你看一看LinearLayoutManager例如執行裏面,你會看到這個類是如何管理的狀態(存儲/恢復)

相關問題