0、addedittask包,是新增/编辑页面
1、首先是interface的介绍,噢耶
/**
* This specifies the contract between the view and the presenter.
* 指定一个contract,里面有View接口、presenter接口
*/
public interface AddEditTaskContract {
/**
* 在AddEditTask页下的操作,会引起View的一些变化,对的,业务逻辑决定了View的变化,大牛太牛了
*/
interface View extends BaseView<Presenter> {
void showEmptyTaskError(); //界面上展示Task为Empty时的错误情况,当业务逻辑上,打开Task页后,如果Task挂了,View上要有展示
void showTasksList(); //界面上展示Task列表吗?对的,就是这样的业务。View上就是把EditTask页面要关闭掉,就会展示TasksList页
void setTitle(String title); //设置标题,业务逻辑上,不同的入口进来后,AddEditTask页面要根据是新建、还是编辑、展示对应的标题
void setDescription(String description); //设置Task详情,在业务上,当进入AddEditTask页后,如果是编辑,View上需要展示要编辑的内容
boolean isActive(); //判断当前的fragment是否已经依附到Activity上
}
/**
* 具体的在AddEditTask下可以进行的动作或者行为,这些动作或者行为,会引起View的变化
* 就是业务逻辑
*/
interface Presenter extends BasePresenter {
void saveTask(String title, String description); //在新增、修改页保存Task
void populateTask(); // 填充Task详情,翻译的准确吗我?
boolean isDataMissing(); //业务上,还要知道数据有没有意外丢失,比如进程被回收了
}
}
2、然后介绍Activity吧
/**
* Displays an add or edit task screen.
* 哈哈,编辑Task页
*/
public class AddEditTaskActivity extends AppCompatActivity {
public static final int REQUEST_ADD_TASK = 1; //请求码,呵呵,常量的干活
public static final String SHOULD_LOAD_DATA_FROM_REPO_KEY = "SHOULD_LOAD_DATA_FROM_REPO_KEY"; //从Bundle里面恢复对象的时候,要用到的key
private AddEditTaskPresenter mAddEditTaskPresenter; //Presenter的引用
private ActionBar mActionBar; //View的引用,这里是ActionBar
/**
* 生命周期方法,老朋友了,牛逼
* @param savedInstanceState 当Activity对象被意外销毁时,序列化的对象会存储在Bundle里,以再从Bundle里获取
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); //必须先call基类的onCreate方法
setContentView(R.layout.addtask_act); //设置布局
// Set up the toolbar. 初始化Toolbar
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
mActionBar = getSupportActionBar();
mActionBar.setDisplayHomeAsUpEnabled(true);
mActionBar.setDisplayShowHomeEnabled(true);
AddEditTaskFragment addEditTaskFragment = (AddEditTaskFragment) getSupportFragmentManager()
.findFragmentById(R.id.contentFrame); //通过id找fragment,醉了,大写F都不让用,艹
String taskId = getIntent().getStringExtra(AddEditTaskFragment.ARGUMENT_EDIT_TASK_ID); //从TaskDetailFragment中传递过来的Intent中取Task id,key用的就是这个AddEditTaskFragment.ARGUMENT_EDIT_TASK_ID
setToolbarTitle(taskId); //设置toolBar的标题
if (addEditTaskFragment == null) { //如果没有拿到fragment
addEditTaskFragment = AddEditTaskFragment.newInstance(); //这边当然要创建一个fragment了
if (getIntent().hasExtra(AddEditTaskFragment.ARGUMENT_EDIT_TASK_ID)) { //如果Intent中包含ARGUMENT_EDIT_TASK_ID这个key
Bundle bundle = new Bundle(); //new一个Bundle
bundle.putString(AddEditTaskFragment.ARGUMENT_EDIT_TASK_ID, taskId); //Bundle对象中放入taskId
addEditTaskFragment.setArguments(bundle); //然后把这个Bundle放入到fragment里
}
ActivityUtils.addFragmentToActivity(getSupportFragmentManager(), //将fragment依附到activity中
addEditTaskFragment, R.id.contentFrame); //需要传入fragmentManager、fragment、还有布局中的占位id
}
boolean shouldLoadDataFromRepo = true; //标志位,是否从仓库中读取数据
// Prevent the presenter from loading data from the repository if this is a config change.
if (savedInstanceState != null) { //若Bundle不为null,那就是说,是从上次意外的Activity对象中恢复
// Data might not have loaded when the config change happen, so we saved the state.
shouldLoadDataFromRepo = savedInstanceState.getBoolean(SHOULD_LOAD_DATA_FROM_REPO_KEY); //根据key,从Bundle中取标志位,并赋值给shouldLoadDataFromRepo
}
// Create the presenter 创建presenter
mAddEditTaskPresenter = new AddEditTaskPresenter(
taskId, //传入taskId对象,其实是一个String对象
Injection.provideTasksRepository(getApplicationContext()), //传入TasksRepository对象
addEditTaskFragment, //传入fragment对象
shouldLoadDataFromRepo); //传入一个标志位,是否从仓库中读取数据
}
/**
* 设置ToolbarTitle的标题
* @param taskId
*/
private void setToolbarTitle(@Nullable String taskId) {
if(taskId == null) { //先判断,如果taskId为null
mActionBar.setTitle(R.string.add_task); //标题就写为New TO-DO
} else {
mActionBar.setTitle(R.string.edit_task); //如果taskId不是null,那就是Edit TO-DO
}
}
/**
* 如果Activity对象被意外回收,无论是进程被撸掉,还是Activity Task栈被撸掉,都会调用该方法
* @param outState
*/
@Override
protected void onSaveInstanceState(Bundle outState) {
// Save the state so that next time we know if we need to refresh data.
outState.putBoolean(SHOULD_LOAD_DATA_FROM_REPO_KEY, mAddEditTaskPresenter.isDataMissing());//把想保留的对象放进Bundle中
super.onSaveInstanceState(outState);
}
/**
* 又看见这个方法了,鬼知道怎么回事
* @return
*/
@Override
public boolean onSupportNavigateUp() {
onBackPressed();
return true;
}
/**
* 单元测试用的
* @return
*/
@VisibleForTesting
public IdlingResource getCountingIdlingResource() {
return EspressoIdlingResource.getIdlingResource();
}
}
3、Presenter的实现类,欧耶
/**
* Listens to user actions from the UI ({@link AddEditTaskFragment}), retrieves the data and updates
* the UI as required.
* AddEditTask页用的Presenter
*/
public class AddEditTaskPresenter implements AddEditTaskContract.Presenter,
TasksDataSource.GetTaskCallback {
@NonNull
private final TasksDataSource mTasksRepository; //Task仓库对象,TasksDataSource
@NonNull
private final AddEditTaskContract.View mAddTaskView; //AddEditTaskContract.View对象
@Nullable
private String mTaskId; //TaskId对象
private boolean mIsDataMissing; //是否数据丢失的标志位
/**
* Creates a presenter for the add/edit view.
*
* @param taskId ID of the task to edit or null for a new task
* @param tasksRepository a repository of data for tasks
* @param addTaskView the add/edit view
* @param shouldLoadDataFromRepo whether data needs to be loaded or not (for config changes)
*/
public AddEditTaskPresenter(@Nullable String taskId, @NonNull TasksDataSource tasksRepository,
@NonNull AddEditTaskContract.View addTaskView, boolean shouldLoadDataFromRepo) {
mTaskId = taskId; //传进来的Task id
mTasksRepository = checkNotNull(tasksRepository); //传进来的Tasks仓库对象
mAddTaskView = checkNotNull(addTaskView); //传进来的fragment对象,因为fragment对象实现了AddEditTaskContract.View
mIsDataMissing = shouldLoadDataFromRepo; //从仓库加载数据时,如果数据错过了
mAddTaskView.setPresenter(this); //调用fragment中的setPresenter方法,把当前的Presenter对象传过去。setPresenter是AddEditTaskContract.View的接口方法,fragment实现了
}
/**
* fragment创建后,会在它的onResume中调用start()方法
*
*/
@Override
public void start() {
if (!isNewTask() && mIsDataMissing) { //如果不是新的Task、又有数据丢失了
populateTask();
}
}
/**
* 保存Task
* @param title 标题
* @param description 内容
*/
@Override
public void saveTask(String title, String description) {
if (isNewTask()) { //如果新的Task
createTask(title, description); //创建Task
} else {
updateTask(title, description); //不是新的Task,那就是更新Task
}
}
/**
* 这个方法是,从仓库获取Task吗?populate这个单词要怎么解释,确实是,如果不是新的Task,那就从仓库中拿Task
*/
@Override
public void populateTask() {
if (isNewTask()) { //如果是新的Task
throw new RuntimeException("populateTask() was called but task is new."); //抛出RuntimeException异常,提示为"populateTask() was called but task is new."
}
mTasksRepository.getTask(mTaskId, this); //旧的Task,通过TaskId,调用Task仓库对象的getTask
//getTask方法需要一个GetTaskCallback对象,我们传this进去就可以了,因为 AddEditTaskPresenter 实现了GetTaskCallback接口
}
/**
* GetTaskCallback下的接口方法,这个方法在mTasksRepository中的getTask方法中会被调用哦
* @param task 要传入的Task对象
*/
@Override
public void onTaskLoaded(Task task) {
// The view may not be able to handle UI updates anymore
if (mAddTaskView.isActive()) { //如果fragment已经依附到Activity中
mAddTaskView.setTitle(task.getTitle()); //调用其设置标题
mAddTaskView.setDescription(task.getDescription()); //调用其设置详细描述
}
mIsDataMissing = false; //并且把标志
}
/**
* 当数据,即Task没有获得到的时候
*/
@Override
public void onDataNotAvailable() {
// The view may not be able to handle UI updates anymore
if (mAddTaskView.isActive()) { //首先fragment得依附到Activity上吧
mAddTaskView.showEmptyTaskError(); //展示一个错误的Task Error
}
}
/**
* 判断数据是否丢失
* @return 丢失状态标志位
*/
@Override
public boolean isDataMissing() {
return mIsDataMissing;
}
/**
* 判断是否为新的Task,如果mTaskId为null的话,则为新的Task
* @return
*/
private boolean isNewTask() {
return mTaskId == null;
}
/**
* 创建Task的方法
* @param title 标题
* @param description 详细内容
*/
private void createTask(String title, String description) {
Task newTask = new Task(title, description); //先new个Task对象,传入了标题和描述
if (newTask.isEmpty()) { //判断Task是否为空
mAddTaskView.showEmptyTaskError(); //如果为空,展示空Task的Error提示
} else { //不为空
mTasksRepository.saveTask(newTask); //放入Task仓库中,存储Task
mAddTaskView.showTasksList(); //马上finish掉当前的edit页,展示TasksList页
}
}
/**
* 更新Task的方法
* @param title 要更新的标题
* @param description 要更新的详细描述
*/
private void updateTask(String title, String description) {
if (isNewTask()) { //如果是新的Task
throw new RuntimeException("updateTask() was called but task is new."); //直接抛出RuntimeException异常,给的描述:"updateTask() was called but task is new."
}
mTasksRepository.saveTask(new Task(title, description, mTaskId)); //去仓库保存Task
mAddTaskView.showTasksList(); // After an edit, go back to the list. //更新完了之后,back返回到Task列表页
}
}
4、Fragment,其实也是interface中的View的实现类,欧耶
/**
* Main UI for the add task screen. Users can enter a task title and description.
*/
public class AddEditTaskFragment extends Fragment implements AddEditTaskContract.View {
public static final String ARGUMENT_EDIT_TASK_ID = "EDIT_TASK_ID"; //嘿嘿,放入Bundle时,用的key
private AddEditTaskContract.Presenter mPresenter; //嘿嘿,Presenter
private TextView mTitle; //一个View的引用,标题
private TextView mDescription; //一个View的引用,详细内容
/**
* 创建fragment对象的静态方法,倒是挺轻松的哈
* @return
*/
public static AddEditTaskFragment newInstance() {
return new AddEditTaskFragment(); //new一个构造方法嘛
}
/**
* 默认的构造方法,必须得有,没招
*/
public AddEditTaskFragment() {
// Required empty public constructor
}
/**
* 生命周期方法
*/
@Override
public void onResume() {
super.onResume();
mPresenter.start(); //在这里调用Presenter的start()方法
//有没有担心mPresenter没有初始化完成,这个完全不用担心,因为是在AddEditTaskActivity的onCreate
//那就更没有必要担心,Activity的onResume()方法没有调用完的话,fragment的onResume怎么会执行呢,在速度上,肯定presenter不会是null嘛
//我这么理解比较风骚
}
/**
* 嘿嘿,就是这个方法,在AddEditTaskPresenter的构造方法里调用,而AddEditTaskPresenter是在AddEditTaskActivity创建的哦
* @param presenter
*/
@Override
public void setPresenter(@NonNull AddEditTaskContract.Presenter presenter) {
mPresenter = checkNotNull(presenter);
}
/**
* fragment的生命周期方法,在activity的onCreate()调用完后,会调用这个,它是在onCreateView方法后面调用的
* @param savedInstanceState
*/
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState); //老规矩,基类到还是必须调用的
FloatingActionButton fab =
(FloatingActionButton) getActivity().findViewById(R.id.fab_edit_task_done); //从依附的Activity中获取View,这是一个FAB的控件
fab.setImageResource(R.drawable.ic_done); //给fab设置图标
fab.setOnClickListener(new View.OnClickListener() { //给fab设置点击事件监听器对象
@Override
public void onClick(View v) {
mPresenter.saveTask(mTitle.getText().toString(), mDescription.getText().toString()); //调用mPresenter的saveTask方法,传入title和内容
}
});
}
/**
* 生命周期方法
* @param inflater LayoutInflater对象,用于解析布局xml文件
* @param container ViewGroup对象,View的容器对象
* @param savedInstanceState 一个Bundle对象
* @return
*/
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.addtask_frag, container, false); //初始化root view
mTitle = (TextView) root.findViewById(R.id.add_task_title); //获取view title
mDescription = (TextView) root.findViewById(R.id.add_task_description); //获取view 详细description
setHasOptionsMenu(true); //设置展示optionsMenu
return root; //返回初始化后的View
}
/**
* 展示一个空Task的Error提示,展示一个Snackbar
*/
@Override
public void showEmptyTaskError() {
Snackbar.make(mTitle, getString(R.string.empty_task_message), Snackbar.LENGTH_LONG).show();
}
/**
* 展示TasksList,怎么展示呢,把当前的EditTaskActivity,finish掉
*/
@Override
public void showTasksList() {
getActivity().setResult(Activity.RESULT_OK); //首先设置结果码
getActivity().finish(); //finish掉整个Activity
}
/**
* 设置标题
* @param title
*/
@Override
public void setTitle(String title) {
mTitle.setText(title);
}
/**
* 设置详细内容
* @param description
*/
@Override
public void setDescription(String description) {
mDescription.setText(description);
}
/**
* 判断一下fragment是否已经依附到Activity上
* @return
*/
@Override
public boolean isActive() {
return isAdded();
}
}