基于Fragment实现Tab的切换,滑出侧边栏

原创
2013/01/11 16:36
阅读数 1.9W
最近在学习Fragment(碎片)这是android3.0以后提出的概念,很多pad上面的设置部分都是通过Fragment来实现的,先看看具体的效果吧
(图一)  (图二)  (图三)
第一章图片是初始时的状态,第二章点击右上角设置或者向左划屏时的状态,第三章是按返回键或者向右划屏时的状态。
注:文章例子还存在一些小问题,比如在图二状态时点击设置选项没有反应,原因是因为他的位置区域并没有随着动画而改变。大家在实现动画的过程里面应该有碰到过类似的问题:比如你对一个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

展开阅读全文
打赏
1
18 收藏
分享
加载中
源码的地址打不开 求源码
2014/06/04 15:38
回复
举报
厉害
2013/11/06 16:16
回复
举报
我希冀着博主

引用来自“邱垂昱”的评论

動畫效果透過ValueAnimator來做的話,除了會改變View顯示的樣子也會改變View的屬性,例如位置,用TranslateAnimation的話,只會改變外觀,不會改變屬性。
3.0之後新增的功能可以試試看,正在嘗試中。

动画之后要重新layout才可以,不然会有一些小问题
2013/08/09 15:14
回复
举报
動畫效果透過ValueAnimator來做的話,除了會改變View顯示的樣子也會改變View的屬性,例如位置,用TranslateAnimation的話,只會改變外觀,不會改變屬性。
3.0之後新增的功能可以試試看,正在嘗試中。
2013/07/30 15:26
回复
举报
呵呵,就是这个跳转到指定tab的方法不知道该怎么写。
2013/04/13 07:05
回复
举报
我希冀着博主

引用来自“lhi007”的评论

第1个问题其实就是想实现I以前TabActivity.setTab(1)这样的功能,不知道要怎么做

第2个问题我已经做好了
Intent intent = new Intent();
intent.setClass(getActivity(), SettingActivity.class);
startActivity(intent);

第一个问题你可以在FragmentActivity里面写一个方法跳转到你指定的tab,然后再fragmeng调用
2013/04/12 08:59
回复
举报
第1个问题其实就是想实现I以前TabActivity.setTab(1)这样的功能,不知道要怎么做

第2个问题我已经做好了
Intent intent = new Intent();
intent.setClass(getActivity(), SettingActivity.class);
startActivity(intent);
2013/04/11 13:40
回复
举报
我希冀着博主

引用来自“lhi007”的评论

请教下,
1. 如果我希望点message页里面的按钮,转到app页要怎么做?
2. 要是希望点按钮条到其他普通的Activity又要怎么做?

没有明白你的需求,直接用startactivity不行吗?
2013/04/10 20:20
回复
举报
请教下,
1. 如果我希望点message页里面的按钮,转到app页要怎么做?
2. 要是希望点按钮条到其他普通的Activity又要怎么做?
2013/04/10 15:59
回复
举报
更多评论
打赏
9 评论
18 收藏
1
分享
返回顶部
顶部