Android 仿微信底部菜单栏 + 显示未读消息数量

版权声明:本文为 like_program 原创文章,未经博主允许不得转载。 https://blog.csdn.net/like_program/article/details/53576927

转载请注明出处: http://blog.csdn.net/like_program/article/details/53576927

微信想必大家都很熟悉了,它的底部菜单栏和显示未读消息数量功能,被很多 app 模仿,这篇博客就来带领大家,一步一步实现底部菜单栏,并且在底部菜单栏显示未读消息数量。

我们先来看下最终效果图。

最终效果图

1.底部菜单栏

底部菜单栏最终效果图

2.显示未读消息数量

显示未读消息数量最终效果图

1.实现底部菜单栏

我们先来实现底部菜单栏。

打开 Android Studio,新建 TabTest 项目。

底部菜单栏是由 4 个 tab 组成,我们先来实现 tab 的布局,在 layout 目录下新建 view_tab.xml ,代码如下:

<?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="wrap_content"
                android:gravity="center">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="vertical"
        android:padding="5dp">

        <ImageView
            android:id="@+id/iv_tab"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/tab_wechat_normal"/>

        <TextView
            android:id="@+id/tv_tab"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="微信"/>
    </LinearLayout>

</RelativeLayout>

嗯,布局看上去有些繁琐。。。不过为了后面布局修改方便,暂时先无视这些。。。

tab 布局完成后,我们接着实现底部菜单栏,底部菜单栏,我们使用 FragmentTabHost 来实现。

修改 activity_main.xml 文件,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.app.FragmentTabHost
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@android:id/tabhost"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white"
    tools:context="com.example.tabnews.MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <!-- Fragment 容器 -->
        <FrameLayout
            android:id="@android:id/tabcontent"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"/>

        <!-- 底部菜单栏 -->
        <TabWidget
            android:id="@android:id/tabs"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@android:color/darker_gray"/>
    </LinearLayout>
</android.support.v4.app.FragmentTabHost>

没有使用过 FragmentTabHost 的同学看布局文件可能会有些晕。为了便于大家理解,我简单的画张图。

FragmentTabHost图解

我们可以看到,最外层的控件就是 FragmentTabHost,在 FragmentTabHost 里面从上至下,依次是
tabcontent 和 tabs。这里要注意下,控件的 id 必须是 Android 提供的标准 id , 即 @android:id

  • FragmentTabHost 用来托管 Fragment 和 TabWidget(底部菜单栏),所以我们把它的 id 指定为 @android:id/tabhost
  • FrameLayout 用来放置 Fragment,也就是具体展示的内容,所以我们把它的 id 指定为 @android:id/tabcontent
  • TabWidget 用来放置底部菜单栏,底部菜单栏有很多 tab。所以我们把它的 id 指定为 @android:id/tabs

当点击不同的 tab 时,对应的 Fragment 就会被切换到 FrameLayout 中。

布局文件完成后,我们修改 MainActivity.java,代码如下:

package com.example.tabtest;

import android.graphics.Color;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTabHost;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.ImageView;
import android.widget.TabHost;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private List<TabItem> mFragmentList;

    private FragmentTabHost mFragmentTabHost;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initTabItemData();
    }

    /**
     * 初始化 Tab 数据
     */
    private void initTabItemData() {
        mFragmentList = new ArrayList<>();
        mFragmentList.add(new TabItem(
                R.drawable.tab_wechat_normal,
                R.drawable.tab_wechat_selected,
                "微信",
                WeChatFragment.class
        ));

        mFragmentList.add(new TabItem(
                R.drawable.tab_friend_normal,
                R.drawable.tab_friend_selected,
                "朋友",
                FriendFragment.class
        ));

        mFragmentList.add(new TabItem(
                R.drawable.tab_contacts_normal,
                R.drawable.tab_contacts_selected,
                "联系人",
                ContactsFragment.class
        ));

        mFragmentList.add(new TabItem(
                R.drawable.tab_settings_normal,
                R.drawable.tab_settings_selected,
                "设置",
                SettingFragment.class
        ));

        mFragmentTabHost = (FragmentTabHost) findViewById(android.R.id.tabhost);
        // 绑定 FragmentManager
        mFragmentTabHost.setup(this, getSupportFragmentManager(), android.R.id.tabcontent);
        // 删除分割线
        mFragmentTabHost.getTabWidget().setDividerDrawable(null);

        for (int i = 0; i < mFragmentList.size(); i++) {
            TabItem tabItem = mFragmentList.get(i);
            // 创建 tab
            TabHost.TabSpec tabSpec = mFragmentTabHost.newTabSpec(
                    tabItem.getTabText()).
                    setIndicator(tabItem.getTabView());
            // 将创建的 tab 添加到底部 tab 栏中( @android:id/tabs )
            // 将 Fragment 添加到页面中( @android:id/tabcontent )
            mFragmentTabHost.addTab(tabSpec, tabItem.getFragmentClass(), null);
            // 底部 tab 栏设置背景图片
            mFragmentTabHost.getTabWidget().setBackgroundResource(R.drawable.bottom_bar);

            // 默认选中第一个 tab
            if (i == 0) {
                tabItem.setChecked(true);
            } else {
                tabItem.setChecked(false);
            }
        }

        // 切换 tab 时,回调此方法
        mFragmentTabHost.setOnTabChangedListener(new TabHost.OnTabChangeListener() {
            @Override
            public void onTabChanged(String tabId) {
                for (int i = 0; i < mFragmentList.size(); i++) {
                    TabItem tabItem = mFragmentList.get(i);
                    // 通过 tag 检查用户点击的是哪个 tab
                    if (tabId.equals(tabItem.getTabText())) {
                        tabItem.setChecked(true);
                    } else {
                        tabItem.setChecked(false);
                    }
                }
            }
        });
    }

    class TabItem {

        private ImageView mIvTab;

        /**
         * 正常状态的图片
         */
        private int imageNormal;

        /**
         * 选中状态的图片
         */
        private int imageSelected;

        private TextView mTvTab;

        /**
         * 文字
         */
        private String tabText;

        /**
         * Fragment
         */
        private Class<? extends Fragment> fragmentClass;

        private View mTabView;

        public TabItem(int imageNormal,
                       int imageSelected,
                       String text,
                       Class<? extends Fragment>
                fragmentClass) {
            this.imageNormal = imageNormal;
            this.imageSelected = imageSelected;
            this.tabText = text;
            this.fragmentClass = fragmentClass;
        }

        public Class<? extends Fragment> getFragmentClass() {
            return fragmentClass;
        }

        /**
         * 获取 tab 上的文字
         *
         * @return tab 上的文字
         */
        public String getTabText() {
            return tabText;
        }

        /**
         * 设置选中
         *
         * @param checked 是否选中
         */
        public void setChecked(boolean checked) {
            if (checked) {
                mTvTab.setTextColor(getResources().getColor(R.color.colorTabText));
                mIvTab.setImageResource(imageSelected);
            } else {
                mTvTab.setTextColor(Color.WHITE);
                mIvTab.setImageResource(imageNormal);
            }
        }

        public View getTabView() {
            mTabView = View.inflate(MainActivity.this, R.layout.view_tab, null);
            mIvTab = (ImageView) mTabView.findViewById(R.id.iv_tab);
            mTvTab = (TextView) mTabView.findViewById(R.id.tv_tab);
            mIvTab.setImageResource(imageNormal);
            mTvTab.setText(tabText);
            return mTabView;
        }
    }
}

使用 FragmentTabHost ,Activity 需要继承自 FragmentActivity。但是 Android Studio 创建的 Activity 默认继承自 AppCompatActivity,而 AppCompatActivity 又继承自 FragmentActivity,所以我们不需要修改 Activity 的继承关系。

为了更加方便的使用 FragmentTabHost,我们把很多数据都封装在 TabItem 中,比如每个 Tab 的 ImageView,TextView,还有对应的文字,图片,设置选中状态,等等。

我们首先实例化了 FragmentTabHost:

mFragmentTabHost = (FragmentTabHost) findViewById(android.R.id.tabhost);

然后将 FragmentManager 和 android.R.id.tabcontent(Fragment 容器 id)绑定了起来,这样,FragmentManager 等会才能管理Fragment,将不同的 Fragment 切换到容器里。

mFragmentTabHost.setup(this, getSupportFragmentManager(), android.R.id.tabcontent);

接下来,我们遍历 mFragmentList,依次取出每一个 tabItem。

接着,我们使用 mFragmentTabHost.newTabSpec() 方法创建 tab,并设置了指示器。newTabSpec() 方法中接收一个 tag 作为参数,我们为了方便起见,直接传入每个 tab 的文字作为 tag。指示器我们传入的是每个 tab 的 View。

TabHost.TabSpec tabSpec = mFragmentTabHost.newTabSpec(
        tabItem.getTabText()).
        setIndicator(tabItem.getTabView());

这样,tab 就创建好了。创建好之后,我们就要把 tab 和对应的 Fragment 添加到 FragmentTabHost 中。Fragment 代码就不贴了,就是一个 TextView。相信大家都能写的出来。

// 将创建的 tab 添加到底部 tab 栏中( @android:id/tabs )
// 将 Fragment 添加到页面中( @android:id/tabcontent )
mFragmentTabHost.addTab(tabSpec, tabItem.getFragmentClass(), null);

为了让底部菜单栏看着更美观,我们给底部菜单栏设置了背景。这里要注意下,给底部菜单栏设置背景,不能直接调用 mFragmentTabHost 的 setBackgroundResource() 方法,而是要先获取到底部菜单栏,再设置背景:

mFragmentTabHost.getTabWidget().setBackgroundResource(R.drawable.bottom_bar);

然后,我们给 mFragmentTabHost 设置了 tab 切换的监听器,当我们点击 tab 时,就会回调此方法:

mFragmentTabHost.setOnTabChangedListener(new TabHost.OnTabChangeListener() {
    @Override
    public void onTabChanged(String tabId) {
        for (int i = 0; i < mFragmentList.size(); i++) {
            TabItem tabItem = mFragmentList.get(i);
            // 通过 tag 检查用户点击的是哪个 tab
            if (tabId.equals(tabItem.getTabText())) {
                tabItem.setChecked(true);
            } else {
                tabItem.setChecked(false);
            }
        }
    }
});

我们可以看到,onTabChanged 方法中有一个参数,tabId,这个 tabId 就是我们使用 mFragmentTabHost.newTabSpec() 方法创建 tab 时,传入的 tag。因为我们传入的 tag 就是每个 tab 的文字。所以我们要用 tag 和每一个 tabItem 的文字作比较,如果一样,就切换图标和文字颜色。

最后我们默认选中第一个 tab:

if (i == 0) {
    tabItem.setChecked(true);
} else {
    tabItem.setChecked(false);
}

好了,运行一下程序:

底部菜单栏最终效果图

底部菜单栏已经成功的实现了。

2.实现显示未读消息数量

我们先思考一下,显示未读消息数量这个功能,该怎么实现?

其实这个功能的实现很简单,就是在每个 tab 的布局右上角,放一个 TextView,当有未读消息时,就让 TextView 显示未读消息的数量。

那我们先来修改下 tab 的布局文件,修改 view_tab.xml ,代码如下:

<?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="wrap_content"
                android:gravity="center">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="vertical"
        android:padding="5dp">

        ......
    </LinearLayout>

    <!-- 消息数量小圆点 -->
    <TextView
        android:id="@+id/tv_new_msg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_marginRight="10dp"
        android:gravity="center"
        android:textColor="#fcff00"
        android:textSize="12sp"/>

</RelativeLayout>

可以看到,我们在 LinearLayout 的下面放置了一个 TextView。

接下来,我们修改 Activity 的代码,实现显示未读消息的功能。

package com.example.tabtest;

import android.graphics.Color;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTabHost;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.ImageView;
import android.widget.TabHost;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    public static final int TAB_WECHAT      = 0;
    public static final int TAB_FRIEND      = 1;
    public static final int TAB_CONTACTS    = 2;
    public static final int TAB_SETTING     = 3;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initTabItemData();
    }

    /**
     * 更新新消息数量
     *
     * @param tabIndex tab 的索引
     * @param msgCount 消息数量
     */
    public void updateMsgCount(int tabIndex, int msgCount) {
        mFragmentList.get(tabIndex).setNewMsgCount(msgCount);
    }

    class TabItem {

        ......

        /**
         * 新消息
         */
        private TextView mTvNewMsg;

        public View getTabView() {
            mTabView = View.inflate(MainActivity.this, R.layout.view_tab, null);
            ......
            mTvNewMsg = (TextView) mTabView.findViewById(R.id.tv_new_msg);
            return mTabView;
        }

        /**
         * 设置新消息数量
         */
        public void setNewMsgCount(int count) {
            if (count > 0) {
                mTvNewMsg.setText(String.valueOf(count));
            }
            mTvNewMsg.setBackgroundResource(R.drawable.ic_new_msg);
        }
    }
}

我们给 TabItem 加上了 设置新消息数量 的功能:让 TextView 显示新消息数量,并显示背景。
然后又创建一个了 updateMsgCount() 方法,用来更新某一个 tab 的新消息数量。

接下来,我们要在 WeChatFragment 中设置新消息数量。

我们先修改 WeChatFragment 的布局文件,修改 fragment_wechat.xml,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical">
    <!-- 微信 -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <EditText
            android:id="@+id/et_wechat"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:hint="微信  未读消息数量"
            android:inputType="number"/>

        <Button
            android:id="@+id/btn_wechat"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="确定"/>
    </LinearLayout>

    <!-- 朋友 -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <EditText
            android:id="@+id/et_friend"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:hint="朋友  未读消息数量"
            android:inputType="number"/>

        <Button
            android:id="@+id/btn_friend"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="确定"/>
    </LinearLayout>

    <!-- 联系人 -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <EditText
            android:id="@+id/et_contacts"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:hint="联系人  未读消息数量"
            android:inputType="number"/>

        <Button
            android:id="@+id/btn_contacts"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="确定"/>
    </LinearLayout>

    <!-- 设置 -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <EditText
            android:id="@+id/et_setting"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:hint="设置  未读消息数量"
            android:inputType="number"/>

        <Button
            android:id="@+id/btn_setting"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="确定"/>
    </LinearLayout>
</LinearLayout>

代码看着有些长,但是很简单,就是 4 个 EditText + 4 个 Button。没什么难度,就不多说了。

修改 WeChatFragment.java ,代码如下:

package com.example.tabtest;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;

/**
 * 微信 Fragment
 */
public class WeChatFragment extends Fragment implements View.OnClickListener {

    private EditText mEdWeChat;
    private EditText mEdFriend;
    private EditText mEdContacts;
    private EditText mEdSetting;

    public WeChatFragment() {
        // Required empty public constructor
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_wechat, container, false);

        mEdWeChat = (EditText) view.findViewById(R.id.et_wechat);
        mEdFriend = (EditText) view.findViewById(R.id.et_friend);
        mEdContacts = (EditText) view.findViewById(R.id.et_contacts);
        mEdSetting = (EditText) view.findViewById(R.id.et_setting);

        Button btnWeChat = (Button) view.findViewById(R.id.btn_wechat);
        Button btnFrrend = (Button) view.findViewById(R.id.btn_friend);
        Button btnContacts = (Button) view.findViewById(R.id.btn_contacts);
        Button btnSetting = (Button) view.findViewById(R.id.btn_setting);

        btnWeChat.setOnClickListener(this);
        btnFrrend.setOnClickListener(this);
        btnContacts.setOnClickListener(this);
        btnSetting.setOnClickListener(this);
        return view;
    }

    @Override
    public void onClick(View v) {
        // 新消息数量
        String number;
        // 获取 Fragment 的托管 Activity
        MainActivity mainActivity = (MainActivity) getActivity();

        switch (v.getId()) {
            case R.id.btn_wechat:
                number = mEdWeChat.getText().toString().trim();
                if (!TextUtils.isEmpty(number)) {
                    mainActivity.updateMsgCount(MainActivity.TAB_WECHAT, Integer.valueOf(number));
                }
                break;
            case R.id.btn_friend:
                number = mEdFriend.getText().toString().trim();
                if (!TextUtils.isEmpty(number)) {
                    mainActivity.updateMsgCount(MainActivity.TAB_FRIEND, Integer.valueOf(number));
                }
                break;
            case R.id.btn_contacts:
                number = mEdContacts.getText().toString().trim();
                if (!TextUtils.isEmpty(number)) {
                    mainActivity.updateMsgCount(MainActivity.TAB_CONTACTS, Integer.valueOf(number));
                }
                break;
            case R.id.btn_setting:
                number = mEdSetting.getText().toString().trim();
                if (!TextUtils.isEmpty(number)) {
                    mainActivity.updateMsgCount(MainActivity.TAB_SETTING, Integer.valueOf(number));
                }
                break;
        }
    }
}

代码同样很简单,先实例化布局文件中的 4 个 EditText 和 4 个 Button,然后给 Button 设置了点击监听。当点击 Button 时,就获取对应的 EditText 中输入的新消息数量,然后调用 MainActivity 的 updateMsgCount() 方法设置新消息数量。

这里有一点要注意下:因为 WeChatFragment 的托管 Activity 是 MainActivity,所以在 WeChatFragment 中调用 getActivity() 后,可以向下转型为 MainActivity。

MainActivity mainActivity = (MainActivity) getActivity();

好了,运行一下程序:

可以看到,已经可以成功显示未读消息数量了。

显示未读消息数量最终效果图

源码下载

猜你喜欢

转载自blog.csdn.net/like_program/article/details/53576927