Material Design——RecyclerView(二)

上一篇文章讲了RecyclerView的各种布局形式,这次实现一下给RecyclerView的子项定义高度(其实也是笔记,听老师讲了然后自己实现,加深印象)。


MainActivity.java

package com.example.lsn2_materialdesign_recyclerview;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.view.View;
import android.widget.Button;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private ArrayList<String> list;
    private MyStaggedRecyclerAdapter adapter;
    private RecyclerView recyclerView;
    private boolean isGrid = false;
    private Button changeButton;
    private Button addItem;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        list = new ArrayList<String>();
        for(int i=0; i<60; i++){
            list.add("item"+i);
        }

        changeButton = (Button) findViewById(R.id.change);
        addItem = (Button) findViewById(R.id.addItem);

        changeButton.setOnClickListener(this);
        addItem.setOnClickListener(this);

        recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
        adapter = new MyStaggedRecyclerAdapter(list);
        //瀑布流布局
        recyclerView.setLayoutManager(new StaggeredGridLayoutManager(4, LinearLayoutManager.VERTICAL));
        recyclerView.setAdapter(adapter);

    }


    //实现点击按钮切换RecyclerView的布局
    public void change(){
        if(!isGrid){
            recyclerView.setLayoutManager(new LinearLayoutManager(this));
        }else{
            recyclerView.setLayoutManager(new GridLayoutManager(this, 3));
        }
        isGrid = !isGrid;
    }

    //向RecyclerView添加元素
    public void addView(View v){
        adapter.addData(3);
    }


    @Override
    public void onClick(View view){
        switch(view.getId()){
            case R.id.change:
                change();
                break;
            case R.id.addItem:
                addView(view);
                break;
        }
    }
}

MyStaggedRecyclerAdapter.java

package com.example.lsn2_materialdesign_recyclerview;

import android.graphics.Color;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;

public class MyStaggedRecyclerAdapter extends RecyclerView.Adapter<MyStaggedRecyclerAdapter.MyViewHolder> {
    private List<String> list;
    class MyViewHolder extends RecyclerView.ViewHolder{
        TextView tv ;

        public MyViewHolder(View view){
            super(view);
            tv = (TextView) view.findViewById(android.R.id.text1);
        }

    }

    public MyStaggedRecyclerAdapter(List<String> list){
        this.list = list;
    }

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

    @Override
    public void onBindViewHolder(MyViewHolder holder, int position){
        //获取viewHolder的布局参数
        ViewGroup.LayoutParams params = holder.tv.getLayoutParams();
        params.height = 100;//定义子项的高度为100
        holder.tv.setLayoutParams(params);
        //随机定义背景颜色
        holder.tv.setBackgroundColor(Color.rgb(100, (int)(Math.random()*255), (int)(Math.random()*255)));
        holder.tv.setText(list.get(position));
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup viewGroup, int arg1){
        //创建ViewHolder
        //使用下面这句会报空指针异常,稍后分析
        //MyViewHolder holder = new MyViewHolder(View.inflate(viewGroup.getContext(), android.R.layout.simple_list_item_1, null));
        MyViewHolder holder = new MyViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(android.R.layout.simple_list_item_1, viewGroup, false));
        return holder;
    }

    //在Adapter实现添加数据的逻辑
    public void addData(int position){
        list.add(position, "item"+position);
        // notifyDataSetChanged();//提示刷新,但是会影响效率
        notifyItemInserted(position);//通知RecyclerView有数据插入
    }
}

运行结果如下:




现在来分析上面说的空指针异常:


它是说ViewGroup$LayoutParams.height这个参数为空,那么就很容易想到是LayoutParams为空,就是这句holder.tv.getLayoutParams()返回的结果为空。

刚看到这错误大概会以为是以下部分出了问题,我刚开始也这么认为,其实不是的。

既然是LayoutParams为空,有可能是在我们为子项加载布局的时候,里面的LayoutParam就是空的,即是下面这句话的问题,


但是光看是没有办法看出有什么问题的,因此我们的要从源码中去寻找答案


对应上面的View.inflate(viewGroup.getContext(), android.R.layout.simple_list_item_1, null)

public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
        LayoutInflater factory = LayoutInflater.from(context);
        return factory.inflate(resource, root);//到这就变成调用factory.inflate(resource,null)
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);//传进来的root为null,因此就变成inflate(resource, null, false)
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {//这里传进来的root为空,attchToRoot为false
        final Resources res = getContext().getResources();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                    + Integer.toHexString(resource) + ")");
        }
        //解析资源文件
        final XmlResourceParser parser = res.getLayout(resource);
        try {
             //继续调用inflate(parser, null, false)
             return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

            final Context inflaterContext = mContext;
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            mConstructorArgs[0] = inflaterContext;
            View result = root;//root为null,因此result为null

            try {
                // 下面的部分是解析xml文件的逻辑,掠过
                int type;
                while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                }
                if (type != XmlPullParser.START_TAG) {
                    throw new InflateException(parser.getPositionDescription()
                            + ": No start tag found!");
                }
                final String name = parser.getName();
                if (DEBUG) {
                    System.out.println("**************************");
                    System.out.println("Creating root view: "
                            + name);
                    System.out.println("**************************");
                }
                if (TAG_MERGE.equals(name)) {
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                    ViewGroup.LayoutParams params = null;                              
                    
                    //传进来的root为null,因此不会执行下面的逻辑,也就是说根本就不会创建LayoutParams,那必须得报空指针呀,
                    //但是注意看下面的代码,必须同时root不为空以及attachToRoot为false才可以得,否则即使创建了LayoutParam也不会渲染到View中
                    //综上,所以想要达到root不为空同时attachToRoot为false,只能调用Inflater.from(...).inflate(...)
                    if (root != null) {
                        if (DEBUG) {
                            System.out.println("Creating params from root: " +
                                    root);
                        }
                        //在这里创建LayoutParams
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {//zheli
                            temp.setLayoutParams(params);//将LayoutParam渲染进view中
                        }
                    }

                    if (DEBUG) {
                        System.out.println("-----> start inflating children");
                    }

                    // Inflate all children under temp against its context.
                    rInflateChildren(parser, temp, attrs, true);

                    if (DEBUG) {
                        System.out.println("-----> done inflating children");
                    }

                    // We are supposed to attach all the views we found (int temp)
                    // to root. Do that now.
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }

                    // Decide whether to return the root that was passed in or the
                    // top view found in xml.
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }

            } catch (XmlPullParserException e) {
                final InflateException ie = new InflateException(e.getMessage(), e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
            } catch (Exception e) {
                final InflateException ie = new InflateException(parser.getPositionDescription()
                        + ": " + e.getMessage(), e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
            } finally {
                // Don't retain static reference on context.
                mConstructorArgs[0] = lastContext;
                mConstructorArgs[1] = null;

                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }

            return result;
        }
    }
总结一下我觉得比较关键的:源码是最好的老师,在时间充足的,条件允许的情况下,尽量从源码中去找答案,可能会有些枯燥,但是非常友好处的,记得是带着问题去看源码




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值