本片我們主要的內容是利用fragment argument來實現fragment和Activity之間的數據傳遞,從而完成日記列表和創建日記頁面的交互過程。
在前面的基礎上我們先來爲RecyclerView的列表項添加點擊事件。回憶一下我們在上篇中實現了NoteListAdapter,其中ViewHolder和列表視圖進行關聯,所以我們可以通過爲ViewHolder實現點擊事件來實現。
修改NoteListAdapter代碼:
static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView noteTitle;
TextView noteContent;
TextView noteCreateDate;
public ViewHolder(@NonNull View itemView) {
super(itemView);
noteTitle = itemView.findViewById(R.id.tv_note_title);
noteContent = itemView.findViewById(R.id.tv_note_content);
noteCreateDate = itemView.findViewById(R.id.tv_note_create_date);
itemView.setOnClickListener(this);
}
@Override
public void onClick(View v) {
Toast.makeText(v.getContext(),
noteTitle.getText() + " clicked!", Toast.LENGTH_SHORT)
.show();
}
}
我們試試效果:
接下來我們在點擊列表的時候啓動創建日記頁面,在fragment中啓動Activity類似於從activity中啓動activity。這裏我把NotePageActivity重命名爲NoteItemActivity,NoteCreateFragment重命名爲NoteItemFragment,fragment_note_create.xml修改爲fragment_note_item.xml.
替換上面的Toast語句如下:
Intent intent = new Intent(v.getContext(), NoteItemActivity.class);
v.getContext().startActivity(intent);
由於我們的adapter是獨立的java類,所以這裏我們通過v.getContext()獲取上下文。啓動一個新的Activity,其實本質上還是調用關聯的Activity的startActivity方法。
看看程序是否如我們所項能夠正確啓動NoteItemActivity:
可以看到卻是可以啓動我們的NoteItemActivity。
🐖:如果我們的Adapter和ViewHolder作爲fragment的內部類實現,那麼可以直接調用getActivity()方法獲取
。
大家也注意到了,雖然頁面正確的跳轉了,但是我們的創建日記頁面並沒有將我們點擊的那一項的內容給傳遞過來,接下來我們就來實現這一內容。
首先我們在NoteListAdapter定義下面的變量
public static final String EXTRA_NOTE_ID = "com.qiushangge.likenotes.adapter.note";
回憶下Activity使用Intent傳遞數據,這裏我們在靜態類ViewHolder中使用,我們需要訪問非靜態的變量,顯然這是無法完成的。所以這裏我們修改ViewHolder的類限定符爲public,在onClick方法中,獲取當前NoteItem對象。
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
....
@Override
public void onClick(View v) {
Intent intent = new Intent(v.getContext(), NoteItemActivity.class);
NoteItem noteItem = noteItemList.get(getAdapterPosition());
intent.putExtra(EXTRA_NOTE_ID, noteItem);
v.getContext().startActivity(intent);
}
}
看看上面的代碼,貌似沒什麼毛病,不過呢putExtra方法並沒有一個對應的傳遞類對象的方法供我們使用,所以這裏需要藉助Gson將NoteItem對象轉化爲json字符串進行傳遞。
intent.putExtra(EXTRA_NOTE_ID,new Gson().toJson(noteItem));
getAdapterPosition()
函數定義:
public final int getAdapterPosition()
該方法返回數據在 Adapter 中的位置,當使用 Adapter 的時候考慮使用。
另外還有一個getLayoutPosition方法,返回佈局中最新的計算位置,和用戶所見到的位置一致,當做用戶輸入(例如點擊事件)的時候考慮使用。
兩者的區別是:
getLayoutPosition 和 getAdapterPosition 通常情況下是一樣的,只有當 Adapter裏面的內容改變了,而 Layout 還沒來得及繪製的這段時間之內纔有可能不一樣,這個時間小於16ms。
如果調用的是 notifyDataSetChanged(),因爲要重新繪製所有 Item,所以在繪製完成之前 RecyclerView是不知道 adapterPosition 的,這時會返回-1(NO_POSITION)。大多數情況下用getAdapterPosition,只要不用 notifyDataSetChanged() 來刷新數據就總能立即獲取到正確position 值。
那麼接下來我們就需要進行數據的獲取了,不過這裏我們需要在NoteItemFragment獲取這些數據。這和我們前面使用Activity獲取Intent傳遞的數據還是有些差異的。
先來看看我們一下就能想到的解決方案。既然我們需要在fragment中獲取intent的extra信息,那麼我們可以通過getActivity()獲取到Activity的實例,然後在獲取extra信息.
NoteItemFragment修改代碼如下:
@Override
protected void initFragmentView() {
etNodeTitle = fragmentRoot.findViewById(R.id.et_note_title);
etNodeContent = fragmentRoot.findViewById(R.id.et_note_content);
tvNodeCreateDate = fragmentRoot.findViewById(R.id.tv_note_create_date);
btnSave = fragmentRoot.findViewById(R.id.btn_save);
Intent intent = getActivity().getIntent();
String noteIemJson = intent.getStringExtra(NoteListAdapter.EXTRA_NOTE_ID);
if (noteIemJson != null) {
NoteItem noteItem = new Gson().fromJson(noteIemJson, NoteItem.class);
etNodeTitle.setText(noteItem.getNoteTitle());
etNodeContent.setText(noteItem.getNoteContent());
tvNodeCreateDate.setText(noteItem.getDateCreate().toString());
}
}
考慮到NoteItemFragment這裏可能會同時完成創建日記和編輯日記的功能,此處添加對應的判讀。如果能或獲取對應的Extra信息,那麼我們在完成對應的賦值操作。
但是使用這種方式進行數據的傳遞,無疑會讓我們的Fragment依賴與Activity,顯然這不是一個很好的解決方案。
使用fragment argument進行數據傳遞
該實現的基本思路時將數據存放在 fragment 的 argument bundle 中,這樣無需託管的 activity 的 intent 內指定 extra 的存在,fragment 本身就能獲取自己所需的 extra 數據。
修改NoteItemFragment.java,添加靜態方法newInstance(Object extra),其中extra就是我們需要傳遞的extra信息。
public static NoteItemFragment newInstance(Parcelable extra) {
Bundle args = new Bundle();
args.putParcelable(NoteListAdapter.EXTRA_NOTE_ID, extra);
NoteItemFragment fragment = new NoteItemFragment();
fragment.setArguments(args);
return fragment;
}
由於這裏我們直接傳遞NoteItem的類對象,所以我們直接將參數類型設置爲Parcelable類型。同時我們需要修改下NoteItem的代碼讓其擴展Parcelable。
public class NoteItem implements Parcelable {
public static final Creator<NoteItem> CREATOR = new Creator<NoteItem>() {
@Override
public NoteItem createFromParcel(Parcel source) {
return new NoteItem(source);
}
@Override
public NoteItem[] newArray(int size) {
return new NoteItem[size];
}
};
private static final String TAG = "NoteItem";
//日記唯一標識
private UUID uid;
//日記標題
private String noteTitle;
//日記內容
private String noteContent;
//日記創建時間
private Date dateCreate;
//日記修改時間
private Date dateUpdate;
public NoteItem() {
uid = UUID.randomUUID();
dateCreate = new Date();
Log.d(TAG, uid.toString());
Log.d(TAG, dateCreate.toString());
}
protected NoteItem(Parcel in) {
this.uid = (UUID) in.readSerializable();
this.noteTitle = in.readString();
this.noteContent = in.readString();
long tmpDateCreate = in.readLong();
this.dateCreate = tmpDateCreate == -1 ? null : new Date(tmpDateCreate);
long tmpDateUpdate = in.readLong();
this.dateUpdate = tmpDateUpdate == -1 ? null : new Date(tmpDateUpdate);
}
public UUID getUid() {
return uid;
}
public void setUid(UUID uid) {
this.uid = uid;
}
public String getNoteTitle() {
return noteTitle;
}
public void setNoteTitle(String noteTitle) {
this.noteTitle = noteTitle;
}
public String getNoteContent() {
return noteContent;
}
public void setNoteContent(String noteContent) {
this.noteContent = noteContent;
}
public Date getDateCreate() {
return dateCreate;
}
public void setDateCreate(Date dateCreate) {
this.dateCreate = dateCreate;
}
public Date getDateUpdate() {
return dateUpdate;
}
public void setDateUpdate(Date dateUpdate) {
this.dateUpdate = dateUpdate;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeSerializable(this.uid);
dest.writeString(this.noteTitle);
dest.writeString(this.noteContent);
dest.writeLong(this.dateCreate != null ? this.dateCreate.getTime() : -1);
dest.writeLong(this.dateUpdate != null ? this.dateUpdate.getTime() : -1);
}
}
在NoteListAdapter中修改傳遞的數據如下:
@Override
public void onClick(View v) {
Intent intent = new Intent(v.getContext(), NoteItemActivity.class);
NoteItem noteItem = noteItemList.get(getAdapterPosition());
intent.putExtra(EXTRA_NOTE_ID,noteItem);
v.getContext().startActivity(intent);
}
接下來我們去處理NoteItemActivity的邏輯,在創建fragment的時候,我們需要調用NoteItemFragment.newInstance()方法,傳入NoteItem類。
@Override
protected Fragment createFragment() {
NoteItem noteItem = getIntent().getParcelableExtra(NoteListAdapter.EXTRA_NOTE_ID);
return NoteItemFragment.newInstance(noteItem);
}
然後在NoteItemFragment中修改接收參數的方式:
@Override
protected void initFragmentView() {
etNodeTitle = fragmentRoot.findViewById(R.id.et_note_title);
etNodeContent = fragmentRoot.findViewById(R.id.et_note_content);
tvNodeCreateDate = fragmentRoot.findViewById(R.id.tv_note_create_date);
btnSave = fragmentRoot.findViewById(R.id.btn_save);
noteItem = (NoteItem) getArguments().getParcelable(EXTRA_NOTE);
if (noteItem != null) {
etNodeTitle.setText(noteItem.getNoteTitle());
etNodeContent.setText(noteItem.getNoteContent());
tvNodeCreateDate.setText(noteItem.getDateCreate().toString());
} else {
noteItem = new NoteItem();
}
}
這裏我們獲取fragment argument 的方法比較簡單,直接調用 getArguments().getX() 方法即可。
現在我們點擊對應的列表項能夠攜帶正確的信息顯示日記的詳細內容,此時我們如果修改日記的內容,然後點擊保存返回。
先來修改NoteItemFragment的按鈕點擊事件:
btnSave.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
noteItem.setDateCreate(new Date());
tvNodeCreateDate.setText(noteItem.getDateCreate().toString());
Toast.makeText(getContext(), R.string.note_save_success, Toast.LENGTH_SHORT).show();
Log.d(TAG, "創建日期:" + noteItem.getDateCreate().toString());
Log.d(TAG, "標題:" + noteItem.getNoteTitle());
Log.d(TAG, "日記內容:" + noteItem.getNoteContent());
getActivity().finish();
}
});
這裏我們直接調用託管的Activity的finish方法,銷燬當前的Activity。然而返回到日記列表頁面後, RecyclerView視圖並沒有刷新,顯然這不是我們所期望的。
NoteListFragment啓動NoteItemActivity實例後, NoteItemActivity被置於回退棧頂。這導致原先處於棧頂的NoteListActivity實例被暫停並停止。用戶點擊後退鍵返回到列表項界面, NoteItemActivity隨即彈出棧外並被銷燬。此時NoteListActivity立即重新啓動並恢復運行。
NoteListActivity恢復運行後,操作系統會發出調用onResume()生命週期方法的指令。
NoteListActivity接到指令後,它的FragmentManager會調用當前被activity託管的fragment
的onResume()方法。這裏的fragment就是指NoteListFragment。
所以我們可以通過覆蓋NoteListFragment的onResume()方法來進行UI的更新操作。
顯然此時我們沒有將數據的變化返回給NoteListFragment,所以同樣需要接收來自NoteItemFragment的返回值。
Fragment有自己的startActivityForResult(…)方法和onActivityResult(…)方法,但沒有setResult(…)方法。所以如果NoteItemFragment要返回值必須依賴託管的Activity的setResult()方法。
NoteItemFragment修改代碼如下:
🐖:這裏將EXTRA_NOTE提取至該文件內了。
public static final String EXTRA_NOTE = "com.qiushangge.likenotes.fragment.note_extra";
public static final String EXTRA_NOTE_RESULT = "com.qiushangge.likenotes.fragment.note_result";
@Override
protected void initFragmentListener() {
btnSave.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
noteItem.setDateCreate(new Date());
tvNodeCreateDate.setText(noteItem.getDateCreate().toString());
Intent intent = new Intent();
new Intent().putExtra(EXTRA_NOTE_RESULT, noteItem);
getActivity().setResult(Activity.RESULT_OK, intent);
getActivity().finish();
}
});
那麼在啓動Activity的時候,就需要調用fragment的startActivityForResult方法。原來我們啓動Activity的代碼實在NoteListAdapter中實現的,爲了方便我們創建一個接口並在fragment中實現。
創建接口NoteListItemClickListener:
package com.qiushangge.likenotes.fragment;
public interface NoteListItemClickListener {
public void onNoteListItemClicked(int position);
}
position爲數據項的索引
在適配器中使用NoteListItemClickListener接口
NoteListAdapter修改代碼如下:
public class NoteListAdapter extends RecyclerView.Adapter<NoteListAdapter.ViewHolder> {
private NoteListItemClickListener noteListItemClickListener;
public void setNoteListItemClickListener(NoteListItemClickListener noteListItemClickListener) {
this.noteListItemClickListener = noteListItemClickListener;
}
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public ViewHolder(@NonNull View itemView) {
super(itemView);
noteTitle = itemView.findViewById(R.id.tv_note_title);
noteContent = itemView.findViewById(R.id.tv_note_content);
noteCreateDate = itemView.findViewById(R.id.tv_note_create_date);
itemView.setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (noteListItemClickListener != null) {
noteListItemClickListener.onNoteListItemClicked(getAdapterPosition());
}
}
}
}
在NoteListFragment中實現接口
public class NoteListFragment extends BaseFragment implements NoteListItemClickListener {
@Override
public void onNoteListItemClicked(int position) {
NoteItem noteItem = noteItemList.get(position);
Log.d(TAG, "onNoteListItemClicked: " + noteItem.getNoteTitle());
Intent intent = new Intent(getActivity(), NoteItemActivity.class);
intent.putExtra(NoteItemFragment.EXTRA_NOTE, noteItem);
startActivityForResult(intent, 1);
}
}
接下來我們就需要實現fragment的onActivityResult方法接收返回值:
@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (resultCode == Activity.RESULT_OK) {
NoteItem newNote = data.getParcelableExtra(NoteItemFragment.EXTRA_NOTE_RESULT);
noteItemList.remove(currentNoteItem);
noteItemList.add(currentNoteItem, newNote);
}
}
然後在onResume方法中通知RecyclerView更新數據:
@Override
public void onResume() {
super.onResume();
adapter.notifyItemChanged(currentNoteItem);
}
這裏我們都是單個日記記錄進行修改所以使用notifyItemChanged刷新列表項中的單個NoteItem項
運行效果:
本篇的內容就算結束了,中間邏輯寫的不是很清晰,但願大家能看懂。。。