本文主要是针对fragment的 fragment的重影问题和按返回键无法像activity那样返回上个fragment, 对这两个坑进行阐述。
Fragment 的优点:
1.它可以数据保存,特别在网页加载并且需要反复使用的页面上。
2.UI组件切换的灵活性及流畅性 ,在配合使用 table时更能体现。
3.可以非常方便与主activity进行数据交换。
Fragment 的缺点:
1.各个fragment的管理非自动化,需要人为手动管理添加和移除。
2.fragment的重影问题。
3.按返回键无法像activity那样返回上个fragment。
这两个问题点的解决需要理解fragment的生命周期,如下
如图一,是Fragment的生命周期;图二,是Activity与Fragment生命周期的对比图;
图一 图二
一、fragment的重影问题的探究。
fragment的重影问题产生的原因是:你的activity未移除fragment,而你只是销毁你之前添加的layout。
我们需要明确一点:fragment!=layout。他们两者是独立的,只有在onCreateView中把相应的layout添加到fragment,他们才产生了关联,此时fragment显示出来。在onDestoryView 后他们又是独立的个体,此时在界面上看去你的fragment已经移除了,但是实际上它还是存在的只是不显示画面。fragment是容器,跟Activity一样。它可以添加许多layout。
那问题来了,fragment什么时候才是真正的从activity上移除呢?
从fragment的生命周期上看是onDetach,在我们实际的使用中FragmentManager.popBackStack()、FragmentTransaction.remove(Fragment fragment) 、FragmentTransaction.replace()。这三个方法能把fragment从Aciivity上移除。
在实际开发中避免重影问题的方法有:
1.在onCreateView中判断view是否已经存在如下。
public abstract class BaseFragment extends Fragment {
public View view;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// TODO Auto-generated method stub
if (view==null) {
view=inflater.inflate(R.layout.auto_root, container, false);
}
return view;
}
2.AndroidManifest.xml
添加 <android:configChanges="locale|touchscreen|keyboardHidden|orientation|screenSize|screenLayout|layoutDirection">
3.在主activity oncreate中如下设置:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mFgCotrol=new FragmentCotrol(this); //mFgCotrol是一个fragment的管理类。如果saved...存在就把fragment全部移除并且再重新加载一次。这样就能避免重影问题
if (savedInstanceState!=null) {
mFgCotrol.popBackAllStacks();
}
* @author Bill
* 2018-3-21
* @description:
* fragment 管理类
*/
public class FragmentCotrol {
private MainActivity mMainActivity;
private final String TAG="FragmentCotrol";
private FragmentManager fm;
private List<ResultData> allFragment=new ArrayList<ResultData>();
public FragmentCotrol(MainActivity activity){
mMainActivity=activity;
init();
}
private void init(){
fm=mMainActivity.getFragmentManager();
}
/**
*2018-3-24
* @description : 移除所有的fragment
*/
public void popBackAllStacks(){
if (fm.getBackStackEntryCount()>0) {
for (int i = fm.getBackStackEntryCount(); i >= 1; i--) {
BackStackEntry backStack= fm.getBackStackEntryAt(i-1);
String backStackName=backStack.getName();
removeFragmentFromLis(backStackName);
fm.popBackStackImmediate();
}
}
}
/**
*2018-3-24
* @description :移除单个fragment
*/
public void popBackStacks(){
int count=fm.getBackStackEntryCount();
if (count<=0) {
return ;
}
BackStackEntry backStack= fm.getBackStackEntryAt(count-1);
String backStackName=backStack.getName();
removeFragmentFromLis(backStackName);
fm.popBackStackImmediate();
}
private void removeFragmentFromLis(String backStackName){
int size=allFragment.size();
if (size<=0) {
return;
}
int j = 0;
for (int i = 0; i < size; i++) {
ResultData rd=allFragment.get(i);
String name=rd.getTag();
if (name.equals(backStackName)) {
j=i;
}
}
allFragment.remove(j);
TLog.i(TAG, "removeFragmentFromLis -- backStackName="+backStackName+" size="+allFragment.size());
}
public void replaceFragment(ResultData rd){
BaseFragment bf=rd.getFragment();
String tag=rd.getTag();
int id=rd.getLayoutId();
allFragment.add(rd);
FragmentTransaction ft=fm.beginTransaction();
ft.replace(id, bf, tag).addToBackStack(tag).commit();
}
/**
*2018-3-24
* @description :增加到fragmentmanager上,并且也增加到allfragment上。
* 新增的fragment会隐藏已经添加到fragmentManager上的frament。
*/
public void addFragmentToFragmentManager(ResultData rd){
BaseFragment bf=rd.getFragment();
String tag=rd.getTag();
int id=rd.getLayoutId();
TLog.i(TAG, "BaseFragmentName:"+bf.getClass().getSimpleName()+"Fragment isadd: "+bf.isAdded()+" isHidden: "+bf.isHidden()+
" isVisible "+bf.isVisible());
if (!bf.isAdded()) {
allFragment.add(rd);
addFragmentToFragmentManager(bf, tag, id);
}
}
private void addFragmentToFragmentManager(BaseFragment bf,String tag,int id){
FragmentTransaction ft=fm.beginTransaction();
if (!bf.isAdded()) {
ft.add(id, bf, tag).addToBackStack(tag);
int size=allFragment.size();
for (int i = 0; i < size; i++) {
ResultData rd=allFragment.get(i);
String tagName=rd.getTag();
if (tagName.equals(tag)) {
continue;
}
ft.hide(fm.findFragmentByTag(tagName));
}
ft.commit();
}
}
public FragmentManager getFregmentmanager(){
return fm;
}
public void showFragment(String tag) {
// TODO Auto-generated method stub
FragmentTransaction ft=fm.beginTransaction();
int size=allFragment.size();
if (size<=1) {
return;
}
for (int i = 0; i < size; i++) {
ResultData rd=allFragment.get(i);
String tagName=rd.getTag();
if (tagName.equals(tag)) {
continue;
}
//TLog.i(TAG, "showFragment--tagName:"+tagName);
ft.hide(fm.findFragmentByTag(tagName));
}
ft.show(fm.findFragmentByTag(tag));
ft.commit();
}
public void hideFragment(String tag) {
// TODO Auto-generated method stub
FragmentTransaction ft=fm.beginTransaction();
ft.hide(fm.findFragmentByTag(tag)).commit();
}
public void printy(){
int size=allFragment.size();
for (int i = 0; i < size; i++) {
ResultData rd=allFragment.get(i);
BaseFragment bf=rd.getFragment();
TLog.i(TAG, "printy-BaseFragmentName:"+bf.getClass().getSimpleName()+"Fragment isadd: "+bf.isAdded()+" isHidden: "+bf.isHidden()+
" isVisible "+bf.isVisible());
}
int count=fm.getBackStackEntryCount();
for (int i = count-1; i >=0; i--) {
TLog.i(TAG, "printy-BackStackEntryName:"+fm.getBackStackEntryAt(i).getName());
}
}
public FragmentManager getFragmentManager(){
if (fm==null) {
mMainActivity.getFragmentManager();
}
return fm;
}
}
以上几步,重影问题基本能解决掉了。
二、按返回键自动销毁fragment,实现跟activity一样的功能。
原理是:按返回键,会调用Activity的onBackPressed() 方法。
那如何让onBackPressed() 方法与每个fragment联系起来呢?
答案是:使用接口回调函数。
实现步骤:
1.写个接口类
package com.cn.along.interfaces;
import com.cn.along.fragment.BaseFragment;
public interface HandledFragment {
void setSelectedFragment(BaseFragment selectedFragment);
}
2.Activity实现此接口
public class MainActivity extends Activity implements HandledFragment {
private final String TAG="MainActivity";
private FragmentCotrol mFgCotrol;
private BaseFragment bf;//当前在活动fragment。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mFgCotrol=new FragmentCotrol(this);
if (savedInstanceState!=null) {
mFgCotrol.popBackAllStacks();
}
init();
}
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
mFgCotrol.popBackAllStacks();
}
@Override
public void onBackPressed() {
// TODO Auto-generated method stub
//判断fragment自己是否处理onBackPressed事件。
if (bf!=null&&bf.onBackPressed()){
return;
}
//如果只剩余一个fragment连续按两下返回键销毁activity,并且返回桌面。
if (mFgCotrol.getFragmentManager().getBackStackEntryCount()==1) {
oldTime=newTime;
long currentTime=System.currentTimeMillis();
newTime=currentTime;
long interval=newTime-oldTime;
if (interval<=FINISHTIME) {
//可以返回桌面。
super.onBackPressed();
finish();
}else{
String show=getResources().getString(R.string.finish_activity);
Toast.makeText(this, show, Toast.LENGTH_SHORT).show();
}
}else{
mFgCotrol.popBackStacks();
}
}
//接口的实现
@Override
public void setSelectedFragment(BaseFragment selectedFragment) {
// TODO Auto-generated method stub
bf=selectedFragment;
}
3.fragment的实现
public abstract class BaseFragment extends Fragment {
public MainActivity mMainActivity;
public View view;
private final String TAG="BaseFragment";
public HandledFragment mHandledFragment;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// 获取mHandledFragment实例
mMainActivity=(MainActivity) getActivity();
if(!(mMainActivity instanceof HandledFragment)){
throw new ClassCastException("Hosting Activity must implement HandledFragment");
}else{
mHandledFragment = (HandledFragment)mMainActivity;
}
return view;
}
public abstract boolean onBackPressed();//子类复写此方法。
@Override
public void onStart() {
// TODO Auto-generated method stub
super.onStart();
mHandledFragment.setSelectedFragment(this);// 获取把此fragment传递到activity。
}
经过以上三个步骤就能实现按返回键自动销毁fragment,实现activity一样的功能。
至此重影问题和返回功能的实现已经可以解决了。