一、概述

记得好久以前针对ListView类控件写过一篇打造万能的ListView GridView 适配器,如今RecyclerView异军突起,其Adapter的用法也与ListView类似,那么我们也可以一步一步的为其打造通用的Adapter,使下列用法书写更加简单:

  • 简单的数据绑定(单种Item)
  • 多种Item Type 数据绑定
  • 增加onItemClickListener , onItenLongClickListener
  • 优雅的添加分类header

二、使用方式和效果图

在一步一步完成前,我们先看下使用方式和效果图:

(1)简单的数据绑定

首先看我们最常用的单种Item的书写方式:

 
 
 
 
 

Java

 
1
2
3
4
5
6
7
8
mRecyclerView.setAdapter(new CommonAdapter(this, R.layout.item_list, mDatas)
{
    @Override
    public void convert(ViewHolder holder, String s)
    {
        holder.setText(R.id.id_item_list_title, s);
    }
});

是不是相当方便,在convert方法中完成数据、事件绑定即可。

(2)多种ItemViewType

多种ItemViewType,正常考虑下,我们需要根据Item指定ItemType,并且根据ItemType指定相应的布局文件。我们通过MultiItemTypeSupport完成指定:

 
 
 
 

Java

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
MultiItemTypeSupport  multiItemSupport = new MultiItemTypeSupport()
{
    @Override
    public int getLayoutId(int itemType)
    {
       //根据itemType返回item布局文件id
    }
 
    @Override
    public int getItemViewType(int postion, ChatMessage msg)
    {
       //根据当前的bean返回item type
    }
}

剩下就简单了,将其作为参数传入到MultiItemCommonAdapter即可。

 
 
 
 
 

Java

 
1
2
3
4
5
6
7
8
mRecyclerView.setAdapter(new SectionAdapter(this, mDatas, multiItemSupport)
{
    @Override
    public void convert(ViewHolder holder, String s)
    {
        holder.setText(R.id.id_item_list_title, s);
    }
});

贴个效果图:

(3)添加分类header

其实属于多种ItemViewType的一种了,只是比较常用,我们就简单封装下。

依赖正常考虑下,这种方式需要额外指定header的布局,以及布局中显示标题的TextView了,以及根据Item显示什么样的标题。我们通过SectionSupport对象指定:

 
 
 
 

Java

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
SectionSupport sectionSupport = new SectionSupport()
{
    @Override
    public int sectionHeaderLayoutId()
    {
        return R.layout.header;
    }
 
    @Override
    public int sectionTitleTextViewId()
    {
        return R.id.id_header_title;
    }
 
    @Override
    public String getTitle(String s)
    {
        return s.substring(0, 1);
    }
};

3个方法,一个指定header的布局文件,一个指定布局文件中显示title的TextView,最后一个用于指定显示什么样的标题(根据Adapter的Bean)。

接下来就很简单了:

 
 
 
 
 

Java

 
1
2
3
4
5
6
7
8
mRecyclerView.setAdapter(new SectionAdapter(this, R.layout.item_list, mDatas, sectionSupport)
{
    @Override
    public void convert(ViewHolder holder, String s)
    {
        holder.setText(R.id.id_item_list_title, s);
    }
});

这样就完了,效果图如下:

ok,看完上面简单的介绍,相信你已经基本了解了,没错,和我上篇ListView万能Adapter的使用方式基本一样,并且已经封装到同一个库了,链接为:https://github.com/hongyangAndroid/base-adapter,此外还提供了ItemClick,ItemLongClick,添加EmptyView等支持。

说了这么多,下面进入正题,看我们如何一步步完成整个封装的过程。

三、通用的ViewHolder

RecyclerView要求必须使用ViewHolder模式,一般我们在使用过程中,都需要去建立一个新的ViewHolder然后作为泛型传入Adapter。那么想要建立通用的Adapter,必须有个通用的ViewHolder。

首先我们确定下ViewHolder的主要的作用,实际上是通过成员变量存储对应的convertView中需要操作的字View,避免每次findViewById,从而提升运行的效率。

那么既然是通用的View,那么对于不同的ItemType肯定没有办法确定创建哪些成员变量View,取而代之的只能是个集合来存储了。

那么代码如下:

 
 
 
 
 

Java

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class ViewHolder extends RecyclerView.ViewHolder
{
    private SparseArray mViews;
    private View mConvertView;
    private Context mContext;
 
    public ViewHolder(Context context, View itemView, ViewGroup parent)
    {
        super(itemView);
        mContext = context;
        mConvertView = itemView;
        mViews = new SparseArray();
    }
 
    public static ViewHolder get(Context context, ViewGroup parent, int layoutId)
    {
 
        View itemView = LayoutInflater.from(context).inflate(layoutId, parent,
                false);
        ViewHolder holder = new ViewHolder(context, itemView, parent, position);
        return holder;
    }
 
    /**
     * 通过viewId获取控件
     *
     * @param viewId
     * @return
     */
    public  T getView(int viewId)
    {
        View view = mViews.get(viewId);
        if (view == null)
        {
            view = mConvertView.findViewById(viewId);
            mViews.put(viewId, view);
        }
        return (T) view;
    }
}

代码很简单,我们的ViewHolder继承自RecyclerView.ViewHolder,内部通过SparseArray来缓存我们itemView内部的子View,从而得到一个通用的ViewHolder。每次需要创建ViewHolder只需要传入我们的layoutId即可。

ok,有了通用的ViewHolder之后,我们的通用的Adapter分分钟就出来了。

四、通用的Adapter

我们的每次使用过程中,针对的数据类型Bean肯定是不同的,那么这里肯定要引入泛型代表我们的Bean,内部通过一个List代表我们的数据,ok,剩下的看代码:

 
 
 
 
 

Java

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.zhy.base.adapter.recyclerview;
 
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
 
import com.zhy.base.adapter.ViewHolder;
 
import java.util.List;
 
/**
* Created by zhy on 16/4/9.
*/
public abstract class CommonAdapter extends RecyclerView.Adapter
{
    protected Context mContext;
    protected int mLayoutId;
    protected List mDatas;
    protected LayoutInflater mInflater;
 
    public CommonAdapter(Context context, int layoutId, List datas)
    {
        mContext = context;
        mInflater = LayoutInflater.from(context);
        mLayoutId = layoutId;
        mDatas = datas;
    }
 
    @Override
    public ViewHolder onCreateViewHolder(final ViewGroup parent, int viewType)
    {
        ViewHolder viewHolder = ViewHolder.get(mContext, parent, mLayoutId);
        return viewHolder;
    }
 
    @Override
    public void onBindViewHolder(ViewHolder holder, int position)
    {
        holder.updatePosition(position);
        convert(holder, mDatas.get(position));
    }
 
    public abstract void convert(ViewHolder holder, T t);
 
    @Override
    public int getItemCount()
    {
        return mDatas.size();
    }
}

继承自RecyclerView.Adapter,需要复写的方法还是比较少的。首先我们使用过程中传输我们的数据集mDatas,和我们item的布局文件layoutId。

onCreateViewHolder时,通过layoutId即可利用我们的通用的ViewHolder生成实例。

onBindViewHolder这里主要用于数据、事件绑定,我们这里直接抽象出去,让用户去操作。可以看到我们修改了下参数,用户可以拿到当前Item所需要的对象和viewHolder去操作。

那么现在用户的使用是这样的:

 
 
 
 
 

Java

 
1
2
3
4
5
6
7
8
9
mRecyclerView.setAdapter(new CommonAdapter(this, R.layout.item_list, mDatas)
{
    @Override
    public void convert(ViewHolder holder, String s)
    {
        TextView tv = holder.getView(R.id.id_item_list_title);
        tv.setText(s);
    }
});

看到这里,爽了很多,目前我们仅仅写了很少的代码,但是我们的通用的Adapter感觉已经初步完成了。

可以看到我们这里通过viewholder根据控件的id拿到控件,然后再进行数据绑定和事件操作,我们还能做些什么简化呢?

恩,我们可以通过一些辅助方法简化我们的代码,所以继续往下看。

五、进一步封装ViewHolder

我们的Item实际上使用的控件较多时候可能都是TextView,ImageView等,我们一般在convert方法都是去设置文本,图片什么的,那么我们可以在ViewHolder里面,写上如下的一些辅助方法:

 
 
 
 

Java

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class ViewHolder extends RecyclerView.AdapterViewHolder>
{
    //...
    public ViewHolder setText(int viewId, String text)
    {
        TextView tv = getView(viewId);
        tv.setText(text);
        return this;
    }
 
    public ViewHolder setImageResource(int viewId, int resId)
    {
        ImageView view = getView(viewId);
        view.setImageResource(resId);
        return this;
    }
 
    public ViewHolder setOnClickListener(int viewId,
                                         View.OnClickListener listener)
    {
        View view = getView(viewId);
        view.setOnClickListener(listener);
        return this;
    }
}

当然上面只给出了几个方法,你可以把常用控件的方法都写进去,并且在使用过程中不断完善即可。

有了一堆辅助方法后,我们的操作更加简化了一步。

 
 
 
 
 

Java

 
1
2
3
4
5
6
7
8
9
10
mRecyclerView.setAdapter(new CommonAdapter(this, R.layout.item_list, mDatas)
{
    @Override
    public void convert(ViewHolder holder, String s)
    {
        //TextView tv = holder.getView(R.id.id_item_list_title);
        //tv.setText(s);
        holder.setText(R.id.id_item_list_title,s);
    }
});

ok,到这里,我们的针对单种ViewItemType的通用Adapter就完成了,代码很简单也很少,但是简化效果非常明显。

ok,接下来我们考虑多种ItemViewType的情况。

六、多种ItemViewType

多种ItemViewType,一般我们的写法是:

  • 复写getItemViewType,根据我们的bean去返回不同的类型
  • onCreateViewHolder中根据itemView去生成不同的ViewHolder

如果大家还记得,我们的ViewHolder是通用的,唯一依赖的就是个layoutId。那么上述第二条就变成,根据不同的itemView告诉我用哪个layoutId即可,生成viewholder这种事我们通用adapter来做。

于是,引入一个接口:

 
 
 
 

Java

 
1
2
3
4
5
6
public interface MultiItemTypeSupport
{
    int getLayoutId(int itemType);
 
    int getItemViewType(int position, T t);
}

可以很清楚的看到,这个接口实际就是完成我们上述的两条工作。用户在使用过程中,通过实现上面两个方法,指明不同的Bean返回什么itemViewType,不同的itemView所对应的layoutId.

ok,有了上面这个接口,我们的参数就够了,下面开始我们的MultiItemCommonAdapter的编写。

 
 
 
 
 

Java

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public abstract class MultiItemCommonAdapterT> extends CommonAdapterT>
{
    protected MultiItemTypeSupport mMultiItemTypeSupport;
 
    public MultiItemCommonAdapter(Context context, List datas,
                                  MultiItemTypeSupport multiItemTypeSupport)
    {
        super(context, -1, datas);
        mMultiItemTypeSupport = multiItemTypeSupport;
    }
 
    @Override
    public int getItemViewType(int position)
    {
        return mMultiItemTypeSupport.getItemViewType(position, mDatas.get(position));
    }
 
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
    {
        int layoutId = mMultiItemTypeSupport.getLayoutId(viewType);
        ViewHolder holder = ViewHolder.get(mContext, parent, layoutId;
        return holder;
    }
 
}

几乎没有几行代码,感觉简直不需要消耗脑细胞。getItemViewType用户的传入的MultiItemTypeSupport.getItemViewType完成,onCreateViewHolder中根据MultiItemTypeSupport.getLayoutId返回的layoutId,去生成ViewHolder即可。

ok,这样的话,我们的多种ItemViewType的支持也就完成了,一路下来感觉还是蛮轻松的~~~

最后,我们还有个添加分类的header,为什么想起来封装这个呢,这个是因为我看到了这么个项目:https://github.com/ragunathjawahar/simple-section-adapter,这个项目给了种类似装饰者模式的方法,为ListView添加了header,有兴趣可以看下。我想我们的RecylerView也来个吧,不过我们这里直接通过继承Adapter完成。

七、添加分类Header

话说添加分类header,其实就是我们多种ItemViewType的一种,那么我们需要知道哪些参数呢?

简单思考下,我们需要:

  1. header所对应的布局文件
  2. 显示header的title对应的TextView
  3. 显示的title是什么(一般肯定根据Bean生成)

ok,这样的话,我们依然引入一个接口,用于提供上述3各参数

 
 
 
 

Java

 
1
2
3
4
5
6
7
8
public interface SectionSupport
{
    public int sectionHeaderLayoutId();
 
    public int sectionTitleTextViewId();
 
    public String getTitle(T t);
}

方法名应该很明确了,这里引入泛型,对应我们使用时的数据类型Bean。

刚才也说了我们的分类header是多种ItemViewType的一种,那么直接继承MultiItemCommonAdapter实现。

 
 
 
 
 

Java

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
public abstract class SectionAdapter extends MultiItemCommonAdapter
{
    private SectionSupport mSectionSupport;
    private static final int TYPE_SECTION = 0;
    private LinkedHashMap mSections;
 
    private MultiItemTypeSupport headerItemTypeSupport = new MultiItemTypeSupport()
    {
        @Override
        public int getLayoutId(int itemType)
        {
            if (itemType == TYPE_SECTION)
                return mSectionSupport.sectionHeaderLayoutId();
            else
                return mLayoutId;
        }
        @Override
        public int getItemViewType(int position, T o)
        {
             return mSections.values().contains(position) ?
                    TYPE_SECTION :
                    1;
        }
    };
 
    @Override
    public int getItemViewType(int position)
    {
        return mMultiItemTypeSupport.getItemViewType(position, null);
    }
 
    final RecyclerView.AdapterDataObserver observer = new RecyclerView.AdapterDataObserver()
    {
        @Override
        public void onChanged()
        {
            super.onChanged();
            findSections();
        }
    };
 
    public SectionAdapter(Context context, int layoutId, List datas, SectionSupport sectionSupport)
    {
        super(context, datas, null);
        mLayoutId = layoutId;
        mMultiItemTypeSupport = headerItemTypeSupport;
        mSectionSupport = sectionSupport;
        mSections = new LinkedHashMap();
        findSections();
        registerAdapterDataObserver(observer);
    }
 
    @Override
    protected boolean isEnabled(int viewType)
    {
        if (viewType == TYPE_SECTION)
            return false;
        return super.isEnabled(viewType);
    }
 
    @Override
    public void onDetachedFromRecyclerView(RecyclerView recyclerView)
    {
        super.onDetachedFromRecyclerView(recyclerView);
        unregisterAdapterDataObserver(observer);
    }
 
    public void findSections()
    {
        int n = mDatas.size();
        int nSections = 0;
        mSections.clear();
 
        for (int i = 0; i get(i));
 
            if (!mSections.containsKey(sectionName))
            {
                mSections.put(sectionName, i + nSections);
                nSections++;
            }
        }
 
    }
 
    @Override
    public int getItemCount()
    {
        return super.getItemCount() + mSections.size();
    }
 
    public int getIndexForPosition(int position)
    {
        int nSections = 0;
 
        Set> entrySet = mSections.entrySet();
        for (Map.Entry entry : entrySet)
        {
            if (entry.getValue() return position - nSections;
    }
 
    @Override
    public void onBindViewHolder(ViewHolder holder, int position)
    {
        position = getIndexForPosition(position);
        if (holder.getItemViewType() == TYPE_SECTION)
        {
            holder.setText(mSectionSupport.sectionTitleTextViewId(), mSectionSupport.getTitle(mDatas.get(position)));
            return;
        }
        super.onBindViewHolder(holder, position);
    }
}

根据我们之前的代码,使用MultiItemCommonAdapter,需要提供一个MultiItemTypeSupport,我们这里当然也不例外。可以看到上述代码,我们初始化了成员变量headerItemTypeSupport,分别对getLayoutIdgetItemViewType进行了实现。

  • getLayoutId如果type是header类型,则返回mSectionSupport.sectionHeaderLayoutId();否则则返回mLayout.
  • getItemViewType根据位置判断,如果当前是header所在位置,返回header类型常量;否则返回1.

ok,可以看到我们构造方法中调用了findSections(),主要为了存储我们的title和对应的position,通过一个MapmSections来存储。

那么对应的getItemCount()方法,我们多了几个title肯定总数会增加,所以需要复写。

onBindViewHolder中我们有一行重置position的代码,因为我们的position变大了,所以在实际上绑定我们数据时,这个position需要还原,代码逻辑见getIndexForPosition(position)

最后一点就是,每当我们的数据发生变化,我们的title集合,即mSections就可能会发生变化,所以需要重新生成,本来准备复写notifyDataSetChanged方法,在里面重新生成,没想到这个方法是final的,于是利用了registerAdapterDataObserver(observer);,在数据发生变化回调中重新生成,记得在onDetachedFromRecyclerView里面对注册的observer进行解注册。

ok,到此我们的增加Header就结束了~~

恩,上面是针对普通的Item增加header的代码,如果是针对多种ItemViewType呢?其实也很简单,这种方式需要传入MultiItemTypeSupport。那么对于headerItemTypeSupport中的getItemViewType等方法,不是header类型时,交给传入的MultiItemTypeSupport即可,大致的代码如下:

 
 
 
 

Java

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
headerItemTypeSupport = new MultiItemTypeSupport()
{
    @Override
    public int getLayoutId(int itemType)
    {
        if (itemType == TYPE_SECTION)
            return mSectionSupport.sectionHeaderLayoutId();
        else
            return multiItemTypeSupport.getLayoutId(itemType);
    }
 
    @Override
    public int getItemViewType(int position, T o)
    {
        int positionVal = getIndexForPosition(position);
        return mSections.values().contains(position) ?
                TYPE_SECTION :
                multiItemTypeSupport.getItemViewType(positionVal, o);
    }
};

那么这样的话,今天的博客就结束了,有几点需要说明下:

本来是想接着以前的万能Adapter后面写,但是为了本文的独立和完整性,还是尽可能没有去依赖上篇博客的内容了。

此外,文章最后给出的开源代码与上述代码存在些许的差异,因为开源部分源码整合了ListView,RecyclerView等,而本文上述代码完全针对RecyclerView进行编写。

QQ技术交流群290551701 http://cxy.liuzhihengseo.com/537.html

为RecyclerView打造通用Adapter 让RecyclerView更加好用的更多相关文章

  1. 为RecyclerView打造通用Adapter

    ##RecycleView简单介绍 RecyclerView控件和ListView的原理有非常多相似的地方,都是维护少量的View来进行显示大量的数据.只是RecyclerView控件比ListVie ...

  2. RecyclerView打造通用的万能Adapter

    既然想做到通用那么现在摆在面前的就三个问题:数据怎么办?布局怎么办? 绑定怎么办?.数据决定采用泛型,布局打算直接构造传递,绑定显示效果肯定就只能回传. 1 基本改造 数据决定采用泛型,布局打算直接构 ...

  3. Android教你怎样一步步打造通用适配器

    前言 在Android开发中ListView是最为经常使用的控件之中的一个,基本每一个应用都会涉及到它,要使用ListView列表展示,就不可避免地涉及到另外一个东西--Adapter,我们都知道,A ...

  4. RecyclerView高速通用适配Adapter

    RecyclerView Adapter 为RecyclerView提供更简单的适配器实现方式,不断更新完好中. Demo视频演示 GitHub地址 博客 使用 BaseViewHolder 的使用 ...

  5. RecyclerView的通用适配器

    本来这一个主题应该早就写了,只是项目多,属于自己的时间不多,所以现在才开动!! 前一段时间写了一篇文章,是关于ListView,GriView万能适配器,没有看过的同学,可以先看看那篇文章,然后在来学 ...

  6. 打造android偷懒神器———RecyclerView的万能适配器

    转载请注明出处谢谢:http://www.cnblogs.com/liushilin/p/5720926.html 很不好意思让大家久等了,本来昨天就应该写这个的,无奈公司昨天任务比较紧,所以没能按时 ...

  7. RecyclerView更通用——listView的onItemClick,onLongItemClick,addHeaderView,addFooterView

    一.点击事件 setOnItemClickListener,setOnItemLongClickListener RecyclerView中虽然没有提供上面这两个接口,但是给我们提供了另外一个接口:O ...

  8. android开发游记:ItemTouchHelper 使用RecyclerView打造可拖拽的GridView

    以下是RecyclerView结合ItemTouchHelper实现的列表和网格布局的拖拽效果. 效果图例如以下:(gif图有点顿卡,事实上执行是非常流畅的) demo下载地址: DragRecycl ...

  9. Android中适用于ListView、GridView等组件的通用Adapter

    今天随便逛逛CSDN,看到主页上推荐了一篇文章Android 高速开发系列 打造万能的ListView GridView 适配器,刚好这两天写项目自己也封装了相似的CommonAdapter,曾经也在 ...

随机推荐

  1. kill 挂起 Apache Web Server

    [root@hadoop1 ~]# kill -l 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8 ...

  2. Joomla中的Task 和view 深入学习

    [本文转自:梦溪笔记] Joomla 是一个优秀的CMS系统,她可以让你快速的完成一个网站的建设,她提供组件,模块,模板能够满足你大部分的网站需求.而组件在其中举足轻重. 一.基本知识 组件(comp ...

  3. JS中prototype,js原型扩展

    作者:轩脉刃(yjf512)出处:(http://www.cnblogs.com/yjf512/)版权声明:本文的版权归作者与博客园共有.欢迎转载阅读,转载时须注明本文的详细链接. 原文 http:/ ...

  4. css3中animation的应用

    1.css3 的相关属性: 相关代码: div { animation-name: myfirst; //动画的名称 animation-duration: 5s; //动画一个周期需要5秒 anim ...

  5. HDU1520 Anniversary party —— 树形DP

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1520 Anniversary party Time Limit: 2000/1000 MS (Java ...

  6. vue2实现自定义样式radio单选框

    先上效果 <div class="reply"> 主编已回复: <div class="radio-box" v-for="(ite ...

  7. html5--6-28 css盒模型4

    html5--6-28 css盒模型4 实例 学习要点 了解盒模型 元素内容.内边距.边框 和 外边距 了解盒模型的概念: CSS 盒模型规定了处理元素内容.内边距.边框 和 外边距 的方式. 最内部 ...

  8. SPOJ:Decreasing Number of Visible Box(不错的,背包?贪心?)

    Shadowman loves to collect box but his roommates woogieman and itman don't like box and so shadowman ...

  9. 「USACO」「LuoguP2731」 骑马修栅栏 Riding the Fences(欧拉路径

    Description Farmer John每年有很多栅栏要修理.他总是骑着马穿过每一个栅栏并修复它破损的地方. John是一个与其他农民一样懒的人.他讨厌骑马,因此从来不两次经过一个栅栏.你必须编 ...

  10. 「LuoguP2170」 选学霸(01背包

    Description 老师想从N名学生中选M人当学霸,但有K对人实力相当,如果实力相当的人中,一部分被选上,另一部分没有,同学们就会抗议.所以老师想请你帮他求出他该选多少学霸,才能既不让同学们抗议, ...