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

到此,我们的工作就完毕了。
编译运行,看到如开头的图中所演示的效果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值