一、前期基础知识储备
现在的手机应用,很多都会采用列表滚动的方式来加载和显示内容,微信、QQ、淘宝等应用就是其中的典型代表,每个用户每天都在打开这些应用,每天都在接触这种列表这种展示方式,和列表上线滚动加载信息这种交互方式,所以掌握好列表滚动控件是开发者的硬性要求。谷歌官方推出的列表滚动控件主要有两种ListView和RecyclerView,这两种滚动控件都十分的强大。先来看一下ListView的官方文档中的介绍:
“Displays a vertically-scrollable collection of views, where eachview is positioned immediatelybelow the previous view in the list. For a more modern, flexible, and performantapproach to displaying lists, use RecyclerView.”
ListView是最先出来的列表控件,过去由于它强大的展示功能,在开发中可谓是功勋卓著。不过ListView并不是完美的,我们在实际开发中必须使用一系列的技巧来提升它的运行效率,否则ListView的性能会受到很大的影响。而且ListView的扩展性也不好,它只能实现数据竖直方向的滚动,很难实现其他主流的展示方法。从官方文档,我们也可以看到,官方推荐我们使用更加流行、更加灵活、表现更好的RecyclerView。
RecyclerView可以说是一个增强版的ListView,不仅可以轻松实现ListView同样的效果,而且还优化了ListView中的不足之处。目前Android官方更加推荐使用RecyclerView,今天本节文章就来分析一下RecyclerView的常用方法。
二、上代码,具体实现
第一步:build.gradle文件中添加依赖;
compile'com.android.support:recyclerview-v7:23.2.1'
第二步:主布局文件中添加RecyclerView控件;
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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/recycley_view"
android:layout_width="match_parent"
android:layout_height="match_parent"></android.support.v7.widget.RecyclerView>
</LinearLayout>
第三步:新建歌曲实体类,里面添加歌曲名和歌曲图片两种属性;
public class Song {
private String name;
private int imageId;
public Song(String name, int imageId) {
this.name = name;
this.imageId = imageId;
}
public String getName() {
return name;
}
public int getImageId() {
return imageId;
}
}
第四步:创建RecyclerView中的子项布局,这里我们依据实体类中定义的属性,放入一个文本控件和ImageView控件;
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp">
<ImageView
android:id="@+id/song_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/song_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textSize="20sp"
android:textColor="#2c2c2c"
android:layout_marginLeft="35dp"/>
</LinearLayout>
第五步:然后为RecyclerView创建一个适配器,这个适配器继承自RecyclerView.Adapter,并将泛型指定为SongAdapter.viewHolder。其中ViewHolder是我们在适配器内部创建的一个内部类,作用和使用ListView时类似;
public class SongAdapter extends RecyclerView.Adapter<SongAdapter.ViewHolder> {
private List<Song> mSongList;
//静态内部类 构造出ViewHolder
static class ViewHolder extends RecyclerView.ViewHolder {
ImageView songImage;
TextView songName;
public ViewHolder(View itemView) {
super(itemView);
songImage = (ImageView) itemView.findViewById(R.id.song_image);
songName = (TextView) itemView.findViewById(R.id.song_name);
}
}
public SongAdapter(List<Song> songList) {
mSongList = songList;
}
//重写RecyclerView.Adapter的三个构造方法
@Override
public SongAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//第一个构造方法用来加载布局
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.song_item, parent, false);
ViewHolder holder = new ViewHolder(itemView);
return holder;
}
@Override
public void onBindViewHolder(SongAdapter.ViewHolder holder, int position) {
//第二个构造方法用来赋值数据
Song song = mSongList.get(position);
holder.songImage.setImageResource(song.getImageId());
holder.songName.setText(song.getName());
// 以下为执行Item的删除操作
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mSongList.remove(position);
notifyItemRemoved(position);
}
});
}
@Override
public int getItemCount() {
//第三个构造方法用来提示长度
return mSongList.size();
}
}
第六步:主Activity代码中为RecyclerView绑定适配器,同时初始化歌曲数据;
public class MainActivity extends AppCompatActivity {
private List<Song> songList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
intSongs();//初始化音乐数据
//以下三行代码,就是RecyclerView真正的威力所在,这里指定的是线性布局
//实现的效果和ListView类似,但是这里我们指定其他排列方式,实现更为丰富的效果
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycley_view);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
SongAdapter songAdapter = new SongAdapter(songList);
recyclerView.setAdapter(songAdapter);
}
private void intSongs() {
for (int i = 0; i < 1; i++) {
Song Hold = new Song("Hold Back the River", R.drawable.aa);
songList.add(Hold);
Song Dream = new Song("Dream it possible", R.drawable.bb);
songList.add(Dream);
Song Cocoom = new Song("Cocoom", R.drawable.cc);
songList.add(Cocoom);
Song Million = new Song("93 Million Miles", R.drawable.dd);
songList.add(Million);
Song See = new Song("See You Again", R.drawable.ee);
songList.add(See);
Song Need = new Song("Need You Now", R.drawable.ff);
songList.add(Need);
Song I = new Song("I will Survive", R.drawable.gg);
songList.add(I);
Song The = new Song("The truth that you leave", R.drawable.ii);
songList.add(The);
}
}
}
运行效果如下:
通过以上六步,我们就基本上实现了一个和ListView一样的常见的竖直方向的列表滚动控件示例。
这张图片,很好的展示了上述六步中涉及的各个类(RecyclerView、Adapter、ViewHolder、JavaBean、Activity、LayouManager)之间的关系,建议读者可以参考下。
————————————————————我是分隔线————————————————————
接着,实现瀑布流布局
我们修改那关键的三行代码,使用StaggeredGridLayoutManager代替LinearLayoutManager,实现瀑布流的布局方式,这里,我们传入2个参数,第一个是瀑布流的列数,第二个参数是排列方式。其余的代码保持不变。(为显示效果,给View加了个背景颜色)
public class MainActivity extends AppCompatActivity {
private List<Song> songList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
intSongs();//初始化音乐数据
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycley_view);
//这里使用RecyclerView新的排列方式,传入两个参数,一个是指定的列数,一个是排列的方向
StaggeredGridLayoutManager layoutManager = new
StaggeredGridLayoutManager(4,StaggeredGridLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
SongAdapter songAdapter = new SongAdapter(songList);
recyclerView.setAdapter(songAdapter);
}
... ...
}
运行效果如图:
三、上代码,实现RecyclerView的点击事件
RecyclerView在点击事件这一块没有想ListView一样提供类似于setOnItemClickListener()这样的注册监听器的方法,而是需要我们自己给子项具体的View去注册点击事件,相比于ListView来说,实现起来要复杂一些。
由于是子项View具体去注册事件,那么RecyclerView点击事件的逻辑实现在适配器中的ViewHolder中,本例中代码如下,我们为view整体和图片两部分设置点击事件。
@Override
public void onBindViewHolder(MyAdapter.ViewHolder holder, final int position) {
holder.songName.setText(mSongList.get(position));
holder.songName.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
itemClickListener.onItemClick(v,position);
}
});
}
private onRecyclerViewItemClick itemClickListener;
public interface onRecyclerViewItemClick {
void onItemClick(View v, int pos);
}
然后在Activity中通过set()方法注入接口:
testAdapter = new TestAdapter(this, list);
testAdapter.setOnItemClickListener(new TestAdapter.OnItemClickListener() {
@Override
public void onClick(int position) {
// do something
// 通过set方法注入接口
}
});
也可以通过Adapter的构造方法注入接口:
// 适配器构造方法
MyAdapter(List<String> data,onRecyclerViewItemClick itemChangeListener) {
mSongList = data;
this.itemClickListener = itemChangeListener;
}
private onRecyclerViewItemClick itemClickListener;
public interface onRecyclerViewItemClick {
void onItemClick(View v, int pos);
}
// Activity中通过适配器构造方法注入接口
mAdapter = new MyAdapter(data, new MyAdapter.onRecyclerViewItemClick() {
@Override
public void onItemClick(View v, int pos) {
}
});
点击运行,可以看到,不管点击到这个View还是点击到这个图片都可以弹出提示。
四、RecyclerView联合ViewPager使用
效果如下图所示:
(1)ViewPager联动RecyclerView,页面滑动时,RecyclerView跟随“滑动”;
viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
/*ViewPager 联动RecyclerView*/
mRecyclerView.smoothScrollToPosition(position);
barAdapter.setClickPostion(position);
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
在ViewPager的监听中,在onPageSelected()方法(ViewPager滑动之后调用)内部调用RecyclerView的滑动方法-smoothScrollToPostion()方法,传入position参数即可,然后调用setClickPostion()方法(自定义),用于改变RecyclerView的子Item的状态,起到一个提示的作用。
(2)RecyclerView联动ViewPager,RecyclerView发生点击事件时,ViewPager当即跳转到对应的界面中;
①RecyclerView适配器中写入RecyclerView的点击事件,定义好接口和接口方法;
public interface OnItemClickListener{
void onItemClick(int position);
}
private OnItemClickListener mOnItemClickListener;
public void setOnItemClickListener(OnItemClickListener onItemClickListener){
this.mOnItemClickListener = onItemClickListener;
}
②Activity中调用适配器注册该接口,并实现接口中定义的点击方法:点击RecyclerView子Item时,ViewPager跳转对应界面;
barAdapter.setOnItemClickListener(new Adapter_bar.OnItemClickListener() {
@Override
public void onItemClick(int position) {
/*RecclerView 联动ViewPager*/
viewPager.setCurrentItem(position);
barAdapter.setClickPostion(position);
barAdapter.notifyDataSetChanged();
}
});
③实现RecyclerView子Item状态改变时的相关代码;
子Item的布局文件:
<ImageView
android:id="@+id/song_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@drawable/barbackground"/>
//这里用一个drawable实现状态选择器,用于不同状态时ImageView的背景切换
drawable文件:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ic_show_unselect" android:state_selected="true" />
<item android:drawable="@drawable/ic_show_sekect" android:state_selected="false" />
</selector>
Adapter中的setClickPostion实现代码:
private int click_position;
public void setClickPostion(int postion){
this.click_position = postion;
notifyDataSetChanged();
}
Song song = mSongList.get(position);
Drawable drawable =context.getResources().getDrawable(song.getImageId());
holder.songImage.setImageDrawable(drawable);
if (position == click_position){
holder.songImage.setSelected(true);
} else {
holder.songImage.setSelected(false);
}
Activity中的setClickPostion调用代码:
如上,共有两处调用setClickPostion()方法,ViewPager监听器中调用一次,Adapter点击实现方法中调用一次。
总结一下实现的关键方法:
ViewPager联动RecyclerView实现:ViewPager监听器OnPageChangeListener中调用RecyclerView滑动方法-smoothScrollToPostion(),传入OnPageChangeListener监听器中获得的position参数即可;
RecyclerView联动ViewPager实现:Adapter注册点击事件时,调用ViewPager的setCurrentItem()方法,传入OnItemClickListener监听器中获得的position参数即可;
总结:简单使用RecyclerView还是比较简单的,要探究RecyclerView更加高级的用法,建议读者参考鸿洋大神的
《AndroidRecyclerView 使用完全解析 体验艺术般的控件》
最后附上四节中的子Item的资源文件:
六、RecyclerView滚动
1)点击列表最边缘项,自动往前或者往后滚动一项:
public class RecyclerViewHelper {
public static void autoOffsetPosition(RecyclerView recyclerView, int position) {
try {
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
int firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition();
int firstCompletelyVisibleItemPosition = layoutManager.findFirstCompletelyVisibleItemPosition();
int lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition();
int lastCompletelyVisibleItemPosition = layoutManager.findLastCompletelyVisibleItemPosition();
RecyclerView.Adapter adapter = recyclerView.getAdapter();
int itemCount = adapter.getItemCount();
boolean isFirst = firstCompletelyVisibleItemPosition == 0;
boolean isLast = lastCompletelyVisibleItemPosition == itemCount - 1;
boolean isFirstVisible = firstVisibleItemPosition == position
|| firstCompletelyVisibleItemPosition == position;//最前一个 或者完整显示的一个
boolean isLastVisible = lastVisibleItemPosition == position
|| lastCompletelyVisibleItemPosition == position;//最后一个 或者完整显示的一个
if(!isFirst && isFirstVisible) {
int target = position - 1;
recyclerView.smoothScrollToPosition(target < 0 ? 0: target);
}
else if(!isLast && isLastVisible) {
int target = position + 1;
recyclerView.smoothScrollToPosition(target > itemCount-1 ? itemCount-1: target);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
2)打开列表自动滚动到前一次选中的哪一项:
filterListView.scrollToPosition(6);
filterListView.smoothScrollToPosition(0);
利用SharePreference存储好上一次滚动的item项的编号,然后调用上面两个滚动方法,即可以实现下次打开列表自动滚动到上次选好的item项。减去某个int值,即可实现选中项滚动居中。
3)每次列表滚动,最后停止时都实现列表某一项居中显示,不会出现杂乱的现象:
若使用LinearSnapHelper时报错,
java.lang.IllegalStateException: An instance of OnFlingListener already set.
LinearSnapHelper mLinearSnapHelper = new LinearSnapHelper();
mLinearSnapHelper.attachToRecyclerView(mRecyclerView);
其他文章: