欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

深入浅析Android Fragment(下篇)

程序员文章站 2023-11-20 16:28:34
在上篇文章给大家介绍深入浅析android fragment(上篇),包括一些基本的用法和各种api,如果还想深入学习请继续关注本篇文章。 本篇将介绍上篇提到的:如何管理...

在上篇文章给大家介绍深入浅析android fragment(上篇),包括一些基本的用法和各种api,如果还想深入学习请继续关注本篇文章。

本篇将介绍上篇提到的:如何管理fragment回退栈,fragment如何与activity交互,fragment与activity交互的最佳实践,没有视图的fragment的用处,使用fragment创建对话框,如何与actionbar,menuitem集成等~~

1、管理fragment回退栈

类似与android系统为activity维护一个任务栈,我们也可以通过activity维护一个回退栈来保存每次fragment事务发生的变化。如果你将fragment任务添加到回退栈,当用户点击后退按钮时,将看到上一次的保存的fragment。一旦fragment完全从后退栈中弹出,用户再次点击后退键,则退出当前activity。

看这样一个效果图:

深入浅析Android Fragment(下篇)

点击第一个按钮,切换到第二个界面,点击第二个按钮,切换到第三个界面,然后点击back键依次回退。这像不像初学android时的activity跳转,当然了,这里肯定不是,不然我就跪了。这里是fragment实现的,用户点击back,实际是fragment回退栈不断的弹栈。

如何添加一个fragment事务到回退栈:

fragmenttransaction.addtobackstack(string)

下面讲解代码:很明显一共是3个fragment和一个activity.

先看activity的布局文件:

<relativelayout 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" > 
  <framelayout 
    android:id="@+id/id_content" 
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent" > 
  </framelayout> 
</relativelayout> 

不同的fragment就在这个framelayout中显示。

mainactivity.java

package com.zhy.zhy_fragments; 
import android.app.activity; 
import android.app.fragmentmanager; 
import android.app.fragmenttransaction; 
import android.os.bundle; 
import android.view.window; 
public class mainactivity extends activity 
{ 
  @override 
  protected void oncreate(bundle savedinstancestate) 
  { 
    super.oncreate(savedinstancestate); 
    requestwindowfeature(window.feature_no_title); 
    setcontentview(r.layout.activity_main); 
    fragmentmanager fm = getfragmentmanager(); 
    fragmenttransaction tx = fm.begintransaction(); 
    tx.add(r.id.id_content, new fragmentone(),"one"); 
    tx.commit(); 
  } 
}

很简单,直接将fragmentone添加到布局文件中的framelayout中,注意这里并没有调用fragmenttransaction.addtobackstack(string),因为我不喜欢在当前显示时,点击back键出现白板。而是正确的相应back键,即退出我们的activity.

下面是fragmentone

package com.zhy.zhy_fragments; 
import android.app.fragment; 
import android.app.fragmentmanager; 
import android.app.fragmenttransaction; 
import android.os.bundle; 
import android.view.layoutinflater; 
import android.view.view; 
import android.view.view.onclicklistener; 
import android.view.viewgroup; 
import android.widget.button; 
public class fragmentone extends fragment implements onclicklistener 
{ 
  private button mbtn; 
  @override 
  public view oncreateview(layoutinflater inflater, viewgroup container, 
      bundle savedinstancestate) 
  { 
    view view = inflater.inflate(r.layout.fragment_one, container, false); 
    mbtn = (button) view.findviewbyid(r.id.id_fragment_one_btn); 
    mbtn.setonclicklistener(this); 
    return view; 
  } 
  @override 
  public void onclick(view v) 
  { 
    fragmenttwo ftwo = new fragmenttwo(); 
    fragmentmanager fm = getfragmentmanager(); 
    fragmenttransaction tx = fm.begintransaction(); 
    tx.replace(r.id.id_content, ftwo, "two"); 
    tx.addtobackstack(null); 
    tx.commit(); 
  } 
}

我们在点击fragmentone中的按钮时,使用了replace方法,如果你看了前一篇博客,一定记得replace是remove和add的合体,并且如果不添加事务到回退栈,前一个fragment实例会被销毁。这里很明显,我们调用tx.addtobackstack(null);将当前的事务添加到了回退栈,所以fragmentone实例不会被销毁,但是视图层次依然会被销毁,即会调用ondestoryview和oncreateview,证据就是:仔细看上面的效果图,我们在跳转前在文本框输入的内容,在用户back得到第一个界面的时候不见了。

接下来fragmenttwo

package com.zhy.zhy_fragments; 
import android.app.fragment; 
import android.app.fragmentmanager; 
import android.app.fragmenttransaction; 
import android.os.bundle; 
import android.view.layoutinflater; 
import android.view.view; 
import android.view.view.onclicklistener; 
import android.view.viewgroup; 
import android.widget.button; 
public class fragmenttwo extends fragment implements onclicklistener 
{ 
  private button mbtn ; 
  @override 
  public view oncreateview(layoutinflater inflater, viewgroup container, 
      bundle savedinstancestate) 
  { 
    view view = inflater.inflate(r.layout.fragment_two, container, false); 
    mbtn = (button) view.findviewbyid(r.id.id_fragment_two_btn); 
    mbtn.setonclicklistener(this); 
    return view ;  
  } 
  @override 
  public void onclick(view v) 
  { 
    fragmentthree fthree = new fragmentthree(); 
    fragmentmanager fm = getfragmentmanager(); 
    fragmenttransaction tx = fm.begintransaction(); 
    tx.hide(this); 
    tx.add(r.id.id_content , fthree, "three"); 
//   tx.replace(r.id.id_content, fthree, "three"); 
    tx.addtobackstack(null); 
    tx.commit(); 
  } 
} 

这里点击时,我们没有使用replace,而是先隐藏了当前的fragment,然后添加了fragmentthree的实例,最后将事务添加到回退栈。这样做的目的是为了给大家提供一种方案:如果不希望视图重绘该怎么做,请再次仔细看效果图,我们在fragmenttwo的edittext填写的内容,用户back回来时,数据还在~~~

最后fragmentthree就是简单的toast了:

package com.zhy.zhy_fragments; 
import android.app.fragment; 
import android.os.bundle; 
import android.view.layoutinflater; 
import android.view.view; 
import android.view.view.onclicklistener; 
import android.view.viewgroup; 
import android.widget.button; 
import android.widget.toast; 
public class fragmentthree extends fragment implements onclicklistener 
{ 
  private button mbtn; 
  @override 
  public view oncreateview(layoutinflater inflater, viewgroup container, 
      bundle savedinstancestate) 
  { 
    view view = inflater.inflate(r.layout.fragment_three, container, false); 
    mbtn = (button) view.findviewbyid(r.id.id_fragment_three_btn); 
    mbtn.setonclicklistener(this); 
    return view; 
  } 
  @override 
  public void onclick(view v) 
  { 
    toast.maketext(getactivity(), " i am a btn in fragment three", 
        toast.length_short).show(); 
  } 
} 

好了,经过上面的介绍,应该已经知道fragment回退栈是怎么一回事了,以及hide,replace等各自的应用的场景。
这里极其注意一点:上面的整体代码不具有任何参考价值,纯粹为了显示回退栈,在后面讲解了fragment与activity通信以后,会重构上面的代码!

2、fragment与activity通信

因为所有的fragment都是依附于activity的,所以通信起来并不复杂,大概归纳为:

a、如果你activity中包含自己管理的fragment的引用,可以通过引用直接访问所有的fragment的public方法

b、如果activity中未保存任何fragment的引用,那么没关系,每个fragment都有一个唯一的tag或者id,可以通过getfragmentmanager.findfragmentbytag()或者findfragmentbyid()获得任何fragment实例,然后进行操作。

c、在fragment中可以通过getactivity得到当前绑定的activity的实例,然后进行操作。

注:如果在fragment中需要context,可以通过调用getactivity(),如果该context需要在activity被销毁后还存在,则使用getactivity().getapplicationcontext()。

3、fragment与activity通信的最佳实践

因为要考虑fragment的重复使用,所以必须降低fragment与activity的耦合,而且fragment更不应该直接操作别的fragment,毕竟fragment操作应该由它的管理者activity来决定。

下面我通过两种方式的代码,分别重构,fragmentone和fragmenttwo的点击事件,以及activity对点击事件的响应:

首先看fragmentone

package com.zhy.zhy_fragments; 
import android.app.fragment; 
import android.os.bundle; 
import android.view.layoutinflater; 
import android.view.view; 
import android.view.view.onclicklistener; 
import android.view.viewgroup; 
import android.widget.button; 
public class fragmentone extends fragment implements onclicklistener 
{ 
  private button mbtn; 
  /** 
   * 设置按钮点击的回调 
   * @author zhy 
   * 
   */ 
  public interface fonebtnclicklistener 
  { 
    void onfonebtnclick(); 
  } 
  @override 
  public view oncreateview(layoutinflater inflater, viewgroup container, 
      bundle savedinstancestate) 
  { 
    view view = inflater.inflate(r.layout.fragment_one, container, false); 
    mbtn = (button) view.findviewbyid(r.id.id_fragment_one_btn); 
    mbtn.setonclicklistener(this); 
    return view; 
  } 
  /** 
   * 交给宿主activity处理,如果它希望处理 
   */ 
  @override 
  public void onclick(view v) 
  { 
    if (getactivity() instanceof fonebtnclicklistener) 
    { 
      ((fonebtnclicklistener) getactivity()).onfonebtnclick(); 
    } 
  } 
} 

可以看到现在的fragmentone不和任何activity耦合,任何activity都可以使用;并且我们声明了一个接口,来回调其点击事件,想要管理其点击事件的activity实现此接口就即可。可以看到我们在onclick中首先判断了当前绑定的activity是否实现了该接口,如果实现了则调用。

再看fragmenttwo

package com.zhy.zhy_fragments; 
import android.app.fragment; 
import android.os.bundle; 
import android.view.layoutinflater; 
import android.view.view; 
import android.view.view.onclicklistener; 
import android.view.viewgroup; 
import android.widget.button; 
public class fragmenttwo extends fragment implements onclicklistener 
{ 
  private button mbtn ; 
  private ftwobtnclicklistener ftwobtnclicklistener ; 
  public interface ftwobtnclicklistener 
  { 
    void onftwobtnclick(); 
  } 
  //设置回调接口 
  public void setftwobtnclicklistener(ftwobtnclicklistener ftwobtnclicklistener) 
  { 
    this.ftwobtnclicklistener = ftwobtnclicklistener; 
  } 
  @override 
  public view oncreateview(layoutinflater inflater, viewgroup container, 
      bundle savedinstancestate) 
  { 
    view view = inflater.inflate(r.layout.fragment_two, container, false); 
    mbtn = (button) view.findviewbyid(r.id.id_fragment_two_btn); 
    mbtn.setonclicklistener(this); 
    return view ;  
  } 
  @override 
  public void onclick(view v) 
  { 
    if(ftwobtnclicklistener != null) 
    { 
      ftwobtnclicklistener.onftwobtnclick(); 
    } 
  } 
}

与fragmentone极其类似,但是我们提供了setlistener这样的方法,意味着activity不仅需要实现该接口,还必须显示调用mftwo.setftwobtnclicklistener(this)。

最后看activity :

package com.zhy.zhy_fragments; 
import android.app.activity; 
import android.app.fragmentmanager; 
import android.app.fragmenttransaction; 
import android.os.bundle; 
import android.view.window; 
import com.zhy.zhy_fragments.fragmentone.fonebtnclicklistener; 
import com.zhy.zhy_fragments.fragmenttwo.ftwobtnclicklistener; 
public class mainactivity extends activity implements fonebtnclicklistener, 
    ftwobtnclicklistener 
{ 
  private fragmentone mfone; 
  private fragmenttwo mftwo; 
  private fragmentthree mfthree; 
  @override 
  protected void oncreate(bundle savedinstancestate) 
  { 
    super.oncreate(savedinstancestate); 
    requestwindowfeature(window.feature_no_title); 
    setcontentview(r.layout.activity_main); 
    mfone = new fragmentone(); 
    fragmentmanager fm = getfragmentmanager(); 
    fragmenttransaction tx = fm.begintransaction(); 
    tx.add(r.id.id_content, mfone, "one"); 
    tx.commit(); 
  } 
  /** 
   * fragmentone 按钮点击时的回调 
   */ 
  @override 
  public void onfonebtnclick() 
  { 
    if (mftwo == null) 
    { 
      mftwo = new fragmenttwo(); 
      mftwo.setftwobtnclicklistener(this); 
    } 
    fragmentmanager fm = getfragmentmanager(); 
    fragmenttransaction tx = fm.begintransaction(); 
    tx.replace(r.id.id_content, mftwo, "two"); 
    tx.addtobackstack(null); 
    tx.commit(); 
  } 
  /** 
   * fragmenttwo 按钮点击时的回调 
   */ 
  @override 
  public void onftwobtnclick() 
  { 
    if (mfthree == null) 
    { 
      mfthree = new fragmentthree(); 
    } 
    fragmentmanager fm = getfragmentmanager(); 
    fragmenttransaction tx = fm.begintransaction(); 
    tx.hide(mftwo); 
    tx.add(r.id.id_content, mfthree, "three"); 
    // tx.replace(r.id.id_content, fthree, "three"); 
    tx.addtobackstack(null); 
    tx.commit(); 
  } 
}

代码重构结束,与开始的效果一模一样。上面两种通信方式都是值得推荐的,随便选择一种自己喜欢的。这里再提一下:虽然fragment和activity可以通过getactivity与findfragmentbytag或者findfragmentbyid,进行任何操作,甚至在fragment里面操作另外的fragment,但是没有特殊理由是绝对不提倡的。activity担任的是fragment间类似总线一样的角色,应当由它决定fragment如何操作。另外虽然fragment不能响应intent打开,但是activity可以,activity可以接收intent,然后根据参数判断显示哪个fragment。

4、如何处理运行时配置发生变化

运行时配置发生变化,最常见的就是屏幕发生旋转,如果你不知道如何处理屏幕变化可以参考:android 屏幕旋转 处理 asynctask 和 progressdialog 的最佳方案

这里提一下:很多人觉得强制设置屏幕的方向就可以了,但是有一点,当你的应用被至于后台(例如用户点击了home),长时间没有返回的时候,你的应用也会被重新启动。比如上例:如果你把上面的例子你至于fragmentthree界面,然后处于后台状态,长时间后你会发现当你再次通过home打开时,上面fragmentthree与fragmentone叠加在一起,这就是因为你的activity重新启动,在原来的fragmentthree上又绘制了一个fragmentone。

好了,下面看一段代码:

activity:

package com.zhy.zhy_fragments; 
import android.app.activity; 
import android.app.fragmentmanager; 
import android.app.fragmenttransaction; 
import android.os.bundle; 
import android.view.window; 
public class mainactivity extends activity 
{ 
  private fragmentone mfone; 
  @override 
  protected void oncreate(bundle savedinstancestate) 
  { 
    super.oncreate(savedinstancestate); 
    requestwindowfeature(window.feature_no_title); 
    setcontentview(r.layout.activity_main); 
    mfone = new fragmentone(); 
    fragmentmanager fm = getfragmentmanager(); 
    fragmenttransaction tx = fm.begintransaction(); 
    tx.add(r.id.id_content, mfone, "one"); 
    tx.commit(); 
  } 
} 

fragment

package com.zhy.zhy_fragments; 
import android.app.fragment; 
import android.os.bundle; 
import android.util.log; 
import android.view.layoutinflater; 
import android.view.view; 
import android.view.viewgroup; 
public class fragmentone extends fragment 
{ 
  private static final string tag = "fragmentone"; 
  @override 
  public view oncreateview(layoutinflater inflater, viewgroup container, 
      bundle savedinstancestate) 
  { 
    log.e(tag, "oncreateview"); 
    view view = inflater.inflate(r.layout.fragment_one, container, false); 
    return view; 
  } 
  @override 
  public void oncreate(bundle savedinstancestate) 
  { 
    // todo auto-generated method stub 
    super.oncreate(savedinstancestate); 
    log.e(tag, "oncreate"); 
  } 
  @override 
  public void ondestroyview() 
  { 
    // todo auto-generated method stub 
    super.ondestroyview(); 
    log.e(tag, "ondestroyview"); 
  } 
  @override 
  public void ondestroy() 
  { 
    // todo auto-generated method stub 
    super.ondestroy(); 
    log.e(tag, "ondestroy"); 
  } 
}

很简单的代码,当你运行之后,不断的旋转屏幕,你会发现每旋转一次屏幕,屏幕上就多了一个fragmentone的实例,并且后台log会打印出许多套生命周期的回调。

类似:

07-20 08:18:46.651: e/fragmentone(1633): oncreate 
07-20 08:18:46.651: e/fragmentone(1633): oncreate 
07-20 08:18:46.651: e/fragmentone(1633): oncreate 
07-20 08:18:46.681: e/fragmentone(1633): oncreateview 
07-20 08:18:46.831: e/fragmentone(1633): oncreateview 
07-20 08:18:46.891: e/fragmentone(1633): oncreateview 

这是为什么呢,因为当屏幕发生旋转,activity发生重新启动,默认的activity中的fragment也会跟着activity重新创建;这样造成当旋转的时候,本身存在的fragment会重新启动,然后当执行activity的oncreate时,又会再次实例化一个新的fragment,这就是出现的原因。

那么如何解决呢:

其实通过检查oncreate的参数bundle savedinstancestate就可以判断,当前是否发生activity的重新创建:

默认的savedinstancestate会存储一些数据,包括fragment的实例:通过打印可以看出:

07-20 08:23:12.952: e/fragmentone(1782): bundle[{android:fragments=android.app.fragmentmanagerstate@40d0b7b8, android:viewhierarchystate=bundle[{android:focusedviewid=2131230721, android:views=android.util.sparsearray@40d0af68}]}] 

所以,我们简单改一下代码,只有在savedinstancestate==null时,才进行创建fragment实例:

package com.zhy.zhy_fragments; 
import android.app.activity; 
import android.app.fragmentmanager; 
import android.app.fragmenttransaction; 
import android.os.bundle; 
import android.util.log; 
import android.view.window; 
public class mainactivity extends activity 
{ 
  private static final string tag = "fragmentone"; 
  private fragmentone mfone; 
  @override 
  protected void oncreate(bundle savedinstancestate) 
  { 
    super.oncreate(savedinstancestate); 
    requestwindowfeature(window.feature_no_title); 
    setcontentview(r.layout.activity_main); 
    log.e(tag, savedinstancestate+""); 
    if(savedinstancestate == null) 
    { 
      mfone = new fragmentone(); 
      fragmentmanager fm = getfragmentmanager(); 
      fragmenttransaction tx = fm.begintransaction(); 
      tx.add(r.id.id_content, mfone, "one"); 
      tx.commit(); 
    } 
  } 
} 

现在无论进行多次旋转都只会有一个fragment实例在activity中。

现在还存在一个问题,就是重新绘制时,fragment发生重建,原本的数据如何保持?

其实和activity类似,fragment也有onsaveinstancestate的方法,在此方法中进行保存数据,然后在oncreate或者oncreateview或者onactivitycreated进行恢复都可以。

由于篇幅原因,就不贴测试代码了。

5、fragmeny与actionbar和menuitem集成

fragment可以添加自己的menuitem到activity的actionbar或者可选菜单中。

a、在fragment的oncreate中调用 sethasoptionsmenu(true);

b、然后在fragment子类中实现oncreateoptionsmenu

c、如果希望在fragment中处理menuitem的点击,也可以实现onoptionsitemselected;当然了activity也可以直接处理该menuitem的点击事件。

代码:

fragment

package com.zhy.zhy_fragments; 
import android.app.fragment; 
import android.os.bundle; 
import android.view.layoutinflater; 
import android.view.menu; 
import android.view.menuinflater; 
import android.view.menuitem; 
import android.view.view; 
import android.view.viewgroup; 
import android.widget.toast; 
public class fragmentone extends fragment 
{ 
  @override 
  public void oncreate(bundle savedinstancestate) 
  { 
    super.oncreate(savedinstancestate); 
    sethasoptionsmenu(true); 
  } 
  @override 
  public view oncreateview(layoutinflater inflater, viewgroup container, 
      bundle savedinstancestate) 
  { 
    view view = inflater.inflate(r.layout.fragment_one, container, false); 
    return view; 
  } 
  @override 
  public void oncreateoptionsmenu(menu menu, menuinflater inflater) 
  { 
    inflater.inflate(r.menu.fragment_menu, menu); 
  } 
  @override 
  public boolean onoptionsitemselected(menuitem item) 
  { 
    switch (item.getitemid()) 
    { 
    case r.id.id_menu_fra_test: 
      toast.maketext(getactivity(), "fragmentmenuitem1", toast.length_short).show(); 
      break; 
    } 
    return true; 
  } 
}

activity

package com.zhy.zhy_fragments; 
import android.app.activity; 
import android.app.fragmentmanager; 
import android.app.fragmenttransaction; 
import android.os.bundle; 
import android.util.log; 
import android.view.menu; 
import android.view.menuitem; 
import android.view.window; 
import android.widget.toast; 
public class mainactivity extends activity 
{ 
  private static final string tag = "fragmentone"; 
  private fragmentone mfone; 
  @override 
  protected void oncreate(bundle savedinstancestate) 
  { 
    super.oncreate(savedinstancestate); 
    requestwindowfeature(window.feature_no_title); 
    setcontentview(r.layout.activity_main); 
    log.e(tag, savedinstancestate + ""); 
    if (savedinstancestate == null) 
    { 
      mfone = new fragmentone(); 
      fragmentmanager fm = getfragmentmanager(); 
      fragmenttransaction tx = fm.begintransaction(); 
      tx.add(r.id.id_content, mfone, "one"); 
      tx.commit(); 
    } 
  } 
  @override 
  public boolean oncreateoptionsmenu(menu menu) 
  { 
    super.oncreateoptionsmenu(menu); 
    getmenuinflater().inflate(r.menu.main, menu); 
    return true; 
  } 
  @override 
  public boolean onoptionsitemselected(menuitem item) 
  { 
    switch (item.getitemid()) 
    { 
    case r.id.action_settings: 
      toast.maketext(this, "setting", toast.length_short).show(); 
      return true; 
    default: 
      //如果希望fragment自己处理menuitem点击事件,一定不要忘了调用super.xxx 
      return super.onoptionsitemselected(item); 
    } 
  } 
}

效果图:

深入浅析Android Fragment(下篇)

好了,可以很好的看到,fragment可以添加menuitem,也可以自己处理点击~~~

6、没有布局的fragment的作用

没有布局文件fragment实际上是为了保存,当activity重启时,保存大量数据准备的

请参考博客:android 屏幕旋转 处理 asynctask 和 progressdialog 的最佳方案

7、使用fragment创建对话框

这是google推荐的方式,我也单独写过博客介绍,请参考:android 官方推荐 : dialogfragment 创建对话框

好了,终于把fragment相关的联系到一起了,上述基本包含了fragment所有的用法~~~相信大家如果能够看完,一定有不少的收获~~~通过两种文章的结合学习,相信对fragment有不少收获吧,结合实际应用到项目中去吧,有任何问题,欢迎给我留言,谢谢!