您的位置:首页 > 移动开发 > Android开发

浅谈RecyclerView

2015-10-26 15:31 459 查看
RecyclerView是一种新的视图组,目标是为任何基于适配器的视图提供相似的渲染方式。它被作为ListView和GridView控件的升级版,在最新的support-V7版本中提供支持。

整体上看RecyclerView架构,提供了一种更精美的体验,高度的适应力,异常的灵活,通过设置它提供的不同LayoutManager,ItemDecoration , ItemAnimator实现很多炫酷的效果。

基础配置

jar包的导入

在项目中加入android-support-v7-recyclerview.jar包,这个包在extras\android\support\v7\recyclerview\libs这个目录下。


如果使用RecyclerView,你需要了解几个要素:

RecyclerView.Adapter 它包含的一是适配器

LayoutManager可控制其显示的方式

ItemAnimator可控制Item增删的动画

ItemDecoration可控制Item间的间隔(可绘制)

基本使用

鉴于我们对ListView使用比较熟悉,在这里直接对比一下RecyclerView的使用方法:

mRecyclerView = findView(R.id.id_recyclerview);
//设置布局管理器
mRecyclerView.setLayoutManager(layout);
//设置adapter
mRecyclerView.setAdapter(adapter)
//设置Item增加、移除动画
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
//添加分割线
mRecyclerView.addItemDecoration(new DividerItemDecoration(
getActivity(), DividerItemDecoration.HORIZONTAL_LIST));


相比较于ListView的代码,ListView可能只需要去设置一个adapter就能正常使用了。而RecyclerView基本需要上面一系列的步骤,而RecyclerView的使用配置相对繁琐一点,因为RecyclerView只管回收与复用View,其他的你可以自己去设置。可以看出其高度的解耦,给予你充分的定制自,所以你才可以轻松的通过这个控件实现ListView,GirdView,瀑布流等效果。

和ListView相比:

Activity

package com.zhy.sample.demo_recyclerview;

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

import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class HomeActivity extends ActionBarActivity
{

private RecyclerView mRecyclerView;
private List<String> mDatas;
private HomeAdapter mAdapter;

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

initData();
mRecyclerView = (RecyclerView) findViewById(R.id.id_recyclerview);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mRecyclerView.setAdapter(mAdapter = new HomeAdapter());

}

protected void initData()
{
mDatas = new ArrayList<String>();
for (int i = 1; i < 10; i++)
{
mDatas.add("" + (char) i);
}
}

class HomeAdapter extends RecyclerView.Adapter<HomeAdapter.MyViewHolder>
{

@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
MyViewHolder holder = new MyViewHolder(LayoutInflater.from(
HomeActivity.this).inflate(R.layout.item_home, parent,
false));
return holder;
}

@Override
public void onBindViewHolder(MyViewHolder holder, int position)
{
holder.tv.setText(mDatas.get(position));
}

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

class MyViewHolder extends ViewHolder
{

TextView tv;

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

}


XML布局文件

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >

<android.support.v7.widget.RecyclerView
android:id="@+id/id_recyclerview"
android:divider="#ffff0000"
android:dividerHeight="10dp"
android:layout_width="match_parent"
android:layout_height="match_parent" />

</RelativeLayout>


Item布局

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:background="#44ff0000"
android:layout_height="wrap_content" >

<TextView
android:id="@+id/id_num"
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center"
android:text="1" />
</FrameLayout>


这么看起来用法与ListView的用法基本一致

看下效果图:



RecyclerView.Adapter使用

RecyclerView包含了一种新型适配器。它与现在使用的适配器类似,但也稍有不同,例如它需要使用ViewHolder。使用时需要重写两个主要方法:一个用来展现视图和它的持有者,而另一个用来把数据绑定到视图上。这么做的好处是,第一种方法只有当我们真正需要创建一个新视图时才被调用,不需要检查它是否已经被回收。

public class MyRecyclerAdapter extends RecyclerView.Adapter<MyRecyclerAdapter.ViewHolder> {

private List<ViewModel> items;
private int itemLayout;

public MyRecyclerAdapter(List<ViewModel> items, int itemLayout) {
this.items = items;
this.itemLayout = itemLayout;
}

@Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(itemLayout, parent, false);
return new ViewHolder(v);
}

@Override public void onBindViewHolder(ViewHolder holder, int position) {
ViewModel item = items.get(position);
holder.text.setText(item.getText());
holder.image.setImageBitmap(null);
Picasso.with(holder.image.getContext()).cancelRequest(holder.image);
Picasso.with(holder.image.getContext()).load(item.getImage()).into(holder.image);
holder.itemView.setTag(item);
}

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

public static class ViewHolder extends RecyclerView.ViewHolder {
public ImageView image;
public TextView text;

public ViewHolder(View itemView) {
super(itemView);
image = (ImageView) itemView.findViewById(R.id.image);
text = (TextView) itemView.findViewById(R.id.text);
}
}
}


这是一个简单的适配器,但是事情逐渐开始变得有点复杂。在RecyclerView中,没有onItemClickListener方法。如果想要从适配器上添加或移除条目,需要明确通知适配器。这与先前的notifyDataSetChanged()方法稍微有些不同。

public void add(ViewModel item, int position) {
items.add(position, item);
notifyItemInserted(position);
}

public void remove(ViewModel item) {
int position = items.indexOf(item);
items.remove(position);
notifyItemRemoved(position);
}


ItemDecoration使用

我们可以通过该方法添加分割线:

mRecyclerView.addItemDecoration()


该方法的参数为RecyclerView.ItemDecoration,该类为抽象类,官方目前并没有提供默认的实现类。

该类的源码:

public static abstract class ItemDecoration {

public void onDraw(Canvas c, RecyclerView parent, State state) {
onDraw(c, parent);
}

public void onDrawOver(Canvas c, RecyclerView parent, State state) {
onDrawOver(c, parent);
}

public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
parent);
}

@Deprecated
public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
outRect.set(0, 0, 0, 0);
}


当我们调用mRecyclerView.addItemDecoration()方法添加decoration的时候,RecyclerView在绘制的时候,去会绘制decorator,即调用该类的onDraw和onDrawOver方法,

onDraw方法先于drawChildren

onDrawOver在drawChildren之后,一般我们选择复写其中一个即可

getItemOffsets 可以通过outRect.set()为每个Item设置一定的偏移量,主要用于绘制Decorator

接下来我们看一个RecyclerView.ItemDecoration的实现类,该类很好的实现了RecyclerView添加分割线(当使用LayoutManager为LinearLayoutManager时):

package com.zhy.sample.demo_recyclerview;

/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* limitations under the License.
*/

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.State;
import android.util.Log;
import android.view.View;

/**
* This class is from the v7 samples of the Android SDK. It's not by me!
* <p/>
* See the license above for details.
*/
public class DividerItemDecoration extends RecyclerView.ItemDecoration {

private static final int[] ATTRS = new int[]{
android.R.attr.listDivider
};

public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;

public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;

private Drawable mDivider;

private int mOrientation;

public DividerItemDecoration(Context context, int orientation) {
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
a.recycle();
setOrientation(orientation);
}

public void setOrientation(int orientation) {
if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
throw new IllegalArgumentException("invalid orientation");
}
mOrientation = orientation;
}

@Override
public void onDraw(Canvas c, RecyclerView parent) {
Log.v("recyclerview - itemdecoration", "onDraw()");

if (mOrientation == VERTICAL_LIST) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}

}

public void drawVertical(Canvas c, RecyclerView parent) {
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();

final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
android.support.v7.widget.RecyclerView v = new android.support.v7.widget.RecyclerView(parent.getContext());
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getBottom() + params.bottomMargin;
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}

public void drawHorizontal(Canvas c, RecyclerView parent) {
final int top = parent.getPaddingTop();
final int bottom = parent.getHeight() - parent.getPaddingBottom();

final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int left = child.getRight() + params.rightMargin;
final int right = left + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}

@Override
public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
if (mOrientation == VERTICAL_LIST) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
}
}


该实现类可以看到通过读取系统主题中的 android.R.attr.listDivider作为Item间的分割线,并且支持横向和纵向获取到listDivider以后,该属性的值是个Drawable,在getItemOffsets中,outRect去设置了绘制的范围。onDraw中实现了真正的绘制。

分割的效果图如下:



LayoutManager的使用

这个类决定视图被放在画面中哪个位置,但这只是它的众多职责之一。它可以管理滚动和循环利用。

RecyclerView.LayoutManager是一个抽象类,好在系统提供了3个实现类:

1. LinearLayoutManager 现行管理器,支持横向、纵向
2. GridLayoutManager 网格布局管理器
3. StaggeredGridLayoutManager 瀑布就式布局管理器


LinearLayoutManager :

这个比较简单也很常用不多说直接上代码

横向

private void initHorizaontal(List<ItemModel> models) {
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.activity_main_horizontal_recyclerview);

LinearLayoutManager layoutManager = new LinearLayoutManager(MainActivity.this);
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
recyclerView.setLayoutManager(layoutManager);

RecyclerViewAdapter adapter = new RecyclerViewAdapter(MainActivity.this, models);
recyclerView.setAdapter(adapter);
}


纵向

public void initVertical(List<ItemModel> models) {
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.activity_main_vertical_recyclerview);

LinearLayoutManager layoutManager = new LinearLayoutManager(MainActivity.this);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);

RecyclerViewAdapter adapter = new RecyclerViewAdapter(MainActivity.this, models);
recyclerView.setAdapter(adapter);
}


GridLayoutManager:

这个实现出来的效果是GridView的升级版,简单方便:

mRecyclerView.setLayoutManager(new GridLayoutManager(this,4));


当然了我们也可以根据上面的提供的方法对各种样式的分割线进行编辑,已达到更好看的效果

StaggeredGridLayoutManager:

瀑布流式的布局,其实他可以实现GridLayoutManager一样的功能,仅仅按照下列代码:

mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(4,StaggeredGridLayoutManager.VERTICAL));


这两种写法显示的效果是一致的,但是StaggeredGridLayoutManager构造的第二个参数传一个orientation,如果传入的是StaggeredGridLayoutManager.VERTICAL代表有多少列;那么传入的如果是StaggeredGridLayoutManager.HORIZONTAL就代表有多少行,比如:

mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(4,
StaggeredGridLayoutManager.HORIZONTAL));


这样效果就是显示4行可左右滑动的类似GridView的效果 当然了以上的方法也是可以非常简单的就实现瀑布流效果,只要使用StaggeredGridLayoutManager我们就已经实现了,只是上面的item布局我们使用了固定的高度,下面我们仅仅在适配器的onBindViewHolder方法中为我们的item设置个随机的高度就ok了

ItemAnimator使用

ItemAnimator也是一个抽象类,好在系统为我们提供了一种默认的实现类,期待系统多

添加些默认的实现。

借助默认的实现,当Item添加和移除的时候,添加动画效果很简单:

// 设置item动画
mRecyclerView.setItemAnimator(new DefaultItemAnimator());


注意,这里更新数据集不是用adapter.notifyDataSetChanged()而是 notifyItemInserted(position)与notifyItemRemoved(position) 否则没有动画效果。 (这两个方法开篇已经介绍过)

自定义组合上拉和下拉的实现

对于下下拉刷新,Android自身有一个控件SwipeRefreshLayout已经有所实现。我们把SwipeRefreshLayout包在RecyclerView的外面,然后再设置其绑定到一个OnRefreshListener上。

布局:

<android.support.v4.widget.SwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/swipe_refresh_widget"
android:layout_width="match_parent"
android:layout_height="match_parent" >

<android.support.v7.widget.RecyclerView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:cacheColorHint="@null"
android:scrollbars="vertical" />

</android.support.v4.widget.SwipeRefreshLayout>


监听:

@Override
public void onRefresh() {

}


Adapter:

package com.demo.recyclerview.adapter;

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

import com.demo.recyclerview.R;

import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.TextView;

public class SampleAdapter extends RecyclerView.Adapter<ViewHolder> {
private List<Integer> list;

private static final int TYPE_ITEM = 0;
private static final int TYPE_FOOTER = 1;

public List<Integer> getList() {
return list;
}

public SampleAdapter() {
list = new ArrayList<Integer>();
}

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

@Override
public int getItemViewType(int position) {
if (position + 1 == getItemCount()) {
return TYPE_FOOTER;
} else {
return TYPE_ITEM;
}
}

@Override
public void onBindViewHolder(ViewHolder holder, final int position) {
if (holder instanceof ItemViewHolder) {
((ItemViewHolder) holder).textView.setText(String.valueOf(list.get(position)));
}
}

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_ITEM) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_text, null);
view.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
return new ItemViewHolder(view);
} else if (viewType == TYPE_FOOTER) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.footerview, null);
view.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
return new FooterViewHolder(view);
}

return null;
}

class FooterViewHolder extends ViewHolder {

public FooterViewHolder(View view) {
super(view);
}

}

class ItemViewHolder extends ViewHolder {
TextView textView;

public ItemViewHolder(View view) {
super(view);
textView = (TextView) view.findViewById(R.id.text);
}

}

}


Activity:

package com.demo.recyclerview.activity;

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

import com.demo.recyclerview.R;
import com.demo.recyclerview.adapter.SampleAdapter;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v4.widget.SwipeRefreshLayout.OnRefreshListener;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends Activity implements OnRefreshListener {
private SwipeRefreshLayout mSwipeRefreshWidget;
private LinearLayoutManager mLayoutManager;
private RecyclerView mRecyclerView;
private SampleAdapter adapter;

private int lastVisibleItem;

private Handler handler = new Handler() {

@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
Toast.makeText(MainActivity.this, "DOWN", 0).show();
mSwipeRefreshWidget.setRefreshing(false);

adapter.getList().clear();
addList();
break;
case 1:
Toast.makeText(MainActivity.this, "UP", 0).show();
addList();
break;
default:
break;
}

}

};

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

mSwipeRefreshWidget = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh_widget);
mRecyclerView = (RecyclerView) findViewById(android.R.id.list);

mSwipeRefreshWidget.setColorScheme(R.color.color1, R.color.color2, R.color.color3, R.color.color4);
mSwipeRefreshWidget.setOnRefreshListener(this);

mRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {

@Override
public void onScrollStateChanged(RecyclerView recyclerView,
int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE && lastVisibleItem + 1 == adapter.getItemCount()) {
handler.sendEmptyMessageDelayed(1, 3000);
}
}

@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
lastVisibleItem = mLayoutManager.findLastVisibleItemPosition();
}

});

mRecyclerView.setHasFixedSize(true);
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setItemAnimator(new DefaultItemAnimator());

adapter = new SampleAdapter();
mRecyclerView.setAdapter(adapter);

addList();

}

private void addList() {
List<Integer> list = getList();
adapter.getList().addAll(list);
adapter.notifyDataSetChanged();
}

private List<Integer> getList() {
List<Integer> list = new ArrayList<Integer>();
int size = adapter.getList().size();
int lastPosition = size > 0 ? adapter.getList().get(size - 1) : 0;
for (int i = 1; i < 20; i++) {
list.add(lastPosition + i);
}

return list;
}

@Override
public void onRefresh() {
handler.sendEmptyMessageDelayed(0, 3000);
}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Android