仿qq界面

最近反覆研究日常經典必用的幾個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就作了這樣一件事:

  1. <span style="font-family:Comic Sans MS;font-size:18px;">package org.yanzi.ui;  
  2.   
  3. import org.yanzi.constant.Constant;  
  4.   
  5. import android.content.Context;  
  6. import android.graphics.Color;  
  7. import android.util.AttributeSet;  
  8. import android.view.LayoutInflater;  
  9. import android.view.MotionEvent;  
  10. import android.view.View;  
  11. import android.view.ViewGroup;  
  12. import android.widget.ImageView;  
  13. import android.widget.LinearLayout;  
  14. import android.widget.TextView;  
  15.   
  16. import com.example.fragmentproject.R;  
  17.   
  18.   
  19. public class ImageText extends LinearLayout{  
  20.     private Context mContext = null;  
  21.     private ImageView mImageView = null;  
  22.     private TextView mTextView = null;  
  23.     private final static int DEFAULT_IMAGE_WIDTH = 64;  
  24.     private final static int DEFAULT_IMAGE_HEIGHT = 64;  
  25.     private int CHECKED_COLOR = Color.rgb(29118199); //選中藍色  
  26.     private int UNCHECKED_COLOR = Color.GRAY;   //自然灰色  
  27.     public ImageText(Context context) {  
  28.         super(context);  
  29.         // TODO Auto-generated constructor stub  
  30.         mContext = context;  
  31.     }  
  32.   
  33.     public ImageText(Context context, AttributeSet attrs) {  
  34.         super(context, attrs);  
  35.         // TODO Auto-generated constructor stub  
  36.         mContext = context;  
  37.         LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);  
  38.         View parentView = inflater.inflate(R.layout.image_text_layout, thistrue);  
  39.         mImageView = (ImageView)findViewById(R.id.image_iamge_text);  
  40.         mTextView = (TextView)findViewById(R.id.text_iamge_text);  
  41.     }  
  42.     public void setImage(int id){  
  43.         if(mImageView != null){  
  44.             mImageView.setImageResource(id);  
  45.             setImageSize(DEFAULT_IMAGE_WIDTH, DEFAULT_IMAGE_HEIGHT);  
  46.         }  
  47.     }  
  48.   
  49.     public void setText(String s){  
  50.         if(mTextView != null){  
  51.             mTextView.setText(s);  
  52.             mTextView.setTextColor(UNCHECKED_COLOR);  
  53.         }  
  54.     }  
  55.   
  56.     @Override  
  57.     public boolean onInterceptTouchEvent(MotionEvent ev) {  
  58.         // TODO Auto-generated method stub  
  59.         return true;  
  60.     }  
  61.     private void setImageSize(int w, int h){  
  62.         if(mImageView != null){  
  63.             ViewGroup.LayoutParams params = mImageView.getLayoutParams();  
  64.             params.width = w;  
  65.             params.height = h;  
  66.             mImageView.setLayoutParams(params);  
  67.         }  
  68.     }  
  69.     public void setChecked(int itemID){  
  70.         if(mTextView != null){  
  71.             mTextView.setTextColor(CHECKED_COLOR);  
  72.         }  
  73.         int checkDrawableId = -1;  
  74.         switch (itemID){  
  75.         case Constant.BTN_FLAG_MESSAGE:  
  76.             checkDrawableId = R.drawable.message_selected;  
  77.             break;  
  78.         case Constant.BTN_FLAG_CONTACTS:  
  79.             checkDrawableId = R.drawable.contacts_selected;  
  80.             break;  
  81.         case Constant.BTN_FLAG_NEWS:  
  82.             checkDrawableId = R.drawable.news_selected;  
  83.             break;  
  84.         case Constant.BTN_FLAG_SETTING:  
  85.             checkDrawableId = R.drawable.setting_selected;  
  86.             break;  
  87.         default:break;  
  88.         }  
  89.         if(mImageView != null){  
  90.             mImageView.setImageResource(checkDrawableId);  
  91.         }  
  92.     }  
  93.   
  94.   
  95.       
  96.   
  97.   
  98. }  
  99. </span>  

對應的佈局:

  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.   
  7.     <ImageView  
  8.         android:id="@+id/image_iamge_text"  
  9.         android:layout_width="wrap_content"  
  10.         android:layout_height="wrap_content"  
  11.         android:layout_gravity="center_horizontal" />  
  12.   
  13.     <TextView  
  14.         android:id="@+id/text_iamge_text"  
  15.         android:layout_width="wrap_content"  
  16.         android:layout_height="wrap_content"  
  17.         android:layout_gravity="center_horizontal" />  
  18.   
  19. </LinearLayout></span>  

代碼裏用到了Constant.java,這裏面放的都是常量:

  1. <span style="font-family:Comic Sans MS;font-size:18px;">package org.yanzi.constant;  
  2.   
  3. public class Constant {  
  4.     //Btn的標識  
  5.     public static final int BTN_FLAG_MESSAGE = 0x01;  
  6.     public static final int BTN_FLAG_CONTACTS = 0x01 << 1;  
  7.     public static final int BTN_FLAG_NEWS = 0x01 << 2;  
  8.     public static final int BTN_FLAG_SETTING = 0x01 << 3;  
  9.       
  10.     //Fragment的標識  
  11.     public static final String FRAGMENT_FLAG_MESSAGE = "消息";   
  12.     public static final String FRAGMENT_FLAG_CONTACTS = "聯繫人";   
  13.     public static final String FRAGMENT_FLAG_NEWS = "新聞";   
  14.     public static final String FRAGMENT_FLAG_SETTING = "設置";   
  15.     public static final String FRAGMENT_FLAG_SIMPLE = "simple";   
  16.       
  17.       
  18. }  
  19. </span>  
第一排是複合Button的標識,下面的string類型的是將來創建fragment的標識。

完成了ImageText之後,下面就是將4個這樣的控件放到一個佈局裏。爲了控制方便,我們將底部欄抽象爲一個對象BottomControlPanel.java,這樣在維護底部欄相關內容時直接找他就行了。BottomControlPanel繼承自RelativeLayout,先來看它的佈局:

bottom_panel_layout.xml

  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.   
  10.     <org.yanzi.ui.ImageText  
  11.         android:id="@+id/btn_message"  
  12.         android:layout_width="wrap_content"  
  13.         android:layout_height="wrap_content"  
  14.         android:layout_alignParentLeft="true" />  
  15.   
  16.     <org.yanzi.ui.ImageText  
  17.         android:id="@+id/btn_contacts"  
  18.         android:layout_width="wrap_content"  
  19.         android:layout_height="wrap_content"  
  20.         android:layout_toRightOf="@id/btn_message" />  
  21.   
  22.     <org.yanzi.ui.ImageText  
  23.         android:id="@+id/btn_news"  
  24.         android:layout_width="wrap_content"  
  25.         android:layout_height="wrap_content"  
  26.         android:layout_toRightOf="@id/btn_contacts" />  
  27.   
  28.     <org.yanzi.ui.ImageText  
  29.         android:id="@+id/btn_setting"  
  30.         android:layout_width="wrap_content"  
  31.         android:layout_height="wrap_content"  
  32.         android:layout_alignParentRight="true" />  
  33.   
  34. </org.yanzi.ui.BottomControlPanel></span>  
對應的java文件:

BottomControlPanel.java

  1. <span style="font-family:Comic Sans MS;font-size:18px;">package org.yanzi.ui;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.List;  
  5.   
  6. import org.yanzi.constant.Constant;  
  7.   
  8. import android.content.Context;  
  9. import android.graphics.Color;  
  10. import android.util.AttributeSet;  
  11. import android.util.Log;  
  12. import android.view.View;  
  13. import android.widget.RelativeLayout;  
  14.   
  15. import com.example.fragmentproject.R;  
  16.   
  17. public class BottomControlPanel extends RelativeLayout implements View.OnClickListener {  
  18.     private Context mContext;  
  19.     private ImageText mMsgBtn = null;  
  20.     private ImageText mContactsBtn = null;  
  21.     private ImageText mNewsBtn = null;  
  22.     private ImageText mSettingBtn = null;  
  23.     private int DEFALUT_BACKGROUND_COLOR = Color.rgb(243243243); //Color.rgb(192, 192, 192)  
  24.     private BottomPanelCallback mBottomCallback = null;  
  25.     private List<ImageText> viewList = new ArrayList<ImageText>();  
  26.   
  27.     public interface BottomPanelCallback{  
  28.         public void onBottomPanelClick(int itemId);  
  29.     }  
  30.     public BottomControlPanel(Context context, AttributeSet attrs) {  
  31.         super(context, attrs);  
  32.         // TODO Auto-generated constructor stub  
  33.     }  
  34.     @Override  
  35.     protected void onFinishInflate() {  
  36.         // TODO Auto-generated method stub  
  37.         mMsgBtn = (ImageText)findViewById(R.id.btn_message);  
  38.         mContactsBtn = (ImageText)findViewById(R.id.btn_contacts);  
  39.         mNewsBtn = (ImageText)findViewById(R.id.btn_news);  
  40.         mSettingBtn = (ImageText)findViewById(R.id.btn_setting);  
  41.         setBackgroundColor(DEFALUT_BACKGROUND_COLOR);  
  42.         viewList.add(mMsgBtn);  
  43.         viewList.add(mContactsBtn);  
  44.         viewList.add(mNewsBtn);  
  45.         viewList.add(mSettingBtn);  
  46.   
  47.     }  
  48.     public void initBottomPanel(){  
  49.         if(mMsgBtn != null){  
  50.             mMsgBtn.setImage(R.drawable.message_unselected);  
  51.             mMsgBtn.setText("消息");  
  52.         }  
  53.         if(mContactsBtn != null){  
  54.             mContactsBtn.setImage(R.drawable.contacts_unselected);  
  55.             mContactsBtn.setText("聯繫人");  
  56.         }  
  57.         if(mNewsBtn != null){  
  58.             mNewsBtn.setImage(R.drawable.news_unselected);  
  59.             mNewsBtn.setText("新聞");  
  60.         }  
  61.         if(mSettingBtn != null){  
  62.             mSettingBtn.setImage(R.drawable.setting_unselected);  
  63.             mSettingBtn.setText("設置");  
  64.         }  
  65.         setBtnListener();  
  66.   
  67.     }  
  68.     private void setBtnListener(){  
  69.         int num = this.getChildCount();  
  70.         for(int i = 0; i < num; i++){  
  71.             View v = getChildAt(i);  
  72.             if(v != null){  
  73.                 v.setOnClickListener(this);  
  74.             }  
  75.         }  
  76.     }  
  77.     public void setBottomCallback(BottomPanelCallback bottomCallback){  
  78.         mBottomCallback = bottomCallback;  
  79.     }  
  80.     @Override  
  81.     public void onClick(View v) {  
  82.         // TODO Auto-generated method stub  
  83.         initBottomPanel();  
  84.         int index = -1;  
  85.         switch(v.getId()){  
  86.         case R.id.btn_message:  
  87.             index = Constant.BTN_FLAG_MESSAGE;  
  88.             mMsgBtn.setChecked(Constant.BTN_FLAG_MESSAGE);  
  89.             break;  
  90.         case R.id.btn_contacts:  
  91.             index = Constant.BTN_FLAG_CONTACTS;  
  92.             mContactsBtn.setChecked(Constant.BTN_FLAG_CONTACTS);  
  93.             break;  
  94.         case R.id.btn_news:  
  95.             index = Constant.BTN_FLAG_NEWS;  
  96.             mNewsBtn.setChecked(Constant.BTN_FLAG_NEWS);  
  97.             break;  
  98.         case R.id.btn_setting:  
  99.             index = Constant.BTN_FLAG_SETTING;  
  100.             mSettingBtn.setChecked(Constant.BTN_FLAG_SETTING);  
  101.             break;  
  102.         default:break;  
  103.         }  
  104.         if(mBottomCallback != null){  
  105.             mBottomCallback.onBottomPanelClick(index);  
  106.         }  
  107.     }  
  108.     public void defaultBtnChecked(){  
  109.         if(mMsgBtn != null){  
  110.             mMsgBtn.setChecked(Constant.BTN_FLAG_MESSAGE);  
  111.         }  
  112.     }  
  113.     @Override  
  114.     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {  
  115.         // TODO Auto-generated method stub  
  116.         super.onLayout(changed, left, top, right, bottom);  
  117.         layoutItems(left, top, right, bottom);  
  118.     }  
  119.     /**最左邊和最右邊的view由母佈局的padding進行控制位置。這裏需對第2、3個view的位置重新設置 
  120.      * @param left 
  121.      * @param top 
  122.      * @param right 
  123.      * @param bottom 
  124.      */  
  125.     private void layoutItems(int left, int top, int right, int bottom){  
  126.         int n = getChildCount();  
  127.         if(n == 0){  
  128.             return;  
  129.         }  
  130.         int paddingLeft = getPaddingLeft();  
  131.         int paddingRight = getPaddingRight();  
  132.         Log.i("yanguoqi""paddingLeft = " + paddingLeft + " paddingRight = " + paddingRight);  
  133.         int width = right - left;  
  134.         int height = bottom - top;  
  135.         Log.i("yanguoqi""width = " + width + " height = " + height);  
  136.         int allViewWidth = 0;  
  137.         for(int i = 0; i< n; i++){  
  138.             View v = getChildAt(i);  
  139.             Log.i("yanguoqi""v.getWidth() = " + v.getWidth());  
  140.             allViewWidth += v.getWidth();  
  141.         }  
  142.         int blankWidth = (width - allViewWidth - paddingLeft - paddingRight) / (n - 1);  
  143.         Log.i("yanguoqi""blankV = " + blankWidth );  
  144.   
  145.         LayoutParams params1 = (LayoutParams) viewList.get(1).getLayoutParams();  
  146.         params1.leftMargin = blankWidth;  
  147.         viewList.get(1).setLayoutParams(params1);  
  148.   
  149.         LayoutParams params2 = (LayoutParams) viewList.get(2).getLayoutParams();  
  150.         params2.leftMargin = blankWidth;  
  151.         viewList.get(2).setLayoutParams(params2);  
  152.     }  
  153.   
  154.   
  155.   
  156. }  
  157. </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

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

佈局文件head_panel_layout.xml

  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.           
  19. </org.yanzi.ui.HeadControlPanel>  
  20. </span>  

三、總控中心Activity和Fragment

先交代下Fragment的使用大致分兩種,一種是將Fragment作爲一個View寫死在佈局中,佈局裏使用android:name來告訴它對應的是哪個實體Fragment。這種添加fragment的方式不能delete和replace掉。另一種是通過獲得activity的fragmentmanager和fragmentTransaction和進行動態的添加。這種方式更加靈活,一般使用此種方法。

先看Activity的佈局activity_main.xml:

  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.   
  8.     <include  
  9.         android:id="@+id/bottom_layout"  
  10.         layout="@layout/bottom_panel_layout" />  
  11.   
  12.     <View  
  13.         android:layout_width="match_parent"  
  14.         android:layout_height="1dip"  
  15.         android:layout_above="@id/bottom_layout"  
  16.         android:background="#FFE7E7E7" />  
  17.   
  18.     <include  
  19.         android:id="@+id/head_layout"  
  20.         layout="@layout/head_panel_layout" />  
  21.   
  22.     <View  
  23.         android:layout_width="match_parent"  
  24.         android:layout_height="1dip"  
  25.         android:layout_below="@id/head_layout"  
  26.         android:background="#FFE7E7E7" />  
  27.     <FrameLayout  
  28.         android:id="@+id/fragment_content"  
  29.         android:layout_width="match_parent"  
  30.         android:layout_height="wrap_content"  
  31.         android:layout_below="@id/head_layout"  
  32.         android:layout_above="@id/bottom_layout" >  
  33.     </FrameLayout>  
  34.   
  35. </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代碼:

  1. <span style="font-family:Comic Sans MS;font-size:18px;">package org.yanzi.activity;  
  2.   
  3. import org.yanzi.constant.Constant;  
  4. import org.yanzi.fragment.BaseFragment;  
  5. import org.yanzi.fragment.ContactsFragment;  
  6. import org.yanzi.fragment.MessageFragment;  
  7. import org.yanzi.fragment.NewsFragment;  
  8. import org.yanzi.fragment.SettingFragment;  
  9. import org.yanzi.ui.BottomControlPanel;  
  10. import org.yanzi.ui.BottomControlPanel.BottomPanelCallback;  
  11. import org.yanzi.ui.HeadControlPanel;  
  12.   
  13. import android.app.Activity;  
  14. import android.app.Fragment;  
  15. import android.app.FragmentManager;  
  16. import android.app.FragmentTransaction;  
  17. import android.os.Bundle;  
  18. import android.text.TextUtils;  
  19. import android.util.Log;  
  20. import android.view.Menu;  
  21. import android.widget.Toast;  
  22.   
  23. import com.example.fragmentproject.R;  
  24.   
  25. public class MainActivity extends Activity implements BottomPanelCallback {  
  26.     BottomControlPanel bottomPanel = null;  
  27.     HeadControlPanel headPanel = null;  
  28.       
  29.     private FragmentManager fragmentManager = null;  
  30.     private FragmentTransaction fragmentTransaction = null;  
  31.       
  32. /*  private MessageFragment messageFragment; 
  33.     private ContactsFragment contactsFragment; 
  34.     private NewsFragment newsFragment; 
  35.     private SettingFragment settingFragment;*/  
  36.       
  37.     public static String currFragTag = "";  
  38.     @Override  
  39.     protected void onCreate(Bundle savedInstanceState) {  
  40.         super.onCreate(savedInstanceState);  
  41.         setContentView(R.layout.activity_main);  
  42.         initUI();  
  43.         fragmentManager = getFragmentManager();  
  44.         setDefaultFirstFragment(Constant.FRAGMENT_FLAG_MESSAGE);  
  45.     }  
  46.   
  47.     @Override  
  48.     public boolean onCreateOptionsMenu(Menu menu) {  
  49.         // Inflate the menu; this adds items to the action bar if it is present.  
  50.         getMenuInflater().inflate(R.menu.main, menu);  
  51.         return true;  
  52.     }  
  53.     private void initUI(){  
  54.         bottomPanel = (BottomControlPanel)findViewById(R.id.bottom_layout);  
  55.         if(bottomPanel != null){  
  56.             bottomPanel.initBottomPanel();  
  57.             bottomPanel.setBottomCallback(this);  
  58.         }  
  59.         headPanel = (HeadControlPanel)findViewById(R.id.head_layout);  
  60.         if(headPanel != null){  
  61.             headPanel.initHeadPanel();  
  62.         }  
  63.     }  
  64.   
  65.     /* 處理BottomControlPanel的回調 
  66.      * @see org.yanzi.ui.BottomControlPanel.BottomPanelCallback#onBottomPanelClick(int) 
  67.      */  
  68.     @Override  
  69.     public void onBottomPanelClick(int itemId) {  
  70.         // TODO Auto-generated method stub  
  71.         String tag = "";  
  72.         if((itemId & Constant.BTN_FLAG_MESSAGE) != 0){  
  73.             tag = Constant.FRAGMENT_FLAG_MESSAGE;  
  74.         }else if((itemId & Constant.BTN_FLAG_CONTACTS) != 0){  
  75.             tag = Constant.FRAGMENT_FLAG_CONTACTS;  
  76.         }else if((itemId & Constant.BTN_FLAG_NEWS) != 0){  
  77.             tag = Constant.FRAGMENT_FLAG_NEWS;  
  78.         }else if((itemId & Constant.BTN_FLAG_SETTING) != 0){  
  79.             tag = Constant.FRAGMENT_FLAG_SETTING;  
  80.         }  
  81.         setTabSelection(tag); //切換Fragment  
  82.         headPanel.setMiddleTitle(tag);//切換標題   
  83.     }  
  84.       
  85.     private void setDefaultFirstFragment(String tag){  
  86.         Log.i("yan""setDefaultFirstFragment enter... currFragTag = " + currFragTag);  
  87.         setTabSelection(tag);  
  88.         bottomPanel.defaultBtnChecked();  
  89.         Log.i("yan""setDefaultFirstFragment exit...");  
  90.     }  
  91.       
  92.     private void commitTransactions(String tag){  
  93.         if (fragmentTransaction != null && !fragmentTransaction.isEmpty()) {  
  94.             fragmentTransaction.commit();  
  95.             currFragTag = tag;  
  96.             fragmentTransaction = null;  
  97.         }  
  98.     }  
  99.       
  100.     private FragmentTransaction ensureTransaction( ){  
  101.         if(fragmentTransaction == null){  
  102.             fragmentTransaction = fragmentManager.beginTransaction();  
  103.             fragmentTransaction  
  104.             .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);  
  105.               
  106.         }  
  107.         return fragmentTransaction;  
  108.           
  109.     }  
  110.       
  111.     private void attachFragment(int layout, Fragment f, String tag){  
  112.         if(f != null){  
  113.             if(f.isDetached()){  
  114.                 ensureTransaction();  
  115.                 fragmentTransaction.attach(f);  
  116.                   
  117.             }else if(!f.isAdded()){  
  118.                 ensureTransaction();  
  119.                 fragmentTransaction.add(layout, f, tag);  
  120.             }  
  121.         }  
  122.     }  
  123.       
  124.     private Fragment getFragment(String tag){  
  125.           
  126.         Fragment f = fragmentManager.findFragmentByTag(tag);  
  127.           
  128.         if(f == null){  
  129.             Toast.makeText(getApplicationContext(), "fragment = null tag = " + tag, Toast.LENGTH_SHORT).show();  
  130.             f = BaseFragment.newInstance(getApplicationContext(), tag);  
  131.         }  
  132.         return f;  
  133.           
  134.     }  
  135.     private void detachFragment(Fragment f){  
  136.           
  137.         if(f != null && !f.isDetached()){  
  138.             ensureTransaction();  
  139.             fragmentTransaction.detach(f);  
  140.         }  
  141.     }  
  142.     /**切換fragment  
  143.      * @param tag 
  144.      */  
  145.     private  void switchFragment(String tag){  
  146.         if(TextUtils.equals(tag, currFragTag)){  
  147.             return;  
  148.         }  
  149.         //把上一個fragment detach掉   
  150.         if(currFragTag != null && !currFragTag.equals("")){  
  151.             detachFragment(getFragment(currFragTag));  
  152.         }  
  153.         attachFragment(R.id.fragment_content, getFragment(tag), tag);  
  154.         commitTransactions( tag);  
  155.     }   
  156.       
  157.     /**設置選中的Tag 
  158.      * @param tag 
  159.      */  
  160.     public  void setTabSelection(String tag) {  
  161.         // 開啓一個Fragment事務  
  162.         fragmentTransaction = fragmentManager.beginTransaction();  
  163. /*       if(TextUtils.equals(tag, Constant.FRAGMENT_FLAG_MESSAGE)){ 
  164.            if (messageFragment == null) { 
  165.                 messageFragment = new MessageFragment(); 
  166.             }  
  167.              
  168.         }else if(TextUtils.equals(tag, Constant.FRAGMENT_FLAG_CONTACTS)){ 
  169.             if (contactsFragment == null) { 
  170.                 contactsFragment = new ContactsFragment(); 
  171.             }  
  172.              
  173.         }else if(TextUtils.equals(tag, Constant.FRAGMENT_FLAG_NEWS)){ 
  174.             if (newsFragment == null) { 
  175.                 newsFragment = new NewsFragment(); 
  176.             } 
  177.              
  178.         }else if(TextUtils.equals(tag,Constant.FRAGMENT_FLAG_SETTING)){ 
  179.             if (settingFragment == null) { 
  180.                 settingFragment = new SettingFragment(); 
  181.             } 
  182.         }else if(TextUtils.equals(tag, Constant.FRAGMENT_FLAG_SIMPLE)){ 
  183.             if (simpleFragment == null) { 
  184.                 simpleFragment = new SimpleFragment(); 
  185.             }  
  186.              
  187.         }*/  
  188.          switchFragment(tag);  
  189.            
  190.     }  
  191.   
  192.     @Override  
  193.     protected void onStop() {  
  194.         // TODO Auto-generated method stub  
  195.         super.onStop();  
  196.         currFragTag = "";  
  197.     }  
  198.   
  199.     @Override  
  200.     protected void onSaveInstanceState(Bundle outState) {  
  201.         // TODO Auto-generated method stub  
  202.     }  
  203.   
  204. }  
  205. </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

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

  1. <span style="font-family:Comic Sans MS;font-size:18px;">package org.yanzi.fragment.adapter;  
  2.   
  3. import java.util.List;  
  4.   
  5. import org.yanzi.bean.MessageBean;  
  6.   
  7. import com.example.fragmentproject.R;  
  8.   
  9. import android.content.Context;  
  10. import android.view.LayoutInflater;  
  11. import android.view.View;  
  12. import android.view.ViewGroup;  
  13. import android.widget.BaseAdapter;  
  14. import android.widget.ImageView;  
  15. import android.widget.TextView;  
  16.   
  17. public class MessageAdapter extends BaseAdapter {  
  18.     private List<MessageBean> mListMsgBean = null;  
  19.     private Context mContext;  
  20.     private LayoutInflater mInflater;  
  21.     public MessageAdapter(List<MessageBean> listMsgBean, Context context){  
  22.         mListMsgBean = listMsgBean;  
  23.         mContext = context;  
  24.         mInflater = LayoutInflater.from(mContext);  
  25.     }  
  26.     @Override  
  27.     public int getCount() {  
  28.         // TODO Auto-generated method stub  
  29.         return mListMsgBean.size();  
  30.     }  
  31.   
  32.     @Override  
  33.     public Object getItem(int position) {  
  34.         // TODO Auto-generated method stub  
  35.         return mListMsgBean.get(position);  
  36.     }  
  37.   
  38.     @Override  
  39.     public long getItemId(int position) {  
  40.         // TODO Auto-generated method stub  
  41.         return position;  
  42.     }  
  43.   
  44.     @Override  
  45.     public View getView(int position, View convertView, ViewGroup parent) {  
  46.         // TODO Auto-generated method stub  
  47.         View v = mInflater.inflate(R.layout.message_item_layout, null);  
  48.           
  49.         ImageView imageView = (ImageView) v.findViewById(R.id.img_msg_item);  
  50.         imageView.setImageResource(mListMsgBean.get(position).getPhotoDrawableId());  
  51.           
  52.         TextView nameMsg = (TextView)v.findViewById(R.id.name_msg_item);  
  53.         nameMsg.setText(mListMsgBean.get(position).getMessageName());  
  54.   
  55.         TextView contentMsg = (TextView)v.findViewById(R.id.content_msg_item);  
  56.         contentMsg.setText(mListMsgBean.get(position).getMessageContent());  
  57.           
  58.         TextView timeMsg = (TextView)v.findViewById(R.id.time_msg_item);  
  59.         timeMsg.setText(mListMsgBean.get(position).getMessageTime());  
  60.   
  61.         return v;  
  62.     }  
  63.   
  64. }  
  65. </span>  
因爲是示例,getView裏沒用ViewHolder。
最後是MessageFragment裏通過對listview設置適配器,將MessageBean作爲信息的提供者也填充到適配器裏。

MessageFragment.java代碼:

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

      


橫屏情況下:


--------------本文系原創,轉載請註明作者yanzi1225627

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章