Android应用经典主界面框架之一:仿QQ (使用Fragment, 附源码) 分类: Android开发 17633人阅读 评论(98) 收藏 举报 android应用 框架 Fragment

目录(?)[+]

  1. 一底部控制栏
  2. 二顶部控制栏
  3. 三总控中心Activity和Fragment

最近反复研究日常经典必用的几个android app,从主界面带来的交互方式入手进行分析,我将其大致分为三类。今天记录第一种方式,即主界面下面有几个tab页,最上端是标题栏,tab页和tab页之间不是通过滑动切换的,而是通过点击切换tab页。早期这种架构一直是使用tabhost+activitygroup来使用,随着fragment的出现及google官方也大力推荐使用fragment,后者大有代替前者之势。本文也使用fragment进行搭建,标题中的“经典”指这种交互经典,非本文的代码框架结构,欢迎大家提出指出不足,帮助完善。文中的fragment部分参考了郭神的博文(链接1 链接2 链接3),代码也是在郭神代码基础上加入了自己对框架的理解。

再次重申下这种主界面交互的特点:1,多个tab,不能滑动切换只能点击切换;2,上有标题栏。这种模式也是目前app中使用最多的。如qq、百度云盘、招商银行、微博、支付宝。几个月前支付宝还是能滑动切换的,后来取消了。视图如下:



下面本文从底部控制栏、顶部控制栏及中间的内容显示载体fragment三部分叙述。

一、底部控制栏

底部控制栏里每个控件都不是单一基础控件,上面是图片、下面是文字,右上角是红点,当有更新时红点显示,否则隐藏。另外像qq的右上角还能显示未读消息的个数,我的参考链接里是通过大量的layout一点一点搭出来的,这样的好处是方便控制比较直观,另外是可以利用Linearlayout里的layout_weight这个属性,让底部的这些item均匀分布,缺点是代码上有很多重复,维护起来不方便。既然是整理app的通用模板框架,因此我将每个item视为一个对象,然后将其放在底部就ok了。本代码里只封装了上面是图片下面是文字,右上角的红点么有封装进来。

ImageText.java就作了这样一件事:

[java] view plain copy print ?
  1. <SPANstyle="FONT-FAMILY:ComicSansMS;FONT-SIZE:18px">packageorg.yanzi.ui;
  2. importorg.yanzi.constant.Constant;
  3. importandroid.content.Context;
  4. importandroid.graphics.Color;
  5. importandroid.util.AttributeSet;
  6. importandroid.view.LayoutInflater;
  7. importandroid.view.MotionEvent;
  8. importandroid.view.View;
  9. importandroid.view.ViewGroup;
  10. importandroid.widget.ImageView;
  11. importandroid.widget.LinearLayout;
  12. importandroid.widget.TextView;
  13. importcom.example.fragmentproject.R;
  14. publicclassImageTextextendsLinearLayout{
  15. privateContextmContext=null;
  16. privateImageViewmImageView=null;
  17. privateTextViewmTextView=null;
  18. privatefinalstaticintDEFAULT_IMAGE_WIDTH=64;
  19. privatefinalstaticintDEFAULT_IMAGE_HEIGHT=64;
  20. privateintCHECKED_COLOR=Color.rgb(29,118,199);//选中蓝色
  21. privateintUNCHECKED_COLOR=Color.GRAY;//自然灰色
  22. publicImageText(Contextcontext){
  23. super(context);
  24. //TODOAuto-generatedconstructorstub
  25. mContext=context;
  26. }
  27. publicImageText(Contextcontext,AttributeSetattrs){
  28. super(context,attrs);
  29. //TODOAuto-generatedconstructorstub
  30. mContext=context;
  31. LayoutInflaterinflater=(LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  32. ViewparentView=inflater.inflate(R.layout.image_text_layout,this,true);
  33. mImageView=(ImageView)findViewById(R.id.image_iamge_text);
  34. mTextView=(TextView)findViewById(R.id.text_iamge_text);
  35. }
  36. publicvoidsetImage(intid){
  37. if(mImageView!=null){
  38. mImageView.setImageResource(id);
  39. setImageSize(DEFAULT_IMAGE_WIDTH,DEFAULT_IMAGE_HEIGHT);
  40. }
  41. }
  42. publicvoidsetText(Strings){
  43. if(mTextView!=null){
  44. mTextView.setText(s);
  45. mTextView.setTextColor(UNCHECKED_COLOR);
  46. }
  47. }
  48. @Override
  49. publicbooleanonInterceptTouchEvent(MotionEventev){
  50. //TODOAuto-generatedmethodstub
  51. returntrue;
  52. }
  53. privatevoidsetImageSize(intw,inth){
  54. if(mImageView!=null){
  55. ViewGroup.LayoutParamsparams=mImageView.getLayoutParams();
  56. params.width=w;
  57. params.height=h;
  58. mImageView.setLayoutParams(params);
  59. }
  60. }
  61. publicvoidsetChecked(intitemID){
  62. if(mTextView!=null){
  63. mTextView.setTextColor(CHECKED_COLOR);
  64. }
  65. intcheckDrawableId=-1;
  66. switch(itemID){
  67. caseConstant.BTN_FLAG_MESSAGE:
  68. checkDrawableId=R.drawable.message_selected;
  69. break;
  70. caseConstant.BTN_FLAG_CONTACTS:
  71. checkDrawableId=R.drawable.contacts_selected;
  72. break;
  73. caseConstant.BTN_FLAG_NEWS:
  74. checkDrawableId=R.drawable.news_selected;
  75. break;
  76. caseConstant.BTN_FLAG_SETTING:
  77. checkDrawableId=R.drawable.setting_selected;
  78. break;
  79. default:break;
  80. }
  81. if(mImageView!=null){
  82. mImageView.setImageResource(checkDrawableId);
  83. }
  84. }
  85. }
  86. </SPAN>

对应的布局:

[html] view plain copy print ?
  1. <SPANstyle="FONT-FAMILY:ComicSansMS;FONT-SIZE:18px"><?xmlversion="1.0"encoding="utf-8"?>
  2. <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:orientation="vertical">
  6. <ImageView
  7. android:id="@+id/image_iamge_text"
  8. android:layout_width="wrap_content"
  9. android:layout_height="wrap_content"
  10. android:layout_gravity="center_horizontal"/>
  11. <TextView
  12. android:id="@+id/text_iamge_text"
  13. android:layout_width="wrap_content"
  14. android:layout_height="wrap_content"
  15. android:layout_gravity="center_horizontal"/>
  16. </LinearLayout></SPAN>

代码里用到了Constant.java,这里面放的都是常量:

[java] view plain copy print ?
  1. <SPANstyle="FONT-FAMILY:ComicSansMS;FONT-SIZE:18px">packageorg.yanzi.constant;
  2. publicclassConstant{
  3. //Btn的标识
  4. publicstaticfinalintBTN_FLAG_MESSAGE=0x01;
  5. publicstaticfinalintBTN_FLAG_CONTACTS=0x01<<1;
  6. publicstaticfinalintBTN_FLAG_NEWS=0x01<<2;
  7. publicstaticfinalintBTN_FLAG_SETTING=0x01<<3;
  8. //Fragment的标识
  9. publicstaticfinalStringFRAGMENT_FLAG_MESSAGE="消息";
  10. publicstaticfinalStringFRAGMENT_FLAG_CONTACTS="联系人";
  11. publicstaticfinalStringFRAGMENT_FLAG_NEWS="新闻";
  12. publicstaticfinalStringFRAGMENT_FLAG_SETTING="设置";
  13. publicstaticfinalStringFRAGMENT_FLAG_SIMPLE="simple";
  14. }
  15. </SPAN>
第一排是复合Button的标识,下面的string类型的是将来创建fragment的标识。

完成了ImageText之后,下面就是将4个这样的控件放到一个布局里。为了控制方便,我们将底部栏抽象为一个对象BottomControlPanel.java,这样在维护底部栏相关内容时直接找他就行了。BottomControlPanel继承自RelativeLayout,先来看它的布局:

bottom_panel_layout.xml

[html] view plain copy print ?
  1. <SPANstyle="FONT-FAMILY:ComicSansMS;FONT-SIZE:18px"><?xmlversion="1.0"encoding="utf-8"?>
  2. <org.yanzi.ui.BottomControlPanelxmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="60dp"
  5. android:layout_alignParentBottom="true"
  6. android:gravity="center_vertical"
  7. android:paddingLeft="20dp"
  8. android:paddingRight="20dp">
  9. <org.yanzi.ui.ImageText
  10. android:id="@+id/btn_message"
  11. android:layout_width="wrap_content"
  12. android:layout_height="wrap_content"
  13. android:layout_alignParentLeft="true"/>
  14. <org.yanzi.ui.ImageText
  15. android:id="@+id/btn_contacts"
  16. android:layout_width="wrap_content"
  17. android:layout_height="wrap_content"
  18. android:layout_toRightOf="@id/btn_message"/>
  19. <org.yanzi.ui.ImageText
  20. android:id="@+id/btn_news"
  21. android:layout_width="wrap_content"
  22. android:layout_height="wrap_content"
  23. android:layout_toRightOf="@id/btn_contacts"/>
  24. <org.yanzi.ui.ImageText
  25. android:id="@+id/btn_setting"
  26. android:layout_width="wrap_content"
  27. android:layout_height="wrap_content"
  28. android:layout_alignParentRight="true"/>
  29. </org.yanzi.ui.BottomControlPanel></SPAN>
对应的java文件:

BottomControlPanel.java

[java] view plain copy print ?
  1. <SPANstyle="FONT-FAMILY:ComicSansMS;FONT-SIZE:18px">packageorg.yanzi.ui;
  2. importjava.util.ArrayList;
  3. importjava.util.List;
  4. importorg.yanzi.constant.Constant;
  5. importandroid.content.Context;
  6. importandroid.graphics.Color;
  7. importandroid.util.AttributeSet;
  8. importandroid.util.Log;
  9. importandroid.view.View;
  10. importandroid.widget.RelativeLayout;
  11. importcom.example.fragmentproject.R;
  12. publicclassBottomControlPanelextendsRelativeLayoutimplementsView.OnClickListener{
  13. privateContextmContext;
  14. privateImageTextmMsgBtn=null;
  15. privateImageTextmContactsBtn=null;
  16. privateImageTextmNewsBtn=null;
  17. privateImageTextmSettingBtn=null;
  18. privateintDEFALUT_BACKGROUND_COLOR=Color.rgb(243,243,243);//Color.rgb(192,192,192)
  19. privateBottomPanelCallbackmBottomCallback=null;
  20. privateList<ImageText>viewList=newArrayList<ImageText>();
  21. publicinterfaceBottomPanelCallback{
  22. publicvoidonBottomPanelClick(intitemId);
  23. }
  24. publicBottomControlPanel(Contextcontext,AttributeSetattrs){
  25. super(context,attrs);
  26. //TODOAuto-generatedconstructorstub
  27. }
  28. @Override
  29. protectedvoidonFinishInflate(){
  30. //TODOAuto-generatedmethodstub
  31. mMsgBtn=(ImageText)findViewById(R.id.btn_message);
  32. mContactsBtn=(ImageText)findViewById(R.id.btn_contacts);
  33. mNewsBtn=(ImageText)findViewById(R.id.btn_news);
  34. mSettingBtn=(ImageText)findViewById(R.id.btn_setting);
  35. setBackgroundColor(DEFALUT_BACKGROUND_COLOR);
  36. viewList.add(mMsgBtn);
  37. viewList.add(mContactsBtn);
  38. viewList.add(mNewsBtn);
  39. viewList.add(mSettingBtn);
  40. }
  41. publicvoidinitBottomPanel(){
  42. if(mMsgBtn!=null){
  43. mMsgBtn.setImage(R.drawable.message_unselected);
  44. mMsgBtn.setText("消息");
  45. }
  46. if(mContactsBtn!=null){
  47. mContactsBtn.setImage(R.drawable.contacts_unselected);
  48. mContactsBtn.setText("联系人");
  49. }
  50. if(mNewsBtn!=null){
  51. mNewsBtn.setImage(R.drawable.news_unselected);
  52. mNewsBtn.setText("新闻");
  53. }
  54. if(mSettingBtn!=null){
  55. mSettingBtn.setImage(R.drawable.setting_unselected);
  56. mSettingBtn.setText("设置");
  57. }
  58. setBtnListener();
  59. }
  60. privatevoidsetBtnListener(){
  61. intnum=this.getChildCount();
  62. for(inti=0;i<num;i++){
  63. Viewv=getChildAt(i);
  64. if(v!=null){
  65. v.setOnClickListener(this);
  66. }
  67. }
  68. }
  69. publicvoidsetBottomCallback(BottomPanelCallbackbottomCallback){
  70. mBottomCallback=bottomCallback;
  71. }
  72. @Override
  73. publicvoidonClick(Viewv){
  74. //TODOAuto-generatedmethodstub
  75. initBottomPanel();
  76. intindex=-1;
  77. switch(v.getId()){
  78. caseR.id.btn_message:
  79. index=Constant.BTN_FLAG_MESSAGE;
  80. mMsgBtn.setChecked(Constant.BTN_FLAG_MESSAGE);
  81. break;
  82. caseR.id.btn_contacts:
  83. index=Constant.BTN_FLAG_CONTACTS;
  84. mContactsBtn.setChecked(Constant.BTN_FLAG_CONTACTS);
  85. break;
  86. caseR.id.btn_news:
  87. index=Constant.BTN_FLAG_NEWS;
  88. mNewsBtn.setChecked(Constant.BTN_FLAG_NEWS);
  89. break;
  90. caseR.id.btn_setting:
  91. index=Constant.BTN_FLAG_SETTING;
  92. mSettingBtn.setChecked(Constant.BTN_FLAG_SETTING);
  93. break;
  94. default:break;
  95. }
  96. if(mBottomCallback!=null){
  97. mBottomCallback.onBottomPanelClick(index);
  98. }
  99. }
  100. publicvoiddefaultBtnChecked(){
  101. if(mMsgBtn!=null){
  102. mMsgBtn.setChecked(Constant.BTN_FLAG_MESSAGE);
  103. }
  104. }
  105. @Override
  106. protectedvoidonLayout(booleanchanged,intleft,inttop,intright,intbottom){
  107. //TODOAuto-generatedmethodstub
  108. super.onLayout(changed,left,top,right,bottom);
  109. layoutItems(left,top,right,bottom);
  110. }
  111. /**最左边和最右边的view由母布局的padding进行控制位置。这里需对第2、3个view的位置重新设置
  112. *@paramleft
  113. *@paramtop
  114. *@paramright
  115. *@parambottom
  116. */
  117. privatevoidlayoutItems(intleft,inttop,intright,intbottom){
  118. intn=getChildCount();
  119. if(n==0){
  120. return;
  121. }
  122. intpaddingLeft=getPaddingLeft();
  123. intpaddingRight=getPaddingRight();
  124. Log.i("yanguoqi","paddingLeft="+paddingLeft+"paddingRight="+paddingRight);
  125. intwidth=right-left;
  126. intheight=bottom-top;
  127. Log.i("yanguoqi","width="+width+"height="+height);
  128. intallViewWidth=0;
  129. for(inti=0;i<n;i++){
  130. Viewv=getChildAt(i);
  131. Log.i("yanguoqi","v.getWidth()="+v.getWidth());
  132. allViewWidth+=v.getWidth();
  133. }
  134. intblankWidth=(width-allViewWidth-paddingLeft-paddingRight)/(n-1);
  135. Log.i("yanguoqi","blankV="+blankWidth);
  136. LayoutParamsparams1=(LayoutParams)viewList.get(1).getLayoutParams();
  137. params1.leftMargin=blankWidth;
  138. viewList.get(1).setLayoutParams(params1);
  139. LayoutParamsparams2=(LayoutParams)viewList.get(2).getLayoutParams();
  140. params2.leftMargin=blankWidth;
  141. viewList.get(2).setLayoutParams(params2);
  142. }
  143. }
  144. </SPAN>
在onFinishInflate()函数里实例化里面的子元素,在initBottomPanel()里设置每个孩子的图片和文字、监听.onLayout()里对中间的2个孩子的位置进行调整,使其均匀分布,见我的前文。这个BottomControlPanel实现了View.OnClickListener接口,在onClick()里通过id来判断用户点击了哪一个孩子。判断出来后需要做两件事,一是对这个被点击的对象进行处理,如字体颜色、图片资源的变化,右上角小红点的隐藏等等。另一方面,BottomControlPanel要告诉将来它的主人,也就是Activity到底是点了哪个,通知Activity去切换fragment。可以看到,activity类似个总控中心,BottomControlPanel管理属于它的ImageText,同时上报Activity。Activity知道消息后再切换fragment,每个fragment都有自己的事务逻辑。

这里定义了

public interface BottomPanelCallback{
public void onBottomPanelClick(int itemId);
}这个接口,通过传递Id来通知Activity。defaultBtnChecked()函数是apk初次打开后,默认切换到第一个消息fragment上。

这里有个地方需要注意,就是虽然ImageText和BottomControlPanel都是自定义控件,但两者在方式上是有区别的。在ImageText的构造函数里通过inflater将布局加载进来,它对应的布局是个普通的布局。而BottomControlPanel对应的布局文件里,直接使用了定义的BottomControlPanel,在onFinishInflate函数里实例化孩子View。前者是inflate之后实例化的。在使用ImageText到一个新的母布局时是通过<org.yanzi.ui.ImageText />这种方式进行的,那么使用BottomControlPanel有何区别,请见下文介绍Activity的布局时。

二、顶部控制栏

有了底部控制栏,顶部控制栏就可以如法炮制了。这里先交代几句,虽然Android3.0 后Google推出的有ActionBar来做顶部导航栏,参见郭神的这篇博文。但我发现,本文最前面贴图的几款应用应该都没有使用ActionBar,因为它不够灵活。ActionBar使用起来什么样,大家看看微信就知道了,那个的顶部控制栏就是ActionBar做的,这个应该没跑。

通过观察,顶部控制栏除了标题居中外,在右上角通常会再放一个按钮。不是ImageView就是TextView,这里我为了方便放的是两个TextView,右侧的按钮效果可以再TextView上弄个背景来实现。

HeadControlPanel.java

[html] view plain copy print ?
  1. <SPANstyle="FONT-FAMILY:ComicSansMS;FONT-SIZE:18px">packageorg.yanzi.ui;
  2. importorg.yanzi.constant.Constant;
  3. importcom.example.fragmentproject.R;
  4. importandroid.content.Context;
  5. importandroid.graphics.Color;
  6. importandroid.util.AttributeSet;
  7. importandroid.widget.RelativeLayout;
  8. importandroid.widget.TextView;
  9. publicclassHeadControlPanelextendsRelativeLayout{
  10. privateContextmContext;
  11. privateTextViewmMidleTitle;
  12. privateTextViewmRightTitle;
  13. privatestaticfinalfloatmiddle_title_size=20f;
  14. privatestaticfinalfloatright_title_size=17f;
  15. privatestaticfinalintdefault_background_color=Color.rgb(23,124,202);
  16. publicHeadControlPanel(Contextcontext,AttributeSetattrs){
  17. super(context,attrs);
  18. //TODOAuto-generatedconstructorstub
  19. }
  20. @Override
  21. protectedvoidonFinishInflate(){
  22. //TODOAuto-generatedmethodstub
  23. mMidleTitle=(TextView)findViewById(R.id.midle_title);
  24. mRightTitle=(TextView)findViewById(R.id.right_title);
  25. setBackgroundColor(default_background_color);
  26. }
  27. publicvoidinitHeadPanel(){
  28. if(mMidleTitle!=null){
  29. setMiddleTitle(Constant.FRAGMENT_FLAG_MESSAGE);
  30. }
  31. }
  32. publicvoidsetMiddleTitle(Strings){
  33. mMidleTitle.setText(s);
  34. mMidleTitle.setTextSize(middle_title_size);
  35. }
  36. }
  37. </SPAN>

布局文件head_panel_layout.xml

[html] view plain copy print ?
  1. <SPANstyle="FONT-FAMILY:ComicSansMS;FONT-SIZE:18px"><?xmlversion="1.0"encoding="utf-8"?>
  2. <org.yanzi.ui.HeadControlPanelxmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="50dp"
  5. android:layout_alignParentTop="true">
  6. <TextView
  7. android:id="@+id/midle_title"
  8. android:layout_width="wrap_content"
  9. android:layout_height="wrap_content"
  10. android:layout_centerInParent="true"
  11. android:textColor="@android:color/white"/>
  12. <TextView
  13. android:id="@+id/right_title"
  14. android:layout_width="wrap_content"
  15. android:layout_height="wrap_content"
  16. android:layout_alignParentRight="true"
  17. android:textColor="@android:color/white"/>
  18. </org.yanzi.ui.HeadControlPanel>
  19. </SPAN>

三、总控中心Activity和Fragment

先交代下Fragment的使用大致分两种,一种是将Fragment作为一个View写死在布局中,布局里使用android:name来告诉它对应的是哪个实体Fragment。这种添加fragment的方式不能delete和replace掉。另一种是通过获得activity的fragmentmanager和fragmentTransaction和进行动态的添加。这种方式更加灵活,一般使用此种方法。

先看Activity的布局activity_main.xml:

[html] view plain copy print ?
  1. <SPANstyle="FONT-FAMILY:ComicSansMS;FONT-SIZE:18px"><RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:id="@+id/root_layout"
  4. android:layout_width="match_parent"
  5. android:layout_height="match_parent"
  6. tools:context="org.yanzi.fragmentproject.MainActivity">
  7. <include
  8. android:id="@+id/bottom_layout"
  9. layout="@layout/bottom_panel_layout"/>
  10. <View
  11. android:layout_width="match_parent"
  12. android:layout_height="1dip"
  13. android:layout_above="@id/bottom_layout"
  14. android:background="#FFE7E7E7"/>
  15. <include
  16. android:id="@+id/head_layout"
  17. layout="@layout/head_panel_layout"/>
  18. <View
  19. android:layout_width="match_parent"
  20. android:layout_height="1dip"
  21. android:layout_below="@id/head_layout"
  22. android:background="#FFE7E7E7"/>
  23. <FrameLayout
  24. android:id="@+id/fragment_content"
  25. android:layout_width="match_parent"
  26. android:layout_height="wrap_content"
  27. android:layout_below="@id/head_layout"
  28. android:layout_above="@id/bottom_layout">
  29. </FrameLayout>
  30. </RelativeLayout></SPAN>

注意看这里是通过include的方式把刚才自定义的上下panel加过来,而不能直接用<org.yanzi.ui.BottomControlPanel />这种方式直接加载。当然如果也模仿ImageText的构造方式,也是可以这样用的。关于include方式的使用有几个注意事项,就是最好让它的母布局是RelativeLayout,否则的话很难控制include进来的布局的位置。另外,include布局的位置一定要写在include之前,如底部面板在最底部,android:layout_alignParentBottom="true"这句话是在bottom_panel_layout.xml里写的,如果写在activity_main.xml里就是无效的,这着实是个蛋疼的问题。再就是include后设置的id会覆盖掉以前的,所以这里只在include的时候设置id。其中的两个View是分割线。整体是按照底部栏、上部栏、中间Fragment的容器来放置的。

在放Fragment的时候需要注意,究竟是否要将顶部控制栏放到各自的fragment里合适还是放到Activity里合适要看具体情况,如果顶部栏里多是显示标题这种功能或少量的点击事件,应该放到Activity里,即顶部栏的事务逻辑和当前fragment的事务逻辑耦合的不是很紧。举个例子,比如微信的顶部栏,不管你处在哪个Tab页(聊天、发现、通讯录),点击顶部栏里的按钮都呈现出同样的内容。但反过来讲,如果顶部栏里的事务逻辑和fragment耦合很紧,即在不同的fragment,顶部栏呈现的内容都不一样,且点击后处理的事务也和当前fragment紧密联系一起,那就应该一个fragment配套一个顶部栏,方便控制。本文是将两者分开的。所以让fragment的容器在顶部栏之下,底部栏之上,不这样写的话,就会遮挡。

<FrameLayout
android:id="@+id/fragment_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/head_layout"
android:layout_above="@id/bottom_layout" >
</FrameLayout>


MainActivity.java代码:

[java] view plain copy print ?
  1. <SPANstyle="FONT-FAMILY:ComicSansMS;FONT-SIZE:18px">packageorg.yanzi.activity;
  2. importorg.yanzi.constant.Constant;
  3. importorg.yanzi.fragment.BaseFragment;
  4. importorg.yanzi.fragment.ContactsFragment;
  5. importorg.yanzi.fragment.MessageFragment;
  6. importorg.yanzi.fragment.NewsFragment;
  7. importorg.yanzi.fragment.SettingFragment;
  8. importorg.yanzi.ui.BottomControlPanel;
  9. importorg.yanzi.ui.BottomControlPanel.BottomPanelCallback;
  10. importorg.yanzi.ui.HeadControlPanel;
  11. importandroid.app.Activity;
  12. importandroid.app.Fragment;
  13. importandroid.app.FragmentManager;
  14. importandroid.app.FragmentTransaction;
  15. importandroid.os.Bundle;
  16. importandroid.text.TextUtils;
  17. importandroid.util.Log;
  18. importandroid.view.Menu;
  19. importandroid.widget.Toast;
  20. importcom.example.fragmentproject.R;
  21. publicclassMainActivityextendsActivityimplementsBottomPanelCallback{
  22. BottomControlPanelbottomPanel=null;
  23. HeadControlPanelheadPanel=null;
  24. privateFragmentManagerfragmentManager=null;
  25. privateFragmentTransactionfragmentTransaction=null;
  26. /*privateMessageFragmentmessageFragment;
  27. privateContactsFragmentcontactsFragment;
  28. privateNewsFragmentnewsFragment;
  29. privateSettingFragmentsettingFragment;*/
  30. publicstaticStringcurrFragTag="";
  31. @Override
  32. protectedvoidonCreate(BundlesavedInstanceState){
  33. super.onCreate(savedInstanceState);
  34. setContentView(R.layout.activity_main);
  35. initUI();
  36. fragmentManager=getFragmentManager();
  37. setDefaultFirstFragment(Constant.FRAGMENT_FLAG_MESSAGE);
  38. }
  39. @Override
  40. publicbooleanonCreateOptionsMenu(Menumenu){
  41. //Inflatethemenu;thisaddsitemstotheactionbarifitispresent.
  42. getMenuInflater().inflate(R.menu.main,menu);
  43. returntrue;
  44. }
  45. privatevoidinitUI(){
  46. bottomPanel=(BottomControlPanel)findViewById(R.id.bottom_layout);
  47. if(bottomPanel!=null){
  48. bottomPanel.initBottomPanel();
  49. bottomPanel.setBottomCallback(this);
  50. }
  51. headPanel=(HeadControlPanel)findViewById(R.id.head_layout);
  52. if(headPanel!=null){
  53. headPanel.initHeadPanel();
  54. }
  55. }
  56. /*处理BottomControlPanel的回调
  57. *@seeorg.yanzi.ui.BottomControlPanel.BottomPanelCallback#onBottomPanelClick(int)
  58. */
  59. @Override
  60. publicvoidonBottomPanelClick(intitemId){
  61. //TODOAuto-generatedmethodstub
  62. Stringtag="";
  63. if((itemId&Constant.BTN_FLAG_MESSAGE)!=0){
  64. tag=Constant.FRAGMENT_FLAG_MESSAGE;
  65. }elseif((itemId&Constant.BTN_FLAG_CONTACTS)!=0){
  66. tag=Constant.FRAGMENT_FLAG_CONTACTS;
  67. }elseif((itemId&Constant.BTN_FLAG_NEWS)!=0){
  68. tag=Constant.FRAGMENT_FLAG_NEWS;
  69. }elseif((itemId&Constant.BTN_FLAG_SETTING)!=0){
  70. tag=Constant.FRAGMENT_FLAG_SETTING;
  71. }
  72. setTabSelection(tag);//切换Fragment
  73. headPanel.setMiddleTitle(tag);//切换标题
  74. }
  75. privatevoidsetDefaultFirstFragment(Stringtag){
  76. Log.i("yan","setDefaultFirstFragmententer...currFragTag="+currFragTag);
  77. setTabSelection(tag);
  78. bottomPanel.defaultBtnChecked();
  79. Log.i("yan","setDefaultFirstFragmentexit...");
  80. }
  81. privatevoidcommitTransactions(Stringtag){
  82. if(fragmentTransaction!=null&&!fragmentTransaction.isEmpty()){
  83. fragmentTransaction.commit();
  84. currFragTag=tag;
  85. fragmentTransaction=null;
  86. }
  87. }
  88. privateFragmentTransactionensureTransaction(){
  89. if(fragmentTransaction==null){
  90. fragmentTransaction=fragmentManager.beginTransaction();
  91. fragmentTransaction
  92. .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
  93. }
  94. returnfragmentTransaction;
  95. }
  96. privatevoidattachFragment(intlayout,Fragmentf,Stringtag){
  97. if(f!=null){
  98. if(f.isDetached()){
  99. ensureTransaction();
  100. fragmentTransaction.attach(f);
  101. }elseif(!f.isAdded()){
  102. ensureTransaction();
  103. fragmentTransaction.add(layout,f,tag);
  104. }
  105. }
  106. }
  107. privateFragmentgetFragment(Stringtag){
  108. Fragmentf=fragmentManager.findFragmentByTag(tag);
  109. if(f==null){
  110. Toast.makeText(getApplicationContext(),"fragment=nulltag="+tag,Toast.LENGTH_SHORT).show();
  111. f=BaseFragment.newInstance(getApplicationContext(),tag);
  112. }
  113. returnf;
  114. }
  115. privatevoiddetachFragment(Fragmentf){
  116. if(f!=null&&!f.isDetached()){
  117. ensureTransaction();
  118. fragmentTransaction.detach(f);
  119. }
  120. }
  121. /**切换fragment
  122. *@paramtag
  123. */
  124. privatevoidswitchFragment(Stringtag){
  125. if(TextUtils.equals(tag,currFragTag)){
  126. return;
  127. }
  128. //把上一个fragmentdetach掉
  129. if(currFragTag!=null&&!currFragTag.equals("")){
  130. detachFragment(getFragment(currFragTag));
  131. }
  132. attachFragment(R.id.fragment_content,getFragment(tag),tag);
  133. commitTransactions(tag);
  134. }
  135. /**设置选中的Tag
  136. *@paramtag
  137. */
  138. publicvoidsetTabSelection(Stringtag){
  139. //开启一个Fragment事务
  140. fragmentTransaction=fragmentManager.beginTransaction();
  141. /*if(TextUtils.equals(tag,Constant.FRAGMENT_FLAG_MESSAGE)){
  142. if(messageFragment==null){
  143. messageFragment=newMessageFragment();
  144. }
  145. }elseif(TextUtils.equals(tag,Constant.FRAGMENT_FLAG_CONTACTS)){
  146. if(contactsFragment==null){
  147. contactsFragment=newContactsFragment();
  148. }
  149. }elseif(TextUtils.equals(tag,Constant.FRAGMENT_FLAG_NEWS)){
  150. if(newsFragment==null){
  151. newsFragment=newNewsFragment();
  152. }
  153. }elseif(TextUtils.equals(tag,Constant.FRAGMENT_FLAG_SETTING)){
  154. if(settingFragment==null){
  155. settingFragment=newSettingFragment();
  156. }
  157. }elseif(TextUtils.equals(tag,Constant.FRAGMENT_FLAG_SIMPLE)){
  158. if(simpleFragment==null){
  159. simpleFragment=newSimpleFragment();
  160. }
  161. }*/
  162. switchFragment(tag);
  163. }
  164. @Override
  165. protectedvoidonStop(){
  166. //TODOAuto-generatedmethodstub
  167. super.onStop();
  168. currFragTag="";
  169. }
  170. @Override
  171. protectedvoidonSaveInstanceState(BundleoutState){
  172. //TODOAuto-generatedmethodstub
  173. }
  174. }
  175. </SPAN>

注意这块我作了改动,不需要申明

/* private MessageFragment messageFragment;
private ContactsFragment contactsFragment;
private NewsFragment newsFragment;
private SettingFragment settingFragment;*/
这些内容,因为Fragment的生成是通过BaseFragment.newInstance()来生成的,传进去Tag生成相应的Fragment。所有的Fragment,ContactsFragment、MessageFragment、NewsFragment、SettingFragment都继承自BaseFragment,通过BaseFragment里的newInstance()接口进行实例化对应的fragment。优点是方便管理,缺点么也有,因为java继承继承一个类,不能同时继承两个类。所以如ListFragment这些,就没法同时继承了。不过好在有listview这些,也妨碍不了我们做到同样的效果。

Activity里事件的入口是在onBottomPanelClick()监听点击了谁,然后:

setTabSelection(tag); //切换Fragment

headPanel.setMiddleTitle(tag);//切换标题

先切换Fragment再切换顶部栏的标题。setTabSelection()里直接调switchFragment(),在switchFragment函数里先判断标签是否一样,一样则意外着无需切换,否则的话就先把当前Fragment找到然后detach掉,之后进到attachFragment()函数里。在这里,先判断这个fragment是不是被detach掉的,如果是的话意味着之前曾被add过,所以只需attach就ok了。否则的话,意味着这是第一次,进行add.这里记录下Fragment的声明周期:

MessageFragment正常打开
Line 155: 01-04 11:50:46.688 E/MessageFragment( 2546): onAttach-----
Line 159: 01-04 11:50:46.688 E/MessageFragment( 2546): onCreate------
Line 161: 01-04 11:50:46.693 D/MessageFragment( 2546): onCreateView---->
Line 165: 01-04 11:50:46.694 E/MessageFragment( 2546): onActivityCreated-------
Line 169: 01-04 11:50:46.694 E/MessageFragment( 2546): onStart----->
Line 173: 01-04 11:50:46.694 E/MessageFragment( 2546): onresume---->
返回键退出:
Line 183: 01-04 11:52:26.506 E/MessageFragment( 2546): onpause
Line 259: 01-04 11:52:27.131 E/MessageFragment( 2546): onStop
Line 263: 01-04 11:52:27.132 E/MessageFragment( 2546): ondestoryView
Line 269: 01-04 11:52:27.134 E/MessageFragment( 2546): ondestory
Line 271: 01-04 11:52:27.135 D/MessageFragment( 2546): onDetach------

按home按键退出:
Line 97: 01-05 05:06:15.659 E/MessageFragment(18835): onpause
Line 215: 01-05 05:06:16.292 E/MessageFragment(18835): onStop
再次打开
Line 81: 01-05 05:07:02.408 E/MessageFragment(18835): onStart----->
Line 85: 01-05 05:07:02.408 E/MessageFragment(18835): onresume---->

通过detach的方式切换至其他Fragment:
Line 69: 01-04 11:53:33.381 E/MessageFragment( 2546): onpause
Line 73: 01-04 11:53:33.382 E/MessageFragment( 2546): onStop
Line 77: 01-04 11:53:33.382 E/MessageFragment( 2546): ondestoryView
再次切换过来:
Line 55: 01-04 11:54:59.462 D/MessageFragment( 2546): onCreateView---->
Line 59: 01-04 11:54:59.463 E/MessageFragment( 2546): onActivityCreated-------
Line 63: 01-04 11:54:59.463 E/MessageFragment( 2546): onStart----->
Line 67: 01-04 11:54:59.464 E/MessageFragment( 2546): onresume---->


四、适配器和MessageBean

本来要连数据库的,时间原因用个简单的MessageBean代替了。一个消息分联系人头像、名字、消息正文和时间四部分组成,封装到一个MessageBean里。

MessageBean.java

[java] view plain copy print ?
  1. <SPANstyle="FONT-FAMILY:ComicSansMS;FONT-SIZE:18px">packageorg.yanzi.bean;
  2. publicclassMessageBean{
  3. privateintPhotoDrawableId;
  4. privateStringMessageName;
  5. privateStringMessageContent;
  6. privateStringMessageTime;
  7. publicMessageBean(){
  8. }
  9. publicMessageBean(intphotoDrawableId,StringmessageName,
  10. StringmessageContent,StringmessageTime){
  11. super();
  12. PhotoDrawableId=photoDrawableId;
  13. MessageName=messageName;
  14. MessageContent=messageContent;
  15. MessageTime=messageTime;
  16. }
  17. publicintgetPhotoDrawableId(){
  18. returnPhotoDrawableId;
  19. }
  20. publicvoidsetPhotoDrawableId(intmPhotoDrawableId){
  21. this.PhotoDrawableId=mPhotoDrawableId;
  22. }
  23. publicStringgetMessageName(){
  24. returnMessageName;
  25. }
  26. publicvoidsetMessageName(StringmessageName){
  27. MessageName=messageName;
  28. }
  29. publicStringgetMessageContent(){
  30. returnMessageContent;
  31. }
  32. publicvoidsetMessageContent(StringmessageContent){
  33. MessageContent=messageContent;
  34. }
  35. publicStringgetMessageTime(){
  36. returnMessageTime;
  37. }
  38. publicvoidsetMessageTime(StringmessageTime){
  39. MessageTime=messageTime;
  40. }
  41. @Override
  42. publicStringtoString(){
  43. return"MessageBean[mPhotoDrawableId="+PhotoDrawableId
  44. +",MessageName="+MessageName+",MessageContent="
  45. +MessageContent+",MessageTime="+MessageTime+"]";
  46. }
  47. }
  48. </SPAN>
然后就是MessageFragment的ListView里的适配器,MessageAdapter.java

[java] view plain copy print ?
  1. <SPANstyle="FONT-FAMILY:ComicSansMS;FONT-SIZE:18px">packageorg.yanzi.fragment.adapter;
  2. importjava.util.List;
  3. importorg.yanzi.bean.MessageBean;
  4. importcom.example.fragmentproject.R;
  5. importandroid.content.Context;
  6. importandroid.view.LayoutInflater;
  7. importandroid.view.View;
  8. importandroid.view.ViewGroup;
  9. importandroid.widget.BaseAdapter;
  10. importandroid.widget.ImageView;
  11. importandroid.widget.TextView;
  12. publicclassMessageAdapterextendsBaseAdapter{
  13. privateList<MessageBean>mListMsgBean=null;
  14. privateContextmContext;
  15. privateLayoutInflatermInflater;
  16. publicMessageAdapter(List<MessageBean>listMsgBean,Contextcontext){
  17. mListMsgBean=listMsgBean;
  18. mContext=context;
  19. mInflater=LayoutInflater.from(mContext);
  20. }
  21. @Override
  22. publicintgetCount(){
  23. //TODOAuto-generatedmethodstub
  24. returnmListMsgBean.size();
  25. }
  26. @Override
  27. publicObjectgetItem(intposition){
  28. //TODOAuto-generatedmethodstub
  29. returnmListMsgBean.get(position);
  30. }
  31. @Override
  32. publiclonggetItemId(intposition){
  33. //TODOAuto-generatedmethodstub
  34. returnposition;
  35. }
  36. @Override
  37. publicViewgetView(intposition,ViewconvertView,ViewGroupparent){
  38. //TODOAuto-generatedmethodstub
  39. Viewv=mInflater.inflate(R.layout.message_item_layout,null);
  40. ImageViewimageView=(ImageView)v.findViewById(R.id.img_msg_item);
  41. imageView.setImageResource(mListMsgBean.get(position).getPhotoDrawableId());
  42. TextViewnameMsg=(TextView)v.findViewById(R.id.name_msg_item);
  43. nameMsg.setText(mListMsgBean.get(position).getMessageName());
  44. TextViewcontentMsg=(TextView)v.findViewById(R.id.content_msg_item);
  45. contentMsg.setText(mListMsgBean.get(position).getMessageContent());
  46. TextViewtimeMsg=(TextView)v.findViewById(R.id.time_msg_item);
  47. timeMsg.setText(mListMsgBean.get(position).getMessageTime());
  48. returnv;
  49. }
  50. }
  51. </SPAN>
因为是示例,getView里没用ViewHolder。
最后是MessageFragment里通过对listview设置适配器,将MessageBean作为信息的提供者也填充到适配器里。

MessageFragment.java代码:

[java] view plain copy print ?
  1. <SPANstyle="FONT-FAMILY:ComicSansMS;FONT-SIZE:18px">packageorg.yanzi.fragment;
  2. importjava.util.ArrayList;
  3. importjava.util.List;
  4. importorg.yanzi.activity.MainActivity;
  5. importorg.yanzi.bean.MessageBean;
  6. importorg.yanzi.constant.Constant;
  7. importorg.yanzi.fragment.adapter.MessageAdapter;
  8. importandroid.app.Activity;
  9. importandroid.os.Bundle;
  10. importandroid.util.Log;
  11. importandroid.view.LayoutInflater;
  12. importandroid.view.View;
  13. importandroid.view.ViewGroup;
  14. importandroid.widget.AdapterView;
  15. importandroid.widget.Toast;
  16. importandroid.widget.AdapterView.OnItemClickListener;
  17. importandroid.widget.ListView;
  18. importcom.example.fragmentproject.R;
  19. publicclassMessageFragmentextendsBaseFragment{
  20. privatestaticfinalStringTAG="MessageFragment";
  21. privateMainActivitymMainActivity;
  22. privateListViewmListView;
  23. privateMessageAdaptermMsgAdapter;
  24. privateList<MessageBean>mMsgBean=newArrayList<MessageBean>();
  25. publicViewonCreateView(LayoutInflaterinflater,ViewGroupcontainer,
  26. BundlesavedInstanceState){
  27. ViewmessageLayout=inflater.inflate(R.layout.message_layout,
  28. container,false);
  29. Log.d(TAG,"onCreateView---->");
  30. mMainActivity=(MainActivity)getActivity();
  31. mFragmentManager=getActivity().getFragmentManager();
  32. mListView=(ListView)messageLayout.findViewById(R.id.listview_message);
  33. mMsgAdapter=newMessageAdapter(mMsgBean,mMainActivity);
  34. mListView.setAdapter(mMsgAdapter);
  35. mListView.setOnItemClickListener(newOnItemClickListener(){
  36. @Override
  37. publicvoidonItemClick(AdapterView<?>parent,Viewview,
  38. intposition,longid){
  39. //TODOAuto-generatedmethodstub
  40. Toast.makeText(mMainActivity,mMsgBean.get(position).toString(),
  41. Toast.LENGTH_SHORT).show();
  42. }
  43. });
  44. returnmessageLayout;
  45. }
  46. @Override
  47. publicvoidonAttach(Activityactivity){
  48. //TODOAuto-generatedmethodstub
  49. super.onAttach(activity);
  50. Log.e(TAG,"onAttach-----");
  51. }
  52. @Override
  53. publicvoidonCreate(BundlesavedInstanceState){
  54. //TODOAuto-generatedmethodstub
  55. super.onCreate(savedInstanceState);
  56. Log.e(TAG,"onCreate------");
  57. mMsgBean.add(newMessageBean(R.drawable.ic_photo_1,"张三","吃饭没?","昨天"));
  58. mMsgBean.add(newMessageBean(R.drawable.ic_photo_2,"李四","哈哈","昨天"));
  59. mMsgBean.add(newMessageBean(R.drawable.ic_photo_3,"小明","吃饭没?","昨天"));
  60. mMsgBean.add(newMessageBean(R.drawable.ic_photo_4,"王五","吃饭没?","昨天"));
  61. mMsgBean.add(newMessageBean(R.drawable.ic_photo_5,"Jack","吃饭没?","昨天"));
  62. mMsgBean.add(newMessageBean(R.drawable.ic_photo_6,"Jone","吃饭没?","昨天"));
  63. mMsgBean.add(newMessageBean(R.drawable.ic_photo_7,"Jone","吃饭没?","昨天"));
  64. mMsgBean.add(newMessageBean(R.drawable.ic_photo_8,"Jone","吃饭没?","昨天"));
  65. mMsgBean.add(newMessageBean(R.drawable.ic_photo_9,"Jone","吃饭没?","昨天"));
  66. }
  67. @Override
  68. publicvoidonActivityCreated(BundlesavedInstanceState){
  69. //TODOAuto-generatedmethodstub
  70. super.onActivityCreated(savedInstanceState);
  71. Log.e(TAG,"onActivityCreated-------");
  72. }
  73. @Override
  74. publicvoidonStart(){
  75. //TODOAuto-generatedmethodstub
  76. super.onStart();
  77. Log.e(TAG,"onStart----->");
  78. }
  79. @Override
  80. publicvoidonResume(){
  81. //TODOAuto-generatedmethodstub
  82. super.onResume();
  83. Log.e(TAG,"onresume---->");
  84. MainActivity.currFragTag=Constant.FRAGMENT_FLAG_MESSAGE;
  85. }
  86. @Override
  87. publicvoidonPause(){
  88. //TODOAuto-generatedmethodstub
  89. super.onPause();
  90. Log.e(TAG,"onpause");
  91. }
  92. @Override
  93. publicvoidonStop(){
  94. //TODOAuto-generatedmethodstub
  95. super.onStop();
  96. Log.e(TAG,"onStop");
  97. }
  98. @Override
  99. publicvoidonDestroyView(){
  100. //TODOAuto-generatedmethodstub
  101. super.onDestroyView();
  102. Log.e(TAG,"ondestoryView");
  103. }
  104. @Override
  105. publicvoidonDestroy(){
  106. //TODOAuto-generatedmethodstub
  107. super.onDestroy();
  108. Log.e(TAG,"ondestory");
  109. }
  110. @Override
  111. publicvoidonDetach(){
  112. //TODOAuto-generatedmethodstub
  113. super.onDetach();
  114. Log.d(TAG,"onDetach------");
  115. }
  116. }
  117. </SPAN>
最后来看下效果吧,只有MessageFragment填充了数据:



横屏情况下:


--------------本文系原创,转载请注明作者yanzi1225627


版权声明:本文为博主原创文章,未经博主允许不得转载。

更多相关文章

  1. Android中的布局和控件
  2. 【Android(安卓)开发】:Android布局中的几种常用属性
  3. Android(安卓)唯一识别码
  4. Android桌面应用
  5. Android(安卓)Camera数据流分析全程记录(非overlay方式)
  6. [置顶] 对Android(安卓)MVVM的理解
  7. Android(安卓)2.3.3 NFC分析
  8. Android(安卓)Notification 用法的4种形式
  9. Android创建桌面快捷方式

随机推荐

  1. Android---网络交互之登录嘀咕网
  2. 我的实习面经(Android开发,已拿阿里,华为,CVT
  3. 可左右两侧挤压傍边布局的Android抽屉
  4. Android内存监控与分析(二):最常遇见的内存
  5. Android中的布局方式(一)
  6. Android提高十六篇之使用NDK把彩图转换灰
  7. Android(安卓)多分辨率自适应总结
  8. Android系统的Root权限获取与检测
  9. Android实现登录功能,Android与服务器数据
  10. 一个小码农的5年历程