## (一).前言:
仿36Kr客户端开发过程中,因为他们网站上面的新闻文章分类比较多,所以我这边还是打算模仿网易新闻APP的主界面新闻标签Tab以及页面滑动效果来进行实现。要实现的顶部的Tab标签的效果有很多方法例如采用开源项目ViewPagerIndicator中的TabPageIndicator就可以实现,不过查看了源码发现该控件其实就是继承自HorizontalScrollView自定义出来的。那既然这样我这边就准备带着大家直接使用HorizontalScrollView来实现顶部tab标签效果。底部的页面滑动直接采用Fragment+ViewPager+FragmentStatePagerAdapter实现即可。后面我也会更新一篇直接使用ViewPagerIndicator开源控件实现tab标签效果的文章,敬请期待~
本例子具体代码已经上传到下面的项目中,欢迎各位去star和fork一下。
FastDev4Android框架项目地址:[https://github.com/jiangqqlmj/FastDev4Android](https://github.com/jiangqqlmj/FastDev4Android)
## (二).实现原理:
![](https://box.kancloud.cn/2016-01-18_569c8ec65ef12.jpg)
上面我这边直接贴了36Kr官方APP的主界面顶部Tab标签,我这边直接模仿这个做。首先看上面截图红色框起来的部分,这边的Tab是可以横向滑动的那可以采用HorizontalScrollView控件实现,并且里边的每一项Tab Item都是可以进行添加和点击。我们直接往HorizontalScrollView addView子控件即可。Tab下面是若干个新闻文章列表的页面且可以进行左右滑动可以采用ViewPager实现,每个页面采用Fragment实现。
![](https://box.kancloud.cn/2016-01-18_569c8ec678eab.jpg)
上图是具体控件的分布和嵌套,下面我们来看一下具体实现:
## (三).具体实现:
3.1.首先我们需要有一个继承自FragmentActivity的MainInfoActivity,该用来承载Fragment,该Activity中实现基本没有啥代码,就不贴详细到时候去FastDev4Android项目中下载即可,这边我们看一下Activity的布局文件:
~~~
<?xmlversion="1.0" encoding="utf-8"?>
<LinearLayoutxmlns: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="match_parent"
>
<includelayout="@layout/common_top_bar_layout"/>
<fragment
android:id="@+id/info_fragment"
class="com.chinaztt.fda.fragment.InfoFragment"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
tools:layout="@layout/info_fragment_layout"
/>
</LinearLayout>
~~~
该布局文件中直接把承载的InfoFragment写在里边了,当我们Activity加载的时候该Fragment也被加载了。
3.2.接下来就是InfoFragment了,让我们首先看下我这边定义的布局文件:
~~~
<?xmlversion="1.0" encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white">
<!--横向滑动的容器-->
<HorizontalScrollView
android:id="@+id/horizontal_info"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:scrollbars="none">
<!--装入每一个Tab项容器-->
<LinearLayout
android:id="@+id/linearlayout_container"
android:layout_width="fill_parent"
android:layout_height="49dp"
android:orientation="horizontal">
</LinearLayout>
</HorizontalScrollView>
<android.support.v4.view.ViewPager
android:id="@+id/info_viewpager"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
</LinearLayout>
~~~
该布局中主要分为两部分,第一部分就是HorizontalScrollView控件该用来实现横向滑动,为标签Tab的容器,我们可以往里边动态的添加TabItem。第二部分为ViewPager控件该用来实现页面的左右滑动,其中每一项Item为Fragment。以上关键控件已经定义好了,下面就是需要在InfoFragment中实现Tab效果了。
首先定义和初始化控件:
~~~
/**
* 当前选择的分类
*/
private int mCurClassIndex=0;
/**
* 选择的分类字体颜色
*/
private int mColorSelected;
/**
* 非选择的分类字体颜色
*/
private int mColorUnSelected;
/**
* 水平滚动的Tab容器
*/
private HorizontalScrollView mScrollBar;
/**
* 分类导航的容器
*/
private ViewGroup mClassContainer;
/**
* 水平滚动X
*/
private int mScrollX=0;
mScrollBar=(HorizontalScrollView)mView.findViewById(R.id.horizontal_info);
mClassContainer=(ViewGroup)mView.findViewById(R.id.linearlayout_container);
~~~
对于Tab Item的动态添加使用下面写得方法addScrollView():
~~~
/**
* 动态添加顶部Tab滑动的标签
* @param titles
*/
private void addScrollView(String[]titles){
LayoutInflater mLayoutInflater=LayoutInflater.from(FDApplication.getInstance());
final int count=titles.length;
for(int i=0;i<count;i++){
final String title=titles[i];
final Viewview=mLayoutInflater.inflate(R.layout.horizontal_item_layout,null);
final LinearLayout linearLayout=(LinearLayout)view.findViewById(R.id.horizontal_linearlayout_type);
final ImageView img_type=(ImageView)view.findViewById(R.id.horizontal_img_type);
final TextView type_name=(TextView)view.findViewById(R.id.horizontal_tv_type);
type_name.setText(title);
if(i==mCurClassIndex){
//已经选中
type_name.setTextColor(mColorSelected);
img_type.setImageResource(R.drawable.bottom_line_blue);
}else {
//未选中
type_name.setTextColor(mColorUnSelected);
img_type.setImageResource(R.drawable.bottom_line_gray);
}
final int index=i;
//点击顶部Tab标签,动态设置下面的ViewPager页面
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//首先设置当前的Item为正常状态
View currentItem=mClassContainer.getChildAt(mCurClassIndex);
((TextView)(currentItem.findViewById(R.id.horizontal_tv_type))).setTextColor(mColorUnSelected);
((ImageView)(currentItem.findViewById(R.id.horizontal_img_type))).setImageResource(R.drawable.bottom_line_gray);
mCurClassIndex=index;
//设置点击状态
img_type.setImageResource(R.drawable.bottom_line_blue);
type_name.setTextColor(mColorSelected);
//跳转到指定的ViewPager
info_viewpager.setCurrentItem(mCurClassIndex);
}
});
mClassContainer.addView(view);
}
}
~~~
该方法传入了标签的数组,根据标签的数量进行遍历动态添加,主要步骤如下:
* 加载每一项Tab Item的布局,并且获取Item中的相关控件并且设置数据和资源文件
* 判断当前是否选中项,对于选中和未选中设置不同的字体颜色和资源文件
* 给每一项Item添加点击事件,用来切换ViewPager跳转到具体每一项页面(Fragment)
* 最终每一项Tab Item加入到容器中
上面我们有讲到,使用Fragment+ViewPager实现页面滑动切换,那我们需要一个页面的自定义适配器了,我这边创建了CNKFixedPagerAdapter该类继承自FragmengStatePagerAdaper具体实现代码如下,比较简单就不详细讲解了:
~~~
public class CNKFixedPagerAdapter extends FragmentStatePagerAdapter {
private String[] titles;
public void setTitles(String[] titles) {
this.titles = titles;
}
private List<Fragment> fragments;
public CNKFixedPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int position) {
return this.fragments.get(position);
}
@Override
public int getCount() {
return this.fragments.size();
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment fragment=null;
try {
fragment=(Fragment)super.instantiateItem(container,position);
}catch (Exception e){
}
return fragment;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
}
public List<Fragment> getFragments(){
return fragments;
}
public void setFragments(List<Fragment> fragments) {
this.fragments = fragments;
}
}
~~~
然后我们实例化ViewPager以及自定义适配器和显示的Fragment数据绑定即可:
~~~
fragments=new ArrayList<>();
for(int i=0;i<12;i++){
OneFragment oneFragment=new OneFragment();
Bundle bundle=new Bundle();
bundle.putString("extra",titles[i]);
oneFragment.setArguments(bundle);
fragments.add(oneFragment);
}
mPagerAdater=new CNKFixedPagerAdapter(getChildFragmentManager());
mPagerAdater.setTitles(titles);
mPagerAdater.setFragments(fragments);
info_viewpager.setAdapter(mPagerAdater);
~~~
最后我们不要忘记有一点是:当我们的ViewPager页面切换的时候我们需要实习改变顶部Tab Item的选中情况以及字体颜色等。所以我们需要给ViewPager添加页面切换监听器OnPageChangeListener,然后在回调的onPageSelected()方法中重新设置一下Tab Item的效果。
~~~
info_viewpager.setOnPageChangeListener(this);
~~~
~~~
//下面三个回调方法 分别是在ViewPager进行滑动的时候调用
@Override
public void onPageScrolled(int position,float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
//首先设置当前的Item为正常状态
View preView=mClassContainer.getChildAt(mCurClassIndex);
((TextView)(preView.findViewById(R.id.horizontal_tv_type))).setTextColor(mColorUnSelected);
((ImageView)(preView.findViewById(R.id.horizontal_img_type))).setImageResource(R.drawable.bottom_line_gray);
mCurClassIndex=position;
//设置当前为选中状态
View currentItem=mClassContainer.getChildAt(mCurClassIndex);
((ImageView)(currentItem.findViewById(R.id.horizontal_img_type))).setImageResource(R.drawable.bottom_line_blue);
((TextView)(currentItem.findViewById(R.id.horizontal_tv_type))).setTextColor(mColorSelected);
//这边移动的距离 是经过计算粗略得出来的
mScrollX=currentItem.getLeft()-300;
Log.d("zttjiangqq","mScrollX:" + mScrollX);
mScrollBar.post(new Runnable() {
@Override
public void run() {
mScrollBar.scrollTo(mScrollX,0);
}
});
}
@Override
public void onPageScrollStateChanged(int state) {
}
~~~
上面onPageSelected()方法中我们首先设置原先的Item的颜色为正常未选中状态,然后设置当前的位置选中以及字体颜色改变,最后让HorizontalScrollView平移到合适的位置即可:
3.3.以上我们的核心代码已经讲解完成了,下面我们看一下运行效果:
![](https://box.kancloud.cn/2016-01-18_569c8ec69e875.jpg)
3.4.为了大家方便阅读代码,我这边把InfoFragment的全部代码贴出来:
~~~
public class InfoFragment extends Fragment implements ViewPager.OnPageChangeListener{
private View mView;
ViewPager info_viewpager;
private List<Fragment> fragments;
private CNKFixedPagerAdapter mPagerAdater;
private String[] titles=new String[]{"全部","氪TV","O2O","新硬件","Fun!!","企业服务","Fit&Health","在线教育","互联网金融","大公司","专栏","新产品"};
/**
* 当前选择的分类
*/
private int mCurClassIndex=0;
/**
* 选择的分类字体颜色
*/
private int mColorSelected;
/**
* 非选择的分类字体颜色
*/
private int mColorUnSelected;
/**
* 水平滚动的Tab容器
*/
private HorizontalScrollView mScrollBar;
/**
* 分类导航的容器
*/
private ViewGroup mClassContainer;
/**
* 水平滚动X
*/
private int mScrollX=0;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if(mView==null){
mView=inflater.inflate(R.layout.info_fragment_layout,container,false);
initViews();
initValidata();
}
return mView;
}
/**
* 初始化布局控件
*/
private void initViews(){
info_viewpager=(ViewPager)mView.findViewById(R.id.info_viewpager);
mScrollBar=(HorizontalScrollView)mView.findViewById(R.id.horizontal_info);
mClassContainer=(ViewGroup)mView.findViewById(R.id.linearlayout_container);
}
private void initValidata(){
mColorSelected=FDApplication.getInstance().getResources().getColor(R.color.color_selected);
mColorUnSelected=FDApplication.getInstance().getResources().getColor(R.color.color_unselected);
//添加Tab标签
addScrollView(titles);
mScrollBar.post(new Runnable() {
@Override
public void run() {
mScrollBar.scrollTo(mScrollX,0);
}
});
fragments=new ArrayList<>();
for(int i=0;i<12;i++){
OneFragment oneFragment=new OneFragment();
Bundle bundle=new Bundle();
bundle.putString("extra",titles[i]);
oneFragment.setArguments(bundle);
fragments.add(oneFragment);
}
mPagerAdater=new CNKFixedPagerAdapter(getChildFragmentManager());
mPagerAdater.setTitles(titles);
mPagerAdater.setFragments(fragments);
info_viewpager.setAdapter(mPagerAdater);
info_viewpager.setOnPageChangeListener(this);
}
/**
* 动态添加顶部Tab滑动的标签
* @param titles
*/
private void addScrollView(String[]titles){
LayoutInflater mLayoutInflater=LayoutInflater.from(FDApplication.getInstance());
final int count=titles.length;
for(int i=0;i<count;i++){
final String title=titles[i];
final View view=mLayoutInflater.inflate(R.layout.horizontal_item_layout,null);
final LinearLayout linearLayout=(LinearLayout)view.findViewById(R.id.horizontal_linearlayout_type);
final ImageView img_type=(ImageView)view.findViewById(R.id.horizontal_img_type);
final TextView type_name=(TextView)view.findViewById(R.id.horizontal_tv_type);
type_name.setText(title);
if(i==mCurClassIndex){
//已经选中
type_name.setTextColor(mColorSelected);
img_type.setImageResource(R.drawable.bottom_line_blue);
}else {
//未选中
type_name.setTextColor(mColorUnSelected);
img_type.setImageResource(R.drawable.bottom_line_gray);
}
final int index=i;
//点击顶部Tab标签,动态设置下面的ViewPager页面
view.setOnClickListener(newView.OnClickListener() {
@Override
public void onClick(View v) {
//首先设置当前的Item为正常状态
View currentItem=mClassContainer.getChildAt(mCurClassIndex);
((TextView)(currentItem.findViewById(R.id.horizontal_tv_type))).setTextColor(mColorUnSelected);
((ImageView)(currentItem.findViewById(R.id.horizontal_img_type))).setImageResource(R.drawable.bottom_line_gray);
mCurClassIndex=index;
//设置点击状态
img_type.setImageResource(R.drawable.bottom_line_blue);
type_name.setTextColor(mColorSelected);
//跳转到指定的ViewPager
info_viewpager.setCurrentItem(mCurClassIndex);
}
});
mClassContainer.addView(view);
}
}
//下面三个回调方法 分别是在ViewPager进行滑动的时候调用
@Override
public void onPageScrolled(int position,float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
//首先设置当前的Item为正常状态
View preView=mClassContainer.getChildAt(mCurClassIndex);
((TextView)(preView.findViewById(R.id.horizontal_tv_type))).setTextColor(mColorUnSelected);
((ImageView)(preView.findViewById(R.id.horizontal_img_type))).setImageResource(R.drawable.bottom_line_gray);
mCurClassIndex=position;
//设置当前为选中状态
View currentItem=mClassContainer.getChildAt(mCurClassIndex);
((ImageView)(currentItem.findViewById(R.id.horizontal_img_type))).setImageResource(R.drawable.bottom_line_blue);
((TextView)(currentItem.findViewById(R.id.horizontal_tv_type))).setTextColor(mColorSelected);
//这边移动的距离 是经过计算粗略得出来的
mScrollX=currentItem.getLeft()-300;
Log.d("zttjiangqq","mScrollX:" + mScrollX);
mScrollBar.post(new Runnable() {
@Override
public void run() {
mScrollBar.scrollTo(mScrollX,0);
}
});
}
@Override
public void onPageScrollStateChanged(int state) {
}
}
~~~
## (四).最后总结
今天我们通过Fragment+ViewPager+FragmentStatePagerAdapter+HorizontalScrollView实现了仿照网易新闻客户端(或者36Kr)首页的页面滑动和顶部Tab效果。
本次实例代码因为比较多,代码全贴比较浪费篇幅,重点在于讲解思路了。不过实例注释过的全部代码已经上传到Github项目中了。同时欢迎大家去Github站点进行clone或者fork浏览整个开源快速开发框架项目~
[https://github.com/jiangqqlmj/FastDev4Android](https://github.com/jiangqqlmj/FastDev4Android)
- 前言
- Android快速开发框架介绍(一)
- Android首页图片自动无限循环轮播Gallery+FlowIndicator(二)
- Android 列表下拉刷新组件PullToRefreshListView使用(三)
- Android 数据缓存器ACache的详解和使用(四)
- Android崩溃异常捕捉CustomCrash,提升用户体验(五)
- Android实现沉浸式状态栏(六)
- AndroidAnnnotations注入框架介绍和Android Studios基本配置(七)
- AndroidAnnnotations注入框架的工作原理(八)
- AndroidAnnnotations注入框架使用之注入组件Components(九)
- AndroidAnnnotations注入框架使用之Injection标签详解(十)
- AndroidAnnnotations注入框架使用之事件绑定Event Binding(十一)
- AndroidAnnnotations注入框架使用之线程处理Threading(十二)
- AndroidAnnnotations注入框架使用之第三方框架集成RoboGuice(十三)
- AndroidAnnnotations注入框架使用之第三方框架集成Otto事件总线(十四)
- AndroidAnnnotations注入框架使用之第三方框架集成OrmLite(十五)
- AndroidAnnnotations注入框架使用之最佳实践之Adapters和lists(十六)
- AndroidAnnnotations注入框架使用之最佳实践SharedPreferences(十七)
- Android MVP开发模式详解(十九)
- 消息总线EventBus的基本使用(二十)
- 消息总线EventBus源码分析以及与Otto框架对比(二十一)
- 列表头生成带文本或者字母的图片开源库TextDrawable使用和详解(二十二)
- 重写WebView网页加载以及JavaScript注入详解(二十三)
- BaseAdapterHelper的基本使用介绍,让你摆脱狂写一堆Adapter烦恼(二十四)
- BaseAdapterHelper详解源码分析,让你摆脱狂写一堆Adapter烦恼(二十五)
- Volley完全解析之基础使用(二十六)
- Volley完全解析之进阶最佳实践与二次封装(二十七)
- RecyclerView完全解析,让你从此爱上它(二十八)
- RecyclerView完全解析之打造新版类Gallery效果(二十九)
- RecyclerView完全解析之结合AA(Android Annotations)注入框架实例(三十)
- RecyclerView完全解析之下拉刷新与上拉加载SwipeRefreshLayout(三十一)
- CardView完全解析与RecyclerView结合使用(三十二)
- 神器ViewDragHelper完全解析,妈妈再也不担心我自定义ViewGroup滑动View操作啦~(三十三)
- 神器ViewDragHelper完全解析之详解实现QQ5.X侧滑酷炫效果(三十四)
- 实例解析之SwipeRefreshLayout+RecyclerView+CardView(三十五)
- HorizontalScrollView,Fragment,FragmentStatePagerAdapter打造网易新闻Tab及滑动页面效果(三十六)
- Android Design支持库TabLayout打造仿网易新闻Tab标签效果(三十七)
- 打造QQ6.X最新版本侧滑界面效果(三十八)