Android5.x:RecycleView(一):实现ListView + GridView + StaggeredGridLayou效果

参考:
Android RecyclerView 使用完全解析 体验艺术般的控件

Hongyang对RecyclerView的adapter进行了各种封装:
博客: Android 优雅的为RecyclerView添加HeaderView和FooterView
github: https://github.com/hongyangAndroid/baseAdapter

1 RecycleView实现ListView的功能

需要添加依赖:

compile 'com.android.support:recyclerview-v7:24.2.0'

相关方法:

RecyclerView的方法:

方法含义
setLayoutManager(…)设置布局管理者
setAdapter设置适配器

Adapter中的方法:

方法含义
onCreateViewHolder(…)创建ViewHolder
onBindViewHolder(…)给ViewHolder里面的view设置属性
getItemCountitem的数量

步骤

  • 找到RecycleView
  • 给RecycleView设置 LayoutManager
  • 给RecycleView设置 Adapter

下面通过一个demo显示RecycleView的基本使用。

见图:
这里写图片描述

MainActivity

package com.cqc.recyclerview01;

import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

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

public class MainActivity extends AppCompatActivity {

    private RecyclerView recyclerView;
    private Context context;
    private List<String> list = new ArrayList<>();

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

        initData();
        recyclerView = (RecyclerView) findViewById(R.id.recyclerView);

        recyclerView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false));
        recyclerView.setAdapter(new MyAdapter());
    }

    private void initData() {
        list.clear();
        for (int i = 0; i < 100; i++) {
            list.add("item" + i);
        }
    }


    public class MyAdapter extends RecyclerView.Adapter {

        private MyHolder myHolder;

        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            TextView tv = new TextView(context);
            tv.setHeight(50);
            myHolder = new MyHolder(tv);
            return myHolder;
        }

        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            //  MyHolder myHolder = (MyHolder) holder;
            myHolder = (MyHolder) holder;
            myHolder.textView.setText(list.get(position));
        }

        @Override
        public int getItemCount() {
            return 100;
        }
    }


    public class MyHolder extends RecyclerView.ViewHolder {
        public TextView textView;

        public MyHolder(View itemView) {
            super(itemView);
            textView = (TextView) itemView;
        }
    }
}


实现item的分割线

com.android.support:recyclerview-v7:25.0.1官方有了默认的DividerItemDecoration,25.0.1之前没有这个类。

我们发现recyclerview没有分割线,需要调用mRecyclerView.addItemDecoration()添加分割线,
但是ItemDecoration是抽象类,需要我们自己实现。

该类DividerItemDecoration源于:
http://blog.csdn.net/lmj623565791/article/details/45059587

怎么添加默认的分割线?

导入类DividerItemDecoration到项目中,调用方法:

recyclerView.addItemDecoration(new DividerItemDecoration(context,LinearLayoutManager.VERTICAL));

//或者
recyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL));

注意:注意context为null的情况,尽量用getActivity()或者getContext(),最近就报了错,找了半天才发现是这里错了。尽量不要让context变成成员变量,

private  Context context = getActivity();
怎么添加自定义分割线 ?

创建自定义的分割线:item_divider.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
       android:shape="rectangle">
    <size android:height="4dp"/><!--不用设置宽度-->

    <gradient
        android:centerColor="#ff0000ff"
        android:endColor="#ffff0000"
        android:startColor="#ff00ff00"
        android:type="linear"/>
</shape>
<!--recyclerView分割线的设置-->

使用自定义的分割线:styles.xml

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        ...
        <!--自定义item的分割线-->
        <item name="android:listDivider">@drawable/item_divider</item>
    </style>

</resources>

效果图:
这里写图片描述

package com.cqc.recyclerview01;

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.util.Log;
import android.view.View;

/**
 * RecyclerView添加分割线(当使用LayoutManager为LinearLayoutManager时)
 * 该类源于:http://blog.csdn.net/lmj623565791/article/details/45059587
 */
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);
        }
    }
}

然后在mainActivity中调用:

recyclerView.addItemDecoration(new DividerItemDecoration(context,LinearLayoutManager.VERTICAL));//添加分割线

添加点击事件

方法一:在ViewHolder的构造方法里面写点击监听事件
static class MyViewHolder extends RecyclerView.ViewHolder {

    TextView tv;

    public MyViewHolder(View itemView) {
        super(itemView);
        itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int adapterPosition = getAdapterPosition();
            }
        });
    }
}
方法二:在onBindViewHolder(...)里面写itemView的点击监听
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, final int position) {
    MyViewHolder holder = (MyViewHolder) viewHolder;
    holder.tv.setText("" + position);

    holder.itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Log.d(TAG, "position=" + position);
        }
    });
}
方法三:利用回调,在Activity中写点击监听

先在adapter中设置回调

//设置点击回调
public interface OnItemClickListener {
    void onItemClick(View view, int position);

    void onItemLongClick(View view, int position);
}

private OnItemClickListener mOnItemClickListener;

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

然后在onBindViewHolder(…)中调用点击事件监听

//如果设置了回调,则调用点击事件
if (mOnItemClickListener != null) {
    holder.textView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            mOnItemClickListener.onItemClick(v,position);
        }
    });

    holder.textView.setOnLongClickListener(new View.OnLongClickListener() {
        @Override
        public boolean onLongClick(View v) {
            mOnItemClickListener.onItemLongClick(v, position);
            return true;//事件被处理
        }
    });
}

如果只有短点击,同理也需要在onBindViewHolder()中设置.

//如果设置了回调,则调用点击事件
if (mOnItemClickListener != null) {
    holder.textView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            mOnItemClickListener.onItemClick(v,position);
        }
    });
}
public interface OnItemClickListener {
    void onItemClick(View view, int position);
}

private OnItemClickListener mOnItemClickListener;

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

Mactivity中调用

adapter.setOnItemClickListener(new MyAdapter.OnItemClickListener() {
    @Override
    public void onItemClick(View view, int position) {

    }
});

最后在activity中使用点击事件

//点击事件(用adapter调用点击方法)
adapter.setOnItemClickListener(new MyAdapter.OnItemClickListener() {
    @Override
    public void onItemClickListerner(View view, int position) {
        Toast.makeText(context,"短点击",Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onItemLongClickListener(View view, int position) {

        Toast.makeText(context,"长点击",Toast.LENGTH_SHORT).show();
    }
});

效果图:
这里写图片描述

添加item和删除item

方法:
添加item:adapter.notifyItemInserted(position)
删除item:adapter.notifyItemRemoved(position);
    btn_add.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            list.add(0,"btn_add");
//                adapter.notifyDataSetChanged();//也可以,但是没有动画效果
            adapter.notifyItemInserted(0);
            recyclerView.scrollToPosition(0);//滑动到第一个item,不加不会滑动到顶部。
        }
    });

    btn_delete.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            list.remove(0);
            adapter.notifyDataSetChanged();//也可以,但是没有动画效果
//                adapter.notifyItemRemoved(0);
        }
    });
item添加/删除的动画
 recyclerView.setItemAnimator(new DefaultItemAnimator());//item添加移出动画
notifyItemRemoved(int position)引起的角标越界异常

在调用该方法后,

list.remove(position);
adapter.notifyItemRemoved(position);

容易引起角标越界异常,因为调用该方法后,不会重新走onBindViewHolder(…),所以position的值没有变化。
解决方法:

list.remove(position);
adapter.notifyItemRemoved(position);
if (position != list.size()) {//如果删除的是最后一个,则不刷新数据。
    adapter.notifyItemRangeChanged(position, list.size() - deleteIndex);//刷新position后的数据
}

添加HeadView和FootView

1 这里只讨论添加一个HeadView或FootView
2 下面的是没有给HeadView或FootView创建单独的ViewHolder,用的是同一个ViewHolder,并在其构造方法中判断;
3 当然也可以给HeadView或FootView创建单独的ViewHolder, 并在onCreateViewHolder(…)中返回对应的ViewHolder。

首先定义HeadView和FootView的类型

//默认只加一个header 和footer,2种类型不能为0,因为getItemViewType(int position)默认返回0
private int HeaderType = 1;
private int FooterType = 2;

其次:定义HeadView和FootView变量,并设置和获取他们的数量(0/1)

private View headerView;
private View footerView;

public void setHeaderView(View headerView) {
    this.headerView = headerView;
}

public void setFooterView(View footerView) {
    this.footerView = footerView;
}

public int getHeadViewCount() {
    return headerView == null ? 0 : 1;
}

public int getFootViewCount() {
    return footerView == null ? 0 : 1;
}

第三步:重写方法getItemViewType(int position)

此处注意:定义HeaderType 和FooterType的值不能是0,因为该方法默认返回0,如果头或尾定义了0,而你又没有给其它item(HeadView/FootView之外的item)定义type,仍然使用return super.getItemViewType(position);,那么0就代表2种类型了,这是不对的。

这里写图片描述

这里写图片描述

@Override
public int getItemViewType(int position) {
    if (position == 0) {
        return HeaderType;
    }
    if (position == 100 + getHeadViewCount()) {
        return FooterType;
    }
    return super.getItemViewType(position);
}

上面的是确定有header,其实不应该这样判断

@Override
public int getItemViewType(int position) {
    if (position < getHeadViewCount()) {
        return HEAD;
    }
    if (position >= list.size() + getHeadViewCount()) {
        return FOOT;
    }
    return NORMAL;
}

第四步:修改Adapter中的3个方法

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    if (viewType == HeaderType) {
        return new MyViewHolder(headerView);
    }
    if (viewType == FooterType) {
        return new MyViewHolder(footerView);
    }
    View itemView = View.inflate(context, android.R.layout.two_line_list_item, null);
    return new MyViewHolder(itemView);
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
    if (getItemViewType(position) == HeaderType) {
        return;
    }
    if (getItemViewType(position) == FooterType) {
        return;
    }

    MyViewHolder holder = (MyViewHolder) viewHolder;
    holder.tv1.setText("Title");
    holder.tv2.setText("message");
}

@Override
public int getItemCount() {
    return 100 + getHeadViewCount() + getFootViewCount();
}
public class MyViewHolder extends RecyclerView.ViewHolder {

    TextView tv_item;

    public MyViewHolder(View itemView) {
        super(itemView);
        if (itemView == headerView) {
            return;
        }
        if (itemView == footerView) {
            return;
        }
        tv_item = (TextView) itemView.findViewById(R.id.tv_item);
    }
}

第五步:我们添加HeadView和FootView,setHeaderView(view)放在setAdapter(adapter)前后无所谓。

recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(context));
adapter = new MyAdapter();
recyclerView.setAdapter(adapter);

View headView = View.inflate(context, R.layout.layout_header, null);
View footView = View.inflate(context, R.layout.layout_footer, null);

adapter.setHeaderView(headView);
adapter.setFooterView(footView);

这是我们发现:HeadView或FootView的宽度没有充满屏幕,这是什么原因呢? 详见:Android基础:三种inflate的区别
这是因为我们在填充HeadView或FootView使用的是

View headView = View.inflate(context, R.layout.layout_header, null);
View footView = View.inflate(context, R.layout.layout_footer, null);

改成下面这种,注意:第二个参数parent是recyclerView

View headView = LayoutInflater.from(context).inflate(R.layout.layout_header,recyclerView, false);
View footView =LayoutInflater.from(context).inflate(R.layout.layout_footer, recyclerView,false);

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

参考:
Android 优雅的为RecyclerView添加HeaderView和FooterView
Android 简捷地为RecyclerView添加HeadView和FootView
Demo:https://git.oschina.net/RecyclerView/RecyclerViewDemo2

Demo源码:https://git.oschina.net/RecyclerView/RecyclerViewHeaderFooter01

setHasFixedSize(true)

recyclerView.setHasFixedSize(true);

它的作用是当我们队数据进行增删的时候,不会重新计算item宽高。

Developer Guild–> Meterial Design–>创建列表和卡片中是这样介绍的
这里写图片描述

注意事项

1 onCreateViewHolder(…)不可以复用holder,必须new。

if (myHolder == null){
      myHolder = new MyHolder(tv);
}

2 onCreateViewHolder(…)中的holder和onBindViewHolder(RecyclerView.ViewHolder holder, int position) {。。}中的holder就是我们创建的MyHolder,可以直接进行格式转换,也可以声明成员变量。

    public class MyAdapter extends RecyclerView.Adapter {

        private MyHolder myHolder;

        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            TextView tv = new TextView(context);
            tv.setHeight(50);
            myHolder = new MyHolder(tv);
            return myHolder;
        }

        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            //  MyHolder myHolder = (MyHolder) holder;
            myHolder = (MyHolder) holder;
            myHolder.textView.setText(list.get(position));
        }

        @Override
        public int getItemCount() {
            return 100;
        }
    }

3 最好先创建类ViewHolder,再创建Adapter,这样可以使用直接指定泛型,就不需要再格式转换了。

public class MyAdapter extends RecyclerView.Adapter<MyHolder>

4 item的高度设为match_parent,父布局设置100dp,item的高无效

父布局设置具体的高度,子view设match_parent,这是无效的。
方法一:必须给子view(textview设置具体的数值),父布局math——parent
方法二:item的view:使用    LayoutInflater.from(mContext).inflate(R.layout.item_layout, parent, false); 而不是其他任何方法!(View.inflate(..)) 

5 item的子view(textview)宽度match_parent,父布局的宽度也是match_parent,但是仍然无法充满屏幕。

方法:item的view:使用LayoutInflater.from(mContext).inflate(R.layout.item_layout, parent, false); 而不是其他任何方法!(View.inflate(..))

6 总结:结合4+5,使用View.inflate(context, R.layout.item_main, null);创建item的view,会导致item的view高度无效,子view(textview)的高宽无效。

方法:item的view:使用LayoutInflater.from(mContext).inflate(R.layout.item_layout, parent, false); 而不是其他任何方法!
错误:(View.inflate(context, R.layout.item_main, null) 
错误:LayoutInflater.from(context).inflate(R.layout.item_main,null))

7 item宽度没有充满屏幕

RecyclerView子View宽度不能全屏的问题,在Adapter的onCreateViewHolder创建子view的时候要把parent传进去;

//这种inflate方式有事会导致item宽度不充满屏幕
//View itemView = View.inflate(parent.getContext(), R.layout.item_gate_frag, null);
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_gate_frag,parent,false);

源码:RecyclerView01

复杂的RecyclerView

和listview一样,复杂的RecyclerView即使item有多种类型。这是需要重写adapter的2方法
getItemViewType(int position) {...} getItemCount() {...}
,定义View类型:private static int ViewTypeHead = 0; private static int ViewTypeBody = 1;,在创建ViewHolder是也需要判断ViewType,有几种类型就创建几个ViewHolder

这里写图片描述
以下是主要代码:

private static int ViewTypeHead = 0;
private static int ViewTypeBody = 1;
@Override
public int getItemViewType(int position) {
    if (position % 3 == 0) {
        return ViewTypeHead;
    } else {
        return ViewTypeBody;
    }
}

@Override
public int getItemCount() {
    return 100;
}
public class ItemHeadViewHeader extends RecyclerView.ViewHolder {
    ...
    public ItemHeadViewHeader(View itemView) {
        super(itemView);
        ...
    }
}

public class ItemBodyViewHeader extends RecyclerView.ViewHolder {
    ...
    public ItemBodyViewHeader(View itemView) {
        super(itemView);
        ...
    }
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    if (viewType == ViewTypeHead) {
        View itemView = View.inflate(parent.getContext(), R.layout.item_header, null);
        return new ItemHeadViewHeader(itemView);
    } else {
        View itemView = View.inflate(parent.getContext(), R.layout.item_body, null);
        return new ItemBodyViewHeader(itemView);
    }
}

Demo: https://git.oschina.net/Android5x/ShopCartDemo01

RecycleView(二):实现GridView的功能

同ListView基本一样,只是LayoutManager变成了GridLayoutManager

效果图

这里写图片描述

代码

recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new GridLayoutManager(MainActivity.this, 3));
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.addItemDecoration(new DividerGridItemDecoration(MainActivity.this));
adapter = new RecyclerGridAdapter(list);
recyclerView.setAdapter(adapter);

adapter.setOnItemClickListener(new RecyclerGridAdapter.OnItemClickListener() {
    @Override
    public void onItemClick(View view, int position) {
        toast.setText(list.get(position));
        toast.show();
    }
});

分割线

RecyclerView实现GridView没有提供默认的分割线(25.0.1开始有了),V7包中的DividerItemDecoration只是给ListView使用,而GridView应该使用自定义的DividerGridItemDecoration
DividerGridItemDecoration的下载地址

效果图:
这里写图片描述

自定义分割线

ListView的divider 只需要设置高度
GridView的divider 需要设置高度+宽度

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
       android:shape="rectangle" >

    <gradient
        android:centerColor="#ff00ff00"
        android:endColor="#ff0000ff"
        android:startColor="#ffff0000"
        android:type="linear" />
    <!--listView的divider 只需要设置高度-->
    <!--gridView 的divider 需要设置高度+宽度-->
    <size android:height="4dp" android:width="4dp"/>

</shape>

item的margin无效的问题

margin无效 效果图: margin有效 效果图
这里写图片描述 这里写图片描述

要想使itemView 跟布局的layout_margin生效,必须指定root即parent,也就是不能使用第一种方式创建itemView

//第一种
View itemView = View.inflate(parent.getContext(), R.layout.item_grid, null);

//第二种
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_grid, parent, false);

RecylerView实现瀑布流

效果图

这里写图片描述

同ListView和GridView的不同之处

recyclerView.setLayoutManager(new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL));
recyclerView.addItemDecoration(new DividerGridItemDecoration(StaggeredGridActivity.this));

怎么是这种效果的?

通过设置每个item的随机高度,达到显示瀑布流的效果。

heights.add(random.nextInt(100) + 100);
ViewGroup.LayoutParams params = holder.tv.getLayoutParams();
params.height = heights.get(position);
holder.tv.setLayoutParams(params);

源码: https://git.oschina.net/Android5x/RecyclerViewDemo02

ScrollView嵌套RecylerView

下面的内容来源自android ScrollView嵌套RecyclerView ,之验证了重写LinearLayoutManager,可以正常使用,而且乜有重写ScrollView

ScrollView嵌套ListView,我们一般重写onMeasure()方法,但是RecyclerView不行,而是要重写LayoutManager,比如LinearLayoutManagerGridLayoutManagerStaggeredGridLayoutManager。但是值得注意的是,如果recyclerView很长那么强烈不建议去做嵌套,因为这样recyclerView会在展示的时候立刻展示所有内容,效率极低。

重写LinearLayoutManager

public class FullyLinearLayoutManager extends LinearLayoutManager {

    private static final String TAG = FullyLinearLayoutManager.class.getSimpleName();

    public FullyLinearLayoutManager(Context context) {
        super(context);
    }

    public FullyLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
    }

    public FullyLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    private int[] mMeasuredDimension = new int[2];

    @Override
    public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
                          int widthSpec, int heightSpec) {

        final int widthMode = View.MeasureSpec.getMode(widthSpec);
        final int heightMode = View.MeasureSpec.getMode(heightSpec);
        final int widthSize = View.MeasureSpec.getSize(widthSpec);
        final int heightSize = View.MeasureSpec.getSize(heightSpec);

        Log.i(TAG, "onMeasure called. \nwidthMode " + widthMode
                + " \nheightMode " + heightSpec
                + " \nwidthSize " + widthSize
                + " \nheightSize " + heightSize
                + " \ngetItemCount() " + getItemCount());

        int width = 0;
        int height = 0;
        for (int i = 0; i < getItemCount(); i++) {
            measureScrapChild(recycler, i,
                    View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                    View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                    mMeasuredDimension);

            if (getOrientation() == HORIZONTAL) {
                width = width + mMeasuredDimension[0];
                if (i == 0) {
                    height = mMeasuredDimension[1];
                }
            } else {
                height = height + mMeasuredDimension[1];
                if (i == 0) {
                    width = mMeasuredDimension[0];
                }
            }
        }
        switch (widthMode) {
            case View.MeasureSpec.EXACTLY:
                width = widthSize;
            case View.MeasureSpec.AT_MOST:
            case View.MeasureSpec.UNSPECIFIED:
        }

        switch (heightMode) {
            case View.MeasureSpec.EXACTLY:
                height = heightSize;
            case View.MeasureSpec.AT_MOST:
            case View.MeasureSpec.UNSPECIFIED:
        }

        setMeasuredDimension(width, height);
    }

    private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
                                   int heightSpec, int[] measuredDimension) {
        try {
            View view = recycler.getViewForPosition(0);//fix 动态添加时报IndexOutOfBoundsException

            if (view != null) {
                RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();

                int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
                        getPaddingLeft() + getPaddingRight(), p.width);

                int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
                        getPaddingTop() + getPaddingBottom(), p.height);

                view.measure(childWidthSpec, childHeightSpec);
                measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
                measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
                recycler.recycleView(view);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
        }
    }
}

重写GridLayoutManager

public class FullyGridLayoutManager extends GridLayoutManager {
    public FullyGridLayoutManager(Context context, int spanCount) {
        super(context, spanCount);
    }

    public FullyGridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) {
        super(context, spanCount, orientation, reverseLayout);
    }

    private int[] mMeasuredDimension = new int[2];

    @Override
    public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
        final int widthMode = View.MeasureSpec.getMode(widthSpec);
        final int heightMode = View.MeasureSpec.getMode(heightSpec);
        final int widthSize = View.MeasureSpec.getSize(widthSpec);
        final int heightSize = View.MeasureSpec.getSize(heightSpec);

        int width = 0;
        int height = 0;
        int count = getItemCount();
        int span = getSpanCount();
        for (int i = 0; i < count; i++) {
            measureScrapChild(recycler, i,
                    View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                    View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                    mMeasuredDimension);

            if (getOrientation() == HORIZONTAL) {
                if (i % span == 0) {
                    width = width + mMeasuredDimension[0];
                }
                if (i == 0) {
                    height = mMeasuredDimension[1];
                }
            } else {
                if (i % span == 0) {
                    height = height + mMeasuredDimension[1];
                }
                if (i == 0) {
                    width = mMeasuredDimension[0];
                }
            }
        }

        switch (widthMode) {
            case View.MeasureSpec.EXACTLY:
                width = widthSize;
            case View.MeasureSpec.AT_MOST:
            case View.MeasureSpec.UNSPECIFIED:
        }

        switch (heightMode) {
            case View.MeasureSpec.EXACTLY:
                height = heightSize;
            case View.MeasureSpec.AT_MOST:
            case View.MeasureSpec.UNSPECIFIED:
        }

        setMeasuredDimension(width, height);
    }

    private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
                                   int heightSpec, int[] measuredDimension) {
        if (position < getItemCount()) {
            try {
                View view = recycler.getViewForPosition(0);//fix 动态添加时报IndexOutOfBoundsException
                if (view != null) {
                    RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
                    int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
                            getPaddingLeft() + getPaddingRight(), p.width);
                    int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
                            getPaddingTop() + getPaddingBottom(), p.height);
                    view.measure(childWidthSpec, childHeightSpec);
                    measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
                    measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
                    recycler.recycleView(view);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

重写StaggeredGridLayoutManager

public class ExStaggeredGridLayoutManager extends StaggeredGridLayoutManager {

    public ExStaggeredGridLayoutManager(int spanCount, int orientation) {
        super(spanCount, orientation);
    }

    // 尺寸的数组,[0]是宽,[1]是高
    private int[] measuredDimension = new int[2];

    // 用来比较同行/列那个item罪宽/高
    private int[] dimension;


    @Override

    public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
        // 宽的mode+size
        final int widthMode = View.MeasureSpec.getMode(widthSpec);
        final int widthSize = View.MeasureSpec.getSize(widthSpec);
        // 高的mode + size
        final int heightMode = View.MeasureSpec.getMode(heightSpec);
        final int heightSize = View.MeasureSpec.getSize(heightSpec);

        // 自身宽高的初始值
        int width = 0;
        int height = 0;
        // item的数目
        int count = getItemCount();
        // item的列数
        int span = getSpanCount();
        // 根据行数或列数来创建数组
        dimension = new int[span];

        for (int i = 0; i < count; i++) {
            measureScrapChild(recycler, i,
                    View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                    View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED), measuredDimension);

           // 如果是竖直的列表,计算item的高,否则计算宽度
            //Log.d("LISTENER", "position " + i + " height = " + measuredDimension[1]);
            if (getOrientation() == VERTICAL) {
                dimension[findMinIndex(dimension)] += measuredDimension[1];
            } else {
                dimension[findMinIndex(dimension)] += measuredDimension[0];
            }
        }
        if (getOrientation() == VERTICAL) {
            height = findMax(dimension);
        } else {
            width = findMax(dimension);
        }


        switch (widthMode) {
            // 当控件宽是match_parent时,宽度就是父控件的宽度
            case View.MeasureSpec.EXACTLY:
                width = widthSize;
                break;
            case View.MeasureSpec.AT_MOST:
                break;
            case View.MeasureSpec.UNSPECIFIED:
                break;
        }
        switch (heightMode) {
            // 当控件高是match_parent时,高度就是父控件的高度
            case View.MeasureSpec.EXACTLY:
                height = heightSize;
                break;
            case View.MeasureSpec.AT_MOST:
                break;
            case View.MeasureSpec.UNSPECIFIED:
                break;
        }
        // 设置测量尺寸  
        setMeasuredDimension(width, height);
    }

    private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
            int heightSpec, int[] measuredDimension) {

        // 挨个遍历所有item
        if (position < getItemCount()) {
            try {
                View view = recycler.getViewForPosition(position);//fix 动态添加时报IndexOutOfBoundsException

                if (view != null) {
                    RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) view.getLayoutParams();
                    int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec, getPaddingLeft() + getPaddingRight(), lp.width);
                    int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec, getPaddingTop() + getPaddingBottom(), lp.height);
                    // 子view进行测量,然后可以通过getMeasuredWidth()获得测量的宽,高类似
                    view.measure(childWidthSpec, childHeightSpec);
                    // 将item的宽高放入数组中
                    measuredDimension[0] = view.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
                    measuredDimension[1] = view.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
                    recycler.recycleView(view);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private int findMax(int[] array) {
        int max = array[0];
        for (int value : array) {
            if (value > max) {
                max = value;
            }
        }
        return max;
    }

    /**
     * 得到最数组中最小元素的下标
     *
     * @param array
     * @return
     */
    private int findMinIndex(int[] array) {
        int index = 0;
        int min = array[0];
        for (int i = 0; i < array.length; i++) {
            if (array[i] < min) {
                min = array[i];
                index = i;
            }
        }
        return index;
    }
}

此种方法在4.x系统上好用,能显示滑动也流畅,但是在5.x上虽然显示正常,但是滑动的时候好像被粘住了,没有惯性效果。。。

最后解决方法是重写最外层的Scrollview

public class MyScrollview extends ScrollView {
    private int downX;
    private int downY;
    private int mTouchSlop;

    public MyScrollview(Context context) {
        super(context);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

    public MyScrollview(Context context, AttributeSet attrs) {
        super(context, attrs);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

    public MyScrollview(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent e) {
        int action = e.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                downX = (int) e.getRawX();
                downY = (int) e.getRawY();
                break;
            case MotionEvent.ACTION_MOVE:
                int moveY = (int) e.getRawY();
                if (Math.abs(moveY - downY) > mTouchSlop) {
                    return true;
                }
        }
        return super.onInterceptTouchEvent(e);
    }
}

RecyclerView的itemView包含CheckBox的问题

找到了2篇博客,代码如下
Recycleview checkbox 复用出现混乱解决方法
RecyclerView中使用checkbox遇到的问题

感觉还是用标志位方便。

bug效果图+修复后效果图

这里写图片描述 这里写图片描述

解决代码

public class MyRecyclerViewAdapter extends RecyclerView.Adapter {


    private MyViewHolder holder;

    private List<Integer> checkList = new ArrayList<>();
    private List<String> list;

    public MyRecyclerViewAdapter(List<String> list) {
        this.list = list;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = View.inflate(parent.getContext(), R.layout.item, null);
        return new MyViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, final int position) {
        holder = (MyViewHolder) viewHolder;
        holder.tv.setText(list.get(position));

        holder.checkBox.setTag(new Integer(position));//把组件的状态更新为一个合法的状态值
        if (checkList != null) {
            ((MyViewHolder) holder).checkBox.setChecked((checkList.contains(new Integer(position)) ? true : false));
        } else {
            ((MyViewHolder) holder).checkBox.setChecked(false);
        }

        holder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
                int index = (int) compoundButton.getTag();//事件处理前要判断状态
                if (index == -2) {
                    return;
                }
                if (isChecked) {
                    //这句要有 ,否则不复用了,但是你再滑动回去的时候,都成了为选中。这是因为你的item滑出可视范围时,就会触发oncheckchange事件,所以用第一步绑定的tag进行二次判断。防止选中的丢失
                    if (!checkList.contains(holder.checkBox.getTag())) {
                        checkList.add(new Integer(position));
                    }
                } else {
                    if (checkList.contains(holder.checkBox.getTag())) {//这句同上,二次判断
                        checkList.remove(new Integer(position));
                    }
                }
            }
        });
    }

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

    public class MyViewHolder extends RecyclerView.ViewHolder {

        CheckBox checkBox;
        TextView tv;

        public MyViewHolder(View itemView) {
            super(itemView);
            checkBox = (CheckBox) itemView.findViewById(R.id.cb);
            checkBox.setTag(new Integer(-2));

            tv = (TextView) itemView.findViewById(R.id.tv);
        }
    }

    @Override
    public void onViewRecycled(RecyclerView.ViewHolder holder) {
        CheckBox cb = ((MyViewHolder) holder).checkBox;
        cb.setTag(new Integer(-2));
        super.onViewRecycled(holder);
    }

    public List<Integer> getCheckList() {
        return checkList;
    }
}

item margin

我们在使用RecyclerView中的item设置上下间距,如果是第一个,那么设置marginTop即可,但如果是最后一个,就必须设置marginBottom,而如果 都设置那么中间的item的间距就是marginTop+marginBottom纸之和,,怎么实现每一个item的间距都相等,而且第一个和最后一个跟屏幕的间距也是一样的呢。

给item只设置marginTop属性,最后一个item用java代码设置margin属性.

if (position == list.size()-1) {
    FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,DpPxUtil.dip2px(context,100));
    int marginValue = DpPxUtil.dip2px(context, 10);
    params.setMargins(marginValue,marginValue,marginValue,marginValue);
    cardView.setLayoutParams(params);
}

RecyclerView嵌套RecyclerView

不需要特别注意,该怎么用就怎么用,不需要计算高度。

效果图

这里写图片描述 这里写图片描述

创建数据

注意:采用list.clear()有问题。

public class ClassBean {

    private String className;
    private List<StudentBean> students;

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }

    public List<StudentBean> getStudents() {
        return students;
    }

    public void setStudents(List<StudentBean> students) {
        this.students = students;
    }
}
public class StudentBean {

    private String name;
    private String age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }
}
private List<StudentBean> studentList ;
private List<ClassBean> classList = new ArrayList<>();
private void initData() {
    for (int i = 0; i < 30; i++) {
        ClassBean classBean = new ClassBean();
        classBean.setClassName("班级" + i);

        studentList= new ArrayList<>();
//            studentList.clear();//采用clear()无法全部清除,
        int maxValue = new Random().nextInt(5);
        for (int j = 0; j < maxValue; j++) {
            StudentBean student = new StudentBean();
            student.setName("小明"+(j+1));
            student.setAge("15");
            studentList.add(student);
        }

        classBean.setStudents(studentList);
        classList.add(classBean);
    }
}

item_1.xml

<?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="wrap_content"
              android:orientation="vertical">

    <TextView
        android:id="@+id/tv1"
        android:background="@color/colorAccent"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:gravity="center"/>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</LinearLayout>

MyAdapter1

public class MyAdapter extends RecyclerView.Adapter {

    private List<ClassBean> classList;

    public MyAdapter(List<ClassBean> classList) {
        this.classList = classList;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_1, parent, false);
        MyViewHolder holder = new MyViewHolder(itemView);
        return holder;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
        MyViewHolder holder = (MyViewHolder) viewHolder;
        ClassBean info = classList.get(position);
        holder.tv1.setText(info.getClassName());

        //重点
        holder.recyclerView2.setLayoutManager(new LinearLayoutManager(holder.recyclerView2.getContext()));
        MyAdapter2 adapter2 = new MyAdapter2(info.getStudents());
        holder.recyclerView2.setAdapter(adapter2);
    }

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


    static class MyViewHolder extends RecyclerView.ViewHolder {

        TextView tv1;
        RecyclerView recyclerView2;

        public MyViewHolder(View itemView) {
            super(itemView);
            tv1 = (TextView) itemView.findViewById(R.id.tv1);
            recyclerView2 = (RecyclerView) itemView.findViewById(R.id.recyclerView2);
        }
    }
}

当然,还有item_2.xmlMyAdapter2.java,

public class MyAdapter2 extends RecyclerView.Adapter {

    private List<StudentBean> studentList;

    public MyAdapter2(List<StudentBean> studentList) {
        this.studentList = studentList;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_2, parent, false);
        MyViewHolder holder = new MyViewHolder(itemView);
        return holder;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
        MyViewHolder holder = (MyViewHolder) viewHolder;
        StudentBean info = studentList.get(position);
        holder.tv2.setText(info.getName());
        holder.tv3.setText(info.getAge());
    }

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


    static class MyViewHolder extends RecyclerView.ViewHolder {

        TextView tv2;
        TextView tv3;

        public MyViewHolder(View itemView) {
            super(itemView);
            tv2 = (TextView) itemView.findViewById(R.id.tv2);
            tv3 = (TextView) itemView.findViewById(R.id.tv3);
        }
    }
}

其他

Demo:http://git.oschina.net/AndroidUI/nestedrecyclerview01
recyclerview嵌套recyclerview

怎么让最后一个item不显示 分割线

只需要修改一行代码即可。重写DividerItemDecoration这个类,继承RecyclerView.ItemDecoration,把下面的i < childCount改成i < childCount-1,大功告成。

final int childCount = parent.getChildCount();
for (int i = 0; i < childCount-1; i++) {
    final View child = parent.getChildAt(i);
    parent.getDecoratedBoundsWithMargins(child, mBounds);
    final int bottom = mBounds.bottom + Math.round(ViewCompat.getTranslationY(child));
    final int top = bottom - mDivider.getIntrinsicHeight();
    mDivider.setBounds(left, top, right, bottom);
    mDivider.draw(canvas);
}

修改的地方属于这个方法(以竖直排列为例)
这里写图片描述

这里写图片描述
修改的地方:
这里写图片描述

使用补间动画+recyclerView快速滑动, item会堆在一起,造成卡顿现象。

参考:RecyclerView实现Item滑动加载进入动画效果

使用补间动画+recyclerView快速滑动, item会堆在一起,造成卡顿现象,到动画持续时长(比如设置500ms)后才消失。

ScaleAnimation scale = new ScaleAnimation(0.8f, 1.0f, 0.8f, 1.0f, Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
scale.setDuration(500);//或者
//holder.itemView.startAnimation(scale);
holder.itemView.setAnimation(scale);
scale.start();

怎么解决卡顿?
方法一:采用属性动画
方法二:item滑出屏幕的时候立即取消动画。

方法一:使用属性动画

AnimatorSet set = new AnimatorSet();
ObjectAnimator scaleX = ObjectAnimator.ofFloat(holder.itemView, "scaleX", 0.9f, 1.0f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(holder.itemView, "scaleY", 0.9f, 1.0f);
set.setDuration(500);
set.playTogether(scaleX, scaleY);
set.start();

方法二:针对补间动画

ScaleAnimation scale = new ScaleAnimation(0.8f, 1.0f, 0.8f, 1.0f, Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
scale.setDuration(500);//或者
//holder.itemView.startAnimation(scale);
holder.itemView.setAnimation(scale);
scale.start();
@Override
public void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) {
    super.onViewDetachedFromWindow(holder);
    holder.itemView.clearAnimation();
}

相关资料收集

RecyclerView优秀文集,从入门到精通

Android5.x:RecycleView(一):实现ListView + GridView + StaggeredGridLayou效果
Android5.x:RecycleView(二):单选 、多选、item背景色
Android5.x:RecycleView(三):上下拖动和左右滑动删除

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值