备注:代码已传至https://github.com/yanzi1225627/FragmentProject_QQ 欢迎fork,如今来审视这份代码,非常多地方写的不太好,欢迎大家指正。有时间我会继续完好。2015-11-1.

近期重复研究日常经典必用的几个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就作了这样一件事:

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);
}
} }

相应的布局:

<?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>

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

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"; }

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

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

bottom_panel_layout.xml

<?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>

相应的java文件:

BottomControlPanel.java

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);
} }

在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做的,这个应该没跑。

通过观察。顶部控制栏除了标题居中外,在右上角一般会再放一个button。

不是ImageView就是TextView,这里我为了方便放的是两个TextView,右側的button效果能够再TextView上弄个背景来实现。

HeadControlPanel.java

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);
} }

布局文件head_panel_layout.xml

<?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>

三、总控中心Activity和Fragment

先交代下Fragment的使用大致分两种,一种是将Fragment作为一个View写死在布局中,布局里使用android:name来告诉它相应的是哪个实体Fragment。这样的加入fragment的方式不能delete和replace掉。

还有一种是通过获得activity的fragmentmanager和fragmentTransaction和进行动态的加入。

这样的方式更加灵活。一般使用此种方法。

先看Activity的布局activity_main.xml:

<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>

注意看这里是通过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页(聊天、发现、通讯录),点击顶部栏里的button都呈现出相同的内容。但反过来讲,假设顶部栏里的事务逻辑和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代码:

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
} }

注意这块我作了修改。不须要申明

/* 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

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 + "]";
} }

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

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;
} }

由于是演示样例,getView里没用ViewHolder。

最后是MessageFragment里通过对listview设置适配器。将MessageBean作为信息的提供者也填充到适配器里。

MessageFragment.java代码:

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------"); } }

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

      

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveWFuemkxMjI1NjI3/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveWFuemkxMjI1NjI3/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />

横屏情况下:

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

Android应用经典主界面框架之中的一个:仿QQ (使用Fragment, 附源代码)的更多相关文章

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

    另外一种主界面风格则是以网易新闻.凤凰新闻以及新推出的新浪博客(阅读版)为代表.使用ViewPager+Fragment,即ViewPager里适配器里放的不是一般的View.而是Fragment.所 ...

  2. [课程设计]Scrum 1.3 多鱼点餐系统开发进度(系统主界面框架&美化)

    Scrum 1.3 多鱼点餐系统开发进度(系统主界面框架&美化) 1.团队名称:重案组 2.团队目标:长期经营,积累客户充分准备,伺机而行 3.团队口号:矢志不渝,追求完美 4.团队选题:餐厅 ...

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

    文章地址: Android项目Tab类型主界面大总结 Fragment+TabPageIndicator+ViewPager 1.使用ViewPager + PagerAdapter 每个页面的内容都 ...

  4. Android AsynTask更新主界面

    虽然今天礼拜六还在加班,但是在等接口,所以还是有很多时间来自己学点东西的,所以就接着昨天的来.今天继续学的是不通过主线程来更新主线程的界面的问题. 昨天是用的开启线程调用Handler来更新线程,那个 ...

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

    当我们使用activity加fragment的时候,每个界面都要建立一个fragment,每个fragment里面都要重写onCreate(),onCreateView(),onActivityCre ...

  6. android-改进&lt;&lt;仿QQ&gt;&gt;框架源代码

    该文章主要改动于CSDN某大神的一篇文章,本人认为这篇文章的面向对象非常透彻,以下分享例如以下可学习的几点: Android应用经典主界面框架之中的一个:仿QQ (使用Fragment, 附源代码) ...

  7. Android学习笔记(十四)——在执行时加入碎片(附源代码)

    在执行时加入碎片 点击获取源代码 将UI切割为多个可配置的部分是碎片的优势之中的一个,但其真正强大之处在于可在执行时动态地把它们加入到活动中. 1.使用上一篇创建的Fragments项目,在main. ...

  8. 【边做项目边学Android】手机安全卫士05_2:程序主界面,为每一个条目加入事件

    为每一个条目加入点击事件监听器 gv_main.setOnItemClickListener(this); 须要当前Activity实现OnItemClickListener接口.同一时候实现publ ...

  9. Android与server通信的方法之中的一个(json)效率不高安全性不好

    http通信.以xml或者json为载体.相互通信数据. Android对于http的网络通信,提供了标准的java接口--httpURLConnection接口.以及apache的接口--httpc ...

随机推荐

  1. ubuntu下安装vue-cli框架

    首先安装好node.js,安装方式见 http://www.cnblogs.com/teersky/p/7255334.html 之后正式开始vue-cli之旅吧,输入以下代码安装vue-cli模块 ...

  2. spark第七篇:Spark SQL, DataFrame and Dataset Guide

    预览 Spark SQL是用来处理结构化数据的Spark模块.有几种与Spark SQL进行交互的方式,包括SQL和Dataset API. 本指南中的所有例子都可以在spark-shell,pysp ...

  3. python 爬虫系列09-selenium+拉钩

    使用selenium爬取拉勾网职位 from selenium import webdriver from lxml import etree import re import time from s ...

  4. 如何将项目部署到远程的Linux机器上的tomcat上

    下面来练习一下如何将本地的一个项目部署到远程的Linux机器上去. 第一步:将要部署到Linux机器上的项目打成一个war包 war包有一个好处tomcat可以自动解压 第二步:将打好的war上传到L ...

  5. JS识别不同浏览器信息

    总所周知,不同浏览器兼容是不一致的,然而今天我在Coding的时候深深体会到那个痛苦,一样的代码在Firefox里面是没问题的,可以根据索引找到 对应的对象元素然后进行操作,但是同样的却获取不到对象元 ...

  6. 虚拟机vmware 上不去 连不上网问题解决

    开始---设置--控制面板---管理工具---服务 确保 VMware DHCP Service 和VMware NAT Service 服务已经启动

  7. CF 305B——Continued Fractions——————【数学技巧】

    B. Continued Fractions time limit per test 2 seconds memory limit per test 256 megabytes input stand ...

  8. js 用简单案例举模态对话框弹出

    <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ...

  9. SQLAlchemy基本操作和常用技巧

    点击打开链接 Python的ORM框架SQLAlchemy基本操作和常用技巧,包含大量实例,非常好的一个学习SQLAlchemy的教程,需要的朋友可以参考下 python编程语言下的一款开源软件.提供 ...

  10. [转]Asp.Net大型项目实践(11)-基于MVC Action粒度的权限管理【续】【源码在这里】(在线demo,全部源码)

    本文转自:http://www.cnblogs.com/legendxian/archive/2010/01/25/1655551.html 接上篇Asp.Net大型项目实践(10)-基于MVC Ac ...