Android TextView实现底部导航栏
前言
底部导航栏在App开发中是经常碰到的,国内的app大部分都有底部导航栏(QQ、微信、支付宝),我的手机一直都用的Android原生操作系统(从nexus系列到pxiel系列)。可以看出其实google推荐用侧滑菜单,google自带的app基本都使用侧滑菜单。但是我刚刚看的时候YouTube居然使用底部导航了。
分析需求
从效果图中我们可以看到底部有四个按钮,还有中间有个图片。
- 底部Tab在屏幕中的宽度是一致的,那就能想到LinearLayout的weight属性,利用线性布局的权重。
- 中间的图片突出了一点点,那我们得考虑在最外层用RelativeLayout或者FrameLayout,但是FrameLayout灵活性没有RelativeLayout强,所以优先选择RelativeLayout。
- 点击tab之后底部导航栏文字颜色图片都有变化。用淡绿代表选中,灰色代表不选中。
- Tab点击之后内容区域变化。
代码实现
先看布局文件activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<FrameLayout
android:id="@+id/main_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/view_line"/>
<View
android:id="@+id/view_line"
android:layout_height="1dp"
android:layout_width="match_parent"
android:background="#DCDBDB"
android:layout_above="@+id/rl_bottom"/>
<LinearLayout
android:id="@+id/rl_bottom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:background="#F2F2F2"
android:orientation="horizontal" >
<TextView
android:id="@+id/tv_main"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
android:drawableTop="@drawable/tab_item_main_img_selector"
android:drawablePadding="@dimen/main_tab_item_image_and_text"
android:focusable="true"
android:gravity="center"
android:text="@string/main"
android:textColor="@drawable/tabitem_txt_sel" />
<TextView
android:id="@+id/tv_dynamic"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
android:drawableTop="@drawable/tab_item_dynamic_img_selector"
android:drawablePadding="@dimen/main_tab_item_image_and_text"
android:focusable="true"
android:gravity="center"
android:text="@string/dynamic"
android:textColor="@drawable/tabitem_txt_sel" />
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<TextView
android:id="@+id/tv_message"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
android:drawableTop="@drawable/tab_item_message_img_selector"
android:drawablePadding="@dimen/main_tab_item_image_and_text"
android:focusable="true"
android:gravity="center"
android:text="@string/message"
android:textColor="@drawable/tabitem_txt_sel" />
<TextView
android:id="@+id/tv_person"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
android:drawableTop="@drawable/tab_item_person_img_selector"
android:drawablePadding="@dimen/main_tab_item_image_and_text"
android:focusable="true"
android:gravity="center"
android:text="@string/person"
android:textColor="@drawable/tabitem_txt_sel"/>
</LinearLayout>
<ImageView
android:id="@+id/iv_make"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:paddingBottom="10dp"
android:src="@drawable/icon_tab_make_select"/>
</RelativeLayout>
最外层用用RelativeLayout。
- 第一个子View是FrameLayout,tab切换的时候用它来显示Fragment
- 第二个View就是一个分割线
- 第三个View是一个LinearLayout,设置了android:layout_alignParentBottom="true",在父View的底部显示,里面有4个TextView跟一个View,权重都是1,但是最中间那个没有内容,是一个空的View,只是用它在来占个位置。
- 第4个View是一个ImageView,也设置了android:layout_alignParentBottom属性,并且通过android:layout_centerHorizontal属性设置水平居中,在RelativeLayout里面如果两个View的在同一个位置上,后面的View会覆盖在之前的View之上,所以这个ImageView盖住了LinearLayout的最中间的那个View。
我们看到给每个TextView设置了android:drawableTop属性,这个属性就是在TextView的文本上面放一张图片。我们看看首页的TextView的android:drawableTop属性对应的drawable文件。
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 选中状态显示 -->
<item android:drawable="@drawable/icon_tab_main_select" android:state_selected="true"/>
<!--非选中状态显示-->
<item android:drawable="@drawable/icon_tab_main_normal"/>
</selector>
最外层是一个selector,里面有两个item(item可以有多个),我们看到第一个item是选中状态的情况,item有一个属性叫android:state_selected="true",就是View选中状态时显示android:drawable对应的图片。第二个item就是默认情况,就是非选中的情况显示。在java代码中TextView.setSelected(true)对应第一个item。
不只是图片,还有TextView设置文字颜色也是通过selector来控制的。我们可以看到底部的四个TextView的android:textColor属性都引用了drawable/tabitem_txt_sel文件。
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 选中状态 -->
<item android:color="@color/main_tab_item_text_select" android:state_selected="true"/>
<!--非选中状态-->
<item android:color="@color/main_tab_item_text_normal"/>
</selector>
跟图片的选择器有点类似,只不过把android:drawable换成了android:color。
接下来看MainActivity代码,继承自FragmentActivity,这是android-support-v4.jar包里面的一个类,可以兼容3.0以下版本使用Fragment。
public class MainActivity extends FragmentActivity {
//要切换显示的四个Fragment
private HomeFragment homeFragment;
private DynamicFragment dynamicFragment;
private MessageFragment messageFragment;
private PersonFragment personFragment;
private int currentId = R.id.tv_main;// 当前选中id,默认是主页
private TextView tvMain, tvDynamic, tvMessage, tvPerson;//底部四个TextView
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tvMain = (TextView) findViewById(R.id.tv_main);
tvMain.setSelected(true);//首页默认选中
tvDynamic = (TextView) findViewById(R.id.tv_dynamic);
tvMessage = (TextView) findViewById(R.id.tv_message);
tvPerson = (TextView) findViewById(R.id.tv_person);
//默认加载首页
homeFragment = new HomeFragment();
getSupportFragmentManager().beginTransaction().add(R.id.main_container,homeFragment).commit();
tvMain.setOnClickListener(tabClickListener);
tvDynamic.setOnClickListener(tabClickListener);
tvMessage.setOnClickListener(tabClickListener);
tvPerson.setOnClickListener(tabClickListener);
findViewById(R.id.iv_make).setOnClickListener(onClickListener);
}
private OnClickListener onClickListener=new OnClickListener() {
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.iv_make:
Toast.makeText(MainActivity.this,"点击了制作按钮",Toast.LENGTH_SHORT).show();
break;
}
}
};
private OnClickListener tabClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
if (v.getId() != currentId) {//如果当前选中跟上次选中的一样,不需要处理
changeSelect(v.getId());//改变图标跟文字颜色的选中
changeFragment(v.getId());//fragment的切换
currentId = v.getId();//设置选中id
}
}
};
/**
* 改变fragment的显示
* @param resId
*/
private void changeFragment(int resId) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();//开启一个Fragment事务
hideFragments(transaction);//隐藏所有fragment
if(resId==R.id.tv_main){//主页
if(homeFragment==null){//如果为空先添加进来.不为空直接显示
homeFragment = new HomeFragment();
transaction.add(R.id.main_container,homeFragment);
}else {
transaction.show(homeFragment);
}
}else if(resId==R.id.tv_dynamic){//动态
if(dynamicFragment==null){
dynamicFragment = new DynamicFragment();
transaction.add(R.id.main_container,dynamicFragment);
}else {
transaction.show(dynamicFragment);
}
}else if(resId==R.id.tv_message){//消息中心
if(messageFragment==null){
messageFragment = new MessageFragment();
transaction.add(R.id.main_container,messageFragment);
}else {
transaction.show(messageFragment);
}
}else if(resId==R.id.tv_person){//我
if(personFragment==null){
personFragment = new PersonFragment();
transaction.add(R.id.main_container,personFragment);
}else {
transaction.show(personFragment);
}
}
transaction.commit();//一定要记得提交事务
}
/**
* 显示之前隐藏所有fragment
* @param transaction
*/
private void hideFragments(FragmentTransaction transaction){
if (homeFragment != null)//不为空才隐藏,如果不判断第一次会有空指针异常
transaction.hide(homeFragment);
if (dynamicFragment != null)
transaction.hide(dynamicFragment);
if (messageFragment != null)
transaction.hide(messageFragment);
if (personFragment != null)
transaction.hide(personFragment);
}
/**
* 改变TextView选中颜色
* @param resId
*/
private void changeSelect(int resId){
tvMain.setSelected(false);
tvDynamic.setSelected(false);
tvMessage.setSelected(false);
tvPerson.setSelected(false);
switch (resId) {
case R.id.tv_main:
tvMain.setSelected(true);
break;
case R.id.tv_dynamic:
tvDynamic.setSelected(true);
break;
case R.id.tv_message:
tvMessage.setSelected(true);
break;
case R.id.tv_person:
tvPerson.setSelected(true);
break;
}
}
}
在onCreate方法中设置布局文件,查找底部的四个TextView,给首页的TextView设置为选中状态,并且默认加载首页的Fragment,最后给底部的四个Tab设置点击事件。还有最中间的那个ImageView。
tabClickListener处理Tab点击事件,先判断这次点击的Tab跟上次点击的是否一致,如果跟当前的是一样的那就不需要处理。否则的话需要改变图标跟文字选中状态。还有Fragment的切换。并且把当前点击的View的id设置成当前选中的id。
changeSelect方法改变TextView选中颜色,首先全部设置成未选中,然后再来判断当前选中的那个。
changeFragment改变Fragment的显示,首先调用hideFragments方法隐藏所有的Fragment,然后根据当前选中的Tab来决定显示那个Fragment。显示的时候需要先判断这个Fragment有没有显示过,如果没有现实过需要new一个新的Fragment,然后调用FragmentTransaction的add方法添加进去。如果之前有添加过的,直接调用show方法就行。
hideFragments方法就是隐藏所有的Fragment。先判断Fragment是否为null,不为null,就调用transaction.hide方法隐藏Fragment。
底部的四个Tab(TextView)对应四个Fragment。分别是HomeFragment、DynamicFragment、MessageFragment、PersonFragment。因为是Demo所以都只显示了一个TextView。我们看看首页的Fragment。其他三个就不贴代码了。
HomeFragment显示的布局文件fragment_home.xml,里面就一个TextView
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="这是首页"
android:textSize="20sp"/>
</RelativeLayout>
HomeFragment.java
public class HomeFragment extends Fragment{
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_home, null);
return rootView;
}
}
源码下载
Android开发666 - 安卓开发技术分享
扫描二维码加关注
Android开发666