RecyclerView的学习笔记

转载

https://blog.csdn.net/u012124438/article/details/53495951

首先添加依赖

implementation 'com.android.support:recyclerview-v7:28.0.0-alpha3'

版本号要与supportv7库一致,不然报错

如果使用了androidx版本,更改依赖

implementation 'androidx.recyclerview:recyclerview:1.0.0'

更多androidx版本以及配套库的变化,参考开发文档

RecycleView的调用

        LinearLayoutManager linear = new LinearLayoutManager(this.getContext());
        linear.setOrientation(LinearLayoutManager.VERTICAL);
        rvPictures.setLayoutManager(linear);
        RecycleViewAdapter adapter=new RecycleViewAdapter(this.getContext(),datas);
        rvPictures.setAdapter(adapter);

需要实现自己的adapter类和Item布局

1 上Item布局(每个子选项的布局)

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

    <ImageView
        android:id="@+id/iv_pictures"
        android:layout_width="200dp"
        android:layout_height="100dp"
        android:layout_gravity="center"
        android:scaleType="centerCrop"
        />

</LinearLayout>

2然后是自定义适配器

2.1适配器需要有接收的数据和获取的上下文

 

    private Context mContext;
    private List<Integer> datas;

然后是构造器

 

    public RecycleViewAdapter(Context mContext, List<Integer> datas) {
        this.mContext = mContext;
        this.datas = datas;
    }

2.2实现ViewHolder

Viewholder中包含子布局的视图实例和子布局中的控件,并在构造器中初始化

 

    class MyHolder extends RecyclerView.ViewHolder{
        private ImageView mImageview;

        public MyHolder(View itemView) {
            super(itemView);
            this.mImageview = (ImageView) itemView.findViewById(R.id.iv_pictures);
        }
    }

2.3必须重载的3个方法

getItemCount,返回Item数目

 

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

onCreateViewHolder 动态加载子布局,并把获得的view封装在ViewHolder中返回

 

    @NonNull
    @Override
    public RecycleViewAdapter.MyHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View mView= LayoutInflater.from(mContext).inflate(R.layout.recycly_item_layout,null);
        MyHolder myHolder=new MyHolder(mView);
        return myHolder;
    }

onBindViewHolder根据adapter的来初始化holder中控件的属性

    @Override
    public void onBindViewHolder(@NonNull RecycleViewAdapter.MyHolder holder, final int position) {

        holder.mImageview.setImageResource(datas.get(position).intValue());
    }

3 给Item增加点击功能

3.1在adapter中定义回调接口,在活动中具体实现

首先,adapter中保存有监听器变量

 

    private OnItemClickListener mOnItemClickListener;

其次,定义回调接口和监听器变量的设置入口

 

    public interface OnItemClickListener{
        void ItemClick(View view,int position);
    }

    public void setOnItemClickListener(OnItemClickListener itemClickListener){
        this.mOnItemClickListener=itemClickListener;
    }

在绑定ViewHolder的实现中,给每个itemView设置点击事件

 

    @Override
    public void onBindViewHolder(@NonNull RecycleViewAdapter.MyHolder holder, final int position) {

        holder.mImageview.setImageResource(datas.get(position).intValue());
        if (mOnItemClickListener!=null){
            holder.mImageview.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    mOnItemClickListener.ItemClick(v,position);
                }
            });
        }
    }

最后在活动中实现ItemClick()方法

       adapter.setOnItemClickListener(new RecycleViewAdapter.OnItemClickListener() {
            @Override
            public void ItemClick(View view, int position) {
                Toast.makeText(getActivity(), "You click picture "+position, Toast.LENGTH_SHORT).show();
            }
        });

示例代码

adapter文件

public class RecycleViewAdapter extends RecyclerView.Adapter<RecycleViewAdapter.MyHolder>{
    private Context mContext;
    private List<Integer> datas;
    private OnItemClickListener mOnItemClickListener;

    public RecycleViewAdapter(Context mContext, List<Integer> datas) {
        this.mContext = mContext;
        this.datas = datas;
    }


    public interface OnItemClickListener{
        void ItemClick(View view,int position);
    }

    public void setOnItemClickListener(OnItemClickListener itemClickListener){
        this.mOnItemClickListener=itemClickListener;
    }
    @NonNull
    @Override
    public RecycleViewAdapter.MyHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View mView= LayoutInflater.from(mContext).inflate(R.layout.recycly_item_layout,parent,false);
        MyHolder myHolder=new MyHolder(mView);
        return myHolder;
    }

    @Override
    public void onBindViewHolder(@NonNull RecycleViewAdapter.MyHolder holder, final int position) {

        holder.mImageview.setImageResource(datas.get(position).intValue());
        if (mOnItemClickListener!=null){
            holder.mImageview.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    mOnItemClickListener.ItemClick(v,position);
                }
            });
        }
    }

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

    class MyHolder extends RecyclerView.ViewHolder{
        private ImageView mImageview;

        public MyHolder(View itemView) {
            super(itemView);
            this.mImageview = (ImageView) itemView.findViewById(R.id.iv_pictures);
        }
    }
}

 

活动文件

 

public class BlankFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_blank, container, false);
        rvPictures=(RecyclerView)view.findViewById(R.id.rv_pictures);
        List<Integer> datas=new ArrayList<>();
        datas.add(R.drawable.ic_back_1);
        datas.add(R.drawable.ic_back_2);
        datas.add(R.drawable.ic_back_3);
        datas.add(R.drawable.ic_back_4);
        datas.add(R.drawable.ic_back_5);
        datas.add(R.drawable.ic_back_6);
        
        LinearLayoutManager linear = new LinearLayoutManager(this.getContext());
        linear.setOrientation(LinearLayoutManager.VERTICAL);
        rvPictures.setLayoutManager(linear);
        RecycleViewAdapter adapter=new RecycleViewAdapter(this.getContext(),datas);
        adapter.setOnItemClickListener(new RecycleViewAdapter.OnItemClickListener() {
            @Override
            public void ItemClick(View view, int position) {
                Toast.makeText(getActivity(), "You click picture "+position, Toast.LENGTH_SHORT).show();
            }
        });
        rvPictures.setAdapter(adapter);
        return view;
    }
}

 

扩展:给recyclerView添加分隔线

在ListView中,xml配置中是有divider属性的

RecyclerView中同样的有分割线属性,但是仅支持线性布局

来源  https://blog.csdn.net/lindroid20/article/details/76407954

用法

一,默认的分割线

直接给控件设置代码,注意方向要与LinearLayouManage布局方向一致

DividerItemDecoration divider=new DividerItemDecoration(this,DividerItemDecoration.VERTICAL);
rvStructureObject.addItemDecoration(divider);

二,设置一个自定义分割线

实现一个透明的分割线,露出底部view颜色

首先是样式布局

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">

    <solid android:color="@android:color/transparent"/>
    <size android:height="8dp"/>

</shape>

然后是实际应用

DividerItemDecoration divider=new DividerItemDecoration(this,DividerItemDecoration.VERTICAL);
divider.setDrawable(getResources().getDrawable(R.drawable.shape_transparent_divider));
rvStructureObject.addItemDecoration(divider);

三 如何让RecyclerView实现加载不同的ItemView效果

RecyclerView提供了两个接口

获取到对应位置的viewType
getItemViewType(int position)

根据ViewType来加载不同的ViewHolder
onCreateViewHolder(@NonNull ViewGroup parent, int viewType)

如何设置ViewType

这个依据实现,比如根据position参数,偶数位置实现一个效果,奇数位置实现一个效果

然后根据不同的Type,返回不同的ViewHolder

在绑定ViewHolder数据的时候,判断Holder的类型来初始化.

List<T> list;

getItemViewType(int position) {
        return position%2 ? 0:1;
    }

public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
      
        if (viewType == ITEM_TYPE.ITEM_TYPE_LEFT.ordinal()) {
       
            return new LeftViewHolder(mLayoutInflater.inflate(R.layout.item_left, parent, false));
        } else {
          
            return new RightViewHolder(mLayoutInflater.inflate(R.layout.item_right, parent, false));
        }
    }

public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof LeftViewHolder) {
            ((LeftViewHolder) holder).tvContent.setText(list.get(position).getContent());
        } else if (holder instanceof RightViewHolder) {
            ((RightViewHolder) holder).tvContent.setText(list.get(position).getContent());
        }
    }

ViewType的初始化,也可以作为data对象的属性,这样更自由一些

public int getItemViewType(int position) {
        return list.get(position).getType();
    }

 

四 滚动监听

需要实现用户下拉到底部加载新的数据的效果

滑动监听事件,建议参考资料 https://blog.csdn.net/kgpszjia/article/details/51679063

 

首先监听到滑动到底部事件

条件,完全看到最后1条item,并且isloading为假(说明之前没有到达底部)

监听到底部以后,设置isLoading为真.开始模拟下载,为了通知用户,可以添加一个特殊的Item给RecyclerView,或者设置一个progressBar.这个依靠效果而定.这里采用的是添加progressBar.

List<KeyString> list;
//设置标志位,滑动到底部时,截断监听
private boolean isLoading;

然后设置监听
rvList.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
              //int position = linearLayoutManager.findLastCompletelyVisibleItemPosition();
              //int position2 = linearLayoutManager.findLastVisibleItemPosition();
              //Log.i(TAG, "onScrolled: position1=" + position);
              //Log.i(TAG, "onScrolled: position2=" + position2);
         
                if (isLoading == false && linearLayoutManager.findLastCompletelyVisibleItemPosition() == (list.size() - 1)) {
                 
                    isLoading = true;
                    progressBar.setVisibility(View.VISIBLE);
                }
            }
        });   

我们再加个按钮点击事件,模拟下载完毕

删除新加的item/隐藏进度条,重置isLoading为false;

@OnClick({R.id.b_left, R.id.b_right,R.id.cancel})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            case R.id.b_left:
               
                break;
            case R.id.b_right:
             
                break;
            case R.id.cancel:
              
                isLoading=false;
                progressBar.setVisibility(View.GONE);
                break;
        }
    }

 

五,滑动控件的嵌套

有这么一种需求,一个RecyclerView的Item中嵌套一个ListView

遇到两个问题

问题1.ListView显示不全,ListView设置为wrap方式时,只能显示一个item,多余的item被折叠

解决办法,1,固定死ListView的高度,缺点是不太适合高度需要动态变化的情况

解决办法2,计算高度,代码中设置ListView的高度

            //我把item的高度存储在adapter中,在给listView设置adapter后,计算高度并存储
            int itemHeight=adapter.getItemHeight();
            if (itemHeight==0){
                if (list.size()!=0){
                    View itemView=adapter.getView(0,null,lvCustomList);
                    itemView.measure(0,0);
                    itemHeight=itemView.getMeasuredHeight();
                    adapter.setItemHeight(itemHeight);
                    Log.i(TAG, "onBindChild: itemHeight="+itemHeight);
                }
            }
            //然后就可以根据需求设置控件高度
            //我希望的是item最多显示5条
            //ListView除了item还有分割线需要计算高度.我设置的分割线高度为8dp,
            int listHeight=adapter.getListHeight();
            if (listHeight==0){
                if (list.size()!=0){
                    //1dp在屏幕显示的真实高度
                    int dpValue=(int)                     
            TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,1, 
            MyApplication.getmContext().getResources().getDisplayMetrics());
                    if (list.size()>5){
                        listHeight=5*itemHeight+40*dpValue;
                    }else {
                        listHeight=list.size()*itemHeight+list.size()*8*dpValue;
                    }
                    adapter.setListHeight(listHeight);
                    ViewGroup.LayoutParams lp=lvCustomList.getLayoutParams();
                    LogUtil.i(TAG, "onBindChild: listHeight="+listHeight);
                    lp.height=listHeight;
                    //设置高度
                    lvCustomList.setLayoutParams(lp);
                }
            }

 

问题2.嵌套的ListView与RecyclerView滑动冲突.

我希望的是item超过5个时,可以滚动查看.实际情况是只能RecyclerView滚动,

解决办法,设置滚动监听

lvCustomList.setOnTouchListener(new View.OnTouchListener() {
                    @Override
                    public boolean onTouch(View v, MotionEvent event) {
                        Log.i(TAG, "onTouch: parentView="+itemView.getParent());
                        Log.i(TAG, "onTouch: event="+event.getAction());
                        if (event.getAction()==MotionEvent.ACTION_UP){
//                            把触摸事件还给父控件
                            itemView.getParent().requestDisallowInterceptTouchEvent(false);
                        }else {
//                            取消父控件对触摸事件的处理
                            itemView.getParent().requestDisallowInterceptTouchEvent(true);
                        }
                        return false;
                    }
            });

这里有个坑,如果对ListView的Item的整个View设置了点击事件,会导致触摸监听起不到作用,我的办法是,把Item的点击事件,只设置给Item中具体的某一个控件.

 

填坑1

如何监听recyclerView滚动到底部或者顶部

参考资料   https://www.jianshu.com/p/c138055af5d2

方法1:当可视item就是最后一个item的时候,滚动到底部

public static boolean isVisBottom(RecyclerView recyclerView){  
  LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();  
  //屏幕中最后一个可见子项的position
  int lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition();  
  //当前屏幕所看到的子项个数
  int visibleItemCount = layoutManager.getChildCount();  
  //当前RecyclerView的所有子项个数
  int totalItemCount = layoutManager.getItemCount();  
  //RecyclerView的滑动状态
  int state = recyclerView.getScrollState();  
  if(visibleItemCount > 0 && lastVisibleItemPosition == totalItemCount - 1 && state == recyclerView.SCROLL_STATE_IDLE){   
     return true; 
  }else {   
     return false;  
  }
}

方法2:利用view自带的方法,当前显示长度+滚动过得长度,和view的最大范围比较

public static boolean isSlideToBottom(RecyclerView recyclerView) {    
   if (recyclerView == null) return false; 
   if (recyclerView.computeVerticalScrollExtent() + recyclerView.computeVerticalScrollOffset() 
        >= recyclerView.computeVerticalScrollRange())   
     return true;  
   return false;
}

方法3:利用recyclerView封装好的方法2,提供的接口函数

RecyclerView.canScrollVertically(1)的值表示是否能向上滚动,false表示已经滚动到底部
RecyclerView.canScrollVertically(-1)的值表示是否能向下滚动,false表示已经滚动到顶部

方法4:最后一种方法其实是比较呆板的,就是利用LinearLayoutManager的几个方法,1.算出已经滑过的子项的距离,2.算出屏幕的高度,3.算出RecyclerView的总高度。然后用他们做比较,原理类似于方法二。

填坑2

接着上面的坑

如何获得最后一个View

有两种方式

LinearLayoutManager manager;
//方法1
View lastView1=manager.getChildAt(count-1);
//方法2
View lastView2=manager.findViewByPosition(count-1);

但是实际上经常lastView1返回null(当列表的数目超过当前显示的数量时)

这是因为复用机制,getChildAt()方法获得的Child,只存在屏幕中,屏幕中的View一旦划出屏幕,就被复用成新的View

所以方法2获得的View也只有在屏幕中时才不为null

方法1需要修改为

View lastView1=manager.getChildAt(count-manager.findFirstVisibleItemPosition()-1);

 

类似的,ListView也存在复用机制

ListView lvList;
//方法1,获取当前屏幕中view的数量
lvList.getChildCount();
//获取List数据列表中的数量
lvList.getCount();

要获取屏幕中最后一个View

View lastView=lvList.getChildAt(count-lvList.getFirstVisiblePosition()-1);

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值