您的位置:首页 > 移动开发 > Android开发

Android开发技巧——实现底部图标文字的导航栏(已更新)

2014-12-20 14:26 876 查看
本文章的导航栏代码参考了viewpagerindicator的实现。本文叙述的是之前版本的qq或微信中,底部的图标加文字的导航栏的实现。

2014-09-14 13:59:42更新:library的代码已经从Demo中分离出来,见文末。

本例子依赖viewpagerindicator的两个接口:IconPagerAdapter及PageIndicator。这两个接口的方法如下:

[java] view
plaincopy





package com.viewpagerindicator;

public interface IconPagerAdapter {

int getIconResId(int index);

int getCount();

}

[java] view
plaincopy





package com.viewpagerindicator;

import android.support.v4.view.ViewPager;

public interface PageIndicator extends ViewPager.OnPageChangeListener {

void setViewPager(ViewPager view);

void setViewPager(ViewPager view, int initialPosition);

void <span style="BACKGROUND-COLOR: #ffd700">setCurrent</span>Item(int item);

void setOnPageChangeListener(ViewPager.OnPageChangeListener listener);

void notifyDataSetChanged();

}

在本例子中,我把这两个类单独拿出来了。如果你的项目已经有依赖该库,则就不需要再去复制它们。

下面先上两张效果图。





在图中,上面的内容区域是viewpager,下面的是导航栏indicator。点击导航栏可以切换上面的页面,当然,滑动上面的页面下面的导航栏也可以切换。

接着说一下它的实现。类的代码不复杂,大部分参照了viewpagerindicator中的TabPageIndicator类来实现,不过在这里我继承的是LinearLayout,代码如下:

[java] view
plaincopy





package com.githang.navigatordemo;

import android.content.Context;

import android.support.v4.view.PagerAdapter;

import android.support.v4.view.ViewPager;

import android.util.AttributeSet;

import android.view.View;

import android.view.ViewGroup;

import android.widget.ImageView;

import android.widget.LinearLayout;

import android.widget.TextView;

import com.viewpagerindicator.IconPagerAdapter;

import com.viewpagerindicator.PageIndicator;

import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;

/**

* User: Geek_Soledad(msdx.android@qq.com)

* Date: 2014-08-27

* Time: 09:20

* FIXME

*/

public class IconTabPageIndicator extends LinearLayout implements PageIndicator {

/**

* Title text used when no title is provided by the adapter.

*/

private static final CharSequence EMPTY_TITLE = "";

/**

* Interface for a callback when the selected tab has been reselected.

*/

public interface OnTabReselectedListener {

/**

* Callback when the selected tab has been reselected.

*

* @param position Position of the current center item.

*/

void onTabReselected(int position);

}

private Runnable mTabSelector;

private final View.OnClickListener mTabClickListener = new View.OnClickListener() {

public void onClick(View view) {

TabView tabView = (TabView) view;

final int oldSelected = mViewPager.getCurrentItem();

final int newSelected = tabView.getIndex();

mViewPager.<span style="BACKGROUND-COLOR: #ffd700">setCurrent</span>Item(newSelected, false);

if (oldSelected == newSelected && mTabReselectedListener != null) {

mTabReselectedListener.onTabReselected(newSelected);

}

}

};

private final LinearLayout mTabLayout;

private ViewPager mViewPager;

private ViewPager.OnPageChangeListener mListener;

private int mSelectedTabIndex;

private OnTabReselectedListener mTabReselectedListener;

private int mTabWidth;

public IconTabPageIndicator(Context context) {

this(context, null);

}

public IconTabPageIndicator(Context context, AttributeSet attrs) {

super(context, attrs);

setHorizontalScrollBarEnabled(false);

mTabLayout = new LinearLayout(context, null, R.attr.tabPageIndicator);

addView(mTabLayout, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));

}

public void setOnTabReselectedListener(OnTabReselectedListener listener) {

mTabReselectedListener = listener;

}

@Override

public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

final int widthMode = View.MeasureSpec.getMode(widthMeasureSpec);

final boolean lockedExpanded = widthMode == View.MeasureSpec.EXACTLY;

final int childCount = mTabLayout.getChildCount();

if (childCount > 1 && (widthMode == MeasureSpec.EXACTLY || widthMode == MeasureSpec.AT_MOST)) {

mTabWidth = MeasureSpec.getSize(widthMeasureSpec) / childCount;

} else {

mTabWidth = -1;

}

final int oldWidth = getMeasuredWidth();

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

final int newWidth = getMeasuredWidth();

if (lockedExpanded && oldWidth != newWidth) {

// Recenter the tab display if we're at a new (scrollable) size.

<span style="BACKGROUND-COLOR: #ffd700">setCurrent</span>Item(mSelectedTabIndex);

}

}

private void animateToTab(final int position) {

final View tabView = mTabLayout.getChildAt(position);

if (mTabSelector != null) {

removeCallbacks(mTabSelector);

}

mTabSelector = new Runnable() {

public void run() {

final int scrollPos = tabView.getLeft() - (getWidth() - tabView.getWidth()) / 2;

mTabSelector = null;

}

};

post(mTabSelector);

}

@Override

public void onAttachedToWindow() {

super.onAttachedToWindow();

if (mTabSelector != null) {

// Re-post the selector we saved

post(mTabSelector);

}

}

@Override

public void onDetachedFromWindow() {

super.onDetachedFromWindow();

if (mTabSelector != null) {

removeCallbacks(mTabSelector);

}

}

private void addTab(int index, CharSequence text, int iconResId) {

final TabView tabView = new TabView(getContext());

tabView.mIndex = index;

tabView.setOnClickListener(mTabClickListener);

tabView.setText(text);

if (iconResId > 0) {

tabView.setIcon(iconResId);

}

mTabLayout.addView(tabView, new LinearLayout.LayoutParams(0, MATCH_PARENT, 1));

}

@Override

public void onPageScrollStateChanged(int arg0) {

if (mListener != null) {

mListener.onPageScrollStateChanged(arg0);

}

}

@Override

public void onPageScrolled(int arg0, float arg1, int arg2) {

if (mListener != null) {

mListener.onPageScrolled(arg0, arg1, arg2);

}

}

@Override

public void onPageSelected(int arg0) {

<span style="BACKGROUND-COLOR: #ffd700">setCurrent</span>Item(arg0);

if (mListener != null) {

mListener.onPageSelected(arg0);

}

}

@Override

public void setViewPager(ViewPager view) {

if (mViewPager == view) {

return;

}

if (mViewPager != null) {

mViewPager.setOnPageChangeListener(null);

}

final PagerAdapter adapter = view.getAdapter();

if (adapter == null) {

throw new IllegalStateException("ViewPager does not have adapter instance.");

}

mViewPager = view;

view.setOnPageChangeListener(this);

notifyDataSetChanged();

}

public void notifyDataSetChanged() {

mTabLayout.removeAllViews();

PagerAdapter adapter = mViewPager.getAdapter();

IconPagerAdapter iconAdapter = null;

if (adapter instanceof IconPagerAdapter) {

iconAdapter = (IconPagerAdapter) adapter;

}

final int count = adapter.getCount();

for (int i = 0; i < count; i++) {

CharSequence title = adapter.getPageTitle(i);

if (title == null) {

title = EMPTY_TITLE;

}

int iconResId = 0;

if (iconAdapter != null) {

iconResId = iconAdapter.getIconResId(i);

}

addTab(i, title, iconResId);

}

if (mSelectedTabIndex > count) {

mSelectedTabIndex = count - 1;

}

<span style="BACKGROUND-COLOR: #ffd700">setCurrent</span>Item(mSelectedTabIndex);

requestLayout();

}

@Override

public void setViewPager(ViewPager view, int initialPosition) {

setViewPager(view);

<span style="BACKGROUND-COLOR: #ffd700">setCurrent</span>Item(initialPosition);

}

@Override

public void <span style="BACKGROUND-COLOR: #ffd700">setCurrent</span>Item(int item) {

if (mViewPager == null) {

throw new IllegalStateException("ViewPager has not been bound.");

}

mSelectedTabIndex = item;

mViewPager.<span style="BACKGROUND-COLOR: #ff9632">setCurrent</span>Item(item, false);

final int tabCount = mTabLayout.getChildCount();

for (int i = 0; i < tabCount; i++) {

final View child = mTabLayout.getChildAt(i);

final boolean isSelected = (i == item);

child.setSelected(isSelected);

if (isSelected) {

animateToTab(item);

}

}

}

@Override

public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) {

mListener = listener;

}

private class TabView extends LinearLayout {

private int mIndex;

private ImageView mImageView;

private TextView mTextView;

public TabView(Context context) {

super(context, null, R.attr.tabView);

View view = View.inflate(context, R.layout.tab_view, null);

mImageView = (ImageView) view.findViewById(R.id.tab_image);

mTextView = (TextView) view.findViewById(R.id.tab_text);

this.addView(view);

}

@Override

public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

// Re-measure if we went beyond our maximum size.

if (mTabWidth > 0) {

super.onMeasure(MeasureSpec.makeMeasureSpec(mTabWidth, MeasureSpec.EXACTLY),

heightMeasureSpec);

}

}

public void setText(CharSequence text) {

mTextView.setText(text);

}

public void setIcon(int resId) {

if (resId > 0) {

mImageView.setImageResource(resId);

}

}

public int getIndex() {

return mIndex;

}

}

}

改动的地方主要是增加一个表示导航栏按钮宽度的变量,以及导航栏的view的实现,及两个onMeasure方法。由于在这里我继承的是LinearLayout,也就是当导航栏栏目较多时,不会通过左右滑动来显示或隐藏其他按钮,而是直接平分,该部分的代码如下:

[java] view
plaincopy





public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

final int widthMode = View.MeasureSpec.getMode(widthMeasureSpec);

final boolean lockedExpanded = widthMode == View.MeasureSpec.EXACTLY;

final int childCount = mTabLayout.getChildCount();

if (childCount > 1 && (widthMode == MeasureSpec.EXACTLY || widthMode == MeasureSpec.AT_MOST)) {

mTabWidth = MeasureSpec.getSize(widthMeasureSpec) / childCount;

} else {

mTabWidth = -1;

}

final int oldWidth = getMeasuredWidth();

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

final int newWidth = getMeasuredWidth();

if (lockedExpanded && oldWidth != newWidth) {

// Recenter the tab display if we're at a new (scrollable) size.

<span style="BACKGROUND-COLOR: #ffd700">setCurrent</span>Item(mSelectedTabIndex);

}

}

当导航按钮大于1个时,直接平分。每个导航按钮的宽度即为mTabWidth。

然后重写TabView的onMeasure方法,当mTabWidth大于0时,设置它的宽度为mTabWidth,如下:

[java] view
plaincopy





@Override

public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

// Re-measure if we went beyond our maximum size.

if (mTabWidth > 0) {

super.onMeasure(MeasureSpec.makeMeasureSpec(mTabWidth, MeasureSpec.EXACTLY),

heightMeasureSpec);

}

}

在这里的TabView中,我则直接使用布局文件来写,上面是一个ImageView,下面是一个TextView,代码如下(tab_view.xml):

[html] view
plaincopy





<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical"

android:layout_width="match_parent"

android:background="@android:color/white"

android:gravity="center_horizontal"

android:addStatesFromChildren="true"

android:layout_height="wrap_content">

<ImageView

android:id="@+id/tab_image"

android:layout_width="27dp"

android:layout_marginTop="2dp"

android:adjustViewBounds="true"

android:contentDescription="@null"

android:layout_height="27dp" />

<TextView

android:id="@+id/tab_text"

android:layout_marginTop="2dp"

android:gravity="center_horizontal|bottom"

android:padding="2dp"

android:layout_width="wrap_content"

android:textColor="@color/tab_text_selector"

android:textSize="12sp"

android:layout_height="match_parent" />

</LinearLayout>

再看TabView内部类的构造方法代码:

[java] view
plaincopy





private class TabView extends LinearLayout {

public TabView(Context context) {

super(context, null, R.attr.tabView);

View view = View.inflate(context, R.layout.tab_view, null);

mImageView = (ImageView) view.findViewById(R.id.tab_image);

mTextView = (TextView) view.findViewById(R.id.tab_text);

this.addView(view);

}

}

TabView是继承自LinearLayout,然后通过布局文件tab_view获取一个view,并将它加到TabView当中。但是我们并没有定义TabView本身的布局参数,所以加到它里面的view并不是居中的,而是靠左。所以我们还需要设置这个TabView的参数,通过我们定义的属性R.attr.tabView,然后调用它父类的构造方法super(context,
null, R.attr.tabView)。

在IconTabPageIndicator的构造方法当中,你同样可以看到导航栏的容器——mTabLayout,同样是通过属性来创建的。如下代码:

[java] view
plaincopy





public IconTabPageIndicator(Context context, AttributeSet attrs) {

super(context, attrs);

setHorizontalScrollBarEnabled(false);

mTabLayout = new LinearLayout(context, null, R.attr.tabPageIndicator);

addView(mTabLayout, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));

}

定义属性的方法如下,先在res/values下新建一个attrs.xml的文件,然后加入以下内容:

[html] view
plaincopy





<?xml version="1.0" encoding="utf-8"?>

<resources>

<declare-styleable name="TabView">

<attr name="tabPageIndicator" format="reference" />

<attr name="tabView" format="reference" />

</declare-styleable>

</resources>

即在这里声明两个属性,一个是tabPageIndicator,另一个是tabView,它们都是引用类型的。

但仅仅这样还是不够的,因为我们只是声明了两个属性,并没有设定属性的具体内容,所以我们还需要在styles.xml文件当中设置我们的主题,代码如下(styles.xml):

[html] view
plaincopy





<style name="AppTheme" parent="Theme.AppCompat.Light">

<item name="tabView">@style/TabView</item>

<item name="tabPageIndicator">@style/TabIndicator</item>

</style>

<style name="TabIndicator"/>

<style name="TabView">

<item name="android:addStatesFromChildren">true</item>

<item name="android:orientation">vertical</item>

<item name="android:gravity">bottom|center_horizontal</item>

<item name="android:layout_width">0dp</item>

<item name="android:background">@android:color/white</item>

<item name="android:layout_height">match_parent</item>

</style>

到此,我们的IconTabPageIndicator就实现好了。接下来在我们的程序中使用它:

activity的布局文件(activity_my.xml):

[html] view
plaincopy





<RelativeLayout 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"

tools:context=".MyActivity">

<com.githang.navigatordemo.IconTabPageIndicator

android:id="@+id/indicator"

android:layout_alignParentBottom="true"

android:layout_width="match_parent"

android:layout_height="wrap_content"/>

<android.support.v4.view.ViewPager

android:layout_above="@id/indicator"

android:id="@+id/view_pager"

android:layout_width="match_parent"

android:layout_height="match_parent"/>

</RelativeLayout>

fragment的布局文件(fragment.xml):

[html] view
plaincopy





<RelativeLayout 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:paddingLeft="@dimen/activity_horizontal_margin"

android:paddingRight="@dimen/activity_horizontal_margin"

android:paddingTop="@dimen/activity_vertical_margin"

android:gravity="center"

android:paddingBottom="@dimen/activity_vertical_margin"

android:background="#eee"

tools:context=".MyActivity">

<TextView

android:id="@+id/text"

android:textAppearance="@android:style/TextAppearance.Large"

android:layout_width="wrap_content"

android:layout_height="wrap_content"/>

</RelativeLayout>

MyActivity类的代码:

[java] view
plaincopy





package com.githang.navigatordemo;

import android.os.Bundle;

import android.support.v4.app.Fragment;

import android.support.v4.app.FragmentActivity;

import android.support.v4.app.FragmentManager;

import android.support.v4.app.FragmentPagerAdapter;

import android.support.v4.view.ViewPager;

import com.viewpagerindicator.IconPagerAdapter;

import java.util.ArrayList;

import java.util.List;

public class MyActivity extends FragmentActivity {

private ViewPager mViewPager;

private IconTabPageIndicator mIndicator;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_my);

initViews();

}

private void initViews() {

mViewPager = (ViewPager) findViewById(R.id.view_pager);

mIndicator = (IconTabPageIndicator) findViewById(R.id.indicator);

List<BaseFragment> fragments = initFragments();

FragmentAdapter adapter = new FragmentAdapter(fragments, getSupportFragmentManager());

mViewPager.setAdapter(adapter);

mIndicator.setViewPager(mViewPager);

}

private List<BaseFragment> initFragments() {

List<BaseFragment> fragments = new ArrayList<BaseFragment>();

BaseFragment userFragment = new BaseFragment();

userFragment.setTitle("用户");

userFragment.setIconId(R.drawable.tab_user_selector);

fragments.add(userFragment);

BaseFragment noteFragment = new BaseFragment();

noteFragment.setTitle("记事本");

noteFragment.setIconId(R.drawable.tab_record_selector);

fragments.add(noteFragment);

BaseFragment contactFragment = new BaseFragment();

contactFragment.setTitle("联系人");

contactFragment.setIconId(R.drawable.tab_user_selector);

fragments.add(contactFragment);

BaseFragment recordFragment = new BaseFragment();

recordFragment.setTitle("记录");

recordFragment.setIconId(R.drawable.tab_record_selector);

fragments.add(recordFragment);

return fragments;

}

class FragmentAdapter extends FragmentPagerAdapter implements IconPagerAdapter {

private List<BaseFragment> mFragments;

public FragmentAdapter(List<BaseFragment> fragments, FragmentManager fm) {

super(fm);

mFragments = fragments;

}

@Override

public Fragment getItem(int i) {

return mFragments.get(i);

}

@Override

public int getIconResId(int index) {

return mFragments.get(index).getIconId();

}

@Override

public int getCount() {

return mFragments.size();

}

@Override

public CharSequence getPageTitle(int position) {

return mFragments.get(position).getTitle();

}

}

}

BaseFragment类的代码:

[java] view
plaincopy





package com.githang.navigatordemo;

import android.os.Bundle;

import android.support.annotation.Nullable;

import android.support.v4.app.Fragment;

import android.view.LayoutInflater;

import android.view.View;

import android.view.ViewGroup;

import android.widget.TextView;

/**

* User: Geek_Soledad(msdx.android@qq.com)

* Date: 2014-08-27

* Time: 09:01

* FIXME

*/

public class BaseFragment extends Fragment {

private String title;

private int iconId;

public String getTitle() {

return title;

}

public void setTitle(String title) {

this.title = title;

}

public int getIconId() {

return iconId;

}

public void setIconId(int iconId) {

this.iconId = iconId;

}

@Override

public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

View view = inflater.inflate(R.layout.fragment, null, false);

TextView textView = (TextView) view.findViewById(R.id.text);

textView.setText(getTitle());

return view;

}

}

项目代码下载地址:http://zdz.la/xvS4Ab

修订版下载地址:http://download.csdn.net/detail/maosidiaoxian/7913269

git 代码地址:http://git.oschina.net/msdx/IconTabPageIndicator/tree/1.0

最新代码已经将library的代码分离出来: CSDN传送门
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐