• Android RecyclerView*


    一、RechclerView简介。

    RecyclerView比listview更先进更灵活,对于很多的视图它就是一个容器,可以有效的重用和滚动。

    1.可以通过设置LayoutManager可以实现Listview和横向Listview,GridView,横向Gridview和瀑布流等效果。

    2.可以通过addItemDecoration添加Item分割线。

    3.可以通过setItemAnimator()设置Item的增加和移除动画。

    二、RecyclerView相关类介绍。

    1、RecyclerView.Adapter:负责托管数据集,为每一项Item创建布局并绑定数据。

    2、RecyclerView.ItemDecoration,给Item添加分割线。需要继承该类自定义一个类。

    3、RecyclerView.ItemAnimator负责处理Item增加或删除时的动画效果,系统提供了一个默认的动画类DefaultItemAnimator()。

    4、RecyclerView.ViewHolder:负责承载Item视图的子布局。

    5、RecyclerView.LayoutManager:布局管理器,负责Item视图的布局的显示管理。分为:

    (1)、LinearLayoutManager,类似Listview
    他有两个构造函数:
    LinearLayoutManager(Context context)//默认方向为垂直方向。
    LinearLayoutManager(Context context, int orientation, boolean reverseLayout)
    其中第二个参数orientation表示布局的方向,可以取两个值:垂直和水平。分别是纵向Listview的效果和横向Listview的效果。第三个参数reverseLayout表示是否反向布局(即纵向Listview上下颠倒),若为true,纵向Listview默认在最底部,而且第一项在最低下。
    (2)、GridLayoutManager,类似GridView
    三种构造函数:
    GridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) //可以直接在XMl中设置RecyclerView 属性”layoutManager”.
    GridLayoutManager(Context context, int spanCount) //spanCount为列数,默认方向vertical
    GridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout)
    //spanCount为列数,orientation为布局方向,reverseLayout决定布局是否反向。
    (3)、StaggeredGridLayoutManager流式布局
    两个构造函数:
    StaggeredGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
    StaggeredGridLayoutManager(int spanCount, int orientation) //spanCount为列数,orientation为布局方向

    Item Animator动画

    RecyclerView能够通过mRecyclerView.setItemAnimator(ItemAnimator animator)设置添加、删除、移动、改变的动画效果

    RecyclerView提供了默认的ItemAnimator实现类:DefaultItemAnimator。如果没有特殊的需求,默认使用这个动画即可。

    Item Decoration间隔样式

    RecyclerView通过addItemDecoration()方法添加item之间的分割线。Android并没有提供实现好的Divider,因此任何分割线样式都需要自己实现

    自定义间隔样式需要继承RecyclerView.ItemDecoration类,该类是个抽象类,官方目前并没有提供默认的实现类,主要有三个方法。

    • onDraw(Canvas c, RecyclerView parent, State state),在Item绘制之前被调用,该方法主要用于绘制间隔样式。
    • onDrawOver(Canvas c, RecyclerView parent, State state),在Item绘制之前被调用,该方法主要用于绘制间隔样式。
    • getItemOffsets(Rect outRect, View view, RecyclerView parent, State state),设置item的偏移量偏移的部分用于填充间隔样式,即设置分割线的宽、高;在RecyclerView的onMesure()中会调用该方法。

    onDraw()onDrawOver()这两个方法都是用于绘制间隔样式,我们只需要复写其中一个方法即可。

    局部刷新闪屏问题解决

    对于RecyclerView的Item Animator,有一个常见的坑就是“闪屏问题”。
    这个问题的描述是:当Item视图中有图片和文字,当更新文字并调用notifyItemChanged()时,文字改变的同时图片会闪一下。这个问题的原因是当调用notifyItemChanged()时,会调用DefaultItemAnimator的animateChangeImpl()执行change动画,该动画会使得Item的透明度从0变为1,从而造成闪屏。

    解决办法很简单,在rv.setAdapter()之前调用((SimpleItemAnimator)rv.getItemAnimator()).setSupportsChangeAnimations(false)禁用change动画

    点击事件

    RecyclerView并没有像ListView一样暴露出Item点击事件或者长按事件处理的api,也就是说使用RecyclerView时候,需要我们自己来实现Item的点击和长按等事件的处理。
    实现方法有很多:

    • 可以监听RecyclerView的Touch事件然后判断手势做相应的处理,
    • 也可以通过在绑定ViewHolder的时候设置监听,然后通过Apater回调出去

    RecylerView相对于ListView的优点罗列如下:

    • RecyclerView封装了viewholder的回收复用,也就是说RecyclerView标准化了ViewHolder编写Adapter面向的是ViewHolder而不再是View了,复用的逻辑被封装了,写起来更加简单。
      直接省去了listview中convertView.setTag(holder)和convertView.getTag()这些繁琐的步骤。
    • 提供了一种插拔式的体验高度的解耦,异常的灵活,针对一个Item的显示RecyclerView专门抽取出了相应的类,来控制Item的显示,使其的扩展性非常强。
    • 设置布局管理器以控制Item布局方式横向竖向以及瀑布流方式
    • 可设置Item的间隔样式(可绘制)通过继承RecyclerView的ItemDecoration这个类,然后针对自己的业务需求去书写代码。
    • 可以控制Item增删的动画,可以通过ItemAnimator这个类进行控制,当然针对增删的动画,RecyclerView有其自己默认的实现。

         但是关于Item的点击和长按事件,需要用户自己去实现。

    三、基本使用

    1、导入android-support-v7-recyclerview

    2、Activity布局文件

    <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"
        tools:context="com.raphets.recyclerview.MainActivity" >
    
        <android.support.v7.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    
    </RelativeLayout>

    3、Item的布局文件

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="#0099ff"
        android:orientation="vertical" >
    
        <TextView
            android:id="@+id/textView"
            android:layout_width="120dp"
            android:layout_height="match_parent"
            android:layout_gravity="center"
            android:gravity="center" />
    
    </LinearLayout>

    4、Activity类,RecyclerView的主要代码

    public class MainActivity extends Activity {
        private RecyclerView mRecyclerView;
        private List<String> mDatas;
        private MyRecylerViewAdapter adapter;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            // 初始化数据
            initData();
            mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView);
            adapter = new MyRecylerViewAdapter(this, mDatas);
            //绑定适配器
            mRecyclerView.setAdapter(adapter);
            // 给每个item添加分割线
            mRecyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST));
            // 设置item增加和移除的动画
            mRecyclerView.setItemAnimator(new DefaultItemAnimator());
            // 设置布局管理器
            LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
            mRecyclerView.setLayoutManager(linearLayoutManager);
    
        }
    
        /*
         * 初始化数据
         */
        private void initData() {
            mDatas = new ArrayList<String>();
            for (int i = 0; i <= 50; i++) {
                mDatas.add("item---" + i);
            }
        }
    
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            getMenuInflater().inflate(R.menu.main, menu);
            return true;
        }
    
        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
    
            int id = item.getItemId();
            switch (id) {
            case R.id.listview:
                mRecyclerView.setLayoutManager(new LinearLayoutManager(this, OrientationHelper.VERTICAL, false));
                break;
            case R.id.gridView:
                mRecyclerView.setLayoutManager(new GridLayoutManager(this, 3));
                break;
            case R.id.horizonalListview:
                mRecyclerView.setLayoutManager(new LinearLayoutManager(this, OrientationHelper.HORIZONTAL, false));
                break;
            case R.id.horizonalGridview:
                mRecyclerView.setLayoutManager(new GridLayoutManager(this, 5, OrientationHelper.HORIZONTAL, false));
                break;
    
            case R.id.add:
                adapter.notifyItemInserted(1);
                break;
            case R.id.delete:
                adapter.notifyItemRemoved(1);
                break;
            default:
                break;
            }
    
            return super.onOptionsItemSelected(item);
        }
    }

    5、适配器Adapter

    public class MyRecylerViewAdapter extends Adapter<MyViewHolder> {
        private Context mContext;
        private List<String> mDatas;
    
        public MyRecylerViewAdapter(Context context, List<String> datas) {
            this.mContext = context;
            this.mDatas = datas;
        }
    
        @Override
        public int getItemCount() {
            // TODO Auto-generated method stub
            return mDatas.size();
        }
    
    
        @Override
        public void onBindViewHolder(MyViewHolder arg0, int arg1) {
            arg0.textView.setText(mDatas.get(arg1));
    
        }
    
        @Override
        public MyViewHolder onCreateViewHolder(ViewGroup arg0, int arg1) {
            View view = LayoutInflater.from(mContext).inflate(R.layout.item, arg0, false);
            MyViewHolder holder = new MyViewHolder(view);
            return holder;
        }
    
    }
    
    class MyViewHolder extends ViewHolder {
        // Item子布局上的一个元素
        TextView textView;
    
        public MyViewHolder(View itemView) {
            super(itemView);
            // 关联引动该元素 ,在item.xml中findView,注意不要忘写(itemview.)
            textView = (TextView) itemView.findViewById(R.id.textView);
        }
    }

    下拉后从上端刷新

    (在demo中是名为PullDownRefresh的module) 
    下拉从上端刷新,这个比较简单。在布局文件里,用SwipeRefreshLayout把RecyclerView包在里面,然后再在java代码里面写下拉的响应事件就好了。下面直接写代码: 
    1.布局文件,把RecyclerView放在SwipeRefreshLayout里:

    <android.support.v4.widget.SwipeRefreshLayout
            android:id="@+id/srl"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
            <android.support.v7.widget.RecyclerView
                android:id="@+id/rv"
                android:layout_width="match_parent"
                android:layout_height="match_parent"/>

    2.java代码:

      //列表
            recyclerView= (RecyclerView) findViewById(R.id.rv);
            recyclerView.setLayoutManager(new LinearLayoutManager(this));
            //添加数据
            list=new ArrayList<>();
            for (int i = 0; i < 10; i++) {
                list.add("第"+i+"项");
            }
            adapter=new ItemAdapter(list,this);
            recyclerView.setAdapter(adapter);
    
            //下拉加载控件
            swipeRefreshLayout= (SwipeRefreshLayout) findViewById(R.id.srl);
            swipeRefreshLayout.setColorSchemeColors(Color.BLUE);//设置旋转圈的颜色
            //下拉监听
            swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
                @Override
                public void onRefresh() {
                    list.add(0,"下拉加载出现的:"+i++);
                    adapter.notifyDataSetChanged();
                    swipeRefreshLayout.setRefreshing(false);//设置成true的话,下拉过后就会一直在那里转
                }
            });

    (在demo中是名为PullUpRefresh的module)

    3.上拉从下端刷新

    设置一个监听器,在上拉到开始显示最下面一项时,加载更多项。 
    监听器EndLessOnScrollListener代码:

    
    public abstract class EndLessOnScrollListener extends RecyclerView.OnScrollListener {
    
        private static final String TAG = "EndLessOnScrollListener";
    
        LinearLayoutManager linearLayoutManager;
    
        //当前所在页
        private int currentPage=0;
    
        //已经加载出来的item数
        private int totalItemCount=0;
    
        //用来存储上一个totalItemCount
        private int previousTotal=0;
    
        //屏幕可见的item数量
        private int visibleItemCount;
    
        //屏幕可见第一个Item的位置
        private int firstVisibleItem;
    
        //是否上拉数据
        private boolean loading=true;
    
        public EndLessOnScrollListener(LinearLayoutManager linearLayoutManager) {
            this.linearLayoutManager = linearLayoutManager;
        }
    
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
    
            visibleItemCount=recyclerView.getChildCount();
            totalItemCount=linearLayoutManager.getItemCount();
            firstVisibleItem=linearLayoutManager.findFirstVisibleItemPosition();
    //去掉loading也可以,但是性能会下降,在每次滑动时都会判断,所以的加上
            if(loading){
                Log.d(TAG, "firstVisibleItem: " + firstVisibleItem);
                Log.d(TAG, "totalItemCount:" + totalItemCount);
                Log.d(TAG, "visibleItemCount:" + visibleItemCount);
                Log.d(TAG, "currentPage:" + currentPage);
                if(totalItemCount>previousTotal){
                    //说明数据项已经加载结束
                    loading=false;
                    previousTotal=totalItemCount;
                }
            }
            //实际效果是滑动到已加载页最后一项可见的瞬间,添加下一页
            if(!loading&&totalItemCount-visibleItemCount<=firstVisibleItem){
                currentPage++;
                onLoadMore(currentPage);
                loading=true;
            }
    
        }
    
        /**
         * 提供一个抽闲方法,在Activity中监听到这个EndLessOnScrollListener
         * 并且实现这个方法
         * 这个方法在可见的页的最后一项,可见时调用
         * currentPage是加载到的页面编号
         */
        public abstract void onLoadMore(int currentPage);

    给recyclerview添加上拉监听事件即可,这里我让它每次加5项:

      recyclerView.addOnScrollListener(new EndLessOnScrollListener(linearLayoutManager) {
                @Override
                public void onLoadMore(int currentPage) {
                    for (int i = count; i < 5+count; i++) {
                        list.add("上拉加载"+i);
                    }
                    adapter.notifyDataSetChanged();
                    count+=5;
                }
            });

    4.添加尾部首部分别添加footer和Head

    (在demo中是名为HeaderAndFooter的module) 
    实现方法,主要是在适配器里实现。要在适配器必须写的方法里面和getItemViewType()方法里,考虑可能最前和最后一项分别是header和footer情况。

    1.temAdapter里的代码

    
        private static final int TYPE_HEADER = 0;
        private static final int TYPE_FOOTER = 1;
        private static final int TYPE_NORMAL = 2;
    
    
        public ItemAdapter(List<String> list, Context context) {
            this.list = list;
            this.context = context;
        }
    
        @Override
        public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            if (headerView != null && viewType == TYPE_HEADER) {
                return new MyViewHolder(headerView);
            }
            if (footerView != null && viewType == TYPE_FOOTER) {
                return new MyViewHolder(footerView);
            }
    
            MyViewHolder holder = new MyViewHolder(LayoutInflater.from(context).
                    inflate(R.layout.item_layout, parent, false));
            return holder;
        }
    
        @Override
        public void onBindViewHolder(MyViewHolder holder, final int position) {
            if (getItemViewType(position) == TYPE_NORMAL) {
                holder.tv.setText(list.get(position - 1));
                return;
            } else if (getItemViewType(position) == TYPE_HEADER) {
                return;
            } else
                return;
        }
    
        /**
         * 重写这个方法,很重要,是加入Header和Footer的关键,我们通过判断item的类型,从而绑定不同的view
         */
        @Override
        public int getItemViewType(int position) {
            if (headerView == null && footerView == null) {
                return TYPE_NORMAL;
            }
            if (position == 0) {
                //第一个item应该加载Header
                return TYPE_HEADER;
            }
            if (position == getItemCount() - 1) {
                //最后一个,应该加载Footer
                return TYPE_FOOTER;
            }
            return TYPE_NORMAL;
        }
    
        @Override
        public int getItemCount() {
            if (headerView == null && footerView == null) {
                return list.size();
            } else if (headerView == null && footerView != null) {
                return list.size() + 1;
            } else if (headerView != null && footerView == null) {
                return list.size() + 1;
            } else {
                return list.size() + 2;
            }
        }
    
        public View getHeaderView() {
            return headerView;
        }
    
        public void setHeaderView(View headerView) {
            this.headerView=headerView;
            notifyItemInserted(0);
        }
    
        public View getFooterView() {
            return footerView;
        }
    
        public void setFooterView(View footerView) {
            this.footerView=footerView;
            notifyItemInserted(getItemCount()-1);
        }
    
        class MyViewHolder extends RecyclerView.ViewHolder {
    
            TextView tv;
    
            public MyViewHolder(View itemView) {
                super(itemView);
                tv = itemView.findViewById(R.id.tv);
            }
        }

    活动里面的代码:

    RecyclerView recyclerView;
        ItemAdapter adapter;
        List<String>list;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            initView();
        }
     private void initView() {
            list= new ArrayList<>();
            for (int i = 0; i < 10; i++) {
                list.add("第"+i+"项");
            }
            adapter=new ItemAdapter(list,this);
    
            recyclerView= (RecyclerView) findViewById(R.id.rv);
            recyclerView.setAdapter(adapter);
            recyclerView.setLayoutManager(new LinearLayoutManager(this));
            //注意,以下两个方法必须在setAdapter()之后调用,否则长和宽会变成wrap_content
            addHeader();
            addFooter();
    
        }
    
        private void addHeader(){
            View header= LayoutInflater.from(this).inflate(R.layout.header_layout,recyclerView,false);
            adapter.setHeaderView(header);
        }
    
        private  void addFooter(){
            View footer= LayoutInflater.from(this).inflate(R.layout.footer_layout,recyclerView,false);
            adapter.setFooterView(footer);
        }

    tem拖拽和滑动删除

    ItemTouchHelper是一个处理RecyclerView的滑动删除和拖拽的辅助类,RecyclerView 的item拖拽移动和滑动删除就靠它来实现。

    ItemTouchHelper的监听如下

        itemTouchHelper=new ItemTouchHelper(new ItemTouchHelper.Callback() {
     
            //用于设置拖拽和滑动的方向
            @Override
            public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
                int dragFlags=0,swipeFlags=0;
                if(recyclerView.getLayoutManager() instanceof StaggeredGridLayoutManager||recyclerView.getLayoutManager() instanceof GridLayoutManager){
                   //网格式布局有4个方向
                   dragFlags=ItemTouchHelper.UP|ItemTouchHelper.DOWN|ItemTouchHelper.LEFT|ItemTouchHelper.RIGHT;
                }else if(recyclerView.getLayoutManager() instanceof LinearLayoutManager){
                   //线性式布局有2个方向
                   dragFlags=ItemTouchHelper.UP|ItemTouchHelper.DOWN;
     
                   swipeFlags = ItemTouchHelper.START|ItemTouchHelper.END; //设置侧滑方向为从两个方向都可以
                }
                return makeMovementFlags(dragFlags,swipeFlags);//swipeFlags 为0的话item不滑动
            }
     
            //长摁item拖拽时会回调这个方法
            @Override
            public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
                int from=viewHolder.getAdapterPosition();
                int to=target.getAdapterPosition();
     
                Meizi moveItem=meizis.get(from);
                meizis.remove(from);
                meizis.add(to,moveItem);//交换数据链表中数据的位置
     
                mAdapter.notifyItemMoved(from,to);//更新适配器中item的位置
                return true;
            }
     
            @Override
            public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
            //这里处理滑动删除
            }
     
            @Override
            public boolean isLongPressDragEnabled() {
                return false;//返回true则为所有item都设置可以拖拽
            }
        });

    itemTouchHelper需要与recyclerView绑定才有效果,在recyclerView初始化的时候调用

         itemTouchHelper.attachToRecyclerView(recyclerview);

    因为我想要网格布局中的图片item可拖拽,而页数item不可拖拽,所以我isLongPressDragEnabled()方法返回的false,而在item的长点击事件监听中做具体处理。

           mAdapter.setOnItemClickListener(new GridAdapter.OnRecyclerViewItemClickListener() {
           @Override
           public void onItemClick(View view) {
           }
     
            @Override
            public void onItemLongClick(View view) {
                  itemTouchHelper.startDrag(recyclerview.getChildViewHolder(view));//设置拖拽item
               }
           });

    如果你想为item设置拖拽和滑动时的响应动画效果,可以利用ItemTouchHelper的下面三个方法。用线性布局示例:

    //当item拖拽开始时调用
    @Override
    public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
    super.onSelectedChanged(viewHolder, actionState);
     if(actionState==ItemTouchHelper.ACTION_STATE_DRAG){
           viewHolder.itemView.setBackgroundColor(Color.LTGRAY);//拖拽时设置背景色为灰色
        }
    }
     
    //当item拖拽完成时调用
    @Override
    public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
    super.clearView(recyclerView, viewHolder);
     viewHolder.itemView.setBackgroundColor(Color.WHITE);//拖拽停止时设置背景色为白色
    }
     
     //当item视图变化时调用
     @Override
     public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
     super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
      //根据item滑动偏移的值修改item透明度。screenwidth是我提前获得的屏幕宽度
      viewHolder.itemView.setAlpha(1-Math.abs(dX)/screenwidth);
     }

    拖拽和滑动删除.gif
     
     
  • 相关阅读:
    4星|万维刚《你有你的计划,世界另有计划》:前物理学家的读书笔记,主要是社会科学领域的书
    3星|《耕作革命》:免耕、保留作物残茬、不同作物轮作的保护性农业,环保且高产
    2.5星|《逆商》:1997年出版的鸡汤,强调积极乐观面对逆境
    3星|《产品游戏化》:游戏类软件产品的宏观开发流程
    2星|曾仕强《人性管理》:故事会水平,像是没有学术背景的讲师
    3.5星|《是谁出的题这么难,到处都是正确答案》:​麦肯锡的经历,文艺妈妈的笔,温馨的父母与丈夫
    基础连接已经关闭: 未能为 SSL/TLS 安全通道建立信任关系。 根据验证过程,远程证书无效------解决方法
    XmlDocument.Load(url) 本地和http远程
    Windows服务启动进程----Cjwdev.WindowsApi.dll
    winform无需安装pdf阅读器打开pdf文件
  • 原文地址:https://www.cnblogs.com/chenxibobo/p/9584138.html
Copyright © 2020-2023  润新知