最强大的列表滚动控件RecyclerView精炼详解

一、前期基础知识储备

现在的手机应用,很多都会采用列表滚动的方式来加载和显示内容,微信、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.

可参考【Android-Error】java.lang.IllegalStateException An instance of OnFlingListener already set._java.lang.illegalstateexception: an instance of on-CSDN博客解决。

LinearSnapHelper mLinearSnapHelper = new LinearSnapHelper(); 
mLinearSnapHelper.attachToRecyclerView(mRecyclerView);

其他文章:

Android开发:RecyclerView获取item位置的几种方法比较
recyclerview获取当前显示的item位置
判断RecyclerView是否滑到顶部或底部
RecyclerView快速滑动到顶部

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值