Android微信支付宝的底部导航栏是怎么做的?简单的导航栏蕴藏着大智慧!

常见做法1:

套框架


常见做法2:

在底部写一个水平的LinearLayout作为导航栏,每个Item又是一个垂直的LinearLayout。


。。。

不是说上述做法不好。

做法1不能满足个性定制的需求

做法2嵌套层级太多,拖慢了性能;MainActivity中做事太多,负荷太大


正确做法:

Android微信支付宝的底部导航栏是怎么做的?简单的导航栏蕴藏着大智慧!

为了维护,我们的模块化原子化是前期开发时必不可少的工作。这里体现的原子性是:

1.底部用碎片取代LinerLayout

2.每个item都作为一个自定义控件,都维护(缓存)了主界面布局碎片的实例。

3.切换碎片的形式采用attach和detach,而不是hide和show,无形中减小了负担,增加了容错率。

4.切换碎片不再是在MainActivity中一个一个地去实例化碎片,而是交给自定义的item控件去维护


先看一下自定义Item控件的代码

xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="50dp">

    <ImageView
        android:id="@+id/nav_icon"
        android:layout_width="35dp"
        android:layout_height="35dp"
        android:layout_gravity="center_horizontal"
        tools:background="@mipmap/ic_launcher" />

    <TextView
        android:id="@+id/nav_title"
        android:layout_gravity="center_horizontal"
        android:layout_width="wrap_content"
        android:layout_height="15dp"
        tools:text="微信"/>

</LinearLayout>

java

维护的变量

private Fragment mFragment = null;//这个是你维护的主界面的碎片实例

private ImageView mIcon;
private TextView mTitle;

private Class<?> mClx;
private String mTag;

取得xml并加载的形式,this代表这个xml的父布局就是这个自定义view,再用getChildAt的形式取得碎片中的控件,体现了开闭原则

LayoutInflater inflater = LayoutInflater.from(getContext());
inflater.inflate(itemLayoutId, this, true);

ViewGroup vg = (ViewGroup) this.getChildAt(0);
mIcon = (ImageView) vg.getChildAt(0);
mTitle = (TextView) vg.getChildAt(1);

全部代码(完全符合开闭原则,使用的时候不需要修改这个自定义控件任何的代码)

public class NavButton extends FrameLayout {

    private Fragment mFragment = null;//这个是你维护的主界面的碎片实例

    private ImageView mIcon;
    private TextView mTitle;

    private Class<?> mClx;
    private String mTag;

    public NavButton(@NonNull Context context) {
        super(context);
    }

    public NavButton(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public NavButton(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public NavButton(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    private int uncheckedTitle;
    private int checkedTitle;
    private int uncheckedIcon;
    private int checkedIcon;

    public void init(@DrawableRes int uncheckedIcon, @DrawableRes int checkedIcon, @ColorInt int uncheckedTitle, @ColorInt int checkedTitle,  String s, Class<?> clx, int itemLayoutId) {
        LayoutInflater inflater = LayoutInflater.from(getContext());
        inflater.inflate(itemLayoutId, this, true);

        ViewGroup vg = (ViewGroup) this.getChildAt(0);
        mIcon = (ImageView) vg.getChildAt(0);
        mTitle = (TextView) vg.getChildAt(1);

        mTitle.setText(s);

        if (uncheckedTitle == 0) {
            this.uncheckedTitle = mTitle.getCurrentTextColor();
        } else {
            this.uncheckedTitle = uncheckedTitle;
            mTitle.setTextColor(uncheckedTitle);
        }

        this.checkedTitle = checkedTitle;
        this.uncheckedIcon = uncheckedIcon;
        this.checkedIcon = checkedIcon;

        mIcon.setImageDrawable(getResources().getDrawable(uncheckedIcon));
        mClx = clx;
        mTag = mClx.getName();
    }

    public Fragment getFragment() {
        return mFragment;
    }

    public Class<?> getClx() {
        return mClx;
    }

    public String getTag() {
        return mTag;
    }

    public void setFragment(Fragment mFragment) {
        this.mFragment = mFragment;
    }

    @Override
    public void setSelected(boolean selected) {
        super.setSelected(selected);

        if (selected) {
            mTitle.setTextColor(checkedTitle);
            mIcon.setImageDrawable(getResources().getDrawable(checkedIcon));
        } else {
            mTitle.setTextColor(uncheckedTitle);
            mIcon.setImageDrawable(getResources().getDrawable(uncheckedIcon));
        }
    }

}

底部导航栏碎片代码

xml,想修改布局直接去改item的布局即可

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

    <com.example.wechat.nav.NavButton
        android:id="@+id/nav_chat"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="match_parent"/>

    <com.example.wechat.nav.NavButton
        android:id="@+id/nav_friends"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="match_parent"/>

    <com.example.wechat.nav.NavButton
        android:id="@+id/nav_find"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="match_parent"/>

    <com.example.wechat.nav.NavButton
        android:id="@+id/nav_me"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="match_parent"/>

</LinearLayout>

java

初始化前清空所有碎片,防止出错

private void clearOldFragment() {
    FragmentTransaction transaction = getFragmentManager().beginTransaction();
    List<Fragment> fragments = getFragmentManager().getFragments();
    if (transaction == null || fragments == null || fragments.size() == 0)
        return;
    boolean doCommit = false;
    for (Fragment fragment : fragments) {
        if (fragment != this && fragment != null) {
            transaction.remove(fragment);
            doCommit = true;
        }
    }
    if (doCommit)
        transaction.commitNow();
}

切换item的操作

//思路:把将选的item和当前的item作为新老碎片,mCurrent是当前维护的即选中的item
private void doSelect(NavButton newNav) {
    NavButton oldNav = null;

    //拦截点击当前选中的按钮
    if (mCurrentNav != null) {
        oldNav = mCurrentNav;
        if (oldNav == newNav) {
            return;
        }
    }

    //这里进行的操作:1.item的切换2.主布局碎片的切换
    doTabChanged(oldNav, newNav, mContainerId);
    mCurrentNav = newNav;
}

doTabChanged

private void doTabChanged(NavButton oldNav, NavButton newNav, int mContainerId) {
    FragmentTransaction transaction = getFragmentManager().beginTransaction();

    //将设置选中和设置未选中分立开来
    if (oldNav != null) {//这里判空主要防止异常情况和第一次显示设置默认item的选中
        if (newNav.getFragment() != null) {//这里的newNav要修改为oldNav,我擦,被开源中国的代码坑了!这是bug啊。找了我老半天
            transaction.detach(oldNav.getFragment());//切换主布局碎片显示
        }
        oldNav.setSelected(false);//切换item显示
    }

    if (newNav != null) {
        if (newNav.getFragment() == null) {
            Fragment fragment = Fragment.instantiate(getActivity(),
                    newNav.getClx().getName(), null);
            transaction.add(mContainerId, fragment, newNav.getTag());//给tag加快取得速度
            newNav.setFragment(fragment);//交给他去维护,这是一个缓存机制
        } else {
            transaction.attach(newNav.getFragment());//采用attach和detach比hide,show性能更高
        }
        newNav.setSelected(true);
    }

    transaction.commit();
}

全部代码

public class NavFragment extends BaseFragment implements View.OnClickListener {

    private FragmentManager mFragmentManager;
    private Context mContext;
    private int mContainerId;

    public NavFragment() {
    }

    @BindView(R.id.nav_chat)
    protected NavButton mChat;
    @BindView(R.id.nav_friends)
    protected NavButton mFriends;
    @BindView(R.id.nav_find)
    protected NavButton mFind;
    @BindView(R.id.nav_me)
    protected NavButton mMe;

    @Override
    protected int getLayoutId() {
        return R.layout.fragment_nav;
    }

    @Override
    protected void initData() {
        mContainerId = R.id.fl_main_container;

        clearOldFragment(mFragmentManager);
    }

    @Override
    protected void initWidget(View mRoot) {
        //这里就是传的参数:未选中图片,选中图片,0(初始文字,0为不设置)。。
        mChat.init(R.drawable.nav_chat_unchecked, R.drawable.nav_chat_checked, 0, getResources().getColor(R.color.nav), "微信", ChatFragment.class, R.layout.nav_item);
        mFriends.init(R.drawable.nav_friends_unchecked, R.drawable.nav_friends_checked, 0, getResources().getColor(R.color.nav), "通讯录", FriendsFragment.class, R.layout.nav_item);
        mFind.init(R.drawable.nav_find_unchecked, R.drawable.nav_find_checked, 0, getResources().getColor(R.color.nav), "发现", FindFragment.class, R.layout.nav_item);
        mMe.init(R.drawable.nav_me_unchecked, R.drawable.nav_me_checked, 0, getResources().getColor(R.color.nav), "我", MeFragment.class, R.layout.nav_item);

        doSelect(mChat);
    }

    private NavButton mCurrentNav = null;

    //思路:把将选的item和当前的item作为新老碎片,mCurrent是当前维护的即选中的item
    private void doSelect(NavButton newNav) {
        NavButton oldNav = null;

        //拦截点击当前选中的按钮
        if (mCurrentNav != null) {
            oldNav = mCurrentNav;
            if (oldNav == newNav) {
                return;
            }
        }

        //这里进行的操作:1.item的切换2.主布局碎片的切换
        doTabChanged(oldNav, newNav, mContainerId);
        mCurrentNav = newNav;
    }

    private void doTabChanged(NavButton oldNav, NavButton newNav, int mContainerId) {
        FragmentTransaction transaction = getFragmentManager().beginTransaction();

        //将设置选中和设置未选中分立开来
        if (oldNav != null) {//这里判空主要防止异常情况和第一次显示设置默认item的选中
            if (newNav.getFragment() != null) {
                transaction.detach(oldNav.getFragment());//切换主布局碎片显示
            }
            oldNav.setSelected(false);//切换item显示
        }

        if (newNav != null) {
            if (newNav.getFragment() == null) {
                Fragment fragment = Fragment.instantiate(getActivity(),
                        newNav.getClx().getName(), null);
                transaction.add(mContainerId, fragment, newNav.getTag());//给tag加快取得速度
                newNav.setFragment(fragment);//交给他去维护,这是一个缓存机制
            } else {
                transaction.attach(newNav.getFragment());//采用attach和detach比hide,show性能更高
            }
            newNav.setSelected(true);
        }

        transaction.commit();
    }

    private void clearOldFragment(FragmentManager mFragmentManager) {
        FragmentTransaction transaction = mFragmentManager.beginTransaction();
        List<Fragment> fragments = mFragmentManager.getFragments();
        if (transaction == null || fragments == null || fragments.size() == 0)
            return;
        boolean doCommit = false;
        for (Fragment fragment : fragments) {
            if (fragment != this && fragment != null) {
                transaction.remove(fragment);
                doCommit = true;
            }
        }
        if (doCommit)
            transaction.commitNow();
    }

    @OnClick({R.id.nav_chat, R.id.nav_friends, R.id.nav_find, R.id.nav_me})
    @Override
    public void onClick(View v) {
        if (v instanceof NavButton) {
            NavButton nav = (NavButton) v;
            doSelect(nav);
        }
    }

    // TODO: 2017/11/27 chat双击监听

}

效果图(素材找的不是很好)

Android微信支付宝的底部导航栏是怎么做的?简单的导航栏蕴藏着大智慧!


看起来还没有第三方那些绚丽?但是微信支付宝都是这样的,简单素净,高端app必备。而且其代码的可维护性和重用性才是重中之中。