Android微信支付宝的底部导航栏是怎么做的?简单的导航栏蕴藏着大智慧!
常见做法1:
套框架
常见做法2:
在底部写一个水平的LinearLayout作为导航栏,每个Item又是一个垂直的LinearLayout。
。。。
不是说上述做法不好。
做法1不能满足个性定制的需求
做法2嵌套层级太多,拖慢了性能;MainActivity中做事太多,负荷太大
正确做法:
为了维护,我们的模块化原子化是前期开发时必不可少的工作。这里体现的原子性是:
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双击监听 }
效果图(素材找的不是很好)
看起来还没有第三方那些绚丽?但是微信支付宝都是这样的,简单素净,高端app必备。而且其代码的可维护性和重用性才是重中之中。