最近反覆研究日常經典必用的幾個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就作了這樣一件事:
- <span style="font-family:Comic Sans MS;font-size:18px;">package org.yanzi.ui;
- import org.yanzi.constant.Constant;
- import android.content.Context;
- import android.graphics.Color;
- import android.util.AttributeSet;
- import android.view.LayoutInflater;
- import android.view.MotionEvent;
- import android.view.View;
- import android.view.ViewGroup;
- import android.widget.ImageView;
- import android.widget.LinearLayout;
- import android.widget.TextView;
- import com.example.fragmentproject.R;
- public class ImageText extends LinearLayout{
- private Context mContext = null;
- private ImageView mImageView = null;
- private TextView mTextView = null;
- private final static int DEFAULT_IMAGE_WIDTH = 64;
- private final static int DEFAULT_IMAGE_HEIGHT = 64;
- private int CHECKED_COLOR = Color.rgb(29, 118, 199); //選中藍色
- private int UNCHECKED_COLOR = Color.GRAY; //自然灰色
- public ImageText(Context context) {
- super(context);
- // TODO Auto-generated constructor stub
- mContext = context;
- }
- public ImageText(Context context, AttributeSet attrs) {
- super(context, attrs);
- // TODO Auto-generated constructor stub
- mContext = context;
- LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- View parentView = inflater.inflate(R.layout.image_text_layout, this, true);
- mImageView = (ImageView)findViewById(R.id.image_iamge_text);
- mTextView = (TextView)findViewById(R.id.text_iamge_text);
- }
- public void setImage(int id){
- if(mImageView != null){
- mImageView.setImageResource(id);
- setImageSize(DEFAULT_IMAGE_WIDTH, DEFAULT_IMAGE_HEIGHT);
- }
- }
- public void setText(String s){
- if(mTextView != null){
- mTextView.setText(s);
- mTextView.setTextColor(UNCHECKED_COLOR);
- }
- }
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- // TODO Auto-generated method stub
- return true;
- }
- private void setImageSize(int w, int h){
- if(mImageView != null){
- ViewGroup.LayoutParams params = mImageView.getLayoutParams();
- params.width = w;
- params.height = h;
- mImageView.setLayoutParams(params);
- }
- }
- public void setChecked(int itemID){
- if(mTextView != null){
- mTextView.setTextColor(CHECKED_COLOR);
- }
- int checkDrawableId = -1;
- switch (itemID){
- case Constant.BTN_FLAG_MESSAGE:
- checkDrawableId = R.drawable.message_selected;
- break;
- case Constant.BTN_FLAG_CONTACTS:
- checkDrawableId = R.drawable.contacts_selected;
- break;
- case Constant.BTN_FLAG_NEWS:
- checkDrawableId = R.drawable.news_selected;
- break;
- case Constant.BTN_FLAG_SETTING:
- checkDrawableId = R.drawable.setting_selected;
- break;
- default:break;
- }
- if(mImageView != null){
- mImageView.setImageResource(checkDrawableId);
- }
- }
- }
- </span>
對應的佈局:
- <span style="font-family:Comic Sans MS;font-size:18px;"><?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical" >
- <ImageView
- android:id="@+id/image_iamge_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal" />
- <TextView
- android:id="@+id/text_iamge_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal" />
- </LinearLayout></span>
代碼裏用到了Constant.java,這裏面放的都是常量:
- <span style="font-family:Comic Sans MS;font-size:18px;">package org.yanzi.constant;
- public class Constant {
- //Btn的標識
- public static final int BTN_FLAG_MESSAGE = 0x01;
- public static final int BTN_FLAG_CONTACTS = 0x01 << 1;
- public static final int BTN_FLAG_NEWS = 0x01 << 2;
- public static final int BTN_FLAG_SETTING = 0x01 << 3;
- //Fragment的標識
- public static final String FRAGMENT_FLAG_MESSAGE = "消息";
- public static final String FRAGMENT_FLAG_CONTACTS = "聯繫人";
- public static final String FRAGMENT_FLAG_NEWS = "新聞";
- public static final String FRAGMENT_FLAG_SETTING = "設置";
- public static final String FRAGMENT_FLAG_SIMPLE = "simple";
- }
- </span>
完成了ImageText之後,下面就是將4個這樣的控件放到一個佈局裏。爲了控制方便,我們將底部欄抽象爲一個對象BottomControlPanel.java,這樣在維護底部欄相關內容時直接找他就行了。BottomControlPanel繼承自RelativeLayout,先來看它的佈局:
bottom_panel_layout.xml
- <span style="font-family:Comic Sans MS;font-size:18px;"><?xml version="1.0" encoding="utf-8"?>
- <org.yanzi.ui.BottomControlPanel xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="60dp"
- android:layout_alignParentBottom="true"
- android:gravity="center_vertical"
- android:paddingLeft="20dp"
- android:paddingRight="20dp" >
- <org.yanzi.ui.ImageText
- android:id="@+id/btn_message"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentLeft="true" />
- <org.yanzi.ui.ImageText
- android:id="@+id/btn_contacts"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_toRightOf="@id/btn_message" />
- <org.yanzi.ui.ImageText
- android:id="@+id/btn_news"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_toRightOf="@id/btn_contacts" />
- <org.yanzi.ui.ImageText
- android:id="@+id/btn_setting"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentRight="true" />
- </org.yanzi.ui.BottomControlPanel></span>
BottomControlPanel.java
- <span style="font-family:Comic Sans MS;font-size:18px;">package org.yanzi.ui;
- import java.util.ArrayList;
- import java.util.List;
- import org.yanzi.constant.Constant;
- import android.content.Context;
- import android.graphics.Color;
- import android.util.AttributeSet;
- import android.util.Log;
- import android.view.View;
- import android.widget.RelativeLayout;
- import com.example.fragmentproject.R;
- public class BottomControlPanel extends RelativeLayout implements View.OnClickListener {
- private Context mContext;
- private ImageText mMsgBtn = null;
- private ImageText mContactsBtn = null;
- private ImageText mNewsBtn = null;
- private ImageText mSettingBtn = null;
- private int DEFALUT_BACKGROUND_COLOR = Color.rgb(243, 243, 243); //Color.rgb(192, 192, 192)
- private BottomPanelCallback mBottomCallback = null;
- private List<ImageText> viewList = new ArrayList<ImageText>();
- public interface BottomPanelCallback{
- public void onBottomPanelClick(int itemId);
- }
- public BottomControlPanel(Context context, AttributeSet attrs) {
- super(context, attrs);
- // TODO Auto-generated constructor stub
- }
- @Override
- protected void onFinishInflate() {
- // TODO Auto-generated method stub
- mMsgBtn = (ImageText)findViewById(R.id.btn_message);
- mContactsBtn = (ImageText)findViewById(R.id.btn_contacts);
- mNewsBtn = (ImageText)findViewById(R.id.btn_news);
- mSettingBtn = (ImageText)findViewById(R.id.btn_setting);
- setBackgroundColor(DEFALUT_BACKGROUND_COLOR);
- viewList.add(mMsgBtn);
- viewList.add(mContactsBtn);
- viewList.add(mNewsBtn);
- viewList.add(mSettingBtn);
- }
- public void initBottomPanel(){
- if(mMsgBtn != null){
- mMsgBtn.setImage(R.drawable.message_unselected);
- mMsgBtn.setText("消息");
- }
- if(mContactsBtn != null){
- mContactsBtn.setImage(R.drawable.contacts_unselected);
- mContactsBtn.setText("聯繫人");
- }
- if(mNewsBtn != null){
- mNewsBtn.setImage(R.drawable.news_unselected);
- mNewsBtn.setText("新聞");
- }
- if(mSettingBtn != null){
- mSettingBtn.setImage(R.drawable.setting_unselected);
- mSettingBtn.setText("設置");
- }
- setBtnListener();
- }
- private void setBtnListener(){
- int num = this.getChildCount();
- for(int i = 0; i < num; i++){
- View v = getChildAt(i);
- if(v != null){
- v.setOnClickListener(this);
- }
- }
- }
- public void setBottomCallback(BottomPanelCallback bottomCallback){
- mBottomCallback = bottomCallback;
- }
- @Override
- public void onClick(View v) {
- // TODO Auto-generated method stub
- initBottomPanel();
- int index = -1;
- switch(v.getId()){
- case R.id.btn_message:
- index = Constant.BTN_FLAG_MESSAGE;
- mMsgBtn.setChecked(Constant.BTN_FLAG_MESSAGE);
- break;
- case R.id.btn_contacts:
- index = Constant.BTN_FLAG_CONTACTS;
- mContactsBtn.setChecked(Constant.BTN_FLAG_CONTACTS);
- break;
- case R.id.btn_news:
- index = Constant.BTN_FLAG_NEWS;
- mNewsBtn.setChecked(Constant.BTN_FLAG_NEWS);
- break;
- case R.id.btn_setting:
- index = Constant.BTN_FLAG_SETTING;
- mSettingBtn.setChecked(Constant.BTN_FLAG_SETTING);
- break;
- default:break;
- }
- if(mBottomCallback != null){
- mBottomCallback.onBottomPanelClick(index);
- }
- }
- public void defaultBtnChecked(){
- if(mMsgBtn != null){
- mMsgBtn.setChecked(Constant.BTN_FLAG_MESSAGE);
- }
- }
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- // TODO Auto-generated method stub
- super.onLayout(changed, left, top, right, bottom);
- layoutItems(left, top, right, bottom);
- }
- /**最左邊和最右邊的view由母佈局的padding進行控制位置。這裏需對第2、3個view的位置重新設置
- * @param left
- * @param top
- * @param right
- * @param bottom
- */
- private void layoutItems(int left, int top, int right, int bottom){
- int n = getChildCount();
- if(n == 0){
- return;
- }
- int paddingLeft = getPaddingLeft();
- int paddingRight = getPaddingRight();
- Log.i("yanguoqi", "paddingLeft = " + paddingLeft + " paddingRight = " + paddingRight);
- int width = right - left;
- int height = bottom - top;
- Log.i("yanguoqi", "width = " + width + " height = " + height);
- int allViewWidth = 0;
- for(int i = 0; i< n; i++){
- View v = getChildAt(i);
- Log.i("yanguoqi", "v.getWidth() = " + v.getWidth());
- allViewWidth += v.getWidth();
- }
- int blankWidth = (width - allViewWidth - paddingLeft - paddingRight) / (n - 1);
- Log.i("yanguoqi", "blankV = " + blankWidth );
- LayoutParams params1 = (LayoutParams) viewList.get(1).getLayoutParams();
- params1.leftMargin = blankWidth;
- viewList.get(1).setLayoutParams(params1);
- LayoutParams params2 = (LayoutParams) viewList.get(2).getLayoutParams();
- params2.leftMargin = blankWidth;
- viewList.get(2).setLayoutParams(params2);
- }
- }
- </span>
這裏定義了
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
- <span style="font-family:Comic Sans MS;font-size:18px;">package org.yanzi.ui;
- import org.yanzi.constant.Constant;
- import com.example.fragmentproject.R;
- import android.content.Context;
- import android.graphics.Color;
- import android.util.AttributeSet;
- import android.widget.RelativeLayout;
- import android.widget.TextView;
- public class HeadControlPanel extends RelativeLayout {
- private Context mContext;
- private TextView mMidleTitle;
- private TextView mRightTitle;
- private static final float middle_title_size = 20f;
- private static final float right_title_size = 17f;
- private static final int default_background_color = Color.rgb(23, 124, 202);
- public HeadControlPanel(Context context, AttributeSet attrs) {
- super(context, attrs);
- // TODO Auto-generated constructor stub
- }
- @Override
- protected void onFinishInflate() {
- // TODO Auto-generated method stub
- mMidleTitle = (TextView)findViewById(R.id.midle_title);
- mRightTitle = (TextView)findViewById(R.id.right_title);
- setBackgroundColor(default_background_color);
- }
- public void initHeadPanel(){
- if(mMidleTitle != null){
- setMiddleTitle(Constant.FRAGMENT_FLAG_MESSAGE);
- }
- }
- public void setMiddleTitle(String s){
- mMidleTitle.setText(s);
- mMidleTitle.setTextSize(middle_title_size);
- }
- }
- </span>
佈局文件head_panel_layout.xml
- <span style="font-family:Comic Sans MS;font-size:18px;"><?xml version="1.0" encoding="utf-8"?>
- <org.yanzi.ui.HeadControlPanel xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="50dp"
- android:layout_alignParentTop="true">
- <TextView
- android:id="@+id/midle_title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerInParent="true"
- android:textColor="@android:color/white"/>
- <TextView
- android:id="@+id/right_title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentRight="true"
- android:textColor="@android:color/white"/>
- </org.yanzi.ui.HeadControlPanel>
- </span>
三、總控中心Activity和Fragment
先交代下Fragment的使用大致分兩種,一種是將Fragment作爲一個View寫死在佈局中,佈局裏使用android:name來告訴它對應的是哪個實體Fragment。這種添加fragment的方式不能delete和replace掉。另一種是通過獲得activity的fragmentmanager和fragmentTransaction和進行動態的添加。這種方式更加靈活,一般使用此種方法。
先看Activity的佈局activity_main.xml:
- <span style="font-family:Comic Sans MS;font-size:18px;"><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:id="@+id/root_layout"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context="org.yanzi.fragmentproject.MainActivity" >
- <include
- android:id="@+id/bottom_layout"
- layout="@layout/bottom_panel_layout" />
- <View
- android:layout_width="match_parent"
- android:layout_height="1dip"
- android:layout_above="@id/bottom_layout"
- android:background="#FFE7E7E7" />
- <include
- android:id="@+id/head_layout"
- layout="@layout/head_panel_layout" />
- <View
- android:layout_width="match_parent"
- android:layout_height="1dip"
- android:layout_below="@id/head_layout"
- android:background="#FFE7E7E7" />
- <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>
- </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代碼:
- <span style="font-family:Comic Sans MS;font-size:18px;">package org.yanzi.activity;
- import org.yanzi.constant.Constant;
- import org.yanzi.fragment.BaseFragment;
- import org.yanzi.fragment.ContactsFragment;
- import org.yanzi.fragment.MessageFragment;
- import org.yanzi.fragment.NewsFragment;
- import org.yanzi.fragment.SettingFragment;
- import org.yanzi.ui.BottomControlPanel;
- import org.yanzi.ui.BottomControlPanel.BottomPanelCallback;
- import org.yanzi.ui.HeadControlPanel;
- import android.app.Activity;
- import android.app.Fragment;
- import android.app.FragmentManager;
- import android.app.FragmentTransaction;
- import android.os.Bundle;
- import android.text.TextUtils;
- import android.util.Log;
- import android.view.Menu;
- import android.widget.Toast;
- import com.example.fragmentproject.R;
- public class MainActivity extends Activity implements BottomPanelCallback {
- BottomControlPanel bottomPanel = null;
- HeadControlPanel headPanel = null;
- private FragmentManager fragmentManager = null;
- private FragmentTransaction fragmentTransaction = null;
- /* private MessageFragment messageFragment;
- private ContactsFragment contactsFragment;
- private NewsFragment newsFragment;
- private SettingFragment settingFragment;*/
- public static String currFragTag = "";
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- initUI();
- fragmentManager = getFragmentManager();
- setDefaultFirstFragment(Constant.FRAGMENT_FLAG_MESSAGE);
- }
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- // Inflate the menu; this adds items to the action bar if it is present.
- getMenuInflater().inflate(R.menu.main, menu);
- return true;
- }
- private void initUI(){
- bottomPanel = (BottomControlPanel)findViewById(R.id.bottom_layout);
- if(bottomPanel != null){
- bottomPanel.initBottomPanel();
- bottomPanel.setBottomCallback(this);
- }
- headPanel = (HeadControlPanel)findViewById(R.id.head_layout);
- if(headPanel != null){
- headPanel.initHeadPanel();
- }
- }
- /* 處理BottomControlPanel的回調
- * @see org.yanzi.ui.BottomControlPanel.BottomPanelCallback#onBottomPanelClick(int)
- */
- @Override
- public void onBottomPanelClick(int itemId) {
- // TODO Auto-generated method stub
- String tag = "";
- if((itemId & Constant.BTN_FLAG_MESSAGE) != 0){
- tag = Constant.FRAGMENT_FLAG_MESSAGE;
- }else if((itemId & Constant.BTN_FLAG_CONTACTS) != 0){
- tag = Constant.FRAGMENT_FLAG_CONTACTS;
- }else if((itemId & Constant.BTN_FLAG_NEWS) != 0){
- tag = Constant.FRAGMENT_FLAG_NEWS;
- }else if((itemId & Constant.BTN_FLAG_SETTING) != 0){
- tag = Constant.FRAGMENT_FLAG_SETTING;
- }
- setTabSelection(tag); //切換Fragment
- headPanel.setMiddleTitle(tag);//切換標題
- }
- private void setDefaultFirstFragment(String tag){
- Log.i("yan", "setDefaultFirstFragment enter... currFragTag = " + currFragTag);
- setTabSelection(tag);
- bottomPanel.defaultBtnChecked();
- Log.i("yan", "setDefaultFirstFragment exit...");
- }
- private void commitTransactions(String tag){
- if (fragmentTransaction != null && !fragmentTransaction.isEmpty()) {
- fragmentTransaction.commit();
- currFragTag = tag;
- fragmentTransaction = null;
- }
- }
- private FragmentTransaction ensureTransaction( ){
- if(fragmentTransaction == null){
- fragmentTransaction = fragmentManager.beginTransaction();
- fragmentTransaction
- .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
- }
- return fragmentTransaction;
- }
- private void attachFragment(int layout, Fragment f, String tag){
- if(f != null){
- if(f.isDetached()){
- ensureTransaction();
- fragmentTransaction.attach(f);
- }else if(!f.isAdded()){
- ensureTransaction();
- fragmentTransaction.add(layout, f, tag);
- }
- }
- }
- private Fragment getFragment(String tag){
- Fragment f = fragmentManager.findFragmentByTag(tag);
- if(f == null){
- Toast.makeText(getApplicationContext(), "fragment = null tag = " + tag, Toast.LENGTH_SHORT).show();
- f = BaseFragment.newInstance(getApplicationContext(), tag);
- }
- return f;
- }
- private void detachFragment(Fragment f){
- if(f != null && !f.isDetached()){
- ensureTransaction();
- fragmentTransaction.detach(f);
- }
- }
- /**切換fragment
- * @param tag
- */
- private void switchFragment(String tag){
- if(TextUtils.equals(tag, currFragTag)){
- return;
- }
- //把上一個fragment detach掉
- if(currFragTag != null && !currFragTag.equals("")){
- detachFragment(getFragment(currFragTag));
- }
- attachFragment(R.id.fragment_content, getFragment(tag), tag);
- commitTransactions( tag);
- }
- /**設置選中的Tag
- * @param tag
- */
- public void setTabSelection(String tag) {
- // 開啓一個Fragment事務
- fragmentTransaction = fragmentManager.beginTransaction();
- /* if(TextUtils.equals(tag, Constant.FRAGMENT_FLAG_MESSAGE)){
- if (messageFragment == null) {
- messageFragment = new MessageFragment();
- }
- }else if(TextUtils.equals(tag, Constant.FRAGMENT_FLAG_CONTACTS)){
- if (contactsFragment == null) {
- contactsFragment = new ContactsFragment();
- }
- }else if(TextUtils.equals(tag, Constant.FRAGMENT_FLAG_NEWS)){
- if (newsFragment == null) {
- newsFragment = new NewsFragment();
- }
- }else if(TextUtils.equals(tag,Constant.FRAGMENT_FLAG_SETTING)){
- if (settingFragment == null) {
- settingFragment = new SettingFragment();
- }
- }else if(TextUtils.equals(tag, Constant.FRAGMENT_FLAG_SIMPLE)){
- if (simpleFragment == null) {
- simpleFragment = new SimpleFragment();
- }
- }*/
- switchFragment(tag);
- }
- @Override
- protected void onStop() {
- // TODO Auto-generated method stub
- super.onStop();
- currFragTag = "";
- }
- @Override
- protected void onSaveInstanceState(Bundle outState) {
- // TODO Auto-generated method stub
- }
- }
- </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
- <span style="font-family:Comic Sans MS;font-size:18px;">package org.yanzi.bean;
- public class MessageBean {
- private int PhotoDrawableId;
- private String MessageName;
- private String MessageContent;
- private String MessageTime;
- public MessageBean(){
- }
- public MessageBean(int photoDrawableId, String messageName,
- String messageContent, String messageTime) {
- super();
- PhotoDrawableId = photoDrawableId;
- MessageName = messageName;
- MessageContent = messageContent;
- MessageTime = messageTime;
- }
- public int getPhotoDrawableId() {
- return PhotoDrawableId;
- }
- public void setPhotoDrawableId(int mPhotoDrawableId) {
- this.PhotoDrawableId = mPhotoDrawableId;
- }
- public String getMessageName() {
- return MessageName;
- }
- public void setMessageName(String messageName) {
- MessageName = messageName;
- }
- public String getMessageContent() {
- return MessageContent;
- }
- public void setMessageContent(String messageContent) {
- MessageContent = messageContent;
- }
- public String getMessageTime() {
- return MessageTime;
- }
- public void setMessageTime(String messageTime) {
- MessageTime = messageTime;
- }
- @Override
- public String toString() {
- return "MessageBean [mPhotoDrawableId=" + PhotoDrawableId
- + ", MessageName=" + MessageName + ", MessageContent="
- + MessageContent + ", MessageTime=" + MessageTime + "]";
- }
- }
- </span>
- <span style="font-family:Comic Sans MS;font-size:18px;">package org.yanzi.fragment.adapter;
- import java.util.List;
- import org.yanzi.bean.MessageBean;
- import com.example.fragmentproject.R;
- import android.content.Context;
- import android.view.LayoutInflater;
- import android.view.View;
- import android.view.ViewGroup;
- import android.widget.BaseAdapter;
- import android.widget.ImageView;
- import android.widget.TextView;
- public class MessageAdapter extends BaseAdapter {
- private List<MessageBean> mListMsgBean = null;
- private Context mContext;
- private LayoutInflater mInflater;
- public MessageAdapter(List<MessageBean> listMsgBean, Context context){
- mListMsgBean = listMsgBean;
- mContext = context;
- mInflater = LayoutInflater.from(mContext);
- }
- @Override
- public int getCount() {
- // TODO Auto-generated method stub
- return mListMsgBean.size();
- }
- @Override
- public Object getItem(int position) {
- // TODO Auto-generated method stub
- return mListMsgBean.get(position);
- }
- @Override
- public long getItemId(int position) {
- // TODO Auto-generated method stub
- return position;
- }
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- // TODO Auto-generated method stub
- View v = mInflater.inflate(R.layout.message_item_layout, null);
- ImageView imageView = (ImageView) v.findViewById(R.id.img_msg_item);
- imageView.setImageResource(mListMsgBean.get(position).getPhotoDrawableId());
- TextView nameMsg = (TextView)v.findViewById(R.id.name_msg_item);
- nameMsg.setText(mListMsgBean.get(position).getMessageName());
- TextView contentMsg = (TextView)v.findViewById(R.id.content_msg_item);
- contentMsg.setText(mListMsgBean.get(position).getMessageContent());
- TextView timeMsg = (TextView)v.findViewById(R.id.time_msg_item);
- timeMsg.setText(mListMsgBean.get(position).getMessageTime());
- return v;
- }
- }
- </span>
最後是MessageFragment裏通過對listview設置適配器,將MessageBean作爲信息的提供者也填充到適配器裏。
MessageFragment.java代碼:
- <span style="font-family:Comic Sans MS;font-size:18px;">package org.yanzi.fragment;
- import java.util.ArrayList;
- import java.util.List;
- import org.yanzi.activity.MainActivity;
- import org.yanzi.bean.MessageBean;
- import org.yanzi.constant.Constant;
- import org.yanzi.fragment.adapter.MessageAdapter;
- import android.app.Activity;
- import android.os.Bundle;
- import android.util.Log;
- import android.view.LayoutInflater;
- import android.view.View;
- import android.view.ViewGroup;
- import android.widget.AdapterView;
- import android.widget.Toast;
- import android.widget.AdapterView.OnItemClickListener;
- import android.widget.ListView;
- import com.example.fragmentproject.R;
- public class MessageFragment extends BaseFragment {
- private static final String TAG = "MessageFragment";
- private MainActivity mMainActivity ;
- private ListView mListView;
- private MessageAdapter mMsgAdapter;
- private List<MessageBean> mMsgBean = new ArrayList<MessageBean>();
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- View messageLayout = inflater.inflate(R.layout.message_layout,
- container, false);
- Log.d(TAG, "onCreateView---->");
- mMainActivity = (MainActivity) getActivity();
- mFragmentManager = getActivity().getFragmentManager();
- mListView = (ListView)messageLayout.findViewById(R.id.listview_message);
- mMsgAdapter = new MessageAdapter(mMsgBean, mMainActivity);
- mListView.setAdapter(mMsgAdapter);
- mListView.setOnItemClickListener(new OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view,
- int position, long id) {
- // TODO Auto-generated method stub
- Toast.makeText(mMainActivity, mMsgBean.get(position).toString(),
- Toast.LENGTH_SHORT).show();
- }
- });
- return messageLayout;
- }
- @Override
- public void onAttach(Activity activity) {
- // TODO Auto-generated method stub
- super.onAttach(activity);
- Log.e(TAG, "onAttach-----");
- }
- @Override
- public void onCreate(Bundle savedInstanceState) {
- // TODO Auto-generated method stub
- super.onCreate(savedInstanceState);
- Log.e(TAG, "onCreate------");
- mMsgBean.add(new MessageBean(R.drawable.ic_photo_1, "張三", "吃飯沒?", "昨天"));
- mMsgBean.add(new MessageBean(R.drawable.ic_photo_2, "李四", "哈哈", "昨天"));
- mMsgBean.add(new MessageBean(R.drawable.ic_photo_3, "小明", "吃飯沒?", "昨天"));
- mMsgBean.add(new MessageBean(R.drawable.ic_photo_4, "王五", "吃飯沒?", "昨天"));
- mMsgBean.add(new MessageBean(R.drawable.ic_photo_5, "Jack", "吃飯沒?", "昨天"));
- mMsgBean.add(new MessageBean(R.drawable.ic_photo_6, "Jone", "吃飯沒?", "昨天"));
- mMsgBean.add(new MessageBean(R.drawable.ic_photo_7, "Jone", "吃飯沒?", "昨天"));
- mMsgBean.add(new MessageBean(R.drawable.ic_photo_8, "Jone", "吃飯沒?", "昨天"));
- mMsgBean.add(new MessageBean(R.drawable.ic_photo_9, "Jone", "吃飯沒?", "昨天"));
- }
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- // TODO Auto-generated method stub
- super.onActivityCreated(savedInstanceState);
- Log.e(TAG, "onActivityCreated-------");
- }
- @Override
- public void onStart() {
- // TODO Auto-generated method stub
- super.onStart();
- Log.e(TAG, "onStart----->");
- }
- @Override
- public void onResume() {
- // TODO Auto-generated method stub
- super.onResume();
- Log.e(TAG, "onresume---->");
- MainActivity.currFragTag = Constant.FRAGMENT_FLAG_MESSAGE;
- }
- @Override
- public void onPause() {
- // TODO Auto-generated method stub
- super.onPause();
- Log.e(TAG, "onpause");
- }
- @Override
- public void onStop() {
- // TODO Auto-generated method stub
- super.onStop();
- Log.e(TAG, "onStop");
- }
- @Override
- public void onDestroyView() {
- // TODO Auto-generated method stub
- super.onDestroyView();
- Log.e(TAG, "ondestoryView");
- }
- @Override
- public void onDestroy() {
- // TODO Auto-generated method stub
- super.onDestroy();
- Log.e(TAG, "ondestory");
- }
- @Override
- public void onDetach() {
- // TODO Auto-generated method stub
- super.onDetach();
- Log.d(TAG, "onDetach------");
- }
- }
- </span>
橫屏情況下:
--------------本文系原創,轉載請註明作者yanzi1225627