Android - ViewPager + Fragment 實現仿微信界面

效果圖:



要實現的效果如圖所示,簡單歸納:

  • 安置一個底部菜單欄,共有4個功能選項。
  • 4個功能選項分別聯繫4個不同內容的fragment。
  • 支持通過滑動屏幕的方式,完成fragment的切換。
  • 支持通過類似tabhost,點擊選項卡的方式,完成fragment的切換。

項目結構:
    

通過Project的結構,我們可以看到實際上邏輯還是比較清晰。
  • 我們會有一個用於擺放主界面控件的佈局文件,對應的自然有一個MainActivity.java文件。
  • 底部菜單中,共有4個功能選項,其結構都同樣爲一個ImageView(Function-Icon)以及一個TextView(Function-Title)。所以爲了避免重複的編碼工作,我們將其抽離出來,單獨封裝成“bottom_menu_item_view.xml”。
  • 4個功能選項分別聯繫4個功能界面,所以我們還需要用於顯示這4個功能界面的Fragment,所以我們還會有4個fragment佈局文件及fragment類。
  • 爲了達到“通過滑動屏幕切換功能界面”的目的,我們使用了ViewPager。對應的,那麼我們自然會需要一個“PagerAdapter”。在這裏需要注意的是,因爲我們選擇了使用“ViewPager+Fragment”的方式,這種情況android推薦使用“FragmentPagerAdapter”。
  • 最後我們注意到,當我們正常滑動屏幕切換界面時,一切都很美好。但是,假設目前用戶正停留在“聊天”界面中,而此時他通過點擊“我”的功能選項卡選擇進入到最後一個功能界面,這個時候ViewPager會顯示從“聊天”到“我”多個view的滑動切換效果,這看上去始終有點彆扭。所以,我們定義了一個“ViewPagerScroll”的類,通過反射的方式控制ViewPager滑動效果的時間,當滑動橫跨多個界面時,我們將滑動時間設爲0,以此取消掉滑動效果。

進入到實際編碼當中,按照我們的思路:

首先,我們定義好主界面的佈局文件:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="bottom"
    android:orientation="vertical"
    tools:context="com.tsr.bigmousechating.AppMainActivity" >

    <android.support.v4.view.ViewPager
        android:id="@+id/app_main_viewpager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

    <!-- 邊框 -->
    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:layout_gravity="bottom"
        android:background="@color/lightgrey" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom" >

        <LinearLayout
            android:id="@+id/bottom_menu_chats"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:orientation="vertical" >
        </LinearLayout>

        <LinearLayout
            android:id="@+id/bottom_menu_contacts"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:orientation="vertical" >
        </LinearLayout>

        <LinearLayout
            android:id="@+id/bottom_menu_discover"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:orientation="vertical" >
        </LinearLayout>

        <LinearLayout
            android:id="@+id/bottom_menu_me"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:orientation="vertical" >
        </LinearLayout>
    </LinearLayout>

</LinearLayout>

接着,定義功能選項卡的View佈局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:orientation="vertical"
    android:padding="2dp" >

    <ImageView
        android:id="@+id/tab_item_icon"
        android:layout_width="30dp"
        android:layout_height="30dp" />

    <TextView
        android:id="@+id/tab_item_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="2dp"
        android:textSize="12sp" />

</LinearLayout>

接下來,定義好4個fragment的佈局文件以及類文件,這裏因爲定義十分基礎,不貼代碼了。

此時,我們開始編寫AppMainActivity:
public class AppMainActivity extends FragmentActivity {
	// 視圖轉換器對象
	private LayoutInflater mInflater;
	// 存放底部菜單中的四個選項卡的layout
	private LinearLayout mBottomMenuChats, 
	                     mBottomMenuContacts, 
	                     mBottomMenuDiscover, 
	                     mBottomMenuMe;
	private LinearLayout[] mBottomMenuItemViews;
	// 底部菜單欄 選項卡標題
	private int[] mMenuTitles = new int[] { 
			R.string.bottom_menu_chats_item, 
			R.string.bottom_menu_contacts_item,
			R.string.bottom_menu_discover_item, 
			R.string.bottom_menu_me_item };
	// 底部菜單欄 選項卡圖標(非選中狀態)
	private int[] mMenuNormalIcons = new int[] { 
			R.drawable.bottom_menu_chats_normal,
			R.drawable.bottom_menu_contacts_normal, 
			R.drawable.bottom_menu_discover_normal,
			R.drawable.bottom_menu_me_normal };
	// 底部菜單欄 選項卡圖標(選中狀態)
	private int[] mMenuSelectedIcons = new int[] { 
			R.drawable.bottom_menu_chats_selected,
			R.drawable.bottom_menu_contacts_selected, 
			R.drawable.bottom_menu_discover_selected,
			R.drawable.bottom_menu_me_selected };
	// 用於滑動切換視圖的viewPager
	private ViewPager mViewPager;
	// 用於控制viewPager滑動效果
	private ViewPagerScroller mScroller;
	// viewPager的適配器(在viewPager配合fragment使用時,應使用FragmentPagerAdapter)
	private MainBottomMenuFragmentPagerAdapter mAdapter;
	// 需要裝載進viewPager中的各個頁面內容
	private ArrayList<Fragment> mPagerContents = new ArrayList<Fragment>();
	//
	private MainBottomMenuFragmentPagerAdapter.PageChangerListener mPageChangeListener = new MainBottomMenuFragmentPagerAdapter.PageChangerListener(this);

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_app_main);
		// 初始化頁面控件顯示
		initView();
	}

	private void initView() {
		// 獲取視圖轉換器對象
		mInflater = LayoutInflater.from(this);
        // 底部菜單的各個選項卡的初始化工作
		mBottomMenuChats = (LinearLayout) this.findViewById(R.id.bottom_menu_chats);
		mBottomMenuChats.setOnClickListener(new MyPageChangeListener(PAGE_CHATS_INDEX));

		mBottomMenuContacts = (LinearLayout) this.findViewById(R.id.bottom_menu_contacts);
		mBottomMenuContacts.setOnClickListener(new MyPageChangeListener(PAGE_CONTACTS_INDEX));

		mBottomMenuDiscover = (LinearLayout) this.findViewById(R.id.bottom_menu_discover);
		mBottomMenuDiscover.setOnClickListener(new MyPageChangeListener(PAGE_DISCOVER_INDEX));

		mBottomMenuMe = (LinearLayout) this.findViewById(R.id.bottom_menu_me);
		mBottomMenuMe.setOnClickListener(new MyPageChangeListener(PAGE_ME_INDEX));

		// 將選項卡對象存放進一個數組,方便根據數組下標對選項卡的內容進行裝載
		mBottomMenuItemViews = new LinearLayout[] { mBottomMenuChats, mBottomMenuContacts, mBottomMenuDiscover,
				mBottomMenuMe };
		// 裝載選項卡顯示內容
		for (int index = 0; index < mBottomMenuItemViews.length; index++) {
			initBottomMenuItemView(index);
		}

		// 接着進行ViewPager的實例化工作
		initViewPager();
	}

	private void initViewPager() {
		mViewPager = (ViewPager) this.findViewById(R.id.app_main_viewpager);
        mScroller = new ViewPagerScroller(this);
        mScroller.initViewPagerScroll(mViewPager);
		
		// 裝載頁面內容
		mPagerContents.add(new AppChatsFragment());
		mPagerContents.add(new AppContactsFragment());
		mPagerContents.add(new AppDiscoverFragment());
		mPagerContents.add(new AppMeFragment());

		// 爲viewPager綁定適配器
		mAdapter = new MainBottomMenuFragmentPagerAdapter(getSupportFragmentManager(), mPagerContents);
		mViewPager.setAdapter(mAdapter);
		// 設置當前選中的界面
		mViewPager.setCurrentItem(PAGE_DEFAULT_INDEX,false);
		// 綁定頁面改變的監聽事件
		mViewPager.addOnPageChangeListener(mPageChangeListener);
	}

	/**
	 * 給Tab按鈕設置圖標和文字
	 */
	private void initBottomMenuItemView(int index) {
		View view = mInflater.inflate(R.layout.bottom_menu_item_view, null);

		ImageView imageView = (ImageView) view.findViewById(R.id.tab_item_icon);
		int imageSourceID = index == PAGE_DEFAULT_INDEX ? mMenuSelectedIcons[index] : mMenuNormalIcons[index];
		imageView.setImageResource(imageSourceID);

		TextView textView = (TextView) view.findViewById(R.id.tab_item_title);
		textView.setText(mMenuTitles[index]);

		mBottomMenuItemViews[index].addView(view);
	}

	/**
	 * 回調函數,用於在監聽器監聽到page改變時,更新選項卡的圖標顯示
	 * @param beforeIndex   之前選中的菜單選項
	 * @param selectedIndex 此次選中的菜單選項
	 */
	public void onMenuItemSelected(int beforeIndex, int selectedIndex) {
		ImageView beforeIcon = (ImageView) mBottomMenuItemViews[beforeIndex].findViewById(R.id.tab_item_icon);
		beforeIcon.setImageResource(mMenuNormalIcons[beforeIndex]);

		ImageView selectedIcon = (ImageView) mBottomMenuItemViews[selectedIndex].findViewById(R.id.tab_item_icon);
		selectedIcon.setImageResource(mMenuSelectedIcons[selectedIndex]);
	}

	/**
	 * 選項卡點擊事件的監聽器
	 * @author TSR
	 */
	private class MyPageChangeListener implements View.OnClickListener {
		private int pageIndex;

		public MyPageChangeListener(int pageIndex) {
			this.pageIndex = pageIndex;
		}

		@Override
		public void onClick(View v) {
			int duration = Math.abs(mPageChangeListener.getmCurrentPageIndex() - pageIndex) > 1 ? 0 : 2500;
		    mScroller.setScrollDuration(duration);
			mViewPager.setCurrentItem(pageIndex);
		}

	}
	
	public ViewPagerScroller getmScroller() {
		return mScroller;
	}
	
	public static final int PAGE_CHATS_INDEX = 0;
	public static final int PAGE_CONTACTS_INDEX = 1;
	public static final int PAGE_DISCOVER_INDEX = 2;
	public static final int PAGE_ME_INDEX = 3;
	public static final int PAGE_DEFAULT_INDEX = PAGE_CHATS_INDEX;
}

我們在AppMainActivity中用到了ViewPager,所以我們接着根據自己的需求定義適配器類:
public class MainBottomMenuFragmentPagerAdapter extends FragmentPagerAdapter {

	private ArrayList<Fragment> fragmentList;

	public MainBottomMenuFragmentPagerAdapter(FragmentManager fm, ArrayList<Fragment> fragmentList) {
		super(fm);
		this.fragmentList = fragmentList;
	}

	@Override
	public Fragment getItem(int index) {
		return fragmentList.get(index);
	}

	@Override
	public int getCount() {
		return fragmentList.size();
	}

	/**
	 * 頁卡切換監聽
	 */
	public static class PageChangerListener implements OnPageChangeListener {

		private int mCurrentPageIndex;
		private AppMainActivity mContext;

		public PageChangerListener(AppMainActivity context) {
			this.mContext = context;
			mCurrentPageIndex = AppMainActivity.PAGE_DEFAULT_INDEX;
		}

		@Override
		public void onPageScrollStateChanged(int arg0) {

		}

		@Override
		public void onPageScrolled(int arg0, float arg1, int arg2) {
			// TODO Auto-generated method stub
		}

		@Override
		public void onPageSelected(int pageIndex) {
			mContext.getmScroller().setScrollDuration(2000);
			mContext.onMenuItemSelected(mCurrentPageIndex, pageIndex);
			mCurrentPageIndex = pageIndex;
		}

		public int getmCurrentPageIndex() {
			return mCurrentPageIndex;
		}

	}

}

到這裏,我們的工作其實已經算完成了。但因爲我們想要當ViewPager的滑動經過多個頁面時,取消掉滑動效果。所以,我們還需要定義一個Scroller類,來控制ViewPager的滑動動畫時間。
/**
 * ViewPager 滾動速度設置
 * 
 */
public class ViewPagerScroller extends Scroller {
	private int mScrollDuration = 2000; // 滑動速度

	/**
	 * 設置速度速度
	 * 
	 * @param duration
	 */
	public void setScrollDuration(int duration) {
		this.mScrollDuration = duration;
	}

	public ViewPagerScroller(Context context) {
		super(context);
	}

	public ViewPagerScroller(Context context, Interpolator interpolator) {
		super(context, interpolator);
	}

	public ViewPagerScroller(Context context, Interpolator interpolator, boolean flywheel) {
		super(context, interpolator, flywheel);
	}

	@Override
	public void startScroll(int startX, int startY, int dx, int dy, int duration) {
		super.startScroll(startX, startY, dx, dy, mScrollDuration);
	}

	@Override
	public void startScroll(int startX, int startY, int dx, int dy) {
		super.startScroll(startX, startY, dx, dy, mScrollDuration);
	}

	public void initViewPagerScroll(ViewPager viewPager) {
		try {
			Field mScroller = ViewPager.class.getDeclaredField("mScroller");
			mScroller.setAccessible(true);
			mScroller.set(viewPager, this);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

到此,我們的工作就完畢了。
編譯運行,看到如開頭的圖中所演示的效果。

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