上一篇文章讲了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;
}
}
总结一下我觉得比较关键的:源码是最好的老师,在时间充足的,条件允许的情况下,尽量从源码中去找答案,可能会有些枯燥,但是非常友好处的,记得是带着问题去看源码