TabLayout+ViewPager+Fragmet+RecyclerView结合的小demo

2,900 阅读7分钟

前两天想熟悉一下tablayout,所以就弄了个特别简单的tablayout+viewpager+fragmet+recyclerview结合的小demo.虽然特别简单,但还是遇到了不少问题。

tablayout显示标题,viewpager实现滑动,fragment装载数据,recyclerview显示数据。

  • step1:引入依赖

implementation 'com.android.support:appcompat-v7:xxx' implementation 'com.android.support:design:xxx' implementation 'com.android.support:recyclerview-v7:xxx'

  • step2:XML中引入tabLayout
<android.support.design.widget.TabLayout

java文件中:

tabLayout = (TabLayout) findViewById(R.id.tab);
tabLayout.setupWithViewPager(viewPager);

在这里要记得设一下tabLayout的mode,有两个值,分别是 TabLayout.MODE_FIXED :当Tab较少,且占满整个屏幕时可以使用这种模式。 TabLayout.MODE_SCROLLABLE :当Tab数量较多,屏幕宽度不够时使用该模式,整个TabLayout是可以左右滑动的。

tabLayout.setTabMode(TabLayout.MODE_FIXED);
  • step3:再来是viewPager

xml引入

 <android.support.v4.view.ViewPager

java文件中

viewPager = (ViewPager) findViewById(R.id.viewpager);
viewPager.setAdapter(new FragmentAdapter(getSupportFragmentManager(), fragmentList));
  • step4:Fragment
public class MyFragment extends Fragment {

继承了fragmentPagerAdapter的类不需要重写那么多方法,必须有的是getItem(int)和getCount()。不过我还加了这两个方法。

    @Override
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) { }
    
 @Override
    public CharSequence getPageTitle(int position) {
        stringList.add("碎片1");
        stringList.add("碎片2");
        stringList.add("碎片3");
        return stringList.get(position);
    }

解释一下,第一个方法重载方法是为了防止viewpager在滑动切换的时候,里面的fragment被销毁,导致数据需要重新加载。如果此时viewpager里面的数据是从网络获取的,那么每一轮滑动,都要重新进行网络获取,非常影响性能。

查找资料后经试验,直接在destroyItem里面将其super.destroyItem()删除掉。这样就不会重新加载数据了。当然还有其他方式处理这种情况,如果有小伙伴试验成功的,更好的方法欢迎下方留言指教。

第二个重载方法,是为了显示tabLayout的标题,标题这里出了个大坑,网上很多例子都是直接在主activity那里addTab,setText,但是很容易会遇到标题无法显示的问题。解决办法就是在执行了tablayout.setUpWithViewPager之后再设置通过tablayout.getTabAt(position).setText(String)去设置。

然而,知道了tabLayout怎么工作之后相信大家会更愿意选择直接在fragment里面利用getPageTitle来操作。

查看源码可以发现,这里居然把所有添加的tab给remove掉了,所以在这个方法之前添加的tab和标题会都没有效果。另外在for里面我们可以观察到,其实tabLayout会根据adapter的数量添加tab和标题.所以我们可以不用在主acivity添加tab,标题设置直接在fragment里面利用getPageTitle就好了。

void populateFromPagerAdapter() {
        this.removeAllTabs();
        if (this.pagerAdapter != null) {
            int adapterCount = this.pagerAdapter.getCount();

            int curItem;
            for(curItem = 0; curItem < adapterCount; ++curItem) {
                this.addTab(this.newTab().setText(this.pagerAdapter.getPageTitle(curItem)), false);
            }

            if (this.viewPager != null && adapterCount > 0) {
                curItem = this.viewPager.getCurrentItem();
                if (curItem != this.getSelectedTabPosition() && curItem < this.getTabCount()) {
                    this.selectTab(this.getTabAt(curItem));
                }
            }
        }

    }

注:不过有的时候tablayout不跟viewPager和fragment结合在一起,那么这个时候就只能在主activity里面将添加tab和标题了。得注意,添加标题在tablayout.setUpWithViewPager()方法后面添加。

viewPager.setAdapter(new FragmentAdapter(getSupportFragmentManager(), fragmentList));

fragmentList就是你事先要准备好的数据源

List<Fragment> fragmentList = new ArrayList<>();
fragmentList.add(new MyFragment());
        fragmentList.add(new MyFragment());
        fragmentList.add(new MyFragment());

内个MyFragment这些是自己自定义的fragment类,包括相关的xml。fragment类里面做的主要是加载xml视图。xml里面的东西就是你要显示的东西啦,那在这里就是recyclerview。 贴fragment类,太长,弄成两张。

public class MyFragment extends Fragment {
    @Nullable
    List<String> data_top = new ArrayList<>();
    List<String> data_bottom = new ArrayList<>();
    List<String> data_new = new ArrayList<>();
    RecyclerView recyclerView;
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View fragment1 = inflater.inflate(R.layout.fragment_first, container, false);
        recyclerView = (RecyclerView) fragment1.findViewById(R.id.recycler_first);
        initRecycler();
        return fragment1;
    }
    public void initRecycler() {
        data_top.add("Layout Manager:Item的布局");
        data_top.add("为Item提供数据");
        data_top.add("Item Decoration:Item之间的Divider");
        data_top.add("Item Animator:添加、删除Item动画");
        data_bottom.add("学习雷锋好榜样");
        data_bottom.add("学习雷锋好榜样");
        data_bottom.add("学习雷锋好榜样");
        data_bottom.add("学习雷锋好榜样");
        data_new.add("立场坚定斗志强");
        data_new.add("立场坚定斗志强");
        data_new.add("立场坚定斗志强");
        data_new.add("立场坚定斗志强");
        MainRecyclerAdapter mainRecyclerAdapter = new MainRecyclerAdapter(data_top, data_bottom, data_new, getContext());
        recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
        recyclerView.setAdapter(mainRecyclerAdapter);
        mainRecyclerAdapter.notifyDataSetChanged();
    }
}

欸又看到新朋友了哦,来来来RecyclerView,介绍一下你是干嘛的~

  • step5:RecyclerView

如果你是想在fragment里面显示简单的数据,那么好办,但如果你是想通过RecyclerView显示你的数据呢,那可就没那么简单了。 RecyclerView呢,它自己也需要有一个数据适配器。 好咯那就给他造一个咯。插一个完整的文件代码。

public class MainRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    Context context;

    //设置不同类型adapter的区分enum
    public static enum TYPE_ENUM {
        IMAGE_ENUM,
        TEXT_ENUM
    }

    public static class TextHolder extends RecyclerView.ViewHolder {
        TextView text_top, text_bottom;
        ImageView icon;

        public TextHolder(@NonNull View itemView) {
            super(itemView);
            text_top = (TextView) itemView.findViewById(R.id.tv_top);
            text_bottom = (TextView) itemView.findViewById(R.id.tv_bottom);
            icon = (ImageView) itemView.findViewById(R.id.iv_icon);
        }
    }

    public static class ImageHolder extends RecyclerView.ViewHolder {
        TextView textTop;
        EditText textBottom;
        ImageView editIcon;

        public ImageHolder(@NonNull View itemView) {
            super(itemView);
            textTop = (TextView) itemView.findViewById(R.id.tv_top);
            textBottom = (EditText) itemView.findViewById(R.id.tv_bottom);
            editIcon = (ImageView) itemView.findViewById(R.id.iv_image_left);
        }
    }

    List<String> mDatas_top = new ArrayList<>();
    List<String> mData_bottom = new ArrayList<>();
    List<String> mData_new = new ArrayList<>();

    public MainRecyclerAdapter(List<String> mDatas_top, List<String> mData_bottom, List<String> mData_new, Context context) {
        this.mDatas_top = mDatas_top;
        this.mData_bottom = mData_bottom;
        this.mData_new = mData_new;
        this.context = context;
    }

    //判断是哪一类型的adapter
    @Override
    public int getItemViewType(int position) {
        return position % 2 == 0 ? TYPE_ENUM.IMAGE_ENUM.ordinal() : TYPE_ENUM.TEXT_ENUM.ordinal();
    }

    //getItemViewType的返回值就是该方法的int i,所以通过i判断是哪一类型的adapter
    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
        if (i == TYPE_ENUM.IMAGE_ENUM.ordinal()) {
            return new ImageHolder(LayoutInflater.from(context).inflate(R.layout.recycleritemnew, viewGroup, false));
        } else {
            return new TextHolder(LayoutInflater.from(context).inflate(R.layout.recycler_item, viewGroup, false));
        }
    }

    //根据不同类型的holder进行不同操作
    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int i) {
        if (holder instanceof ImageHolder) {
            //((ImageHolder) holder).textTop.setText(mData_new.get(i));
            ((ImageHolder) holder).textBottom.setText("haoya");
            ((ImageHolder) holder).editIcon.setBackgroundResource(R.mipmap.ic_launcher_round);
        } else {
            ((TextHolder) holder).text_top.setText(mDatas_top.get(i));
            ((TextHolder) holder).text_bottom.setText(mData_bottom.get(i));
            ((TextHolder) holder).icon.setBackgroundResource(R.mipmap.ic_launcher);
            //holder.itemView.setBackgroundResource(R.mipmap.ic_launcher);
        }
    }

    @Override
    public int getItemCount() {
        return mDatas_top.size();
    }
}

代码有点长。先不说不同类型的holder,先说单一类型的,用RecyclerView要怎么实现。 RecyclerView的实现相对于我们熟悉的listview来说确实复杂了一点点的。好嘞~现在详细说说各部分。

(1)先写一下列表每一行想要有什么布局,我的是上字下字右图,图是固定的。这个的话就不贴图了,看你自己需要。

(2)一般嘛,要在java文件里将xml里面的东西加载出来,那么RecyclerView里谁完成了这个任务呢?,没错就是他啦~创建holder的视图。

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
        if (i == TYPE_ENUM.IMAGE_ENUM.ordinal()) {
            return new ImageHolder(LayoutInflater.from(context).inflate(R.layout.recycleritemnew, viewGroup, false));
        } else {
            return new TextHolder(LayoutInflater.from(context).inflate(R.layout.recycler_item, viewGroup, false));
        }
    }

(3)那么创建好了,就得绑定是吧,嗯嗯,这里

   @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int i) {
        if (holder instanceof ImageHolder) {
            //((ImageHolder) holder).textTop.setText(mData_new.get(i));
            ((ImageHolder) holder).textBottom.setText("haoya");
            ((ImageHolder) holder).editIcon.setBackgroundResource(R.mipmap.ic_launcher_round);
        } else {
            ((TextHolder) holder).text_top.setText(mDatas_top.get(i));
            ((TextHolder) holder).text_bottom.setText(mData_bottom.get(i));
            ((TextHolder) holder).icon.setBackgroundResource(R.mipmap.ic_launcher);
            //holder.itemView.setBackgroundResource(R.mipmap.ic_launcher);
        }
    }

根据你xml里面的控件,进行设置。一种类型的话,就直接这样就好了 代码为举例,具体你懂的

holder.itemView.setBackgroundResource(R.mipmap.ic_launcher);

还有什么呢看一下,噢~绑定视图的时候数据从哪来,是的,从构造函数来。就是说将准备好的数据这样 MainRecyclerAdapter mainRecyclerAdapter = new MainRecyclerAdapter(data_top, data_bottom, data_new, getContext()); 再这样,

List<String> mDatas_top = new ArrayList<>();
    List<String> mData_bottom = new ArrayList<>();
    List<String> mData_new = new ArrayList<>();

    public MainRecyclerAdapter(List<String> mDatas_top, List<String> mData_bottom, List<String> mData_new, Context context) {
        this.mDatas_top = mDatas_top;
        this.mData_bottom = mData_bottom;
        this.mData_new = mData_new;
        this.context = context;
    }

最后这样,

((TextHolder) holder).text_top.setText(mDatas_top.get(i));
            ((TextHolder) holder).text_bottom.setText(mData_bottom.get(i));
            ((TextHolder) holder).icon.setBackgroundResource(R.mipmap.ic_launcher);
            

哇文章这么长啦。应该完了吧。欸不对好像还少了点什么嗯嗯,咱可不能遗忘了他的存在,他可是很有存在感的呢~ 它!是继承了viewholder的一个住在adapter类里面的类,作用是将xml的控件findviewbyid。

public static class TextHolder extends RecyclerView.ViewHolder {
        TextView text_top, text_bottom;
        ImageView icon;

        public TextHolder(@NonNull View itemView) {
            super(itemView);
            text_top = (TextView) itemView.findViewById(R.id.tv_top);
            text_bottom = (TextView) itemView.findViewById(R.id.tv_bottom);
            icon = (ImageView) itemView.findViewById(R.id.iv_icon);
        }
    }

有一个点,这里的话,如果是一个holder而已,画框部分可以写成MainRecyclerAdapter.VH--VH是你的内部类名。

public class MainRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

我个人对这个adapter的理解,首先他继承了RecyclerView.adapter,实现了几个必要的方法,加了个内部类

holder的话,我觉得是一个兜,兜住了视图的控件。 oncreateviewholder的话,那么就是创建出这个holder。 onbindviewholder的话,就是将控件需要的数据源给到对应的控件。

有可能理解得不准确,希望大家不吝赐教。

好了呢,单类型的adapter就这样啦。单类型的意思就是说RecyclerView从头到尾,控件的位置类型都是一致的,同种风格。但是有的时候,也会遇到需要不同类型的需求。这个时候就要出动另一个父类方法getItemViewType(int position),在这里面根据position设置这一行需要的是什么类型的adapter,详细见上边的完整代码。相关地方有注解。相信各位可以看懂~

完结!

作者介绍

  • 杨晓华:广州芦苇科技 APP 团队 Android 开发实习生

内推信息

  • 我们正在招募小伙伴,有兴趣的小伙伴可以把简历发到 app@talkmoney.cn,备注:来自掘金社区
  • 详情可以戳这里--> 广州芦苇信息科技