仿qq底部tab导航

本篇博客主要实现以下效果:

  • 使用FragmentTabHost实现qq底部Tab切换
  • 使用RadioGroup和RadioButton实现仿qq底部切换
  • 使用RadioGroup和ViewPager 实现可以滑动切换的仿qq底部Tab切换
  • 解决Fragment多次实例化的几种方案
  • Fragemnt的懒加载(网上很多人称之为Fragemnt的最优加载)

效果图

老规矩,废话 不多说,先看效果图

FragmentTabHost实现qq底部Tab实践的效果图

仿qq底部tab导航_第1张图片

RadioGroup和ViewPager 实现可以滑动切换的仿qq底部Tab效果图

仿qq底部tab导航_第2张图片

使用FragmentTabHost实现qq底部Tab切换

第一步先看布局文件



    
    
    
    
    

        
    


其实很简单,没什么好说的 ,就是一个vertical的LinearLayout中放置着一个FrameLayout和FragmentTabHost

接下来我们来看一下代码

public class FirstStyleActivity extends AppCompatActivity {
    FragmentTabHost mTabHost;
    private TabWidget mTabWidget;
    private List mFragmentEntities;
    private static final String TAG = "xujun";
    private static final String tag = "tag";
    public static final String[] mTiltles = new String[]{
            "首页", "课程", "直播", "个人"
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_first_style);
        mTabHost = (FragmentTabHost) findViewById(android.R.id.tabhost);

        mTabHost.setup(this, getSupportFragmentManager(), R.id.main_layout_content);
        mTabWidget = mTabHost.getTabWidget();
        //  去掉分割线
        mTabWidget.setDividerDrawable(null);
        mFragmentEntities = MainFragmentFactory.getInstance().getList();
        initListener();
        initData();


    }

    private void initData() {
        int size = mFragmentEntities.size();
        for (int i = 0; i < size; i++) {
            Log.i(TAG, "size:=" + size);
            FragmentInfo fragmentInfo = mFragmentEntities.get(i);
            String title = fragmentInfo.getTitle();
            TabHost.TabSpec tabSpec = mTabHost.newTabSpec(title).setIndicator(getTabView
                    (i));
            Bundle bundle = new Bundle();
            bundle.putString(tag, mTiltles[i]);

            mTabHost.addTab(tabSpec, fragmentInfo.getClz(), bundle);
        }
        updateTab(0);
    }

    private void initListener() {
        mTabHost.setOnTabChangedListener(new TabHost.OnTabChangeListener() {
            @Override
            public void onTabChanged(String tabId) {
                int currentTab = mTabHost.getCurrentTab();
                Log.i(TAG, "onTabChanged:currentTab:=" + currentTab);
                updateTab(currentTab);
            }
        });
    }

    private View getTabView(int i) {
        View view = View.inflate(this, R.layout.tab_layout, null);
        int currentTab = mTabHost.getCurrentTab();
        Log.i(TAG, "currentTab:=" + currentTab);
        setSingleView(view, currentTab, i);
        return view;
    }

    private void setSingleView(View view, int currentTab, int index) {
        FragmentInfo fragmentInfo = mFragmentEntities.get(index);
        int[] imagIds = fragmentInfo.getImagIds();
        int[] colors = fragmentInfo.getColors();
        TextView tv = (TextView) view.findViewById(R.id.tab_tv);
        ImageView iv = (ImageView) view.findViewById(R.id.tab_icon);
        tv.setText(fragmentInfo.getTitle());
        Resources resources = getResources();
        if (index == currentTab) {

            tv.setTextColor(resources.getColor(colors[1]));
            iv.setImageDrawable(resources.getDrawable(imagIds[1]));
        } else {
            tv.setTextColor(getResources().getColor(colors[0]));
            iv.setImageDrawable(resources.getDrawable(imagIds[0]));
        }
    }

    private void updateTab(int currentTab) {

        int childCount = mTabWidget.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View view = mTabWidget.getChildTabViewAt(i);
            setSingleView(view, currentTab, i);

        }


    }
}

其实说起来也很简单,主要分为以下步骤

  • 第一步,实例化FragmentTabHost并设置相关样式
mTabHost = (FragmentTabHost) findViewById(android.R.id.tabhost);

mTabHost.setup(this, getSupportFragmentManager(), R.id.main_layout_content);
mTabWidget = mTabHost.getTabWidget();
//  去掉分割线
mTabWidget.setDividerDrawable(null);
  • 第二步,添加每一个TabHost
 for (int i = 0; i < size; i++) {
    Log.i(TAG, "size:=" + size);
    FragmentInfo fragmentInfo = mFragmentEntities.get(i);
    String title = fragmentInfo.getTitle();
    TabHost.TabSpec tabSpec = mTabHost.newTabSpec(title).setIndicator(getTabView
            (i));
    Bundle bundle = new Bundle();
    bundle.putString(tag, mTiltles[i]);

    mTabHost.addTab(tabSpec, fragmentInfo.getClz(), bundle);
}



  • 第三步,通过设置 监听器来实现底部tab颜色和图案样式的转换
 mTabHost.setOnTabChangedListener(new TabHost.OnTabChangeListener() {
    @Override
    public void onTabChanged(String tabId) {
        int currentTab = mTabHost.getCurrentTab();
        Log.i(TAG, "onTabChanged:currentTab:=" + currentTab);
        updateTab(currentTab);
    }
});


运行上述代码及可以看到如下效果图

仿qq底部tab导航_第3张图片

使用RadioGroup和RadioButton实现仿qq底部切换

第一步 ,先看布局文件



    
    

    
    
    
        

        

        

        

    



其实每一个tab的选中时利用RadioGroup中RadioButton的互相排斥的特性,即每一次只能选中一个 RadioButton

至于bottom_tab的style,只不过是将相同的arr提取出来,减少布局的代码量和方便统一修改而已,平时我们在写布局代码 的时候也可以这样



第二步,我们来看一下Activity的 代码

public class ThreeActivity extends AppCompatActivity {

    FrameLayout mFl;
    RadioGroup mRg;
    private FragmentManager mFragmentManager;

    private int position = 0;

    public static final String[] mTiltles = new String[]{
            "首页", "课程", "直播", "个人"
    };
    private List mFragments;
    private Fragment mCurFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_three);
        mFl = (FrameLayout) findViewById(R.id.fl);
        mRg = (RadioGroup) findViewById(R.id.rg);
        mFragments = new ArrayList<>();
        for (int i = 0; i < mTiltles.length; i++) {
            ItemFragement itemFragement = ItemFragement.newInstance(mTiltles[i]);
            mFragments.add(itemFragement);
        }
        mCurFragment = mFragments.get(position);
        replaceFragment(mCurFragment);

        ((RadioButton)mRg.getChildAt(position)).setChecked(true);

        initListener();
    }

    private void initListener() {
        mRg.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {

            @Override
            public void onCheckedChanged(RadioGroup group, int checkedId) {

                RadioButton radioButton = (RadioButton) group.findViewById(checkedId);

                if (false == radioButton.isChecked()) {
                    return;
                }


                switch (checkedId) {
                    case R.id.rb_home:
                        position = 0;
                        break;

                    case R.id.rb_course:
                        position = 1;
                        break;

                    case R.id.rb_direct_seeding:
                        position = 2;
                        break;

                    case R.id.rb_me:
                        position = 3;
                        break;
                    default:
                        position = 0;
                        break;

                }
                LUtils.i("position==" + position);

                Fragment to = mFragments.get(position);
                showFragment(mCurFragment, to);
                mCurFragment = to;

            }

        });
    }

    private void showFragment(Fragment from, Fragment to) {
        FragmentManager supportFragmentManager = getSupportFragmentManager();
        FragmentTransaction transaction = supportFragmentManager.beginTransaction();
        if (!to.isAdded()) {    // 先判断是否被add过
            transaction.hide(from).add(R.id.fl, to).commit(); // 隐藏当前的fragment,add下一个到Activity中
        } else {
            transaction.hide(from).show(to).commit(); // 隐藏当前的fragment,显示下一个
        }

    }

    /**
     * 这个方法用老替换fragment
     * xujun
     * 2016/5/3 17:28.
     */

    private void replaceFragment(Fragment fragmeny) {
        FragmentManager supportFragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = supportFragmentManager.beginTransaction();
        fragmentTransaction.replace(R.id.fl, fragmeny).commit();
    }

}

思路解析

  • 实例化各个控件,这里代码就不贴出来了
  • 初始化 Fragemnt 和选中各个tab
 mFragments = new ArrayList<>();
for (int i = 0; i < mTiltles.length; i++) {
    ItemFragement itemFragement = ItemFragement.newInstance(mTiltles[i]);
    mFragments.add(itemFragement);
}
mCurFragment = mFragments.get(position);
replaceFragment(mCurFragment);

((RadioButton)mRg.getChildAt(position)).setChecked(true);

private void replaceFragment(Fragment fragmeny) {
    FragmentManager supportFragmentManager = getSupportFragmentManager();
    FragmentTransaction fragmentTransaction = supportFragmentManager.beginTransaction();
    fragmentTransaction.replace(R.id.fl, fragmeny).commit();
}

  • 第三步,通过监听RadioGroup的 OnCheckedChangeListener事件,来实现tab和Fragemnt的切换
 mRg.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {

    @Override
    public void onCheckedChanged(RadioGroup group, int checkedId) {

        RadioButton radioButton = (RadioButton) group.findViewById(checkedId);

        if (false == radioButton.isChecked()) {
            return;
        }


        switch (checkedId) {
            case R.id.rb_home:
                position = 0;
                break;

            case R.id.rb_course:
                position = 1;
                break;

            case R.id.rb_direct_seeding:
                position = 2;
                break;

            case R.id.rb_me:
                position = 3;
                break;
            default:
                position = 0;
                break;

        }
        LUtils.i("position==" + position);

        Fragment to = mFragments.get(position);
        showFragment(mCurFragment, to);
        mCurFragment = to;

    }

});



使用RadioGroup和ViewPager 实现可以滑动切换的仿qq底部Tab切换

第一步,我们 同样先看布局代码



    
    

    
    
    
        

        

        

        

    


第二步,我们一起来看一下Activity代码

public class SecondStyleActivity extends AppCompatActivity {

    public static final String[] mTiltles = new String[]{
            "首页", "课程", "直播", "个人"
    };
    private List mFragments;
    ViewPager mViewPager;
    RadioGroup mRg;
    private int position = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second_style);
        mViewPager = (ViewPager) findViewById(R.id.viewPager);
        mRg = (RadioGroup) findViewById(R.id.rg);
        initListener();
        initData();


    }

    private void initData() {
        mFragments = new ArrayList<>();
        for (int i = 0; i < mTiltles.length; i++) {
            ItemFragement itemFragement = ItemFragement.newInstance(mTiltles[i]);
            mFragments.add(itemFragement);
        }
        BaseFragmentAdapter fragmentAdapter = new BaseFragmentAdapter
                (getSupportFragmentManager(), mFragments, mTiltles);
        mViewPager.setAdapter(fragmentAdapter);
        mViewPager.setCurrentItem(position);
        ((RadioButton) mRg.getChildAt(position)).setChecked(true);
    }

    private void initListener() {
        mViewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
            @Override
            public void onPageSelected(int position) {
                RadioButton radioButton = (RadioButton) mRg.getChildAt(position);
                radioButton.setChecked(true);
            }
        });

        mRg.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {

            @Override
            public void onCheckedChanged(RadioGroup group, int checkedId) {
                RadioButton rb = (RadioButton) group.findViewById(checkedId);
                if (!rb.isChecked()) {
                    return;
                }
                switch (checkedId) {
                    case R.id.rb_home:
                        position = 0;
                        break;

                    case R.id.rb_course:
                        position = 1;
                        break;

                    case R.id.rb_direct_seeding:
                        position = 2;
                        break;

                    case R.id.rb_me:
                        position = 3;
                        break;
                    default:
                        position = 0;
                        break;

                }
                mViewPager.setCurrentItem(position);
            }
        });
    }
}

思路解析如下

  • 实例化ViewPager和RadioGroup
 mViewPager = (ViewPager) findViewById(R.id.viewPager);
 mRg = (RadioGroup) findViewById(R.id.rg);

  • 第二步,初始化ViewPager的适配器和选中 那个tab
 mFragments = new ArrayList<>();
for (int i = 0; i < mTiltles.length; i++) {
    ItemFragement itemFragement = ItemFragement.newInstance(mTiltles[i]);
    mFragments.add(itemFragement);
}
BaseFragmentAdapter fragmentAdapter = new BaseFragmentAdapter
        (getSupportFragmentManager(), mFragments, mTiltles);
mViewPager.setAdapter(fragmentAdapter);
mViewPager.setCurrentItem(position);
((RadioButton) mRg.getChildAt(position)).setChecked(true);


  • 第三步,监听ViewPager 的滑动事件和RadioGroup的OnCheckedChangeListener事件,分别切换到相应的 Fragemnt 和同步ViewPager中 position 与RadioGroup之间的联系
 mViewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
    @Override
    public void onPageSelected(int position) {
        RadioButton radioButton = (RadioButton) mRg.getChildAt(position);
        radioButton.setChecked(true);
    }
});

mRg.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {

    @Override
    public void onCheckedChanged(RadioGroup group, int checkedId) {
        RadioButton rb = (RadioButton) group.findViewById(checkedId);
        if (!rb.isChecked()) {
            return;
        }
        switch (checkedId) {
            case R.id.rb_home:
                position = 0;
                break;

            case R.id.rb_course:
                position = 1;
                break;

            case R.id.rb_direct_seeding:
                position = 2;
                break;

            case R.id.rb_me:
                position = 3;
                break;
            default:
                position = 0;
                break;

        }
        mViewPager.setCurrentItem(position);
    }
});


注意事项

我们可以通过以下方法设置ViewPager左右页面 能缓存的fragment 数量

//  设置左右页面 能缓存的fragment 数量
mViewPager.setOffscreenPageLimit(fragmentAdapter.getCount() - 1);

到此仿qq底部tab切换的集中方法已经讲解完毕,之所以讲解了 三种方法,是想让大家了解多种实现方式,因为每一个人的习惯都不一样,有些人习惯使用与第一种方式,有人习惯使用第二种方式 。。。。。。了解多种 实现方式以后,我们要读懂别人的代码也容易得多了,其实我们还可以使用自定义控件来实现,方法也比较简单,这里就不讲解了,有兴趣的话,可以自行搜索

下面我将为大家讲解Fragment的 一些优化


解决Fragment多次实例化的几种方案

目前本人了解到的解决方案 ,无非是利用一下两种思想

第一种解决方法

在onCreateView中避免多次实例化View,可通过判断View是否为空,来实现相应的 逻辑操作,核心代码如下

   public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable
            Bundle savedInstanceState) {
        LUtils.i(getClass().getSimpleName()+">>>>>>>>>>>  onCreateView");
        if(mView==null){
            mContext = getContext();
            mView=View.inflate(mContext,getLayoutId(),null);
            initView(mView);
            LUtils.i(getClass().getSimpleName()+">>>>>>>>>>>  initView");
        }else{
            // 缓存的rootView需要判断是否已经被加过parent,如果有parent需要从parent删除,
            // 要不然会发生这个rootview已经有parent的错误。
            ViewGroup parent =(ViewGroup) mView.getParent();
            if(parent!=null){
                parent.removeView(mView);
            }
            LUtils.i(getClass().getSimpleName()+">>>>>>>>>>>  removeView");
        }
        return mView;
    }

第二种解决方案

在项目中需要进行Fragment的切换,用hide()和show()方法结合起来来替代replace()方法来实现Fragment的切换:

private void showFragment(Fragment from, Fragment to) {
    FragmentManager supportFragmentManager = getSupportFragmentManager();
    FragmentTransaction transaction = supportFragmentManager.beginTransaction();
    if (!to.isAdded()) {    // 先判断是否被add过
        transaction.hide(from).add(R.id.fl, to).commit(); // 隐藏当前的fragment,add下一个到Activity中
    } else {
        transaction.hide(from).show(to).commit(); // 隐藏当前的fragment,显示下一个
    }

}

/**
 * 这个方法用老替换fragment
 * xujun
 * 2016/5/3 17:28.
 */

private void replaceFragment(Fragment fragmeny) {
    FragmentManager supportFragmentManager = getSupportFragmentManager();
    FragmentTransaction fragmentTransaction = supportFragmentManager.beginTransaction();
    fragmentTransaction.replace(R.id.fl, fragmeny).commit();
}


关于 避免Fragment的多次实例化的分析与优化到此为止,下面我们一起来看一下 则那样实现Fragemnt 的 懒加载


Fragemnt的懒加载(网上很多人称之为Fragemnt的最优加载)

关于懒加载的这部分,代码是参考这篇 博客的 ViewPager+Fragment LazyLoad最优解

我们知道 ViewPager通常 会有预加载机制,默认情况下会先加载左右一页的数据,有时候我们想等待页面可见的时候在去加载网络 数据 ,解决方案如下

下面 先贴出代码

public abstract class BasePageFragment extends Fragment {

    protected View mView;

    /**
     * 表示View是否被初始化
     */
    protected boolean isViewInitiated;
    /**
     * 表示对用户是否可见
     */
    protected boolean isVisibleToUser;
    /**
     * 表示数据是否初始化
     */
    protected boolean isDataInitiated;
    private Context mContext;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        LUtils.i(getClass().getSimpleName() + ">>>>>>>>>>>  onCreate");
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable
            Bundle savedInstanceState) {
        LUtils.i(getClass().getSimpleName() + ">>>>>>>>>>>  onCreateView");
        if (mView == null) {
            mContext = getContext();
            mView = View.inflate(mContext, getLayoutId(), null);
            initView(mView);
            LUtils.i(getClass().getSimpleName() + ">>>>>>>>>>>  initView");
        } else {
            // 缓存的rootView需要判断是否已经被加过parent,如果有parent需要从parent删除,
            // 要不然会发生这个rootview已经有parent的错误。
            ViewGroup parent = (ViewGroup) mView.getParent();
            if (parent != null) {
                parent.removeView(mView);
            }
            LUtils.i(getClass().getSimpleName() + ">>>>>>>>>>>  removeView");
        }
        return mView;
    }

    protected abstract void initView(View view);

    protected abstract int getLayoutId();

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        LUtils.i(getClass().getSimpleName() + ">>>>>>>>>>>  onActivityCreated");
        isViewInitiated = true;
        initData();
        prepareFetchData();
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        this.isVisibleToUser = isVisibleToUser;
        prepareFetchData();
    }

    public abstract void fetchData();

    public boolean prepareFetchData() {
        return prepareFetchData(false);
    }

    /***
     * 
     * @param forceUpdate 表示是否在界面可见的时候是否强制刷新数据
     * @return
     */
    public boolean prepareFetchData(boolean forceUpdate) {
        if (isVisibleToUser && isViewInitiated && (!isDataInitiated || forceUpdate)) {
            //  界面可见的时候再去加载数据
            fetchData();
            isDataInitiated = true;
            return true;
        }
        return false;
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        LUtils.i(getClass().getSimpleName() + ">>>>>>>>>>>  onDestroyView");
    }

    protected void initData() {
    }

}

思路解析:
其实核心思想主要在setUserVisibleHint(boolean isVisibleToUser)方法中,这个方法在Fragemnt界面切换到可见或者 不可见的时候调用,isVisibleToUser表示当前界面可见 或者不可见

setUserVisibleHint(boolean isVisibleToUser)

Set a hint to the system about whether this fragment's UI is currently visible to the user.

于是我们在prepareFetchData进行判断只有当界面可见的情况下才会尝试判断是否调用fetchData() 方法,于是我们就可以实现等到界面可见的 时候才加载网络数据,将我们加载网络数据的 逻辑的实现放在fetchData()
里面即可


/***
 *
 * @param forceUpdate 表示是否在界面可见的时候是否强制刷新数据
 * @return
 */
public boolean prepareFetchData(boolean forceUpdate) {
    if (isVisibleToUser && isViewInitiated && (!isDataInitiated || forceUpdate)) {
        //  界面可见的时候再去加载数据
        fetchData();
        isDataInitiated = true;
        return true;
    }
    return false;
}


题外话

转眼间校招季已经过去了一个多月了,虽然没能进入到bat,网易之类的公司,不过也找到了相对比较喜欢的公司,接下来的日子就专心做毕业设计了,计划在十一月的时候出省旅游一次,明年春节回来的时候再去公司实习,悄悄地告诉你一件事哦,本人 还没有 出过广东省 ,哈哈,是不是很low呢?

转载请注明博客地址:http://blog.csdn.net/gdutxiaoxu/article/details/52826810

源码下载地址:https://github.com/gdutxiaoxu/QQBottomTab.git

你可能感兴趣的:(仿qq底部tab导航)