封装实践——打造微信底部tab栏
2016-04-11 15:47
441 查看
目前市面上很多App都采用底部一个Tab栏,管理四到五个Tab,然后选择切换页面的方式的设计,这虽然不太符合metro design,但确是一个不容易出错而又符合国人使用习惯的设计方式。比如微信,支付宝,网易新闻,简书等都采用这种设计。而所谓封装一定是基于某种确定的业务需求,所以针对上述的通用设计方式,我们可以做一个比较理想化的封装。
为什么要做封装
你可能会觉得,这就是一个选择切换嘛,我只要做些if else判断就好了。但是Tab栏一般用在首页,业务逻辑代码量就不用说了,如果这时候不想被各种if else , swich case 搞得心力交瘁,那么我们少写些冗余代码又有何妨。毕竟代码不止眼前的苟且,还有设计改版和需求变更,某天产品经理更你说要改版,修改完xml,再去修改if else,然后再去修改click。。。想想也是醉了。所以这里要说的封装当然不会是,一个LinearLayout塞几个布局,然后做swich case去切换fragment,我希望布局里只需要include一个TabView,代码里也不需要N多findviewbyId就能实现这个功能。
基于以上分析,开工编码。我们还是以微信为例吧,假设底部Tab栏共有四个按钮,上面icon,下面文本。那么我们先把这一样式的xml写出来。
上面是四个Tab按钮的通用布局,我们还需要写个TabView来解析这个布局。
到这里我们已经完成了单个TabView按钮的解析,但是假如我有四个按钮,要在xml里include四次嘛,要在代码里findviewById四次嘛,对于这样的hard code我是拒绝的,我希望在xml里只include一个view,代码里只findviewById一次,所以我们还需要给TabView再包一层,给四个Tab按钮一个父容器TabLayout,我们只需要include一个父容器,就能达到现在一片顶过去五片,一口气上五楼,不费劲的效果。那我要在TabLayout里写个LinearLayout塞四个TavView嘛,当然不是,我们把一个TabView看做是一个对象,需要几个就new几个,然后add到TabLayout里。
所以首先我需要一个TabView的对象TabItem。
然后再写个父容器TabLayout
以上都是小学五年级水平的代码,所以我就不写注释了,也不需要做过多讲解,直接看代码。到这里我们基本完成了底部TabLayout代码的编写,那我们写个activity测试下效果先。
先把TabLayout include到布局中
底部需要几个Tab按钮,就在MainActiviy里new几个然后add到TabLayout即可,所以有一天产品经理跟你说需要增加一个按钮,只需要再new一个就好,boss说调整下按钮顺序,就只要调整下new出的TabView顺序即可,代码是不是变得不那么苟且,似乎看见了诗和远方。
但是我们还没有加点击事件,重点来了,我要在点击事件里做if else判断Fragment的显示隐藏嘛?但if else真的很烦耶,如果不用if else判断,反射就要出场了,我们可以在TabItem中增加一个变量
然后构造函数里也加一个参数,先偷个懒姑且写在构造函数里。
相应的MainActivity里的引用也要修改下,第三个参数就传入相应的Fragment。然后点击事件的方法如下:
这样我们就完成了TabView点击切换Fragment,然后再处理下按钮的选中状态。一个模仿微信的底部导航栏就初见雏形了,但是微信是可以滑动切换的,我们这个还不能滑动切换,那就加上呗,所以我们还要对以上代码做些调整,毫无疑问这个时候viewpager要出场了。我们把MainActivity中之前的Framelayout替换成Viewpager。
还要写一个viewPager的适配器,这个时候我们选择把adapter写为内部类,这样会更方便一点获取Fragment。
适配好viewpager后,滑动的时候我们还需要对title栏和底部Tab栏做相应的状态改变。这里viewPager只需要实现OnPageChangeListener接口,在onPageSelected(int position)方法中做相应的处理。我这里的title用了actionbar。
滑动的时候要改变状态,那相应的点击tab栏也要做类似操作。
其中tabLayout中的setCurrentTab(int i)方法如下。我们声明两个变量,tabCount用来记录底部tabView的个数,selectView用来标识被选中的View。
自此一个模仿微信的底部Tab栏的封装基本实现了。伸手党的福音:GitHub。
为方便大家学习和交流Android开发,建了个群,欢迎大家加入。
QQ群:
183899857
为什么要做封装
你可能会觉得,这就是一个选择切换嘛,我只要做些if else判断就好了。但是Tab栏一般用在首页,业务逻辑代码量就不用说了,如果这时候不想被各种if else , swich case 搞得心力交瘁,那么我们少写些冗余代码又有何妨。毕竟代码不止眼前的苟且,还有设计改版和需求变更,某天产品经理更你说要改版,修改完xml,再去修改if else,然后再去修改click。。。想想也是醉了。所以这里要说的封装当然不会是,一个LinearLayout塞几个布局,然后做swich case去切换fragment,我希望布局里只需要include一个TabView,代码里也不需要N多findviewbyId就能实现这个功能。
基于以上分析,开工编码。我们还是以微信为例吧,假设底部Tab栏共有四个按钮,上面icon,下面文本。那么我们先把这一样式的xml写出来。
<?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:id="@+id/tab_image" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/tab_lable" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </merge>
上面是四个Tab按钮的通用布局,我们还需要写个TabView来解析这个布局。
public class TabView extends LinearLayout implements View.OnClickListener{ private ImageView mTabImage; private TextView mTabLable; public TabView(Context context) { super(context); initView(context); } public TabView(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } public TabView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initView(context); } private void initView(Context context){ setOrientation(VERTICAL); setGravity(Gravity.CENTER); LayoutInflater.from(context).inflate(R.layout.tab_view,this,true); mTabImage=(ImageView)findViewById(R.id.tab_image); mTabLable=(TextView)findViewById(R.id.tab_lable); } public void initData(TabItem tabItem){ mTabImage.setImageResource(tabItem.imageResId); mTabLable.setText(tabItem.lableResId); } @Override public void onClick(View v) { } }
到这里我们已经完成了单个TabView按钮的解析,但是假如我有四个按钮,要在xml里include四次嘛,要在代码里findviewById四次嘛,对于这样的hard code我是拒绝的,我希望在xml里只include一个view,代码里只findviewById一次,所以我们还需要给TabView再包一层,给四个Tab按钮一个父容器TabLayout,我们只需要include一个父容器,就能达到现在一片顶过去五片,一口气上五楼,不费劲的效果。那我要在TabLayout里写个LinearLayout塞四个TavView嘛,当然不是,我们把一个TabView看做是一个对象,需要几个就new几个,然后add到TabLayout里。
所以首先我需要一个TabView的对象TabItem。
public class TabItem { /** * icon */ public int imageResId; /** * 文本 */ public int lableResId; public TabItem(int imageResId, int lableResId) { this.imageResId = imageResId; this.lableResId = lableResId; } }
然后再写个父容器TabLayout
public class TabLayout extends LinearLayout implements View.OnClickListener{ private ArrayList<TabItem> tabs; private OnTabClickListener listener; public TabLayout(Context context) { super(context); initView(); } public TabLayout(Context context, AttributeSet attrs) { super(context, attrs); initView(); } public TabLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initView(); } private void initView(){ setOrientation(HORIZONTAL); } public void initData(ArrayList<TabItem>tabs,OnTabClickListener listener){ this.tabs=tabs; this.listener=listener; LinearLayout.LayoutParams params=new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT); params.weight=1; if(tabs!=null&&tabs.size()>0){ TabView mTabView=null; for(int i=0;i< tabs.size();i++){ mTabView=new TabView(getContext()); mTabView.setTag(tabs.get(i)); mTabView.initData(tabs.get(i)); mTabView.setOnClickListener(this); addView(mTabView,params); } }else{ throw new IllegalArgumentException("tabs can not be empty"); } } @Override public void onClick(View v) { listener.onTabClick((TabItem)v.getTag()); } public interface OnTabClickListener{ void onTabClick(TabItem tabItem); } }
以上都是小学五年级水平的代码,所以我就不写注释了,也不需要做过多讲解,直接看代码。到这里我们基本完成了底部TabLayout代码的编写,那我们写个activity测试下效果先。
先把TabLayout include到布局中
<?xml version="1.0" encoding="utf-8"?> <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=".MainActivity"> <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_above="@+id/tab_layout" /> <star.yx.tabview.TabLayout android:id="@+id/tab_layout" android:layout_alignParentBottom="true" android:layout_width="match_parent" android:layout_height="56dp" /> </RelativeLayout>
底部需要几个Tab按钮,就在MainActiviy里new几个然后add到TabLayout即可,所以有一天产品经理跟你说需要增加一个按钮,只需要再new一个就好,boss说调整下按钮顺序,就只要调整下new出的TabView顺序即可,代码是不是变得不那么苟且,似乎看见了诗和远方。
public class MainActivity extends ActionBarActivity implements TabLayout.OnTabClickListener{ private TabLayout mTabLayout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); initData(); } private void initView(){ mTabLayout=(TabLayout)findViewById(R.id.tab_layout); } private void initData(){ ArrayList<TabItem>tabs=new ArrayList<TabItem>(); tabs.add(new TabItem(R.drawable.selector_tab_msg,R.string.wechat)); tabs.add(new TabItem(R.drawable.selector_tab_contact,R.string.contacts)); tabs.add(new TabItem(R.drawable.selector_tab_moments,R.string.discover)); tabs.add(new TabItem(R.drawable.selector_tab_profile,R.string.me)); mTabLayout.initData(tabs, this); } @Override public void onTabClick(TabItem tabItem) { } }
但是我们还没有加点击事件,重点来了,我要在点击事件里做if else判断Fragment的显示隐藏嘛?但if else真的很烦耶,如果不用if else判断,反射就要出场了,我们可以在TabItem中增加一个变量
public Class<? extends BaseFragment>tagFragmentClz;
然后构造函数里也加一个参数,先偷个懒姑且写在构造函数里。
public TabItem(int imageResId, int lableResId, Class<? extends BaseFragment> tagFragmentClz) { this.imageResId = imageResId; this.lableResId = lableResId; this.tagFragmentClz = tagFragmentClz; }
相应的MainActivity里的引用也要修改下,第三个参数就传入相应的Fragment。然后点击事件的方法如下:
@Override public void onTabClick(TabItem tabItem) { try { BaseFragment fragment= tabItem.tagFragmentClz.newInstance(); getSupportFragmentManager().beginTransaction().replace(R.id.fragment,fragment).commitAllowingStateLoss(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } }
这样我们就完成了TabView点击切换Fragment,然后再处理下按钮的选中状态。一个模仿微信的底部导航栏就初见雏形了,但是微信是可以滑动切换的,我们这个还不能滑动切换,那就加上呗,所以我们还要对以上代码做些调整,毫无疑问这个时候viewpager要出场了。我们把MainActivity中之前的Framelayout替换成Viewpager。
<android.support.v4.view.ViewPager android:id="@+id/viewpager" android:layout_above="@id/tab_layout" android:layout_width="match_parent" android:layout_height="match_parent"/>
还要写一个viewPager的适配器,这个时候我们选择把adapter写为内部类,这样会更方便一点获取Fragment。
public class FragAdapter extends FragmentPagerAdapter { public FragAdapter(FragmentManager fm) { super(fm); // TODO Auto-generated constructor stub } @Override public Fragment getItem(int arg0) { // TODO Auto-generated method stub try { return tabs.get(arg0).tagFragmentClz.newInstance(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } return fragment; } @Override public int getCount() { // TODO Auto-generated method stub return tabs.size(); } }
适配好viewpager后,滑动的时候我们还需要对title栏和底部Tab栏做相应的状态改变。这里viewPager只需要实现OnPageChangeListener接口,在onPageSelected(int position)方法中做相应的处理。我这里的title用了actionbar。
@Override public void onPageSelected(int position) { mTabLayout.setCurrentTab(position); actionBar.setTitle(tabs.get(position).lableResId); }
滑动的时候要改变状态,那相应的点击tab栏也要做类似操作。
@Override public void onTabClick(TabItem tabItem) { actionBar.setTitle(tabItem.lableResId); mViewPager.setCurrentItem(tabs.indexOf(tabItem)); }
其中tabLayout中的setCurrentTab(int i)方法如下。我们声明两个变量,tabCount用来记录底部tabView的个数,selectView用来标识被选中的View。
public void setCurrentTab(int i) { if (i < tabCount && i >= 0) { View view = getChildAt(i); if (selectView != view) { view.setSelected(true); if (selectView != null) { selectView.setSelected(false); } selectView = view; } } }
自此一个模仿微信的底部Tab栏的封装基本实现了。伸手党的福音:GitHub。
为方便大家学习和交流Android开发,建了个群,欢迎大家加入。
QQ群:
183899857
相关文章推荐
- Android Studio debug使用release的签名(微信分享)
- 微信JSSDK页面授权实现类(PHP)
- Android自定义实现微信标题栏
- 微信支付中 素要用到的一些参数
- 微信开发测试小事记
- 关于微信公众号开发【微信JS-SDK】报错:config invalid url domian
- 试客小兵任务具体更新时间
- 仿微信滑动退出
- 想学微信的界面设计,来看看WeUI的暴力美学
- 微信公众平台开发之核心Servlet(二)
- 微信公众平台开发之请求校验类(一)
- 微信H5页面分享
- ***微信公众平台开发: 获取用户基本信息+OAuth2.0网页授权
- 微信公众号的开发之旅(1)
- [VB](更正:此前理解错误,static在整个程序运行中保留原值,每次调用过程static都应该保留上次原值,答案懒得改了)常见小程序积累
- 微信开放平台SDK笔记
- JS判断是否为微信客户端
- 关于微信分享接口开发
- 绝招!不用外挂也能最快的抢到微信红包
- Android小程序-简易计算器的实现