效果图:
要实现的效果如图所示,简单归纳:
- 安置一个底部菜单栏,共有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>
<?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();
}
}
}
到此,我们的工作就完毕了。
编译运行,看到如开头的图中所演示的效果。