Android权威指南 ——crimeIntent

来源:互联网 发布:辽宁省软件评测中心 编辑:程序博客网 时间:2024/06/06 17:14
第七章
UI 设计的灵活性需求
为什么要使用fragment?
假设用户正在平板设备上运行CriminalIntent应用。平板以及大尺寸手机的屏幕较大,能够
同时显示列表和记录明细
假设用户正在手机上查看记录明细信息,并想查看列表中的下一条记录信息。如无需返
回列表界面,滑动屏幕就能查看下一条记录信息就好了。每滑动一次屏幕,应用便自动
切换到下一条记录明细。


原因:UI设计具有灵活性是以上假设情景的共同点。也就是说,适应用户或设备的需求,
activity界面可以在运行时组装,甚至重新组装。activity自身并不具有这样的灵活性。 activity视图可以在运行时切换,但控制视图的代码必须在activity中实现。因而,各个activity还是得和特定的用户屏幕紧紧绑定在一起。




fragment 的引入
fragment是一种控制器对象, activity可委派它完成一些任务。这些任务通常就是管理用户界面。


activity视图可预留供fragment视图插入的位置。如果有多个fragment要插入, activity视图也
可提供多个位置。
 


 托管:activity在其视图层级里提供一处位置用来放置fragment的视图,
 



Fragment初始实例
 

Fragment的生命周期


 
fragment生命周期与activity生命周期的一个关键区别就在于, fragment的生命周期方法是由托管activity而不是操作系统调用。操作系统不关心activity用来管理视图的fragment。 fragment的使用是activity内部的事情






托管 UI fragment
为托管UI fragment, activity必须做到:
1、在布局中为fragment的视图安排位置;
2、管理fragment实例的生命周期。
Ps.这是这一章的关键  下列全是围绕这两点来布置代码


activity托管UI fragment有如下两种方式:
1、在activity布局中添加fragment;
2、在activity代码中添加fragment。此处选择第二种方式


定义容器视图
创建fragment容器布局(activity_crime.xml)
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
创建 UI fragment
创建UI fragment的步骤与创建activity的步骤相同,具体如下:
 通过定义布局文件中的组件,组装界面;
 创建fragment类并设置其视图为定义的布局;
 通过代码的方式,组装在布局文件中实例化的组件


fragment视图的布局文件(fragment_crime.xml)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<EditText android:id="@+id/crime_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/crime_title_hint"
/>
</LinearLayout>
为什要实现fragment生命周期,因为应用中的许多生命周期方法帮助我们完成应用功能。
实现fragment生命周期方法
1、 覆盖Fragment.onCreate(Bundle)方法(CrimeFragment.java)
public class CrimeFragment extends Fragment {
·   private Crime mCrime;
   @Override
   public void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   mCrime = new Crime();
}
}首先, Fragment.onCreate(Bundle)是公共方法,而Activity.onCreate(Bundle)是保护
方法。 Fragment.onCreate(...)方法及其他Fragment生命周期方法必须是公共方法,因为托
管fragment的activity要调用它们。
其次,类似于activity, fragment同样具有保存及获取状态的bundle。如同使用Activity.
onSaveInstanceState(Bundle)方法那样,我们也可以根据需要覆盖 Fragment.onSaveInstanceState(Bundle)方法。
另 外 , fragment 的 视 图 并没 有 在 Fragment.onCreate(...) 方 法 中 生 成 。 虽 然 我 们 在
Fragment.onCreate(...)方法中配置了fragment实例,但创建和配置fragment视图是另一个
fragment生命周期方法完成的:
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
该 方 法 实 例 化 fragment 视 图 的 布 局 , 然 后 将 实 例 化 的 View 返 回 给 托 管 activity 。
LayoutInflater及ViewGroup是实例化布局的必要参数。 Bundle用来存储恢复数据,可供该方
法从保存状态下重建视图。
1、 覆盖onCreateView(...)方法(CrimeFragment.java)
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_crime, container, false);
return v;
}


onCreateView(...)方法也是生成EditText组件并响应用户输入的地方。视图生成后,引用
EditText组件并添加对应的监听器方法。生成并使用EditText组件的具体代码如代码清单7-13
所示
在fragment中关联组件
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
  View v = inflater.inflate(R.layout.fragment_crime, container, false);
  mTitleField = (EditText)v.findViewById(R.id.crime_title);  
  mTitleField.addTextChangedListener(new TextWatcher() {
  @Override
  public void beforeTextChanged( 
     CharSequence s, int start, int count, int after) {
     // This space intentionally left blank
     }
  @Override
public void onTextChanged(
    CharSequence s, int start, int before, int count) {
    mCrime.setTitle(s.toString());   
    }


添加 UI fragment 到 FragmentManager  


这里是用来将 fragment的视图显示在屏幕上 
先将crimefragment添加到crimeactivity 
FragmentManager类具体管理的是:
1、fragment队列;

2、fragment事务回退栈


 
获取FragmentManager(CrimeActivity.java)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_crime);
FragmentManager fm = getSupportFragmentManager();
}


fragment 事务
获 取一个 fragment并 交 由FragmentManager管理。
FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fm.findFragmentById(R.id.fragment_container); //这里第一次执行时,会返回一个空值
if (fragment == null) {
   fragment = new CrimeFragment();
   fm.beginTransaction()
   .add(R.id.fragment_container, fragment)
   .commit();
}


add(...) 方 法 是 整 个 事 务 的 核 心 , 它 含 有 两 个 参 数 : 容 器 视 图 资 源 ID 和 新 创 建 的CrimeFragment。
容器视图资源ID的作用有:
1、告诉FragmentManager, fragment视图应该出现在activity视图的什么位置;
2、用作FragmentManager队列中fragment的唯一标识符。


使用容器视图资源ID去识别UI fragment是FragmentManager的内部实现机制。


总结:
这章内容主要介绍了什么是fragment,为何要使用fragment。 通过fragment来帮助完成功能。 在activity上写framelayout布局,来承载fragment。 先初始化fragment 关联组件。后在activity上 使用fragmentmanager来管理fragment。
使用R.id.fragment_container的容器视图资源ID,向FragmentManager请求并获取fragment。如果要获取的fragment已存在于队列中, FragmentManager就直接返回它。为什么要获取的fragment会存在于队列中呢?前面说过,设备旋转或回收内存时, Android会销毁CrimeActivity, 而后重建时,会调用CrimeActivity.onCreate(...)方法。 activity被销毁时,它的FragmentManager会将fragment队列保存下来。这样, activity重建时,新的FragmentManager会首先获取保存的队列,然后重建fragment队列,从而恢复到原来的状态。另一方面,如果指定容器视图资源ID的fragment不存在,则fragment变量为空值。这时应新建CrimeFragment,并启动一个新的fragment事务,将新建fragment添加到队列中。CrimeActivity目前托管着CrimeFragment。运行CriminalIntent应用验证这一点,应该可以看到定义在fragment_crime.xml中的视图,




第8章 使用布局与组件创建用户界面


这一章感觉考点不多


第九章 使用RecyclerView显示列表


本章应用对象图
 
新增一个CrimeLab对象,该对象是一个数据集中存储池,用来存储Crime对象。
显示crime列表需在应用的控制层新增一个activity和一个fragment: CrimeListActivity和
CrimeListFragment。


crime数组对象将存储在一个单例里。 单例是特殊的java类,在创建实例时,一个单例类仅允许创建一个实例。


public class CrimeLab {
private static CrimeLab sCrimeLab;
private List<Crime> mCrimes;
public static CrimeLab get(Context context) {
...
}
private CrimeLab(Context context) {
mCrimes = new ArrayList<>();
}
public List<Crime> getCrimes() {
return mCrimes;
}
public Crime getCrime(UUID id) {
for (Crime crime : mCrimes) {
if (crime.getId().equals(id)) {
return crime;
}
}
return null;
}
}




使用抽象 activity 托管 fragment
通用的 fragment 托管布局 activity_crime.xml




抽象 activity 类 SingleFragmentActivity.java
public abstract class SingleFragmentActivity extends FragmentActivity {
protected abstract Fragment createFragment();
@Override
public void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_fragment);
 FragmentManager fm = getSupportFragmentManager();
 Fragment fragment = fm.findFragmentById(R.id.fragment_container);
 if (fragment == null) {
   fragment = createFragment();
   fm.beginTransaction()
  .add(R.id.fragment_container, fragment)
  .commit();
}
}
}


1. 使用抽象类 SingleFragmentActivity
2. 新建控制类 CrimeListActivity和CrimeListFragment
3. 在配置文件中声明CrimeListActivity
将CrimeListActivity 设置为起始界面
<activity android:name=".CrimeListActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
9.3 RecyclerView、 Adapter 和 ViewHolder 重点
RecyclerView是ViewGroup的子类,每一个列表项都是作为一个View子对象显示的。
9.3.1 ViewHolder 和 Adapter
RecyclerView的任务仅限于回收和定位屏幕上的TextView。
TextView能够显示数据还离不开另外两个类的支持: Adapter子类和ViewHolder子类。
 
RecyclerView自己不创建ViewHolder。这个任务实际是由adapter来完成的。 adapter是个控制器对象,从模型层获取数据,然后提供给RecyclerView显示,起到了沟通的桥梁作用。
adapter负责:
 创建必要的ViewHolder;
 绑定ViewHolder至模型层数据。


RecyclerView如何显示数据
RecyclerView需要显示视图对象时,就会去找它的adapter。图9-7展示了一个RecyclerView
可能发起的会话。
首先,通过调用adapter的getItemCount()方法, RecyclerView询问数组列表中包含多少个
对象。
接 着 , RecyclerView 调 用 adapter 的 createViewHolder(ViewGroup, int) 方 法 创 建
ViewHolder以及ViewHolder要显示的视图。
最后, RecyclerView会传入ViewHolder及其位置,调用onBindViewHolder(ViewHolder,
int)方法。 adapter会找到目标位置的数据并绑定到ViewHolder的视图上。所谓绑定,就是使用
模型数据填充视图。
 
使用 RecyclerView
1、添加依赖库
2、添加RecyclerView视图
<android.support.v7.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/crime_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>


3、将视图和fragment相关联


public class CrimeListFragment extends Fragment {
private RecyclerView mCrimeRecyclerView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
    Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_crime_list, container, false);
    mCrimeRecyclerView = (RecyclerView) view
               .findViewById(R.id.crime_recycler_view);
    mCrimeRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
    return view;
}
}


9.3.3 实现 Adapter 和 ViewHolder
1、首先在CrimeListFragment类中定义ViewHolder内部类,
public class CrimeListFragment extends Fragment {
...
  private class CrimeHolder extends RecyclerView.ViewHolder {
  public TextView mTitleTextView;
  public CrimeHolder(View itemView) {
         super(itemView);
         mTitleTextView = (TextView) itemView;
}
}
}


2、 创建adapter内部类
public class CrimeListFragment extends Fragment {
...
private class CrimeAdapter extends RecyclerView.Adapter<CrimeHolder> {
private List<Crime> mCrimes;
public CrimeAdapter(List<Crime> crimes) {
mCrimes = crimes;
}
}


3、 武装CrimeAdapter(CrimeListFragment.java)
三个方法 其中关键onCreateViewHolder(需要新的View视图来显示列表项时) 
onBindViewHolder(把ViewHolder的View视图和模型层数据绑定起来。)
private class CrimeAdapter extends RecyclerView.Adapter<CrimeHolder> {
...
@Override
public CrimeHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
View view = layoutInflater.inflate(android.R.layout.simple_list_item_1, parent, false);
return new CrimeHolder(view);
}
@Override
public void onBindViewHolder(CrimeHolder holder, int position) {
  Crime crime = mCrimes.get(position);
  holder.mTitleTextView.setText(crime.getTitle());
}
@Override
public int getItemCount() {
return mCrimes.size();
}
}






定制列表项
用户自定义ViewHolder


在CrimeHolder中绑定视图
关联CrimeAdapter和CrimeHolder






为何使用单例 
1、它们比fragment或activity活得久。例如,在设备旋转或是在fragment和activity间跳转的场景下,单例不会受到影响,而旧的fragment或activity已经不复存在了。
2、单例能方便地存储控制模型层对象。






单例的缺点
1、 然单例能存储数据,活得比控制单元长久,但这并不代表它能永存。
2、 单例还不利于单元测试。






总结:
第九章 主要引入recycleview  单例 以及通过抽象方法来简化代码。 重点
SingleFragment的代码和RecycleView的实现原理,如何将RecycleView加到fragment上 














第十章 使用fragment argument


 


从 fragment 中启动 activity
启动CrimeActivity(CrimeListFragment.java)
private class CrimeHolder extends RecyclerView.ViewHolder
implements View.OnClickListener {
...
@Override
public void onClick(View v) {
  Toast.makeText(getActivity(),
  mCrime.getTitle() + " clicked!", Toast.LENGTH_SHORT)
  .show();
  Intent intent = new Intent(getActivity(), CrimeActivity.class);
  startActivity(intent);
}
}










Fragment数据通信方法一 
附加 extra 信息  CrimeActivity.java
启动CrimeActivity时,传递附加到Intent extra上的crime ID, CrimeFragment就能知道该
显示哪个Crime。
public static final String EXTRA_CRIME_ID =
"com.bignerdranch.android.criminalintent.crime_id";
public static Intent newIntent(Context packageContext, UUID crimeId) {
Intent intent = new Intent(packageContext, CrimeActivity.class);
intent.putExtra(EXTRA_CRIME_ID, crimeId);
return intent;
}




获取 extra 信息 CrimeFragment.java
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
UUID crimeId = (UUID) getActivity().getIntent()
.getSerializableExtra(CrimeActivity.EXTRA_CRIME_ID);
mCrime = CrimeLab.get(getActivity()).getCrime(crimeId);
}
...
}
直接获取 extra 信息的缺点
按照当前的编码实现,
CrimeFragment便再也无法用于任何其他的activity了。破坏了fragment的封装性


·Fragment数据通信方法二  fragment argument


每个fragment实例都可附带一个Bundle对象。该bundle包含有键值对,我们可以像附加extra
到Activity的intent中那样使用它们。一个键值对即一个argument。
Bundle args = new Bundle();
args.putSerializable(EXTRA_MY_OBJECT, myObject);
args.putInt(EXTRA_MY_INT, myInt);
args.putCharSequence(EXTRA_MY_STRING, myString);




















附加 argument 给 fragment
需调用Fragment.setArguments(Bundle)方法。而且,还必须在fragment创建后、添加给activity前完成。


通常做法:
1、添加名为newInstance()的静态方法给Fragment类。使用该方法,完成fragment实例及bundle对象的创建,
2、然后将argument放入bundle中,最后再附加给fragment。
3、托管activity需要fragment实例时,转而调用newInstance()方法,而非直接调用其构造方法。而且,为满足fragment创建argument的要求,activity可传入任何需要的参数给newInstance()方法。在CrimeFragment类中,编写可以接受UUID参数的newInstance(UUID)方法。通过该方法,创建argument bundle和fragment实例,然后附加argument给fragment。






编写newInstance(UUID)方法 CrimeActivity.java
public class CrimeFragment extends Fragment {
private static final String ARG_CRIME_ID = "crime_id";

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;
}
...
}


使用newInstance(UUID)方法 CrimeFragment.java
public class CrimeActivity extends SingleFragmentActivity {
private static final String EXTRA_CRIME_ID =
"com.bignerdranch.android.criminalintent.crime_id";
...
@Override
protected Fragment createFragment() {
    return new CrimeFragment();
    UUID crimeId = (UUID) getIntent()
    .getSerializableExtra(EXTRA_CRIME_ID);
    return CrimeFragment.newInstance(crimeId);
}
}




刷新显示列表项
模型层保存的数据如有变化(或可能发生变化) ,应通知RecyclerView的Adapter,以便其及时获取最新数据并刷新显示列表项。在恰当的时机,与系统的ActivityManager回退栈协同运作,可实现列表项的刷新。


CrimeListFragment启动CrimeActivity实例后, CrimeActivity被置于回退栈顶。这导致原先处于栈顶的CrimeListActivity实例被暂停并停止。用户点击后退键返回到列表项界面, CrimeActivity随即弹出栈外并被销毁。此时,CrimeListActivity立即重新启动并恢复运行。
CrimeListActivity恢复运行后,操作系统会发出调用onResume()生命周期方法的指令CrimeListActivity接到指令后,它的FragmentManager会调用当前被activity托管的fragment
的onResume()方法。这里的fragment就是指CrimeListFragment。
在CrimeListFragment中,覆盖onResume()方法,触发调用updateUI()方法刷新显示列表
项,如代码清单10-9所示。如果已配置好CrimeAdapter,就调用notifyDataSetChanged()方
法来修改updateUI()方法。
CrimeListFragment.java
@Override
public void onResume() {
super.onResume();
updateUI();
}
private void updateUI() {
CrimeLab crimeLab = CrimeLab.get(getActivity());
List<Crime> crimes = crimeLab.getCrimes();
if (mAdapter == null) {
mAdapter = new CrimeAdapter(crimes);
mCrimeRecyclerView.setAdapter(mAdapter);
} else {
mAdapter.notifyDataSetChanged();
}
}


为什么选择覆盖onResume()方法来刷新列表项显示,而不用onStart()方法呢?当有其他
activity位于我们的activity之前时,我们无法确定自己的activity是否会被停止。如果前面的activity是透明的,我们的activity可能会被暂停。对于此场景下暂停的activity, onStart()方法中的更新代码是不会起作用的。一般来说,要保证fragment视图得到刷新,在onResume()方法内更新代码是最安全的选择。



图解 
 




通过 fragment 获取返回结果  fragment没有setResult 方法 故调用activity中的setResult
如需从已启动的activity获取返回结果,可使用与GeoQuiz应用中类似的实现代码。具体的代
码调整就是:不调用Activity的startActivityForResult(...)方法,转而调用Fragment.startActivityForResult(...)方法;不覆盖Activity.onActivityResult(...)方法,转而覆盖Fragment.onActivityResult(...)方法。




从fragment中返回结果的处理稍有不同。 fragment能够从activity中接收返回结果,但其自身无
法持有返回结果。只有activity拥有返回结果。因此,尽管Fragment有自己的startActivityForResult(...)和onActivityResult(...)方法,但却没有setResult(...)方法。
相反,我们应让托管activity返回结果值。具体代码如下:
public class CrimeFragment extends Fragment {
...
public void returnResult() {
getActivity().setResult(Activity.RESULT_OK, null);
}
}




总结:主要实现 点击recycleview中的一个viewholder启动activity。  即从fragment 启动 activity
由于存在crimeid 所以 fragment 和activity之间存在数据通信。由于通过intent直接通信 存在缺点。所以使用argument来进行通信。在activity上改变数据,需要通过onResume()来updateUi();

原创粉丝点击