开发过程中通常会碰到这些问题:Activity越来越臃肿,界面中会有越来越多的业务代码,请求代码,上层的耦合越来越严重,维护起来会相当麻烦。项目开发时如果能有一个好的结构是至关重要的,比如最近的MVP架构主要就可以把Activity中的业务代码抽取出来,使Activity层的代码充分解耦,但是实现起来也需要耗费大量精力。这里介绍一种简单的方法来抽取Activity层的业务代码,就是利用Fragment来创建可以复用的模块。
为什么用Fragment
Fragment拥有和Activity很相似的生命周期,onCreate() , onDestroy() 还有自己的生命周期 onAttach() ,onCreateView 。Fragment本身如果不去重写onCreateView()的话是不会有界面的,从而可以把它当做一个纯粹的数据源,或者一个接口模块来进行复用,同时我们还可以利用FragmentManager来很方便的进行通信。
示例
作为dota爱好者这里简单写一个英雄选择的模块
先来实现数据模块 DataFragment,说道英雄选择肯定要有文字和图片
public class DataFragment extends Fragment {
public static final String TAG = "data";
public class DataItem{
private String title;
private int resId;
public DataItem(String title, int resId) {
this.title = title;
this.resId = resId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getResId() {
return resId;
}
public void setResId(int resId) {
this.resId = resId;
}
}
public static DataFragment newInstance() {
DataFragment fragment = new DataFragment();
return fragment;
}
public DataFragment() {
// Required empty public constructor
}
private ArrayList<DataItem> dataList;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
dataList = new ArrayList<>();
dataList.add(new DataItem("敌法师",R.mipmap.df));
dataList.add(new DataItem("冰蛙亲儿子",R.mipmap.fw));
dataList.add(new DataItem("和谐脸",R.mipmap.hexielian));
}
public List<DataItem> getData(){
return dataList;
}
DataFragment 声明了一个Dataitem内部类用来保存文字,和图片资源id,并在onCreate()中进行赋值,最后提供了一个获取数据的方法。很好理解
选择英雄列表
选择英雄列表选择用一个 DialogFragment来做,原因纯粹是拿来熟悉一下
public class ChooseFragment extends DialogFragment implements AdapterView.OnItemClickListener{
//回调接口
private ItemClick mListener;
private ArrayAdapter mAdapter;
//工厂方法给出ChooseFragment的实例
public static ChooseFragment newInstance() {
ChooseFragment fragment = new ChooseFragment();
return fragment;
}
public ChooseFragment() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//使用最简单的ArrayAdapter来实现列表
mAdapter = new ArrayAdapter<DataFragment.DataItem>(getActivity(),android.R.layout.simple_list_item_1){
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null){
convertView = LayoutInflater.from(getActivity()).inflate(android.R.layout.simple_list_item_1,parent,false);
}
//列表的TextView进行赋值
DataFragment.DataItem item = getItem(position);
TextView text = (TextView)convertView.findViewById(android.R.id.text1);
text.setText(item.getTitle());
return convertView;
}
};
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
ListView list = new ListView(getActivity());
list.setAdapter(mAdapter);
list.setOnItemClickListener(this);
return list;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Dialog dialog = super.onCreateDialog(savedInstanceState);
//设置了一个选择框的标题
dialog.setTitle("请选择你的英雄");
return dialog;
}
@Override
public void onResume() {
super.onResume();
//获得最新的数据
DataFragment df = (DataFragment)getFragmentManager().findFragmentByTag(DataFragment.TAG);
if(df != null){
//更新选择数据
mAdapter.clear();
for (DataFragment.DataItem item : df.getData()) {
mAdapter.add(item);
}
mAdapter.notifyDataSetChanged();
}
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
//将onItemClick事件 回调出去
mListener.ItemClick((DataFragment.DataItem) mAdapter.getItem(position));
//如果显示对话框将对话框消失
if(getShowsDialog()){
dismiss();
}
}
/**
*onAttach中连接监听接口 确保Activity支持该接口
*/
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (ItemClick) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnFragmentInteractionListener");
}
}
/**
* onDetach中注销接口
*/
@Override
public void onDetach() {
super.onDetach();
mListener = null;
}
//fragment内部自己声明的回调接口
public interface ItemClick {
// TODO: Update argument type and name
public void ItemClick(DataFragment.DataItem item);
}
ChooseFragment继承DialogFragment 整个界面是一个文字列表,文本用DataItem中的title来进行赋值,使用回调的方式将 listview 的OnItemClickListener事件回调出去。需要注意一点有用的细节就是这里展示了一种Fragment之间通信的方法,onResume()中通过TAG来关联DataFragment,然后通过直接调用DataFragment中的 getData() 方法来获取数据
英雄展示
选择完成后就是展示界面,用一个图片来展示就可以了
public class DetailFragment extends Fragment {
private ImageView imageView;
public DetailFragment() {
// Required empty public constructor
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
imageView = new ImageView(getActivity());
return imageView;
}
//画图
public void paint(int resId){
imageView.setImageResource(resId);
}
非常简单,不多说明了。现在数据模块,列表模块,显示模块,都已经写好。剩下的工作就是把3者整合到一起。
Activity中整合
先来看Activity的布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:onClick="onShowClick"
android:text="进入英雄选择"
android:layout_width="match_parent"
android:layout_height="wrap_content"></Button>
<fragment
android:id="@+id/fragment_detail"
android:name="com.haibuzou.common.DetailFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
button按钮用来调出ChooseFragment 选择列表,已经设置好了点击事件,DetailFragment 用来显示英雄图片。
public class MainActivity extends AppCompatActivity implements ChooseFragment.ItemClick{
private DetailFragment mDetail;
private ChooseFragment mChoose;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DataFragment df = (DataFragment)getSupportFragmentManager().findFragmentByTag(DataFragment.TAG);
if(df == null){
df = DataFragment.newInstance();
//希望保存这个实例,在配置变化时 比如横竖屏时获得同一个实例
df.setRetainInstance(true);
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
//通过Tag而不是id来关联fragment
ft.add(df,DataFragment.TAG);
ft.commit();
}
//通过xml中的id 获得详情fragment
mDetail = (DetailFragment)getSupportFragmentManager().findFragmentById(R.id.fragment_detail);
//获取选择框的Fragment实例
mChoose = ChooseFragment.newInstance();
}
public void onShowClick(View v){
mChoose.show(getSupportFragmentManager(),null);
}
@Override
public void ItemClick(DataFragment.DataItem item) {
mDetail.paint(item.getResId());
}
}
Activity中首先去关联DataFragment,这里有一个小细节setRetainInstance(true) 这个设置是为了告诉FragmentManager即使Ui发生变化比如:横竖屏,也要保存这个DataFragment,保证至始至终只有一个数据模型。接着通过xml中的id R.id.fragment_detail 关联DetailFragment,最后用代码去创建列表 ChooseFragment,并且实现了ChooseFragment中的回调,执行ListView的点击操作。
就这样Fragment把代码整个模块化了。
- DataFragment数据模块 只负责提供数据
- ChooseFragment 列表模块 只负责显示列表数据
- Activity 作为最高指挥官负责整个界面的点击事件,跳转逻辑
3者各司其职,Activity也不再臃肿,并且这些模块都可以进行复用。是不是跃跃欲试了呢