本片我们主要的内容是利用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项
运行效果:
本篇的内容就算结束了,中间逻辑写的不是很清晰,但愿大家能看懂。。。