Android -- listview实现一行多列效果

最近项目需要做一个效果,获取到list书之后,展示的时候需要多列显示。一般来说,我们使用listview的时候,都是只有单行单列的效果,那一行多列的效果该怎么实现呢?

参考打造ListView万能Adapter,且支持一行多列的显示方式一文,我们就可以轻松实现这种效果了。

实现思路是,我们先创建一个viewHolder,获取每列item的view。然后在adapter的getView方法里,每一行我们都循环将column数量的数据输入,然后执行covert方法,将数据输入该行。执行完这一行的数据显示之后,我们在继续进入下一行。

举个例子,当getView的position为0的时候,我们执行循环,假设column为3,我们得到size为3的models,还有一个int[column]的positions。

然后我们执行covert方法,分别获取该列里的id为0,1,2的viewHolder布局,然后对viewHolder的具体控件进行操作,例如viewHolder.setText(id,content);

执行完covert方法之后,我们才进入下一行数据显示。即getView的position变为1.

下面为具体实现。

首先创建viewHolder。

DeviceViewHolder.java

public class DeviceViewHolder {
    //layout文件中的控件集合  SparseArray用法与HashMap类似,但是性能更优
    private SparseArray<View> mViews;
    //BaseAdapter中的getView方法中对应的参数
    private View mConvertView;
    private Context context;

    //私有,禁止外部实例化
    private DeviceViewHolder(Context context,ViewGroup parent,int layoutId){
        this.mViews = new SparseArray<View>();
        this.mConvertView = LayoutInflater.from(context).inflate(layoutId,parent,false);
        this.mConvertView.setTag(this);
        this.context = context;
    }

    public static DeviceViewHolder get(Context context, View convertView, ViewGroup parent,int layoutId){
        if(convertView == null){
            return new DeviceViewHolder(context,parent,layoutId);
        }
        return (DeviceViewHolder)convertView.getTag();
    }

    //根据ViewId获取控件对象,先从mViews集合中查找,如果存在则直接返回对象;不存在则从布局文件中获取该对象,然后添加到mViews集合中,然后再返回该对象。
    public <T extends View> T getView(int viewid){
        View view  = mViews.get(viewid);
        if (view == null){
            view = mConvertView.findViewById(viewid);
            mViews.put(viewid,view);
        }
        return (T) view;
    }

    /**
     * 返回BaseAdapter中的getView方法中对应的参数(convertView)
     *
     * @return
     */
    public View getConvertView() {
        return mConvertView;
    }

    /**
     * 获取TextView控件
     *
     * @param viewid
     *            控件ID
     * @return
     */
    public TextView getTextView(int viewid) {
        return (TextView) getView(viewid);
    }

    /**
     * 获取ConstraintLayout控件
     *
     * @param viewid
     *            控件ID
     * @return
     */
    public ConstraintLayout getConstraintLayout(int viewid) {
        return (ConstraintLayout) getView(viewid);
    }

    /**
     * 获取Button控件
     *
     * @param viewid
     *            控件ID
     * @return
     */
    public Button getButton(int viewid) {
        return (Button) getView(viewid);
    }

    /**
     * 获取ImageView控件
     *
     * @param viewid
     *            控件ID
     * @return
     */
    public ImageView getImageView(int viewid) {
        return (ImageView) getView(viewid);
    }

    /**
     * 获取ImageButton控件
     *
     * @param viewid
     *            控件ID
     * @return
     */
    public ImageButton getImageButton(int viewid) {
        return (ImageButton) getView(viewid);
    }

    /**
     * 获取LinearLayout控件
     *
     * @param viewid
     *            控件ID
     * @return
     */
    public LinearLayout getLinearLayout(int viewid) {
        return (LinearLayout) getView(viewid);
    }

    /**
     * 设置TextView内容
     *
     * @param viewid
     *            TextView控件ID
     * @param content
     *            要设置的内容
     * @return
     */
    public DeviceViewHolder setText(int viewid, String content) {
        getTextView(viewid).setText(content);
        return this;
    }
}

从代码中我们可恶意看到,基本都是getView。用于获取具体的view。主要用于复用性。当然如果需要更简单的方式,我只输入id和内容,让viewHolder自动帮我们操作,我们可以像setText方法一样,直接根据id获取指定类型的view,然后将内容输入。

做好viewHolder之后,我们开始制作基础的adapter。为什么是基础的呢?因为这个adapter除了输入每行item包含的多列数据以外,不做具体实现。这样的话,我们就可以根据具体需求,继承基础的adapter进行设置。

DeviceBaseAdapter.java

public abstract class DeviceBaseAdapter<T> extends BaseAdapter {
    protected Context mContext;
    protected List<T> listDatas = null;
    protected int mLayoutId;
    protected int column = 1;//默认每行一列
    protected int line_int;//计算得到的行数
    protected int column_yu;//一行多列,不能整除时,最后一行的列数

    //适配器
    public DeviceBaseAdapter(Context context,List<T> data,int layoutId,int setcolumn){
        this.mContext = context;
        this.listDatas = data;
        this.mLayoutId = layoutId;
        if (setcolumn >= 1){
            this.column = setcolumn;
        }
    }

    @Override
    public int getCount(){
        column_yu = listDatas.size() % column;
        if (column_yu>0){
            line_int = listDatas.size() / column + 1;
        }else {
            line_int = listDatas.size() / column;
        }
        return line_int;
    }

    @Override
    public T getItem(int position) {
        return listDatas.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    //添加单条数据项
    public void addItem(T item){
        this.listDatas.add(item);
    }

    //设置数据源
    public void setListDatas(List<T> data){
        this.listDatas = data;
    }

    //清除数据源
    public void clear(){
        this.listDatas.clear();
    }

    //刷新数据源
    public void refresh(){
        this.notifyDataSetChanged();
    }

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        DeviceViewHolder holder = DeviceViewHolder.get(mContext,convertView,parent,mLayoutId);
        List<T> models = new ArrayList<T>();
        int[] positions = null;
        if (column_yu == 0){
            //可以被整除,正常返回每行的数据
            positions = new int[column];
            for (int i = 0;i < column; i++){
                int posi = position * column + i;
                T model = listDatas.get(posi);
                models.add(model);
                positions[i] = posi;
            }
        }else {
            //不能被整除,判断是否在最后一行,如果是,返回剩余的列的数据
            if(position == listDatas.size() / column){
                positions = new int[column_yu];
                for (int i = 0;i<column_yu;i++){
                    int posi = position * column + i;
                    T model = listDatas.get(posi);
                    models.add(model);
                    positions[i] = posi;
                }
            }else {
                //否
                positions = new int[column];
                for (int i = 0;i<column;i++){
                    int posi = position * column + i;
                    T model = listDatas.get(posi);
                    models.add(model);
                    positions[i] = posi;
                }
            }

        }
        convert(holder,positions,models);
        return holder.getConvertView();
    }

    /**
     * 在子类中实现该方法
     *
     * @param holder
     *            列表项
     * @param positions
     * @param models
     *            每行的数据集,每行有几列就返回几个model。第一列对应数据下标0,一一对应,以此类推。
     *            不满列数的设置setVisibility(View.INVISIBLE);
     */
    public abstract void convert(DeviceViewHolder holder, int[] positions,
                                 List<T> models);
}

其中我们可以看到。我们每次geiView的时候,都会执行一次convert,这个convert方法就是具体实现对该行的item塞入多列数据。我们将convert设置为抽象类。在子类中来实现这个方法。同时我们考虑到但我们点击其中某个item的时候,或者要对某个item的列表数据进行单独设置的时候,我们需要获取该item在列表中的具体位置,所以我们设置positions,将item在列表数据中的实际位置放进去。在convert里可以进行使用。(包括点击事件获取数据)

以上就是单行多列的前期工作了。

接下来是实例的应用。首先我们设置一个item.xml,假设设置两行行,item则需要有两个。

同样,具体代码就不贴了。需要注意的是,我们的parent_view_1和parent_view_2是需要设置android:visibility=”invisible”的,因为在我们没有数据的时候,他们需要隐藏起来,同时需要占位置。

然后我们的DeviceListAdapter继承DeviceBaseAdapter,然后实现具体方法。

public class DeviceListAdapter extends DeviceBaseAdapter<DeviceItem> {
    Context context;
    public DeviceListAdapter(Context context, List<DeviceItem> data, int layoutId, int setcolumn) {
        super(context, data, layoutId, setcolumn);
        this.context = context;
    }

    @Override
    public void convert(DeviceViewHolder holder, final int[] positions, final List<DeviceItem> models) {
        for (int i = 0;i < positions.length;i++){
            switch (i){
            case 0:
                ConstraintLayout layout = holder.getConstraintLayout(R.id.parent_view_1);
                layout.setVisibility(View.VISIBLE);

                ImageView img_view = holder.getImageView(R.id.item_img_1);
                holder.setText(R.id.item_name_1,models.get(i).getDeviceName());

                    layout.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            DeviceItem deviceItem = ((DeviceItem)models.get(0));
                            Intent intent = new Intent(mContext, RealPlayActivity.class);
                            intent.putExtra("device_position",positions[0]);
                            mContext.startActivity(intent);
                        }
                    });
                break;
            case 1:
                ConstraintLayout layout2 = holder.getConstraintLayout(R.id.parent_view_1);
                layout2.setVisibility(View.VISIBLE);

                ImageView img_view2 = holder.getImageView(R.id.item_img_2);
                holder.setText(R.id.item_name_2,models.get(i).getDeviceName());



                    layout2.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            DeviceItem deviceItem = ((DeviceItem)models.get(1));
                            Intent intent = new Intent(mContext, RealPlayActivity.class);
                            intent.putExtra("device_position",positions[1]);
                            mContext.startActivity(intent);
                        }
                    });

                break;
            default:
                break;
            }
        }
    }
}

设置数据的方法有两种,一种是如原文作者一样,在viewHolder里设置好相关函数,然后在listAdapter里直接调用就好,另一种是我们通过holder根据id获取到具体的view,然后对view进行设置数据。同时点击事件也可以在这里面实现。

不过最好是如原文作者说的一样,在viewHolder里面直接构造好具体的函数。这样的话就可以不考虑view的复用了。

最后,我们为listview设置adapter就可以了。

deviceAdapter = new DeviceListAdapter(getActivity(),device_list,R.layout.device_list_item,2);
listView.setAdapter(deviceAdapter);
deviceAdapter.notifyDataSetChanged();

感谢大神--“帅气的铅笔”的技术分享。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值
>