1. 为什么用Fragment
Android中UI管理一般都靠activity,虽然activity视图可以在运行时切换,但是控制视图的代码必须在activity中实现,因此每个activity还是得和特定的用户屏幕绑定在一起,即每个屏幕只能显示一个activity,因此当我们想几个不同界面组装显示在屏幕上时,activity就无法满足需求了,这时,我们就需要使用Fragment,采用Fragment来进行应用的UI管理可以避免activity的界面显示上的限制。
2. 什么是Fragment
Fragment是一种控制器对象,activity可以委派他完成一些任务,而通常这些任务就是管理用户界面,受管的用户界面可以使整个屏幕或者屏幕的一部分,Fragment也有自己产生于布局文件的视图,视图中包含一些用户可以交互的UI元素,所以可以把Fragment想象成activity中一个模块化的部分,它拥有自己的生命周期,接收自己的输入事件,可以在acvitity运行过程中添加或者移除(有点像"子activity",你可以在不同的activities里面重复使用)。activity视图中需要提供可供Fragment视图插入的位置,这样的位置也可以有多个来提供多个Fragment显示。因此可以实现联合使用Fragment和activity来组装或重新组装用户界面,而此时activity的视图是保持不变的。
3. 版本兼容
由于Fragment是后来版本中添加(API版本11),因此当应用在Android1.6这样的低版本上时应该使用Support libary中的Fragment来达到兼容的目的。 但是如果的你的APP的最低API版本是11或以上,你不必使用Support Library,可以直接使用API中的Fragment。此外因为Fragment是由activity来管理的,所以也需要activity的支持,但是在Honeycomb之前的版本的activity不支持Fragment管理,因此可以让activity继承自Support Library中的FragmentActivity
4. Fragment开发
创建一个Fragment和创建一个activity差不多,首先我们得继承自Fragment类,然后在关键的生命周期方法中插入我们的APP逻辑,但是有一点不同的是我们还需要重写OnCreateView()回调方法来定义布局,实际上这也是让Fragment运行起来唯一必须的方法。下面用一个例子来详细描述下Fragment开发过程。
5. App说明
首先我们开发的项目是一个记录债务的app,每次出去吃饭一般都是某一个人来付账,然后其他人事后再还,一个记录债务的app可以及时记录下欠谁多少钱,已方便事后忘了还钱。当然整体这个项目需要的知识不仅仅只有Fragment,后面将会慢慢开发,这篇文章只先开发简单界面。
6. 开发步骤
首先我们需要建立model类,一个债务包括债主姓名,欠款数量,日期,是否归还,债务内容等等。下面是个简单的model类:
public class Debt {
private UUID _id;
private String _name;
private int _num;
private String _detail;
private Date _date;
private boolean _payed;
public Debt(){
_id = UUID.randomUUID();
setDate(new Date());
setIsPayed(false);
}
public UUID getId() {
return _id;
}
public String getName() {
return _name;
}
public void setName(String _name) {
this._name = _name;
}
public int getNum() {
return _num;
}
public void setNum(int _num) {
this._num = _num;
}
public String getDetail() {
return _detail;
}
public void setDetail(String _detail) {
this._detail = _detail;
}
public Date getDate() {
return _date;
}
public void setDate(Date _date) {
this._date = _date;
}
public boolean isPayed() {
return _payed;
}
public void setIsPayed(boolean _payed) {
this._payed = _payed;
}
}
然后,我们需要创建一个Activity类,创建activity类之前,我们首先需要给它写界面配置文件,因为现在我们用fragment来做UI控件的容器,所以activity界面只是充当一个fragment容器的作用,所以我们创建如下的activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:id="@+id/fragmentContainer"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.baidu.android.activity.DebtActivity">
</FrameLayout>
使用Framelayout可以将整个界面作为备用区域,各个fragment相互覆盖的显示。
然后我们需要创建activity类:DebtActivity。一般activity类名都以activity作为后缀来加以区分,新建activity类,当前我们只需要override onCreate方法即可,如下:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
这样我们就创建了一个空的activity,它使用activity_main.xml作为布局文件。
然后,我们需要来创建我们的主要界面显示容器fragment,创建fragment我们可以采用和activity类似的方法,首先继承自Fragment基类,然后override相关的生命周期方法即可,正如前面提到的,我们主要需要override的方法是onCreateView()方法,通过这个方法fragment来定义自己的布局,下面同样的,我们需要为fragment写相应的布局xml文件:fragment_debt.xml,如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="18dp"
android:layout_marginRight="18dp" tools:context="com.baidu.android.activity.DebtActivity">
<LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="10dp"
android:layout_marginTop="10dp">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:text="@string/debt_name_label"/>
<EditText android:id="@+id/debt_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/debt_name_hint"/>
</LinearLayout>
<LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="10dp">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:text="@string/debt_num_label"/>
<EditText android:id="@+id/debt_num"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/debt_num_hint"/>
</LinearLayout>
<LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:text="@string/debt_date_label"/>
<Button android:id="@+id/debt_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
<LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp">
<CheckBox android:id="@+id/debt_payed"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/debt_payed_label"/>
</LinearLayout>
</LinearLayout>
由于控件较多,这个xml文件较长,可以看到大部分的布局定义都差不多,需要注意的是@+id处,加号表示新建资源id,如果已经有了相应的id则不用加号,直接引用即可。
然后我们创建我们的fragment类:
public class DebtFragment extends Fragment {
private Debt _debt;
private EditText _nameField;
private EditText _numField;
private Button _dateField;
private EditText _detailField;
private CheckBox _payedField;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
_debt = new Debt();
}
...
}
其中先声明了相关的控件,然后在onCreate方法中创建debt实例,因为现在还没有数据存储相关的,所以直接在fragment类中新建实例。
然后我们需要override onCreateView方法,如下:
public View onCreateView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_debt,parent,false);
_nameField = (EditText)v.findViewById(R.id.debt_name);
_nameField.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
_debt.setName(_nameField.getText().toString());
}
@Override
public void afterTextChanged(Editable s) {
}
});
_numField = (EditText)v.findViewById(R.id.debt_num);
_numField.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if(TextUtils.isDigitsOnly(s)){
_debt.setNum(Integer.parseInt(_numField.getText().toString()));
}
}
@Override
public void afterTextChanged(Editable s) {
}
});
_dateField = (Button)v.findViewById(R.id.debt_date);
_dateField.setText(new SimpleDateFormat("yyyy-MM-dd hh-mm-ss").format(_debt.getDate()));
_dateField.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Date _date = new Date();
_debt.setDate(_date);
_dateField.setText(new SimpleDateFormat("yyyy-MM-dd hh-mm-ss").format(_debt.getDate()));
}
});
_payedField = (CheckBox)v.findViewById(R.id.debt_payed);
_payedField.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
_debt.setPayed(isChecked);
}
});
return v;
}
可以看到,我们首先实例化每个控件,然后给某些的控件添加相应的listener,如金额的EditText控件,通过addTextChangedListener方法设置当数据发生变化时将实例的金额值变更。
现在我们已经有自己的fragment类了,下面要做的就是在activity中使用fragment类,下面我们要修改我们的DebtActivity类的onCreate方法,修改后如下:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
FragmentManager fm = getSupportFragmentManager();
Fragment _fragment = fm.findFragmentById(R.id.fragmentContainer);
if(_fragment==null){
_fragment = new DebtFragment();
fm.beginTransaction()
.add(R.id.fragmentContainer,_fragment)
.commit();
}
}
可以看到我们声明了一个FragmentManager类。FragmentManager类主要负责对Fragment的管理,并将它们的视图添加到activity的视图层级结构中去。FragmentManager类主要管理fragment队列已经fragment事务的回退栈,而现在我们只需要关系对fragment队列的管理,如上,我们新建了一个fragmentmanager实例后,去获取fragments实例,如果container中没有相应的fragment实例则创建一个实例。
然后我们调用beginTransaction()方法返回一个FragmentTransaction实例,FragmentTransaction使用fluent interface接口方法,所以以上的操作可以看做是创建一个fragment事务,然后添加一个fragment,然后提交它。
此时我们已经创建完毕,运行程序即可得到如下:
至此,开发的准备工作已经做好,软件还有bug,比如没有override onSaveInstanceState方法,所以每次横屏后都会出问题,后续再做改进,下次开发将主要实现列表显示债务并点击查看功能并以此来熟悉adapter和listview。