一、课程设计(综合实验)的目的与要求
通过本门课程的学习与实践,学生可以加深对移动平台程序设计课程的理解与掌握,有助于提高学生的移动平台程序设计能力,同时可以加深对计算机工作原理的理解,有助于促进后续课程的学习。
本课程要求学生针对课本所学知识从Activity、Fragment、列表实现、滑动实现、消息传递、工具栏和数据库程序等部分学习实践,使学生对移动平台有更深入细致和全面的了解,并能用所学知识解决实际问题,每个实验的具体要求如下:
各个功能模块要求有整体设计思路,难点及解决思路,给出关键代码,并附上调试运行结果。
二、设计(实验)正文
功能模块一 陋习管理软件记录明细界面设计
(1)整体设计思路
首先选择使用fragment和activity共同组装用户界面,设计陋习类、fragment类、和activity类。然后让activity托管UIfragment,显示界面。
- 为了实现UI界面的灵活性,采用fragment来管理应用UI。fragment是一种控制器对象, activity视图能预留位置来放置fragment。只有将fragment视图放置在activity视图层级结构中,fragment视图才能显示在视图上。可联合使用activity和fragment来组装用户界面。
- 用UIfragment(CrimeFragment.java)(fragment_crime.xml)管理用户界面,用activity(MainActiviyy.java)(activity_fragment.xml)来托管CrimeFragment。
- 设计Crime陋习类(Crime.java),表示一条陋习的信息。
- CrimeFragment视图(fragment_crime.xml)由LinearLayout组件和子视图(EditText组件、Button组件、CheckBox组件)组成。CrimeFragment类(CrimeFragment.java)中存储有成员变量并设置监听器,来响应用户操作并更新数据。
- 在activity代码中添加fragment,来托管fragment,并在activity视图中为fragment视图预留位置。
- 用fragmentManager类负责管理fragment并将他们的视图添加到activity视图中。
(2)难点及解决思路
- 如何将新生成的的fragment视图显示出来。
调用LayoutInflater.inflate()函数来找到fragment视图。R.id用来找视图中的组件,inflate用来找视图。
- 具体如何将fragment视图显示出来
如果当前activity中有fragment,则不操作,如果没有,则新生成一个fragment放入activity。
- FragmentManager通过fragment容器的ID识别找到fragment,如果要向activity中添加多个fragment,就要给每个fragment创建有不同ID的不同容器。
(3)程序实现
public class CrimeActivity extends AppCompatActivity{···}
//要使用的fragment来自AppCompat库,需建立
public Crime(){
mID=UUID.randomUUID();
mDate=new Date();
}//在新增加Crime对象的时候, 用UUID类生成唯一ID,并将时间设置为当前时间。并在之后将其他三个变量(mdate,msolved,mTitle)都设置set和get方法,mID只设置get方法。
<FrameLayout ···/> //在activity视图中使用FrameLayout根视图,FrameLayout是容器视图,用来托管fragment
public class CrimeFragment extends Fragment {···}
//CrimeFragment是项目使用的陋习详细页面fragment类继承Fragment类
public void onCreate(Bundle savedInstanceState) {···
}//托管fragment的activity要调用fragment,所以采用public方法。Bundle用来保存和获取状态。
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_crime, container, false);
return v;}//从fragment_crime中实例化并返回视图,调用inflater.inflate()函数传入布局资源ID生成。
mTitleField=(EditText) v.findViewById(R.id.crime_title);
mTitleField.addTextChangedListener(new TextWatcher() {
···
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
mCrime.setTitle(charSequence.toString());
}});//将mTitleField和组件绑定起来,并添加监视,其中的onTextChange方法用来将用户输入的标题设置为mCrime的title;之后的Button和CheckBox也与组件绑定起来,并为CheckBox设置监听,并设置为mCrime的mSolved。
FragmentManager fm=getSupportFragmentManager();
Fragment fragment=fm.findFragmentById(R.id.fragment_container);
//在MainActivity类中定义取得FragmentManager,用来管理显示fragment。之后通过容器视图ID找到fragment,从fragmentManager中获取fragment,如果要获取的fragment在队列中,则直接返回它。如果容器中没有fragment,则新创建一个fragment放入activity。
(4)实验结果
陋习详细页面能正常显示。
功能模块二 用列表显示多条陋习
(1)整体设计思路
为了实现能通过滑动来显示多条陋习,采用RecycleView来显示列表。用CrimeLab类来存储Crime对象,新建两个控制类列表fragment和列表activity,新建列表页每条itemView视图。在CrimeListFragment类中添加创建内部类Adapter和CrimeHolder,来通过Adapter类创建CrimeHolder,然后将Holder与指定的Crime绑定。最后修改代码使列表项能响应点击。
- 新增一个CrimeLab对象,作为数据集中存储池,来存储Crime对象。新增一个activity和一个fragment来显示列表页面。fragment视图根目录是RecyclerView
- 在CrimeLab中定义一个List来存储Crime对象。
- 定义托管列表fragment的activity页面,仍使用之前定义的托管activity布局,因为并没有特别指定fragment。将activity_crime.xml更名为activity_fragment.xml来增强通用性。
- 使用抽象activity类,在抽象类中新增抽象方法creatFragment,子类实现该方法来返回由activity托管的fragment实例。然后让CrimeActivity类和CrimeListActivity都使用该抽象类。
- 在配置文件中声明CrimeListActivity并配置,使打开应用后主界面是列表页。
- 在CrimeListFragment视图布局文件中添加RecyclerView,根视图为RecyclerView。在CrimeListFragment类中定义变量RecyclerView。并通过onCreatView找到列表界面fragment,及RecyclerView。为了RecyclerView的正常使用,需要设置LayoutManager。
- 定义RecyclerView上的列表项itemView的视图(list_item_crime.xml)。
- 在CrimeListFragment类中定义ViewHolder内部类,来实例化并使用list_item_crime布局。
- 在CrimeListFragment类中创建Adapter内部类,将它和RecyclerView关联起来,通过设置一个updateUI方法,创建CrimeAdapter并设置给RecyclerView。RecyclerView需要新的ViewHolder来显示列表时,调用Adapter内部类中的onCreateViewHolder方法来创建CrimeHolder。
- 绑定列表项,将Crime的数据等和组件关联起来,因为是一次性的,选择在CrimeHolder类的构造方法处理。
- 修改CrimeAdapter类中的bind(Crime)方法,每次RecyclerView要求CrimeHolder绑定对应的Crime时,都会调用bind()方法。
- 使列表项能响应用户点击,通过实现OnClickListener接口。对每个itemView,CrimeHolder来实现响应点击事件,修改CrimeHolder内部类中的代码。
(2)难点及解决思路
- RecyclerView是怎么显示视图的?
RecyclerView自身不创建视图,由Adapter创建ViewHolder,绑定ViewHolder至模型层数据,从模型层获得数据然后提供给RecyclerView显示,而ViewHolder实例化并使用itemView视图。
- RecyclerView界面的摆放和滚动是怎么实现的?
RecyclerView不会亲自摆放屏幕上的列表项,LayoutManager负责在屏幕上摆放列表项和定义屏幕的滚动行为。
(3)程序实现
public abstract class SingleFragmentActivity extends AppCompatActivity {
protected abstract Fragment createFragment();
···
if(fragment==null){
fragment=createFragment();
···}
}//因为托管陋习详情fragment和托管列表fragment的activity创建代码基本相同,为了减少总体代码量,采用抽象类SingleFragmentActivity来放置activity创建代码。抽象类中除了新增了createFragment方法来实例化新的fragment,其他代码与第一个activity创建代码相同。
public class MainActivity extends SingleFragmentActivity {
protected Fragment createFragment(){
return CrimeFragment;
}
}//修改两个activity中的代码,继承抽象类并写createFragment函数来实例化不同fragment。CrimeListFragment与上述activity类似。
public class CrimeFragment extends Fragment {}
//创建列表fragment类
<RelativeLayout
···>
<androidx.recyclerview.widget.RecyclerView
···/>
</RelativeLayout>//在列表fragment布局文件中添加RecyclerView视图。来使用RecyclerView。
public class CrimeListFragment extends Fragment {
private RecyclerView mCrimeRecyclerView;
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
View view=inflater.inflate(···);
mCrimeRecyclerView=(RecyclerView) view.findViewById(···);
mCrimeRecyclerView.setLayoutManager(···){
···}
}//配置列表fragment类,然后在onCreateFragment方法中先通过放置列表fragment的activity的ID来找到列表fragment,再通过找到的fragment找到RecyclerView。因为没有LayoutManager的支持,RecyclerView无法正常工作,所以在RecyclerView视图创建完成后,立即转交给LayoutManager对象。RecyclerView类不会亲自摆放屏幕上的列表项,而是委托给LayoutManager来摆放列表项和定义屏幕的滚动行为。
private class CrimeHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
super(inflater.inflate(R.layout.list_item_crime,parent,false));
}//在列表fragment类中定义内部Holder类。在CrimeHolder类中将list_item_crime.xml布局传给super方法,ViewHolder从而可以引用这个视图。
private class CrimeAdapter extends RecyclerView.Adapter<CrimeHolder>{
private List<Crime> mCrimes;
public CrimeAdapter(List<Crime> crimes){
mCrimes=crimes;
}
public CrimeHolder onCreateViewHolder( ViewGroup parent, int viewType) {···}
public void onBindViewHolder( CrimeHolder holder, int position) {···}
public int getItemCount() {···}
}//当需要显示新创建的ViewHolder或让Crime对象和已经创建的Holder关联时,RecyclerView先去找Adapter类。当RecyclerView需要新的ViewHolder来显示列表项时,调用onCreateViewHolder方法来创建LayoutInflater,用来创建CrimeHolder。
private void updateUI() {
···
}//为了将Adapter类与RecyclerView关联起来,需要设置一个updateUI方法来创建CrimeAdapter。
public void bind(Crime crime){
mCrime=crime;
mTitleTextView.setText(mCrime.getTitle());
mDateTextView.setText(mCrime.getDate().toString());
···
}//更新Adapter中的bind方法,来实现只要取到一个Crime,CrimeHolder就会更新标题和日期视图。并在Adapter类中的onBindViewHolder方法中调用bind方法,来使CrimeHolder绑定对应的Crime。
itemView.setOnClickListener(this);
//在CrimeHolder类中设置点击监听,来响应用户点击
@Override
public void onClick(View view) {
Intent intent = CrimePagerActivity.newIntent(getActivity(),mCrime.getID());
startActivity(intent);
}//修改CrimeHolder类中的onClick方法,来在用户点击时做出反应。
(4)实验结果
可以列表显示陋习项
功能模块三 用布局与组件创建用户界面
(1)整体设计思路
给RecyclerView列表项添加样式,通过ConstraintLayout实现,然后通过给图片添加内部逻辑来实现图片的选择实现。
- 转换list_item_crime.xml的布局,改为ConstraintLayout,然后通过ConstraintLayout工具来给布局添加一系列约束。
- 先清除所有约束,然后修改日期和标题的宽度和高度属性。
- 添加一个ImageView视图组件,来插入图片,在陋习解决时显示出来。
- 给三个视图添加具体距离约束。
- 为了将图片的显示与Crime属性联系起来,修改ImageView组件ID为crime_solved。然后在列表fragment中定义ImageView变量mSolvedImageView,并与具体组件联系起来。然后给mSolvedImageView设置逻辑,只有陋习解决后才显示。
- 改变标题和日期的字体属性,来美化显示。
(2)难点及解决思路
- 第一次引入图片后都不显示
经过观察后发现是图片位置引用错了,重新添加图片后解决。
(3)程序实现
通过ConstraintLayout工具修改边距属性。
mSolvedImageView.setVisibility(crime.isSolved() ? View.VISIBLE : View.GONE);
//通过在列表fragment类中bind方法中添加逻辑来实现图片的选择显示。
(4)实验结果
功能模块四 实现在列表项打开每条陋习详细界面
(1)整体设计思路
为了实现点击列表项的每条陋习打开详细界面,将列表部分和详细部分关联起来。通过创建intent来传递crimeID,并用newInstance方法接受传递的crimeID。最后更新列表页。
- 修改列表fragment类中CrimeHolder内部类中的onClick方法,创建一个intent来打开要启动的activity。
- 通过在MainActivity类中创建新intent,用putExtra方法传递附加到Intent上的crimeID,来使MainActivity知道该显示哪个Crime。然后需要修改列表fragment中的CrimeHolder内部类中的onClick方法,传递crimeID到intent里。
- 添加newInstance方法到详细fragment中,来完成fragment实例和Bundle对象的创建,然后将argument放入bundle中,附加给fragment。当托管activity需要fragment时,调用newInstance方法,可给newInstance方法传入任何参数。在详细fragment中编写可接收UUID的newInstance(UUID)方法,创建argument bundle和fragment实例。在MainActivity类中调用newInstance方法传入crimeID。然后再详细fragment中调用getArguments方法接收传入的UUID。
- 覆盖列表fragment类的onCreateView方法中的onResume方法,在其中添加updateUI方法,当详细页返回时,调用onResume()方法时调用内部的更新函数,实现列表的更新。
(2)难点及解决思路
- 为什么直接获取extra信息不好
虽然可让fragment直接从托管activity中获取信息,但这种方法破坏了fragment的封装。这个fragment由某个特定的activity托管,不能再用于其他activity了。
可以先将crimeID储存在列表fragment的argument bundle中,而不是保存在托管activity中。这样列表fragment不依赖托管activity内intent中的的extra就能获取自己所需的extra数据。
- 如何让详细fragment接收到MainActivity类中的crimeID?
在详细fragment类中添加newInstance(UUID)方法,创建bundle,argument和fragment实例,将arguments放入bundle中,在附加给fragment。当MainActivity类需要传递信息时,调用newInstance方法就可将crimeID传给详细fragment。
(3)程序实现
public static Intent newIntent(Context packageContext, UUID crimeID){
Intent intent =new Intent(packageContext,MainActivity.class);
intent.putExtra(EXTRA_CRIME_ID,crimeID);
return intent;
}//在MainActivity类中创建intent并传入对应crimeID的字符串和值。
public void onClick(View view) {
Intent intent =MainActivity.newIntent(getActivity(), mCrime.getId());;
startActivity(intent);
}//更新列表fragment类中的onClick方法,接收MainActivity传来的intent及crimeID信息。
public static CrimeFragment newInstance(UUID crimeId){
Bundle args=new Bundle();
args.putSerializable(ARG_CRIME_ID,crimeId);
CrimeFragment fragment=new CrimeFragment();
fragment.setArguments(args);
return fragment;
}//在详细fragment类中添加newInstance方法,创建argument,bundle和fragment实例。
protected Fragment createFragment(){
UUID crimeID=(UUID) getIntent().getSerializableExtra(···);
return CrimeFragment.newInstance(crimeID);
}//在MainActivity类中接收crimeID并传给fragment。
UUID crimeId=(UUID) getArguments().getSerializable(ARG_CRIME_ID);
//在详细fragment中通过getArguments方法从fragment的argument中获取crimeID
public void onResume() {
super.onResume();
updateUI();
}//覆盖onResume方法,添加updateUI方法,来实现在每次详细面返回时都更新列表项显示。
(4)实验结果
功能模块五 实现详细页面左右滑动
(1)整体设计思路
新创建一个activity(CrimePaperActivity.java)来托管详细页fragment,其视图根目录为ViewPaper,来实现页面左右滑动。在activity中定义ViewPaper,获取数组中指定位置的crimeID,返回一个配置好的详细页fragment。放弃使用MainActivity,改为使用CrimePaperActivity。通过索引位置匹配ID显示指定位置的列表项。
- 新建CrimePaperActivity,来创建并管理ViewPaper。新建其视图布局文件,根视图为ViewPaper。因为ViewPaper的使用需要PaperAdapter的支持,通过FragmentStatePaperAdapter子类来协同处理。
- 在CrimePaperActivity类中先通过ID从Activity视图中找到ViewPaper,并从CrimeLab中获取数据集,然后获取activity的FragmentManager实例。再通过获取的fragmentManager设置adapter为FragmentStatePaperAdapter的一个匿名实例。FragmentStatePapterManager负责与ViewPaper对话并协同工作,传入指定位置的crimeID,返回配置好的CrimeFragment。
- 放弃使用MainActivity,改为使用CrimePaperActivity,新增newIntent方法来传递crimeID。配置启动CrimePaperActivity,更改从列表fragment中打开的activiyu,并更改配置文件。
- 为了点击不同列表项时打开不同详细页面,在PaperActivity的最后循环检查crime的ID,找到所选crime在数组中的索引位置。。如果Crime实例的ID和intent extra的crimeID相同,则显示指定位置的列表项。
(2)难点及解决思路
- 为什么可以直接从CrimeLab中获取数据集?
可以从CrimeLab类中的crime数组中取crime。
- 如何显示指定位置的列表项?
在PaperActivity的onCreate方法最后添加循环检查,当crimeID和Crime实例ID相同时,显示指定的列表项。
(3)程序实现
public class CrimePagerActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_crime_pager);
···}
···}//创建ViewPaper,并配置视图文件。
mViewPager=(ViewPager) findViewById(···);
mCrimes=CrimeLab.get(this).getCrimes();
FragmentManager fragmentManager=getSupportFragmentManager();
mViewPager.setAdapter(new FragmentPagerAdapter(fragmentManager)
···);}//先在activity视图中找到ViewPaper,然后从CrambLab中获取数据集,然后获取activity的fragment。然后给ViewPaper设置adapter,通过FragmentStatePaperAdapter与ViewPaper对话和协同工作。getItem方法获取数组中指定位置的Crime实例,然后用该实例的ID创建并返回一个经配置的CrimeFragment。
Intent intent = CrimePagerActivity.newIntent(getActivity(),mCrime.getID());
//更改列表fragment类中启动的activity为paperActivity,同时更改配置文件,不再使用MainActivity。
for(int i=0;i<mCrimes.size();i++){
if(mCrimes.get(i).getID().equals(crimeId)){
mViewPager.setCurrentItem(i);
break;}//在paperActivity最后添加循环检查方法,当crime在数组中的索引位置和Crime实例的crimeID相同时,设置显示指定的列表项,
(4)实验结果
功能模块六 实现用日期选择框改变日期
(1)整体设计思路
通过对话框AlertDialog显示日期选择器,将AlertDialog封装在fragment中来灵活显示对话框。创建DatePickerFragment类,来创建并配置显示DatePicker组件的AlertDialog实例。日期fragment类由paperActivity托管。
- 在日期fragment中添加onCreateDialog方法,创建一个AlertDialog。通过使用AlertDialog.Builder类,以流接口的方式创建AlertDialog实例。
- DialogFragment实例也由托管activity的FragmentManager管理,将DialogFragment添加给FragmentManager管理,并显示出来。
- 为详细页fragment的按钮添加监听,点击按钮时,打开一个对话框。
- 创建一个DatePicker视图,并通过AlertDialog.Builder的setView方法给对话框添加DatePicker组件。
- 实现信息传递。传递crime的日期给DatePickerFragment,通过新建一个newInstance(Date)方法,将Date作为argument附加给fragment实现。返回新日期给详细fragment,需要将日期打包为extra,附加给Intent,然后调用CrimeFragment.onActivityResult方法,传入准备好的Intent函数。
- 需要创建一个Calendar对象,用Date对象配置,再从Calendar中取回时间信息。然后用时间信息将DatePicker初始化。
- 使详细fragment接收日期fragment返回的日期数据。将详细fragment设置为日期fragment的目标fragment,以便再两个fragment被销毁重建后,系统会重新关联它们。
- 修改对话框中按钮的onClick方法,在按时获取日期并返回给CrimeFragment。然后修改详细fragment代码,接收信息更新Button显示
(2)难点及解决思路
- DatePicker对象的初始化需要整数形式的时间信息,而Date是时间戳,无法直接提供整数
创建一个Calendar对象,用date配置它,然后从Calendar中取回时间信息。
- 怎么实现点击日期按钮打开日期选择器?
先创建activity的manager,再定义一个DatePickerManager,为DatePickerFragment添加一个常量供识别,然后添加到manager并显示在屏幕上。
(3)程序实现
public Dialog onCreateDialog(Bundle savedInstanceState){
return new AlertDialog.Builder(getActivity())
.setView(v)
.setTitle(R.string.date_picker_title)
.setPositiveButton(···)//字符串资源和实现监听器对象
.create();
}//使用AlertDialog.Builder类的构造方法,返回一个AlertDialogBuilder实例,最后调用create方法,返回配置完成的AlertDialog实例。
dialog.show(manager,DIALOG_DATE);
//给日期按钮添加逻辑,打开窗口。将窗口添加到manager并显示在屏幕上。
Bundle args=new Bundle();
args.putSerializable(ARG_DATE,date);
DatePickerFragment fragment =new DatePickerFragment();
fragment.setArguments(args);
return fragment;//替换DatePickerFragment的构造方法,添加一个newInstance(Date)方法,将时间信息附加给fragment,实现信息传递。
DatePickerFragment dialog=DatePickerFragment
.newInstance(mCrime.getDate());//接收传递的时间信息
calendar.setTime(date);
···
mDatePicker=(DatePicker) v.findViewById(R.id.dialog_date_picker);
mDatePicker.init(year,month,day,null);
//需要在时间fragment中创建calendar,用date配置后,再从calendar中取回需要信息才能正常显示,因为Date是时间戳,无法显示整数。
dialog.setTargetFragment(CrimeFragment.this,REQUEST_DATE);
//将CrimeFragment设置为时间fragment实例的目标fragment,来实现信息传递。
(4)实验结果
功能模块七 实现工具栏
(1)整体设计思路
添加菜单文件,添加屏幕左上返回按钮,并添加内部逻辑,来响应点击。
- 新建菜单布局文件,与列表fragment布局文件同名,根视图为menu。在列表fragment类中实例化菜单。定义列表fragment的onCreate方法,使它知道列表fragment需要接收菜单方法回调
- 为了响应菜单的点击,在CrimeLab中添加add方法,来实现陋习的添加。
- 判断菜单项选中的是哪个,来进行不同响应。
- 在屏幕左上方添加返回按钮,通过更改配置文件实现。
- 增加一个菜单项show_subtitle,显示陋习条数,创建新方法updateSubtitle来实现这个需求。
- 通过记录跟踪子标题状态的成员变量来切换显示和隐藏
(2)难点及解决思路
- 如何实现显示和隐藏的切换?
设置一个boolean变量,通过点击来切换变量真假,从而改变标题显示。
(3)程序实现
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
···}//来表示要使用menu
setHasOptionsMenu(true);//让manager知道要接收菜单方法回调
Crime crime=new Crime();
CrimeLab.get(getActivity()).addCrime(crime);
Intent intent=CrimePagerActivity
.newIntent(getActivity(),crime.getID());
startActivity(intent);//通过点击菜单添加成员,实现添加crime实例至数组,并显示。
activity.getSupportActionBar().setSubtitle(subtitle);
//将要显示的副标题设置显示。
(4)实验结果
功能模块八 实现用数据库存储数据
(1)整体设计思路
为了实现数据的保存,使用ormlite将java对象映射到数据库中的表。
- 先添加irmlite框架添加到陋习项目中,然后找到要记录的Crime类,修改定义。
- 再定义DBHelper类,用来创建数据库并覆盖相关函数。
- 定义Dao数据操作接口类,通过Dao数据操作接口类访问数据库。
(2)难点及解决思路
- 在Crime类中引用ormlite失败
在项目结构界面添加dependence,之后引用成功。
(3)程序实现
@DatabaseTable(tableName = "Crimes")
public class Crime implements Serializable {
@DatabaseField(generatedId = true)
private UUID mID;
@DatabaseField
private String mTitle;
···}//修改与数据表相关的模型类Crime类,便于之后使用。
public class CrimeDao {
···}//定义CrimeDao数据访问类,内置方法有create方法,用来添加一个陋习记录updateCrime方法用来修改多个陋习记录。getCrimeList方法用来获取陋习列表。
crimeDao.create(c);//在CrimeLab类中的add方法添加,用来将数据同时写入数据库。并在构造方法中改为从数据库中读取记录。
(4)实验结果
三、心得体会
本次实验实现的陋习管理器是一个综合性的实验,集合了fragment,RecyclerView,viewPaper等多种知识及很多新的方法,是一个很有挑战性的项目。在编写陋习管理器时,遇到了很多困难,通过检查代码和查阅资料大部分得到了解决。