版权声明:本文为博主原创文章,未经博主允许不得转载。https://blog.csdn.net/sinat_25074703/article/details/82959963
recyclerView的Adapter是完成数据缓存和装载功能的。官方解释如下:
Adapters provide a binding from an app-specific data set to views that are displayed within a {@link RecyclerView}.
翻译如下:适配器提供了一种绑定,把app指定的数据设置给显示在RecyclerView内将要被显示的view上。
我们可以知道,adapter主要完成规定数据和itemView的绑定逻辑。
RecyclerView.Adapter是一个抽象类,StaggeredAdapter继承该类必须实现getItemCount()、onCreateViewHolder(ViewGroup parent, int viewType)、onBindViewHolder(VH holder, int position)三个抽象方法
Returns the total number of items in the data set held by the adapter.
翻译:返回adapter的所有item数,不分类型
Called when RecyclerView needs a new {@link ViewHolder} of the given
type to represent an item.
@return A new ViewHolder that holds a View of the given view type.
翻译:当recyclerview需要一个新的ViewHolder时调用,这个ViewHolder是与指定类型的item一一对应的。
返回一个指定view 类型的ViewHolder
可见,不同的ViewType对应不同的ViewHolder,ViewType是int值。也就是说,我们可以定义多个整数代表不同的ViewType,定义不同的ViewHolder展示不同的item
- @see #getItemViewType(int)
- @see #onBindViewHolder(ViewHolder, int)
Return the view type of the item at
position
for the purposes of view recycling.
翻译:返回position处的ViewType,以便视图回收
有了ItemViewType,在onCreateViewHolder中自然可以创建对应的ViewHolder
Called by RecyclerView to display the data at the specified
position. This method should update the contents of the {@link
ViewHolder#itemView} to reflect the item at the given position.
翻译:RecyclerView在指定位置显示数据时调用,用于更新ViewHolder持有的指定的某个itemView
通过以上分析可以知道,adapter通过getItemCount()方法获得item数,通过getItemViewType()获取每个位置的item对应的item类型,onCreateViewHolder()根据itemType创建ViewHolder为初始状态,说白了就是装载布局文件。最后onBindViewHolder()完成更新。
StaggeredAdapter.java代码如下:
package com.edwin.idea.staggered;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Edwin,CHEN on 2018/9/18.
*/
public class StaggeredAdapter extends RecyclerView.Adapter {
private static final String TAG = StaggeredAdapter.class.getSimpleName();
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Log.d(TAG, "======onCreateViewHolder");
return null;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
Log.d(TAG, "======onBindViewHolder");
}
@Override
public int getItemCount() {
Log.d(TAG, "=====getItemCount");
return 0;
}
@Override
public int getItemViewType(int position) {
Log.d(TAG, "===getItemViewType");
return super.getItemViewType(position);
}
}
为了验证以上四个方法的执行顺序,将StaggeredAdapter设置给RecyclerView,在StaggeredFragment中做如下处理:
package com.edwin.idea.staggered;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.edwin.idea.AbstractFragment;
import com.edwin.idea.R;
/**
* Created by Edwin,CHEN on 2018/9/18.
*/
public class StaggeredFragment extends AbstractFragment {
ViewGroup viewGroupRoot;
RecyclerView recyclerView;
StaggeredGridLayoutManager staggeredGridLayoutManager;
StaggeredAdapter staggeredAdapter;
public static Fragment newInstance(){
return new StaggeredFragment();
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
viewGroupRoot = (ViewGroup) inflater.inflate(R.layout.fragment_staggered, null);
recyclerView = (RecyclerView) viewGroupRoot.findViewById(R.id.recycler_view);
staggeredGridLayoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL); // 垂直2列,目前没什么卵用
staggeredAdapter = new StaggeredAdapter();
initData();
recyclerView.setLayoutManager(staggeredGridLayoutManager);
recyclerView.addItemDecoration(staggeredItemDecoration);
recyclerView.setAdapter(staggeredAdapter);
return viewGroupRoot;
}
@Override
protected void initData() {
}
}
Ctrl + R 不出意外的话,得到如下执行结果:
由于getItemCount返回值0,RecyclerView并未做多余的调用。getItemCount返回1试试,发现执行onCreateViewHolder时异常退出了,报了空指针异常。如下图:
根据log可以知道,在recylerView(android25.0.1版本)的6079行,执行ViewHolder.mItemType时发生了空指针异常,该字段是在一个null引用上调用的,回头看一下StaggeredAdapter.java文件,我们发现getItemCount返回值1,但是onCreateViewHolder方法却返回null,这就好像在银行存了100块钱,去取的时候发现是0.00元。扎心了,老铁!必然是要报异常的。
同时,根据上图的log信息知,RecyclerView按照getItemCount、getItemTypeView、onCreateViewHolder的顺序依次调用。再看一下6079行相关代码如下图:
根据声明:译文,该方法调用onCreateViewHolder去创建一个新的ViewHolder对象,并且初始化一些私有字段给RecyclerView使用。
闭上眼睛想一下,onCreateViewHolder(ViewGroup parent, int viewType)是一个带有viewType的抽象方法,viewType由getItemType(int position)返回,我们完全可以复写getItemType(int position)根据不同的position返回不同的类型,然后在实现onCreateViewHolder(ViewGroup parent, int viewType)时,根据不同的ViewType创建不同的ViewHolder。同时,为了解决getItemCount()返回0时,无法执行onCreateViewHolder()的情况,可以在item数为0时,强制返回1并填满整个页面。
对了,目前程序还处于异常状态,原因就是缺少ViewHolder对象,接下来先创建一个展示空页面视觉效果的ViewHolder吧!
右击staggered包,创建StaggeredEmptyViewHolder类,并继承RecyclerView.ViewHolder。在res/layout文件夹下创建布局文件staggered_empty_item.xml,代码如下:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="50dp"
android:gravity="center_horizontal"
android:text="I'm empty"
android:textSize="18sp"
android:textColor="@android:color/holo_blue_dark"/>
<ImageView
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_marginTop="100dp"
android:layout_gravity="center_horizontal"
android:scaleType="centerCrop"
android:src="@drawable/staggered_empty_img"/>
LinearLayout>
注意,ImageView可以用背景颜色代替,或者拍张自拍照重命名为staggered_empty_img放入res/drawable-xxhdpi,没有该文件夹就创建一个,右键res——>Android resource directory——>Resource type 选择drawable,Directory name 填写drawable-xxhdpi
StaggeredEmptyViewHolder.java代码如下:
package com.edwin.idea.staggered;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.edwin.idea.R;
/**
* Created by Edwin,CHEN on 2018/9/18.
*/
public class StaggeredEmptyViewHolder extends RecyclerView.ViewHolder {
/**
* 单例模式避免重复创建实例,且使用方便
* @param viewGroup 仅作为一个提供参数的引用
* @return
*/
public static StaggeredEmptyViewHolder newInstance(ViewGroup viewGroup){
View itemView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.staggered_empty_item, viewGroup, false);
return new StaggeredEmptyViewHolder(itemView);
}
private StaggeredEmptyViewHolder(View itemView) {
super(itemView);
}
}
注意:inflate方法最后一个参数attachToRoot = false,表示仅使用viewGroup的LayoutParams, 并不把R.layout.staggered_empty_item装入到viewGroup容器中,而且必须这样使用。如果改为True就会报异常:
java.lang.IllegalStateException: The specified child already has a parent. You must call >removeView() on the child’s parent first.
因为在RecyclerView中会调用viewGroup.addView(itemView)完成最终的添加显示工作,这一点不再展开,感兴趣的童鞋可以在RecyclerView中搜索一下如下方法:
private void addAnimatingView(ViewHolder viewHolder)
创建完StaggeredEmptyViewHolder这个空的ViewHolder就可以实现空数据展示了。当然,这些逻辑需要在StaggeredAdapter中完成。
首先,在getItemCount方法中返回1,表示需要创建1个ViewHolder,代码如下
@Override
public int getItemCount() {
Log.d(TAG, "=====getItemCount");
return 1;
}
接着,定义一个整形常量EMPTY_ITEM,并在getItemViewType方法中返回该常量作为空类型viewType,代码如下:
private static final int EMPTY_ITEM = 1001;
@Override
public int getItemViewType(int position) {
Log.d(TAG, "===getItemViewType");
return EMPTY_ITEM;
}
然后,在onCreateViewHolder()方法中,创建并返回StaggeredEmptyViewHolder实例,代码如下:
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Log.d(TAG, "======onCreateViewHolder");
return StaggeredEmptyViewHolder.newInstance(parent);
}
Ctrl+R一下吧,是不是看到自己的自拍照片了?!
如下图类似:
staggeredGridLayoutManager = new StaggeredGridLayoutManager(2,
StaggeredGridLayoutManager.VERTICAL);
默认有两列且为垂直方向滚动排列,现在getItemCount返回1,自然在左上角占据1个坑位,没毛病!如何解决这一问题呢?
当然布局问题还是由布局StaggeredGridLayoutManager来解决。在给出解决方案之前,需要看一下logcat下的信息,clear console然后重新运行一遍,结果如下:
最后一个方法onBindViewHolder(RecyclerView.ViewHolder holder, final int position)被愉快地调用了,还记得这个方法的作用吗?RecyclerView在指定位置显示数据时调用,用于更新ViewHolder持有的指定的某个itemView,它是刷新专业户。我们完全可以在这里面做做文章,通过修正StaggeredGridLayoutManager.LayoutParams修改span来完成就好了,查看StaggeredGridLayoutManager.LayoutParams的方法列表,它提供了一个setFullSpan(boolean fullSpan)方法,官方解释如下:
When set to true, the item will layout using all span area. That
means, if orientation is
vertical, the view will have full width; if orientation is horizontal, the view will have full
height.
译文:当设置参数为true时,这个item会使用所有的span区域。也就是说,如果方向是竖
直方向,itemView会使用整个宽度;如果方向是水平方向,itemView会使用整个高度。
根据官方说明,我们只需要将指定位置的itemView的StaggeredGridLayoutManager.LayoutParams.setFullSpan()设为true就好了,代码如下:
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
Log.d(TAG, "======onBindViewHolder");
StaggeredGridLayoutManager.LayoutParams layoutParams = new StaggeredGridLayoutManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
layoutParams.setFullSpan(true);
((StaggeredEmptyViewHolder)holder).itemView.setLayoutParams(layoutParams);
}
执行Ctrl + R,结果如下图,水平居中了吧!
诗曰: