最近在学习Fragment(碎片)这是android3.0以后提出的概念,很多pad上面的设置部分都是通过Fragment来实现的,先看看具体的效果吧
![](http://static.oschina.net/uploads/space/2013/0111/162952_6Qip_127095.png)
![](http://static.oschina.net/uploads/space/2013/0111/163006_GV6c_127095.png)
![](http://static.oschina.net/uploads/space/2013/0111/163018_GeyC_127095.png)
第一章图片是初始时的状态,第二章点击右上角设置或者向左划屏时的状态,第三章是按返回键或者向右划屏时的状态。
注:文章例子还存在一些小问题,比如在图二状态时点击设置选项没有反应,原因是因为他的位置区域并没有随着动画而改变。大家在实现动画的过程里面应该有碰到过类似的问题:比如你对一个textview做动画处理啊,把textview从位置A做translate移动到位置B,这个时候你点击A位置时textview仍有效,但你点击B位置时却无效,解决方案就是动画结束时你在调用layout(left,top,right,bottom)重新布局。文章例子中存在的问题也是一样的。如果有更好的解决方案,可以留言,方便大家一起学习。
我们看看实现的方式吧,底部的tab在脑海里第一反应便是android原生的tabhost,只需要做一些细微的调整就可以得到自己想要的效果。先看看布局文件,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/tabhost"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:id="@+id/setting"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/maintab_toolbar_bg" >
<ListView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent"
android:cacheColorHint="@android:color/transparent"
android:dividerHeight="1dp"/>
</LinearLayout>
<LinearLayout
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="45dp"
android:background="@drawable/app_title_bar"
android:gravity="center_vertical"
android:orientation="horizontal" >
<TextView
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_marginLeft="24dp"
android:gravity="center_vertical"
android:text="@string/app_name"
android:textColor="@android:color/white"
android:textSize="22dp" />
<ImageButton
android:id="@+id/btn_settings"
android:layout_width="48dip"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:background="@android:color/transparent"
android:src="@drawable/action_settings" />
<ImageView
android:layout_width="1dp"
android:layout_height="match_parent"
android:layout_toLeftOf="@id/btn_settings"
android:background="@drawable/app_title_bar_line" />
</RelativeLayout>
<FrameLayout
android:id="@android:id/tabcontent"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="0" >
</FrameLayout>
<FrameLayout
android:id="@+id/containertabcontent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1" >
</FrameLayout>
<TabWidget
android:id="@android:id/tabs"
android:layout_width="match_parent"
android:layout_height="55dip"
android:layout_weight="0"
android:orientation="horizontal" >
</TabWidget>
</LinearLayout>
</TabHost>
整个布局文件就是一个tabhost,然后setting部分是滑出的侧边栏,main是整个内容区域,在main里面的RelativeLayout是标题栏部分,除了containertabcontent以外其他的几个FrameLayout都是tabhost里面。
我们新建一个activity,命名为FragmentTabActivity,然后继承的不在平时我们继承的Activity而是FragmentActivity(需要导入android-surpport-v.jar包),代码如下:
private final static int TRANSLATE_ANIMATION_WIDTH = 150;
private final static int ANIMATION_DURATION_FAST = 450;
private final static int ANIMATION_DURATION_SLOW = 350;
private final static int MOVE_DISTANCE = 50;
private TabHost mTabHost;
private TabManager mTabManager;
private LinearLayout mSettingLinearLayout;
private LinearLayout mMainLinearLayout;
// 屏幕宽度
private int mWidth;
private float mPositionX;
// 滑动状态
private boolean mSlided = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityUtils.requestNotTitleBar(this);
setContentView(R.layout.fragment_tabs);
mWidth = getResources().getDisplayMetrics().widthPixels;
// 继承tabactivity.getTabHost()不需要setup()
mTabHost = (TabHost) findViewById(android.R.id.tabhost);
mTabHost.setup();
mTabManager = new TabManager(this, mTabHost, R.id.containertabcontent);
RelativeLayout app = (RelativeLayout) getLayoutInflater().inflate(
R.layout.app_tab_layout, null);
mTabManager.addTab(mTabHost.newTabSpec("Apps").setIndicator(app),
AppsFragment.class, null);
RelativeLayout contacts = (RelativeLayout) getLayoutInflater().inflate(
R.layout.contacts_tab_layout, null);
mTabManager.addTab(mTabHost.newTabSpec("Contact")
.setIndicator(contacts), ContactsFragment.class, null);
RelativeLayout message = (RelativeLayout) getLayoutInflater().inflate(
R.layout.message_tab_layout, null);
mTabManager.addTab(
mTabHost.newTabSpec("Message").setIndicator(message),
MessageFragment.class, null);
mSettingLinearLayout = (LinearLayout) findViewById(R.id.setting);
mMainLinearLayout = (LinearLayout) findViewById(R.id.main);
mMainLinearLayout.setOnTouchListener(mOnTouchListener);
slideIn();
ListView listView = (ListView) findViewById(R.id.list);
listView.setOnTouchListener(mOnTouchListener);
findViewById(R.id.btn_settings).setOnClickListener(mOnClickListener);
if (savedInstanceState != null) {
mTabHost.setCurrentTabByTag(savedInstanceState.getString("tag"));
}
// 初始化listview
final Resources res = getResources();
String[] mTitles = res.getStringArray(R.array.setting_items);
ArrayAdapter<String> mAdapter = new ArrayAdapter<String>(this,
R.layout.fragment_setting_item, R.id.item, mTitles);
listView.setAdapter(mAdapter);
}
// 点击按钮
private OnClickListener mOnClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_settings :
if (mSlided) {
slideIn();
} else {
slideOut();
}
break;
}
}
};
// 滑动
private OnTouchListener mOnTouchListener = new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (v.getId() == R.id.main) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN :
mPositionX = event.getX();
break;
case MotionEvent.ACTION_MOVE :
final float currentX = event.getX();
// 向左边滑动
if (currentX - mPositionX <= -MOVE_DISTANCE && !mSlided) {
slideOut();
} else if (currentX - mPositionX >= MOVE_DISTANCE && mSlided) {
slideIn();
}
break;
}
return true;
}
return false;
}
};
/**
* 滑出侧边栏
*/
private void slideOut() {
TranslateAnimation translate = new TranslateAnimation(mWidth,
TRANSLATE_ANIMATION_WIDTH, 0, 0);
translate.setDuration(ANIMATION_DURATION_SLOW);
translate.setFillAfter(true);
mSettingLinearLayout.startAnimation(translate);
mSettingLinearLayout.getAnimation().setAnimationListener(
new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation anim) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationEnd(Animation anima) {
TranslateAnimation animation = new TranslateAnimation(
0, TRANSLATE_ANIMATION_WIDTH - mWidth, 0, 0);
animation.setDuration(ANIMATION_DURATION_FAST);
animation.setFillAfter(true);
mMainLinearLayout.startAnimation(animation);
mSlided = true;
}
});
}
/**
* 滑进侧边栏
*/
private void slideIn() {
TranslateAnimation translate = new TranslateAnimation(TRANSLATE_ANIMATION_WIDTH,
mWidth, 0, 0);
translate.setDuration(ANIMATION_DURATION_FAST);
// 动画完成时停在结束位置
translate.setFillAfter(true);
mSettingLinearLayout.startAnimation(translate);
mSettingLinearLayout.getAnimation().setAnimationListener(
new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
TranslateAnimation mainAnimation = new TranslateAnimation(
-mWidth + TRANSLATE_ANIMATION_WIDTH, 0, 0, 0);
mainAnimation.setDuration(ANIMATION_DURATION_SLOW);
mainAnimation.setFillAfter(true);
mMainLinearLayout.startAnimation(mainAnimation);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
mSlided = false;
}
});
}
@Override
public boolean onContextItemSelected(MenuItem item) {
if (mSlided) {
slideIn();
} else {
slideOut();
}
return true;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && mSlided) {
slideIn();
return true;
}
return super.onKeyDown(keyCode, event);
}
/**
* 销毁之前
*/
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("tag", mTabHost.getCurrentTabTag());
}
public static class TabManager implements TabHost.OnTabChangeListener {
private final FragmentTabActivity mActivity;
// 保存tab
private final Map<String, TabInfo> mTabs = new HashMap<String, FragmentTabActivity.TabManager.TabInfo>();
private final TabHost mTabHost;
private final int mContainerID;
private TabInfo mLastTab;
public TabManager(FragmentTabActivity activity, TabHost tabHost,
int containerID) {
mActivity = activity;
mTabHost = tabHost;
mContainerID = containerID;
mTabHost.setOnTabChangedListener(this);
}
static final class TabInfo {
private final String tag;
private final Class<?> clss;
private final Bundle args;
private Fragment fragment;
TabInfo(String _tag, Class<?> _clss, Bundle _args) {
tag = _tag;
clss = _clss;
args = _args;
}
}
static class TabFactory implements TabHost.TabContentFactory {
private Context mContext;
TabFactory(Context context) {
mContext = context;
}
@Override
public View createTabContent(String tag) {
View v = new View(mContext);
v.setMinimumHeight(0);
v.setMinimumWidth(0);
return v;
}
}
// 加入tab
public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args) {
tabSpec.setContent(new TabFactory(mActivity));
String tag = tabSpec.getTag();
TabInfo info = new TabInfo(tag, clss, args);
final FragmentManager fm = mActivity.getSupportFragmentManager();
info.fragment = fm.findFragmentByTag(tag);
// isDetached分离状态
if (info.fragment != null && !info.fragment.isDetached()) {
FragmentTransaction ft = fm.beginTransaction();
ft.detach(info.fragment);
ft.commit();
}
mTabs.put(tag, info);
mTabHost.addTab(tabSpec);
}
@Override
public void onTabChanged(String tabId) {
TabInfo newTab = mTabs.get(tabId);
if (mLastTab != newTab) {
FragmentManager fragmentManager = mActivity
.getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager
.beginTransaction();
// 脱离之前的tab
if (mLastTab != null && mLastTab.fragment != null) {
fragmentTransaction.detach(mLastTab.fragment);
}
if (newTab != null) {
if (newTab.fragment == null) {
newTab.fragment = Fragment.instantiate(mActivity,
newTab.clss.getName(), newTab.args);
fragmentTransaction.add(mContainerID, newTab.fragment,
newTab.tag);
} else {
// 激活
fragmentTransaction.attach(newTab.fragment);
}
}
mLastTab = newTab;
fragmentTransaction.commit();
// 会在进程的主线程中,用异步的方式来执行,如果想要立即执行这个等待中的操作,就要调用这个方法
// 所有的回调和相关的行为都会在这个调用中被执行完成,因此要仔细确认这个方法的调用位置。
fragmentManager.executePendingTransactions();
}
}
}
先看看内部类TabManager,封装了TabHost的addTab动作,其中addTabaddTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args)方法第一个参数是tabhost.tabspec,第二个参数是fragment,第三个是你要从FragmentTabActivity里传递到相关fragment的参数。
// isDetached分离状态
if (info.fragment != null && !info.fragment.isDetached()) {
FragmentTransaction ft = fm.beginTransaction();
ft.detach(info.fragment);
ft.commit();
}
这段代码是设置每个tab的fragment,把fragment从UI中detach,
一个 Fragment 除了可以被 FragmentTransaction remove 删除,以及hide 隐藏外,还可以被detach 。detach 的好处就是在 remove 和 hide 之间 ,当一个fragment 被detach 后,他本身的状态虽然还保持住,但是它的view 却被avtivity 的ViewTree丢弃掉,下次atach 的时候 ,还会调用onCreateView 重新创建视图,注意此时 onattach 不会被调用,它只会第一次被调用。onTabChanged(String tabId)是对TabHost.OnTabChangeListener接口的实现
@Override
public void onTabChanged(String tabId) {
TabInfo newTab = mTabs.get(tabId);
if (mLastTab != newTab) {
FragmentManager fragmentManager = mActivity
.getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager
.beginTransaction();
// 脱离之前的tab
if (mLastTab != null && mLastTab.fragment != null) {
fragmentTransaction.detach(mLastTab.fragment);
}
if (newTab != null) {
if (newTab.fragment == null) {
newTab.fragment = Fragment.instantiate(mActivity,
newTab.clss.getName(), newTab.args);
fragmentTransaction.add(mContainerID, newTab.fragment,
newTab.tag);
} else {
// 激活
fragmentTransaction.attach(newTab.fragment);
}
}
mLastTab = newTab;
fragmentTransaction.commit();
// 会在进程的主线程中,用异步的方式来执行,如果想要立即执行这个等待中的操作,就要调用这个方法
// 所有的回调和相关的行为都会在这个调用中被执行完成,因此要仔细确认这个方法的调用位置。
fragmentManager.executePendingTransactions();
}
}
在你切换tab时,需要先把上一个fragment从viewtree中脱离出来,然后再激活当前的fragment。需要考虑newTab.fragment是否已经生成过,如果生成过直接使用就旧的,旧的会被fragmentTransactioon.attach(newTab.fragment),如果没有则通过Fragment.instantiate()方法生成一个新的,并且通过fragmentTransaction.add,然后再通过fragmentTransaction来commit。
最后只需要在oncreate方法中做:
mTabManager = new TabManager(this, mTabHost, R.id.containertabcontent);
RelativeLayout app = (RelativeLayout) getLayoutInflater().inflate(
R.layout.app_tab_layout, null);
mTabManager.addTab(mTabHost.newTabSpec("Apps").setIndicator(app),
AppsFragment.class, null);
RelativeLayout contacts = (RelativeLayout) getLayoutInflater().inflate(
R.layout.contacts_tab_layout, null);
mTabManager.addTab(mTabHost.newTabSpec("Contact")
.setIndicator(contacts), ContactsFragment.class, null);
RelativeLayout message = (RelativeLayout) getLayoutInflater().inflate(
R.layout.message_tab_layout, null);
mTabManager.addTab(
mTabHost.newTabSpec("Message").setIndicator(message),
MessageFragment.class, null);
就Ok了tab的切换。
接下来是实现侧边栏的滑出,向左边划屏的时候侧边栏显示,向右边划屏出的时候侧边栏隐藏,我们对setting部分作了translate平移,代码如下:
/**
* 滑出侧边栏
*/
private void slideOut() {
TranslateAnimation translate = new TranslateAnimation(mWidth,
TRANSLATE_ANIMATION_WIDTH, 0, 0);
translate.setDuration(ANIMATION_DURATION_SLOW);
translate.setFillAfter(true);
mSettingLinearLayout.startAnimation(translate);
mSettingLinearLayout.getAnimation().setAnimationListener(
new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation anim) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationEnd(Animation anima) {
TranslateAnimation animation = new TranslateAnimation(
0, TRANSLATE_ANIMATION_WIDTH - mWidth, 0, 0);
animation.setDuration(ANIMATION_DURATION_FAST);
animation.setFillAfter(true);
mMainLinearLayout.startAnimation(animation);
mSlided = true;
}
});
}
向右边划屏时也是作了translate平移,代码如下:
/**
* 滑进侧边栏
*/
private void slideIn() {
TranslateAnimation translate = new TranslateAnimation(TRANSLATE_ANIMATION_WIDTH,
mWidth, 0, 0);
translate.setDuration(ANIMATION_DURATION_FAST);
// 动画完成时停在结束位置
translate.setFillAfter(true);
mSettingLinearLayout.startAnimation(translate);
mSettingLinearLayout.getAnimation().setAnimationListener(
new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
TranslateAnimation mainAnimation = new TranslateAnimation(
-mWidth + TRANSLATE_ANIMATION_WIDTH, 0, 0, 0);
mainAnimation.setDuration(ANIMATION_DURATION_SLOW);
mainAnimation.setFillAfter(true);
mMainLinearLayout.startAnimation(mainAnimation);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
mSlided = false;
}
});
}
后来为了侧边栏滑出不会过于的生硬,所以就给main部分也加上了translate平移。所以监听了setting的动画,在setting部分动画结束时和开始时执行main的平移。
最后通过监听main的onTouchListener事件来实现侧边栏的出现和隐藏
里面的move_distance是常量,为了避免你点击屏幕就会触发事件。
代码均在:https://huangsm.googlecode.com/svn/trunk/UiDemo
或:https://github.com/huangsanm/uidemo.git