转载的老板请注明出处: http://blog.csdn.net/cc_xz/article/details/62892609万分感谢!
前言:
本篇是Fragment的第二篇,将在上一篇的基础上再讨论一些使用比较多的知识点,同样整理成一个小Demo。
在本篇中你将了解到:
1.通过接口使用Fragment和Activity进行通信。
2.理解Fragment和Activity的关系以及分工。
3.如何正确的显示、隐藏Fragment。
Fragment与Activity进行通信:
在上一篇中使用了通过对象之间调用方法来使Fragment和Activity进行通信,但这样做的局限性在于,无法对方法进行统一处理。例如,在使用多个Fragment时,希望在原有Fragment的基础上统一添加一些功能,通过方法是不能高效的完成的。这时通过接口,既可以使Fragment和Activity进行数据传递,也可以统一添加功能。
以下代码将以实现Fragment与Activity数据传递为中心,穿插说明显示/隐藏Fragment、在Activity中集中管理Fragment。
以下为效果图:
准备工作:
即分别创建Activity与Fragment的布局文件,代码分别如下:
以下代码在MainActivity的布局文件中写入:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/TextViewShowFragment"
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_gravity="center_vertical"
android:text="这里是MainActivity"
android:textSize="18sp" />
<FrameLayout
android:id="@+id/FrameLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
以下代码在SetFragment的布局文件中写入:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginRight="10dp"
android:layout_marginLeft="10dp"
android:orientation="vertical">
<Button
android:layout_width="match_parent"
android:layout_height="40dp"
android:id="@+id/ButtonMy"
android:text="我的信息"
android:background="@null"
android:textSize="16sp"
android:textColor="#535252"
android:layout_marginTop="5dp"/>
</LinearLayout>
</LinearLayout>
以下代码在MyFragment的布局文件中写入:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/ButtonReturn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
android:text="返回" />
<TextView
android:id="@+id/TextViewNumber"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textColor="#FF00"
android:textSize="100sp" />
</LinearLayout>
接着来讨论一下本篇代码的思路:
1.首先创建一个父类,用于Fragment继承并使用其中的接口。
2.初始化Activity以及两个Fragment。
3.在SetFragment中完成对接口的应用。
4.通过Handler和定时器使MyFragment有自己的逻辑处理。
5.在MainActivity中分别创建创建、隐藏、显示Fragment的方法。
6.在MainActivity中分别创建监听SetFragment和MyFragment接口的方法。
详细代码:
以下代码新建FragmentInterface类后写入:
public class FragmentInterface extends Fragment {
public interface ReturnNowFragmentName {
void returnNowFragmentName(String fragmentName);
}
public ReturnNowFragmentName mReturnNowFragmentName;
public void ListenerNowFragmentName(ReturnNowFragmentName returnNowFragmentName) {
mReturnNowFragmentName = returnNowFragmentName;
}
public interface OnButtonClickListener {
void onButtonClickListener();
}
public OnButtonClickListener mButtonClickListener;
public void ListenerButtonClick(OnButtonClickListener onButtonClickListener) {
mButtonClickListener = onButtonClickListener;
}
}
1.1.这个类继承自Fragment,这是因为希望我们使用的Fragment处本身的功能外具有其他一些功能。
1.2.但是必须是类的形式,而不是接口,因为接口无法继承自一个类。
2.1.首先定义一个接口,在接口中写入一个方法,这个方法的参数可以给Activity提供当前Fragment的类名。
2.2.这便是使用接口使Activity同Fragment进行数据的传输。
2.3.接着使用接口作为数据类型,创建一个该接口的对象,用于Activity和Fragment调用接口使用。
2.4.最后添加一个方法,该方法是用于在MainActivity中重写接口时需要的方法,在该方法中将接口通过匿名类的方式初始化(使用)。
3.再使用同一的方法重写监听Button按钮的接口,用于在Activity中处理关于Fragment对Fragment进行的操作(其他操作可以在Fragment中自行定义)。
以下代码新建SetFragment类后写入:
public class SetFragment extends FragmentInterface {
private Button mButtonMy;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.set_fragment, null);
mButtonMy = (Button) view.findViewById(R.id.ButtonMy);
mReturnNowFragmentName.returnNowFragmentName(getClass().getName());
return view;
}
}
1.首先通过View绑定Fragment的Layout布局文件以及初始化按钮。
2.1.然后通过父类中的接口对象,调用该接口,并且传入当前类的名称作为参数。
2.2.这时如果运行程序,则会报空指针异常,这是因为此时仅仅定义了接口中的参数,但还没有对接口进行初始化。
以下代码新建SetFragment类后写入:
public class MyFragment extends FragmentInterface {
private TextView mTextViewNumber;
private Button mButtonReturn;
private int number;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.my_fragment, null);
mTextViewNumber = (TextView) view.findViewById(R.id.TextViewNumber);
mButtonReturn = (Button) view.findViewById(R.id.ButtonReturn);
mReturnNowFragmentName.returnNowFragmentName(getClass().getName());
return view;
}
}
以下代码MainActivity中写入:
private void init() {
mTextView = (TextView) findViewById(R.id.TextViewShowFragment);
mSetFragment = new SetFragment();
mMyFragment = new MyFragment();
}
记得在onCreate()中调用init()。
以下代码SetFragment中写入:
@Override
public void onStart() {
super.onStart();
mButtonMy.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mReturnNowFragmentName.returnNowFragmentName(new MyFragment().getClass().getName());
mButtonClickListener.onButtonClickListener();
}
});
}
1.1.写入Button的监听事件,当点击按钮后,就分别调用两个接口。使Activity中得到需要处理重写的接口的通知。
1.2.值得注意的是,返回FragmentName的接口,需要以该Fragment启动的Fragment的名称作为参数。
1.3.这是因为如果不这样做,就会导致显示的类名和显示的Fragment不一致。
1.4.并且写了一个getClassName(),也可以在MyFragment中通过调用该方法来获取类名。(进行了封装)
1.5.这也是Fragment之间通信的另外一种方式。
1.6.你可能会考虑到,在初始化Fragment时已经定义了返回名称的接口,为什么在点击事件后又重写调用。
1.7.这是因为onCreate()或者onCreateView()只会在创建时调用一次,而后续再希望实时根据当前类名改变TextView内容时就必须每次点击Button时再调用一次。
以下代码MyFragment中写入:
@Override
public void onStart() {
super.onStart();
final Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
mTextViewNumber.setText(number++ + "");
}
};
new Timer().schedule(new TimerTask() {
@Override
public void run() {
handler.sendEmptyMessage(100);
}
}, 0, 1000);
mButtonReturn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mReturnNowFragmentName.returnNowFragmentName(new SetFragment().getClassName());
mButtonClickListener.onButtonClickListener();
}
});
}
1.1.通过Timer()定时器以及Handler配合完成定义修改UI组件的功能。
1.2.不能直接使用Timer()来修改UI组件,这是由于Timer()是一个新的线程,而Android只允许在主线程中修改UI。
2.1.首先创建Handler,并重写handleMessage(),该方法的参数是一个Key值。后续会单开一篇来讨论Handler。
2.2.现在只需在Handler直接写入给当前用于显示数值的变量++即可。
3.1.在Timer()中,调用Handler对象,发送一个空的消息,参数即是Key值。
3.2.0和1000分别是,第一次调用延时多少毫秒,后续每多少毫秒执行一次。
以下代码MainActivity中写入:
private void addFragment(Fragment fragment) {
getSupportFragmentManager().beginTransaction().add(R.id.FrameLayout, fragment).commit();
}
private void hideFragment(Fragment fragment) {
getSupportFragmentManager().beginTransaction().hide(fragment).commit();
}
private void showFragment(Fragment fragment) {
getSupportFragmentManager().beginTransaction().show(fragment).commit();
}
1.通过同样的格式分别创建添加、隐藏、显示的方法。并且方法接受一个Fragment类型的对象作为参数,用于处理当前Fragment。
2.值得注意的是,每个FragmentTransaction只能提交一次事务,即每次使用FragmentTransaction都需要将其初始化。(求大神解..这个想不通)
3.在添加方法中,不能使用replace(),因为我们需要Fragment在后台继续工作,而不是直接将其销毁。
以下代码MainActivity中写入:
private void setSetFragment() {
mSetFragment.ListenerNowFragmentName(new FragmentInterface.ReturnNowFragmentName() {
@Override
public void returnNowFragmentName(String fragmentName) {
StringBuffer string = new StringBuffer();
string.append(fragmentName).delete(0,12);
mTextView.setText("现在打开的是:" + string.toString());
}
});
mSetFragment.ListenerButtonClick(new FragmentInterface.OnButtonClickListener() {
@Override
public void onButtonClickListener() {
if (mMyFragment.isHidden() == true) {
hideFragment(mSetFragment);
showFragment(mMyFragment);
} else {
addFragment(mMyFragment);
hideFragment(mSetFragment);
}
}
});
}
private void setMyFragment() {
mMyFragment.ListenerNowFragmentName(new FragmentInterface.ReturnNowFragmentName() {
@Override
public void returnNowFragmentName(String fragmentName) {
StringBuffer string = new StringBuffer();
string.append(fragmentName);
string.delete(0, 12);
mTextView.setText("现在打开的是:" + string.toString());
}
});
mMyFragment.ListenerButtonClick(new FragmentInterface.OnButtonClickListener() {
@Override
public void onButtonClickListener() {
showFragment(mSetFragment);
hideFragment(mMyFragment);
}
});
}
1.1.首先初始化FragmentName接口的方法,值得注意的是,默认情况下getClass().getName()返回的不仅是类名,还有包名。
1.2.所以通过StringBuffer对象来处理一下字符串。(好像跑题了)
1.3.通过append()添加一个字符串,通过delete来删除字符串中的固定位置的内容。0为起始位置,12为结束位置。
2.1.在处理setFragment的按钮点击时,需要先判断MyFragment是否被隐藏,如果被隐藏,则隐藏SetFragment,显示MyFragment。
2.2.如果没有被隐藏,就创建MyFragment,然后隐藏SetFragment。切记添加判断,否则会乱套的…
Fragment以及Activity的分工:
众所周知,Fragment是依赖于Activity的,这样做的初衷是,在一个页面中,如果所有的组件、逻辑全部放在一个Activity中,这会使原本庞大的Activity变的更为臃肿,所以通过Fragment,在界面中,Activity只需要创建、显示、隐藏、管理Fragment就可以了。其他组件的逻辑全部放在对应的Fragment中。
也就是说,Activity用来管理Fragment,Fragment管理其他的组件。
另外,网上很多说的不要对Fragment进行嵌套的问题,这是由于Fragment的生命周期比Activity还要复杂,而且Android会自动对长期不在前台的Activity或Fragment进行销毁,这样,依然存在前台的Fragment的依赖于其他Activity或者Fragment的,但此时它所依赖的Activity或Fragment已经被销毁了。在以上案例中,当开启MyFragment计数功能后,对屏幕进行翻转,就会报出异常,这是因为,MainActivity已经被销毁,此时又重新建立,而MyFragment此时仍在活动状态,但是它依附的Activity已经不再是原来的Activity了。
另外,不能嵌套Fragment并不是指不允许一个Fragment打开另外一个Fragment,可以通过getActivity().getLocalClassName()来查看Fragment所依赖的Activity。
后记:
前面说的保存Fragment的状态,这并不是简单的通过Bundle或SharedPreferences就可以解决的问题。原来本篇中打算写一下如何保存Fragment的当前状态,但是后来发现,如果仅仅单纯的保存MyFragment中的数字也无济于事。是需要在MainActivity中保存SetFragment和MyFragment的对象状态,以及重写的接口的对象状态才可以。但是此番的工作量又不亚于一整篇的文章。所以打算在数据库之前再讨论这部分内容。