Android应用经典主界面框架之一:仿QQ (使用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 plaincopyprint?

  1. <span style="font-family:Comic Sans MS;font-size:18px;">package org.yanzi.ui;
  2. import org.yanzi.constant.Constant;
  3. import android.content.Context;
  4. import android.graphics.Color;
  5. import android.util.AttributeSet;
  6. import android.view.LayoutInflater;
  7. import android.view.MotionEvent;
  8. import android.view.View;
  9. import android.view.ViewGroup;
  10. import android.widget.ImageView;
  11. import android.widget.LinearLayout;
  12. import android.widget.TextView;
  13. import com.example.fragmentproject.R;
  14. public class ImageText extends LinearLayout{
  15. private Context mContext = null;
  16. private ImageView mImageView = null;
  17. private TextView mTextView = null;
  18. private final static int DEFAULT_IMAGE_WIDTH = 64;
  19. private final static int DEFAULT_IMAGE_HEIGHT = 64;
  20. private int CHECKED_COLOR = Color.rgb(29, 118, 199); //选中蓝色
  21. private int UNCHECKED_COLOR = Color.GRAY;   //自然灰色
  22. public ImageText(Context context) {
  23. super(context);
  24. // TODO Auto-generated constructor stub
  25. mContext = context;
  26. }
  27. public ImageText(Context context, AttributeSet attrs) {
  28. super(context, attrs);
  29. // TODO Auto-generated constructor stub
  30. mContext = context;
  31. LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  32. View parentView = 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. public void setImage(int id){
  37. if(mImageView != null){
  38. mImageView.setImageResource(id);
  39. setImageSize(DEFAULT_IMAGE_WIDTH, DEFAULT_IMAGE_HEIGHT);
  40. }
  41. }
  42. public void setText(String s){
  43. if(mTextView != null){
  44. mTextView.setText(s);
  45. mTextView.setTextColor(UNCHECKED_COLOR);
  46. }
  47. }
  48. @Override
  49. public boolean onInterceptTouchEvent(MotionEvent ev) {
  50. // TODO Auto-generated method stub
  51. return true;
  52. }
  53. private void setImageSize(int w, int h){
  54. if(mImageView != null){
  55. ViewGroup.LayoutParams params = mImageView.getLayoutParams();
  56. params.width = w;
  57. params.height = h;
  58. mImageView.setLayoutParams(params);
  59. }
  60. }
  61. public void setChecked(int itemID){
  62. if(mTextView != null){
  63. mTextView.setTextColor(CHECKED_COLOR);
  64. }
  65. int checkDrawableId = -1;
  66. switch (itemID){
  67. case Constant.BTN_FLAG_MESSAGE:
  68. checkDrawableId = R.drawable.message_selected;
  69. break;
  70. case Constant.BTN_FLAG_CONTACTS:
  71. checkDrawableId = R.drawable.contacts_selected;
  72. break;
  73. case Constant.BTN_FLAG_NEWS:
  74. checkDrawableId = R.drawable.news_selected;
  75. break;
  76. case Constant.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 plaincopyprint?

  1. <span style="font-family:Comic Sans MS;font-size:18px;"><?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns: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 plaincopyprint?

  1. <span style="font-family:Comic Sans MS;font-size:18px;">package org.yanzi.constant;
  2. public class Constant {
  3. //Btn的标识
  4. public static final int BTN_FLAG_MESSAGE = 0x01;
  5. public static final int BTN_FLAG_CONTACTS = 0x01 << 1;
  6. public static final int BTN_FLAG_NEWS = 0x01 << 2;
  7. public static final int BTN_FLAG_SETTING = 0x01 << 3;
  8. //Fragment的标识
  9. public static final String FRAGMENT_FLAG_MESSAGE = "消息";
  10. public static final String FRAGMENT_FLAG_CONTACTS = "联系人";
  11. public static final String FRAGMENT_FLAG_NEWS = "新闻";
  12. public static final String FRAGMENT_FLAG_SETTING = "设置";
  13. public static final String FRAGMENT_FLAG_SIMPLE = "simple";
  14. }
  15. </span>

第一排是复合Button的标识,下面的string类型的是将来创建fragment的标识。

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

bottom_panel_layout.xml

[html] view plaincopyprint?

  1. <span style="font-family:Comic Sans MS;font-size:18px;"><?xml version="1.0" encoding="utf-8"?>
  2. <org.yanzi.ui.BottomControlPanel xmlns: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 plaincopyprint?

  1. <span style="font-family:Comic Sans MS;font-size:18px;">package org.yanzi.ui;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import org.yanzi.constant.Constant;
  5. import android.content.Context;
  6. import android.graphics.Color;
  7. import android.util.AttributeSet;
  8. import android.util.Log;
  9. import android.view.View;
  10. import android.widget.RelativeLayout;
  11. import com.example.fragmentproject.R;
  12. public class BottomControlPanel extends RelativeLayout implements View.OnClickListener {
  13. private Context mContext;
  14. private ImageText mMsgBtn = null;
  15. private ImageText mContactsBtn = null;
  16. private ImageText mNewsBtn = null;
  17. private ImageText mSettingBtn = null;
  18. private int DEFALUT_BACKGROUND_COLOR = Color.rgb(243, 243, 243); //Color.rgb(192, 192, 192)
  19. private BottomPanelCallback mBottomCallback = null;
  20. private List<ImageText> viewList = new ArrayList<ImageText>();
  21. public interface BottomPanelCallback{
  22. public void onBottomPanelClick(int itemId);
  23. }
  24. public BottomControlPanel(Context context, AttributeSet attrs) {
  25. super(context, attrs);
  26. // TODO Auto-generated constructor stub
  27. }
  28. @Override
  29. protected void onFinishInflate() {
  30. // TODO Auto-generated method stub
  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. public void initBottomPanel(){
  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. private void setBtnListener(){
  61. int num = this.getChildCount();
  62. for(int i = 0; i < num; i++){
  63. View v = getChildAt(i);
  64. if(v != null){
  65. v.setOnClickListener(this);
  66. }
  67. }
  68. }
  69. public void setBottomCallback(BottomPanelCallback bottomCallback){
  70. mBottomCallback = bottomCallback;
  71. }
  72. @Override
  73. public void onClick(View v) {
  74. // TODO Auto-generated method stub
  75. initBottomPanel();
  76. int index = -1;
  77. switch(v.getId()){
  78. case R.id.btn_message:
  79. index = Constant.BTN_FLAG_MESSAGE;
  80. mMsgBtn.setChecked(Constant.BTN_FLAG_MESSAGE);
  81. break;
  82. case R.id.btn_contacts:
  83. index = Constant.BTN_FLAG_CONTACTS;
  84. mContactsBtn.setChecked(Constant.BTN_FLAG_CONTACTS);
  85. break;
  86. case R.id.btn_news:
  87. index = Constant.BTN_FLAG_NEWS;
  88. mNewsBtn.setChecked(Constant.BTN_FLAG_NEWS);
  89. break;
  90. case R.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. public void defaultBtnChecked(){
  101. if(mMsgBtn != null){
  102. mMsgBtn.setChecked(Constant.BTN_FLAG_MESSAGE);
  103. }
  104. }
  105. @Override
  106. protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
  107. // TODO Auto-generated method stub
  108. super.onLayout(changed, left, top, right, bottom);
  109. layoutItems(left, top, right, bottom);
  110. }
  111. /**最左边和最右边的view由母布局的padding进行控制位置。这里需对第2、3个view的位置重新设置
  112. * @param left
  113. * @param top
  114. * @param right
  115. * @param bottom
  116. */
  117. private void layoutItems(int left, int top, int right, int bottom){
  118. int n = getChildCount();
  119. if(n == 0){
  120. return;
  121. }
  122. int paddingLeft = getPaddingLeft();
  123. int paddingRight = getPaddingRight();
  124. Log.i("yanguoqi", "paddingLeft = " + paddingLeft + " paddingRight = " + paddingRight);
  125. int width = right - left;
  126. int height = bottom - top;
  127. Log.i("yanguoqi", "width = " + width + " height = " + height);
  128. int allViewWidth = 0;
  129. for(int i = 0; i< n; i++){
  130. View v = getChildAt(i);
  131. Log.i("yanguoqi", "v.getWidth() = " + v.getWidth());
  132. allViewWidth += v.getWidth();
  133. }
  134. int blankWidth = (width - allViewWidth - paddingLeft - paddingRight) / (n - 1);
  135. Log.i("yanguoqi", "blankV = " + blankWidth );
  136. LayoutParams params1 = (LayoutParams) viewList.get(1).getLayoutParams();
  137. params1.leftMargin = blankWidth;
  138. viewList.get(1).setLayoutParams(params1);
  139. LayoutParams params2 = (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 plaincopyprint?

  1. <span style="font-family:Comic Sans MS;font-size:18px;">package org.yanzi.ui;
  2. import org.yanzi.constant.Constant;
  3. import com.example.fragmentproject.R;
  4. import android.content.Context;
  5. import android.graphics.Color;
  6. import android.util.AttributeSet;
  7. import android.widget.RelativeLayout;
  8. import android.widget.TextView;
  9. public class HeadControlPanel extends RelativeLayout {
  10. private Context mContext;
  11. private TextView mMidleTitle;
  12. private TextView mRightTitle;
  13. private static final float middle_title_size = 20f;
  14. private static final float right_title_size = 17f;
  15. private static final int default_background_color = Color.rgb(23, 124, 202);
  16. public HeadControlPanel(Context context, AttributeSet attrs) {
  17. super(context, attrs);
  18. // TODO Auto-generated constructor stub
  19. }
  20. @Override
  21. protected void onFinishInflate() {
  22. // TODO Auto-generated method stub
  23. mMidleTitle = (TextView)findViewById(R.id.midle_title);
  24. mRightTitle = (TextView)findViewById(R.id.right_title);
  25. setBackgroundColor(default_background_color);
  26. }
  27. public void initHeadPanel(){
  28. if(mMidleTitle != null){
  29. setMiddleTitle(Constant.FRAGMENT_FLAG_MESSAGE);
  30. }
  31. }
  32. public void setMiddleTitle(String s){
  33. mMidleTitle.setText(s);
  34. mMidleTitle.setTextSize(middle_title_size);
  35. }
  36. }
  37. </span>

布局文件head_panel_layout.xml

[html] view plaincopyprint?

  1. <span style="font-family:Comic Sans MS;font-size:18px;"><?xml version="1.0" encoding="utf-8"?>
  2. <org.yanzi.ui.HeadControlPanel xmlns: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 plaincopyprint?

  1. <span style="font-family:Comic Sans MS;font-size:18px;"><RelativeLayout xmlns: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 plaincopyprint?

  1. <span style="font-family:Comic Sans MS;font-size:18px;">package org.yanzi.activity;
  2. import org.yanzi.constant.Constant;
  3. import org.yanzi.fragment.BaseFragment;
  4. import org.yanzi.fragment.ContactsFragment;
  5. import org.yanzi.fragment.MessageFragment;
  6. import org.yanzi.fragment.NewsFragment;
  7. import org.yanzi.fragment.SettingFragment;
  8. import org.yanzi.ui.BottomControlPanel;
  9. import org.yanzi.ui.BottomControlPanel.BottomPanelCallback;
  10. import org.yanzi.ui.HeadControlPanel;
  11. import android.app.Activity;
  12. import android.app.Fragment;
  13. import android.app.FragmentManager;
  14. import android.app.FragmentTransaction;
  15. import android.os.Bundle;
  16. import android.text.TextUtils;
  17. import android.util.Log;
  18. import android.view.Menu;
  19. import android.widget.Toast;
  20. import com.example.fragmentproject.R;
  21. public class MainActivity extends Activity implements BottomPanelCallback {
  22. BottomControlPanel bottomPanel = null;
  23. HeadControlPanel headPanel = null;
  24. private FragmentManager fragmentManager = null;
  25. private FragmentTransaction fragmentTransaction = null;
  26. /*  private MessageFragment messageFragment;
  27. private ContactsFragment contactsFragment;
  28. private NewsFragment newsFragment;
  29. private SettingFragment settingFragment;*/
  30. public static String currFragTag = "";
  31. @Override
  32. protected void onCreate(Bundle savedInstanceState) {
  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. public boolean onCreateOptionsMenu(Menu menu) {
  41. // Inflate the menu; this adds items to the action bar if it is present.
  42. getMenuInflater().inflate(R.menu.main, menu);
  43. return true;
  44. }
  45. private void initUI(){
  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. * @see org.yanzi.ui.BottomControlPanel.BottomPanelCallback#onBottomPanelClick(int)
  58. */
  59. @Override
  60. public void onBottomPanelClick(int itemId) {
  61. // TODO Auto-generated method stub
  62. String tag = "";
  63. if((itemId & Constant.BTN_FLAG_MESSAGE) != 0){
  64. tag = Constant.FRAGMENT_FLAG_MESSAGE;
  65. }else if((itemId & Constant.BTN_FLAG_CONTACTS) != 0){
  66. tag = Constant.FRAGMENT_FLAG_CONTACTS;
  67. }else if((itemId & Constant.BTN_FLAG_NEWS) != 0){
  68. tag = Constant.FRAGMENT_FLAG_NEWS;
  69. }else if((itemId & Constant.BTN_FLAG_SETTING) != 0){
  70. tag = Constant.FRAGMENT_FLAG_SETTING;
  71. }
  72. setTabSelection(tag); //切换Fragment
  73. headPanel.setMiddleTitle(tag);//切换标题
  74. }
  75. private void setDefaultFirstFragment(String tag){
  76. Log.i("yan", "setDefaultFirstFragment enter... currFragTag = " + currFragTag);
  77. setTabSelection(tag);
  78. bottomPanel.defaultBtnChecked();
  79. Log.i("yan", "setDefaultFirstFragment exit...");
  80. }
  81. private void commitTransactions(String tag){
  82. if (fragmentTransaction != null && !fragmentTransaction.isEmpty()) {
  83. fragmentTransaction.commit();
  84. currFragTag = tag;
  85. fragmentTransaction = null;
  86. }
  87. }
  88. private FragmentTransaction ensureTransaction( ){
  89. if(fragmentTransaction == null){
  90. fragmentTransaction = fragmentManager.beginTransaction();
  91. fragmentTransaction
  92. .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
  93. }
  94. return fragmentTransaction;
  95. }
  96. private void attachFragment(int layout, Fragment f, String tag){
  97. if(f != null){
  98. if(f.isDetached()){
  99. ensureTransaction();
  100. fragmentTransaction.attach(f);
  101. }else if(!f.isAdded()){
  102. ensureTransaction();
  103. fragmentTransaction.add(layout, f, tag);
  104. }
  105. }
  106. }
  107. private Fragment getFragment(String tag){
  108. Fragment f = fragmentManager.findFragmentByTag(tag);
  109. if(f == null){
  110. Toast.makeText(getApplicationContext(), "fragment = null tag = " + tag, Toast.LENGTH_SHORT).show();
  111. f = BaseFragment.newInstance(getApplicationContext(), tag);
  112. }
  113. return f;
  114. }
  115. private void detachFragment(Fragment f){
  116. if(f != null && !f.isDetached()){
  117. ensureTransaction();
  118. fragmentTransaction.detach(f);
  119. }
  120. }
  121. /**切换fragment
  122. * @param tag
  123. */
  124. private  void switchFragment(String tag){
  125. if(TextUtils.equals(tag, currFragTag)){
  126. return;
  127. }
  128. //把上一个fragment detach掉
  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. * @param tag
  137. */
  138. public  void setTabSelection(String tag) {
  139. // 开启一个Fragment事务
  140. fragmentTransaction = fragmentManager.beginTransaction();
  141. /*       if(TextUtils.equals(tag, Constant.FRAGMENT_FLAG_MESSAGE)){
  142. if (messageFragment == null) {
  143. messageFragment = new MessageFragment();
  144. }
  145. }else if(TextUtils.equals(tag, Constant.FRAGMENT_FLAG_CONTACTS)){
  146. if (contactsFragment == null) {
  147. contactsFragment = new ContactsFragment();
  148. }
  149. }else if(TextUtils.equals(tag, Constant.FRAGMENT_FLAG_NEWS)){
  150. if (newsFragment == null) {
  151. newsFragment = new NewsFragment();
  152. }
  153. }else if(TextUtils.equals(tag,Constant.FRAGMENT_FLAG_SETTING)){
  154. if (settingFragment == null) {
  155. settingFragment = new SettingFragment();
  156. }
  157. }else if(TextUtils.equals(tag, Constant.FRAGMENT_FLAG_SIMPLE)){
  158. if (simpleFragment == null) {
  159. simpleFragment = new SimpleFragment();
  160. }
  161. }*/
  162. switchFragment(tag);
  163. }
  164. @Override
  165. protected void onStop() {
  166. // TODO Auto-generated method stub
  167. super.onStop();
  168. currFragTag = "";
  169. }
  170. @Override
  171. protected void onSaveInstanceState(Bundle outState) {
  172. // TODO Auto-generated method stub
  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 plaincopyprint?

  1. <span style="font-family:Comic Sans MS;font-size:18px;">package org.yanzi.bean;
  2. public class MessageBean {
  3. private int PhotoDrawableId;
  4. private String MessageName;
  5. private String MessageContent;
  6. private String MessageTime;
  7. public MessageBean(){
  8. }
  9. public MessageBean(int photoDrawableId, String messageName,
  10. String messageContent, String messageTime) {
  11. super();
  12. PhotoDrawableId = photoDrawableId;
  13. MessageName = messageName;
  14. MessageContent = messageContent;
  15. MessageTime = messageTime;
  16. }
  17. public int getPhotoDrawableId() {
  18. return PhotoDrawableId;
  19. }
  20. public void setPhotoDrawableId(int mPhotoDrawableId) {
  21. this.PhotoDrawableId = mPhotoDrawableId;
  22. }
  23. public String getMessageName() {
  24. return MessageName;
  25. }
  26. public void setMessageName(String messageName) {
  27. MessageName = messageName;
  28. }
  29. public String getMessageContent() {
  30. return MessageContent;
  31. }
  32. public void setMessageContent(String messageContent) {
  33. MessageContent = messageContent;
  34. }
  35. public String getMessageTime() {
  36. return MessageTime;
  37. }
  38. public void setMessageTime(String messageTime) {
  39. MessageTime = messageTime;
  40. }
  41. @Override
  42. public String toString() {
  43. return "MessageBean [mPhotoDrawableId=" + PhotoDrawableId
  44. + ", MessageName=" + MessageName + ", MessageContent="
  45. + MessageContent + ", MessageTime=" + MessageTime + "]";
  46. }
  47. }
  48. </span>

然后就是MessageFragment的ListView里的适配器,MessageAdapter.java

[java] view plaincopyprint?

  1. <span style="font-family:Comic Sans MS;font-size:18px;">package org.yanzi.fragment.adapter;
  2. import java.util.List;
  3. import org.yanzi.bean.MessageBean;
  4. import com.example.fragmentproject.R;
  5. import android.content.Context;
  6. import android.view.LayoutInflater;
  7. import android.view.View;
  8. import android.view.ViewGroup;
  9. import android.widget.BaseAdapter;
  10. import android.widget.ImageView;
  11. import android.widget.TextView;
  12. public class MessageAdapter extends BaseAdapter {
  13. private List<MessageBean> mListMsgBean = null;
  14. private Context mContext;
  15. private LayoutInflater mInflater;
  16. public MessageAdapter(List<MessageBean> listMsgBean, Context context){
  17. mListMsgBean = listMsgBean;
  18. mContext = context;
  19. mInflater = LayoutInflater.from(mContext);
  20. }
  21. @Override
  22. public int getCount() {
  23. // TODO Auto-generated method stub
  24. return mListMsgBean.size();
  25. }
  26. @Override
  27. public Object getItem(int position) {
  28. // TODO Auto-generated method stub
  29. return mListMsgBean.get(position);
  30. }
  31. @Override
  32. public long getItemId(int position) {
  33. // TODO Auto-generated method stub
  34. return position;
  35. }
  36. @Override
  37. public View getView(int position, View convertView, ViewGroup parent) {
  38. // TODO Auto-generated method stub
  39. View v = mInflater.inflate(R.layout.message_item_layout, null);
  40. ImageView imageView = (ImageView) v.findViewById(R.id.img_msg_item);
  41. imageView.setImageResource(mListMsgBean.get(position).getPhotoDrawableId());
  42. TextView nameMsg = (TextView)v.findViewById(R.id.name_msg_item);
  43. nameMsg.setText(mListMsgBean.get(position).getMessageName());
  44. TextView contentMsg = (TextView)v.findViewById(R.id.content_msg_item);
  45. contentMsg.setText(mListMsgBean.get(position).getMessageContent());
  46. TextView timeMsg = (TextView)v.findViewById(R.id.time_msg_item);
  47. timeMsg.setText(mListMsgBean.get(position).getMessageTime());
  48. return v;
  49. }
  50. }
  51. </span>

因为是示例,getView里没用ViewHolder。
最后是MessageFragment里通过对listview设置适配器,将MessageBean作为信息的提供者也填充到适配器里。

MessageFragment.java代码:

[java] view plaincopyprint?

  1. <span style="font-family:Comic Sans MS;font-size:18px;">package org.yanzi.fragment;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import org.yanzi.activity.MainActivity;
  5. import org.yanzi.bean.MessageBean;
  6. import org.yanzi.constant.Constant;
  7. import org.yanzi.fragment.adapter.MessageAdapter;
  8. import android.app.Activity;
  9. import android.os.Bundle;
  10. import android.util.Log;
  11. import android.view.LayoutInflater;
  12. import android.view.View;
  13. import android.view.ViewGroup;
  14. import android.widget.AdapterView;
  15. import android.widget.Toast;
  16. import android.widget.AdapterView.OnItemClickListener;
  17. import android.widget.ListView;
  18. import com.example.fragmentproject.R;
  19. public class MessageFragment extends BaseFragment {
  20. private static final String TAG = "MessageFragment";
  21. private MainActivity mMainActivity ;
  22. private ListView mListView;
  23. private MessageAdapter mMsgAdapter;
  24. private List<MessageBean> mMsgBean = new ArrayList<MessageBean>();
  25. public View onCreateView(LayoutInflater inflater, ViewGroup container,
  26. Bundle savedInstanceState) {
  27. View messageLayout = 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 = new MessageAdapter(mMsgBean, mMainActivity);
  34. mListView.setAdapter(mMsgAdapter);
  35. mListView.setOnItemClickListener(new OnItemClickListener() {
  36. @Override
  37. public void onItemClick(AdapterView<?> parent, View view,
  38. int position, long id) {
  39. // TODO Auto-generated method stub
  40. Toast.makeText(mMainActivity, mMsgBean.get(position).toString(),
  41. Toast.LENGTH_SHORT).show();
  42. }
  43. });
  44. return messageLayout;
  45. }
  46. @Override
  47. public void onAttach(Activity activity) {
  48. // TODO Auto-generated method stub
  49. super.onAttach(activity);
  50. Log.e(TAG, "onAttach-----");
  51. }
  52. @Override
  53. public void onCreate(Bundle savedInstanceState) {
  54. // TODO Auto-generated method stub
  55. super.onCreate(savedInstanceState);
  56. Log.e(TAG, "onCreate------");
  57. mMsgBean.add(new MessageBean(R.drawable.ic_photo_1, "张三", "吃饭没?", "昨天"));
  58. mMsgBean.add(new MessageBean(R.drawable.ic_photo_2, "李四", "哈哈", "昨天"));
  59. mMsgBean.add(new MessageBean(R.drawable.ic_photo_3, "小明", "吃饭没?", "昨天"));
  60. mMsgBean.add(new MessageBean(R.drawable.ic_photo_4, "王五", "吃饭没?", "昨天"));
  61. mMsgBean.add(new MessageBean(R.drawable.ic_photo_5, "Jack", "吃饭没?", "昨天"));
  62. mMsgBean.add(new MessageBean(R.drawable.ic_photo_6, "Jone", "吃饭没?", "昨天"));
  63. mMsgBean.add(new MessageBean(R.drawable.ic_photo_7, "Jone", "吃饭没?", "昨天"));
  64. mMsgBean.add(new MessageBean(R.drawable.ic_photo_8, "Jone", "吃饭没?", "昨天"));
  65. mMsgBean.add(new MessageBean(R.drawable.ic_photo_9, "Jone", "吃饭没?", "昨天"));
  66. }
  67. @Override
  68. public void onActivityCreated(Bundle savedInstanceState) {
  69. // TODO Auto-generated method stub
  70. super.onActivityCreated(savedInstanceState);
  71. Log.e(TAG, "onActivityCreated-------");
  72. }
  73. @Override
  74. public void onStart() {
  75. // TODO Auto-generated method stub
  76. super.onStart();
  77. Log.e(TAG, "onStart----->");
  78. }
  79. @Override
  80. public void onResume() {
  81. // TODO Auto-generated method stub
  82. super.onResume();
  83. Log.e(TAG, "onresume---->");
  84. MainActivity.currFragTag = Constant.FRAGMENT_FLAG_MESSAGE;
  85. }
  86. @Override
  87. public void onPause() {
  88. // TODO Auto-generated method stub
  89. super.onPause();
  90. Log.e(TAG, "onpause");
  91. }
  92. @Override
  93. public void onStop() {
  94. // TODO Auto-generated method stub
  95. super.onStop();
  96. Log.e(TAG, "onStop");
  97. }
  98. @Override
  99. public void onDestroyView() {
  100. // TODO Auto-generated method stub
  101. super.onDestroyView();
  102. Log.e(TAG, "ondestoryView");
  103. }
  104. @Override
  105. public void onDestroy() {
  106. // TODO Auto-generated method stub
  107. super.onDestroy();
  108. Log.e(TAG, "ondestory");
  109. }
  110. @Override
  111. public void onDetach() {
  112. // TODO Auto-generated method stub
  113. super.onDetach();
  114. Log.d(TAG, "onDetach------");
  115. }
  116. }
  117. </span>

最后来看下效果吧,只有MessageFragment填充了数据:

      

横屏情况下:

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

http://blog.csdn.net/yanzi1225627/article/details/30763555

时间: 2024-08-01 14:50:43

Android应用经典主界面框架之一:仿QQ (使用Fragment)的相关文章

Android应用经典主界面框架之一:仿QQ (使用Fragment, 附源码)

最近反复研究日常经典必用的几个android app,从主界面带来的交互方式入手进行分析,我将其大致分为三类.今天记录第一种方式,即主界面下面有几个tab页,最上端是标题栏,tab页和tab页之间不是通过滑动切换的,而是通过点击切换tab页.早期这种架构一直是使用tabhost+activitygroup来使用,随着fragment的出现及google官方也大力推荐使用fragment,后者大有代替前者之势.本文也使用fragment进行搭建,标题中的"经典"指这种交互经典,非本文的代

Android应用经典主界面框架之二:仿网易新闻客户端、CSDN 客户端 (Fragment ViewPager)

第二种主界面风格则是以网易新闻.凤凰新闻以及新推出的新浪博客(阅读版)为代表,使用ViewPager+Fragment,即ViewPager里适配器里放的不是一般的View,而是Fragment.所以适配器不能继承PagerAdapter,而要继承FragmentPagerAdapter,这是在android.support.v4.app.FragmentPagerAdapter包里的.有点奇葩的是,FragmentPagerAdapter只在这个包里有,在android.app.*这个包下面么

Android应用经典主界面框架之二:仿网易新闻client、CSDN client (Fragment ViewPager)

另外一种主界面风格则是以网易新闻.凤凰新闻以及新推出的新浪博客(阅读版)为代表.使用ViewPager+Fragment,即ViewPager里适配器里放的不是一般的View.而是Fragment.所以适配器不能继承PagerAdapter,而要继承FragmentPagerAdapter,这是在android.support.v4.app.FragmentPagerAdapter包里的.有点奇葩的是,FragmentPagerAdapter仅仅在这个包里有,在android.app.*这个包以

ScrollView + viewpager实现android的app主界面效果

ScrollView + viewpager实现android的app主界面效果 Android的主界面一般由两部分组成:导航栏,滑动的分屏(我自己这么叫的).其中滑动的分屏肯定是用的fragment,具体的承载的控件是viewpager.而导航分页栏用的控件就有很多了,tabhost,Scrollview或者自定义的都行. 个人认为tabhost和Scrollview都是比较好的,因为后期的可拓展性比较好,除非导航栏界面确实属于"自定义"范畴,基本上我们可以选择这两样就可以了. 其实

Android UI之自定义——最简单的仿QQ音乐歌词颜色渐变

Android UI之自定义--最简单的仿QQ音乐歌词颜色渐变 记得刚开始做android的时候,就发现QQ音乐歌词颜色渐变的效果,就在网上搜索过,但是就是没有找到满意的.今天突然用QQ音乐听歌的时候,看到歌词颜色渐变,决定来分析看看,没想到实现原来如此简单.这篇只是将最简单的歌词颜色渐变功能,不包括歌词滚动等效果. 首先来看下QQ音乐歌词界面 实现步骤 从界面上可以看出,是通过不同颜色的文本叠加所形成的视觉效果.那么android文本一般使用TextView实现,那就来试试用TextView在

Android Tab类型主界面 Fragment+TabPageIndicator+ViewPager

文章地址: Android项目Tab类型主界面大总结 Fragment+TabPageIndicator+ViewPager 1.使用ViewPager + PagerAdapter 每个页面的内容都在一个Activity中,维护起来会比较麻烦 2.FragmentManager + Fragment 每个页面的内容分开,但是只能点击按钮换页 3.ViewPager + FragmentPagerAdapter 综合前两种方式,比较好 4.TabPageIndicator + ViewPager

Android代码优化,主界面卡住

============问题描述============ 程序打开进入MainActivity,其实MainActivity没做什么操作,MainActivity是加载sildingmenu+actionbar+fragment,具体业务操作是在fragment里面进行的,但是程序会在MainActivity那里显示MainActivity的xml,其实什么都没有,但是会停留1~5秒左右,才去显示fragment的xml,感觉很奇怪,如果卡也是会卡在fragment那里,结果fragment又不

Android AsynTask更新主界面

虽然今天礼拜六还在加班,但是在等接口,所以还是有很多时间来自己学点东西的,所以就接着昨天的来.今天继续学的是不通过主线程来更新主线程的界面的问题. 昨天是用的开启线程调用Handler来更新线程,那个效果用的方面比较广阔,那么我们还有几种方法将耗时的代码剥离出来不在主线程里面执行,然后通过各种方法来更新UIThread .今天学到的是利用AsynTask来更新主界面的空间. 然后我们先来说说AsynTask: AsynTask:Asynchronous Task(异步任务) 使用过AsyncTa

[android] 新闻客户端主界面部分

当我们使用activity加fragment的时候,每个界面都要建立一个fragment,每个fragment里面都要重写onCreate(),onCreateView(),onActivityCreated(),方法,我们新建一个基类BaseFragment来重写这些方法 BaseFragment需要定义一个抽象方法initView(),用来初始化View对象,需要子类去实现,我们父类把LayoutInflater对象传进去 BaseFragment需要定义一个抽象方法initData(),用