封装实践——打造微信底部tab栏

简介: 目前市面上很多App都采用底部一个Tab栏,管理四到五个Tab,然后选择切换页面的方式的设计,这虽然不太符合metro design,但确是一个不容易出错而又符合国人使用习惯的设计方式。

目前市面上很多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写出来。

<?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
这里写图片描述

相关文章
|
2月前
|
SQL 存储 数据库
SQL实践篇(二):为什么微信用SQLite存储聊天记录
SQL实践篇(二):为什么微信用SQLite存储聊天记录
91 1
|
2月前
|
JSON 小程序 JavaScript
微信小程序入门实践
微信小程序入门实践
|
8月前
|
消息中间件 缓存 测试技术
企业微信针对百万级组织架构的客户端性能优化实践
本文主要分享的是企业微信在百对百万级大规模组织架构(后文简称大架构)时,是如何对客户端进行性能优化过程的,希望带给你启发。
50 0
|
2月前
|
小程序 JavaScript 前端开发
微信小程序云开发入门实践
微信小程序云开发入门实践
|
2月前
|
存储 缓存 监控
微信团队分享:微信后端海量数据查询从1000ms降到100ms的技术实践
针对大数据量带来的查询性能问题,微信团队对数据层查询接口进行了针对性的优化,将平均查询速度从1000ms+优化到了100ms级别。本文为各位分享优化过程,希望对你有用!
35 2
|
2月前
|
数据采集 前端开发 JavaScript
Java网络爬虫实践:解析微信公众号页面的技巧
Java网络爬虫实践:解析微信公众号页面的技巧
|
5月前
|
Java 应用服务中间件 网络安全
解决无公网IP的微信公众号开发调试问题,利用内网穿透的实践经验分享
解决无公网IP的微信公众号开发调试问题,利用内网穿透的实践经验分享
解决无公网IP的微信公众号开发调试问题,利用内网穿透的实践经验分享
|
8月前
|
存储 算法 JavaScript
微信支付-Native下单API支付封装+签名流程解读
微信支付-Native下单API支付封装+签名流程解读
88 0
|
9月前
|
前端开发 小程序 JavaScript
微信小程序 - DZMDrawingBoard - (Canvas封装的画板、手写签名、生成图片、保存相册...库)
微信小程序 - DZMDrawingBoard - (Canvas封装的画板、手写签名、生成图片、保存相册...库)
138 0
|
10月前
|
SQL 存储 消息中间件
微信安全基于 Flink 实时特征开发平台实践
腾讯专家级工程师李天旺,在 Flink Forward Asia 2022 AI 特征工程专场的分享。
577 0
微信安全基于 Flink 实时特征开发平台实践

热门文章

最新文章