可展开列表,听到这几个字的时候也许你就会问了,为啥要用 RecyclerView,用Android提供的 ExpandableListView不是更好吗?是的,ExpandableListView是很轻松就可以实现,但是,我要告诉你,我就是喜欢用RecyclerView ……
先上效果图,用的模拟器,有点卡,真机测试很流畅,为了便于区分,上了点色,样子有点丑
说一下实现思路:
* 列表组成:主要分成两个布局,默认展示的为父布局,点击父布局,在父布局的下方插入一行(子布局);
*展开效果:点击Item,插入一行数据的同时,该item下方的item向下滚动,也就是展开效果;
*动画效果:咳咳…… RecyclerView自带这种渐变的动画效果(知道我为啥喜欢RecyclerView了吧,哈哈),ListView自身是没有这种效果的。
以上也就是简单的实现思路,下面上码。
一大波代码来袭
首先是实体类,用来封装模拟数据
/**
* Created by hbh on 2017/4/20.
* 实体类,模拟数据
*/
public class DataBean {
public static final int PARENT_ITEM = 0;//父布局
public static final int CHILD_ITEM = 1;//子布局
private int type;// 显示类型
private boolean isExpand;// 是否展开
private DataBean childBean;
private String ID;
private String parentLeftTxt;
private String parentRightTxt;
private String childLeftTxt;
private String childRightTxt;
public String getParentLeftTxt() {
return parentLeftTxt;
}
public void setParentLeftTxt(String parentLeftTxt) {
this.parentLeftTxt = parentLeftTxt;
}
public String getChildRightTxt() {
return childRightTxt;
}
public void setChildRightTxt(String childRightTxt) {
this.childRightTxt = childRightTxt;
}
public String getChildLeftTxt() {
return childLeftTxt;
}
public void setChildLeftTxt(String childLeftTxt) {
this.childLeftTxt = childLeftTxt;
}
public String getParentRightTxt() {
return parentRightTxt;
}
public void setParentRightTxt(String parentRightTxt) {
this.parentRightTxt = parentRightTxt;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public boolean isExpand() {
return isExpand;
}
public void setExpand(boolean expand) {
isExpand = expand;
}
public DataBean getChildBean() {
return childBean;
}
public void setChildBean(DataBean childBean) {
this.childBean = childBean;
}
public String getID() {
return ID;
}
public void setID(String ID) {
this.ID = ID;
}
}
activity_main.xml文件中只有一个RecyclerView控件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.hbh.cl.expandrecyclerviewdemo.MainActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycle_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical"
android:background="@color/white"
android:paddingTop="5dp">
</android.support.v7.widget.RecyclerView>
</RelativeLayout>
MainActivity文件也是没啥东西,只有一点要注意的,就是添加滚动监听,滚动监听的接口在Adapter中定义
public class MainActivity extends AppCompatActivity {
private RecyclerView mRecyclerView;
private List<DataBean> dataBeanList;
private DataBean dataBean;
private RecyclerAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRecyclerView = (RecyclerView) findViewById(R.id.recycle_view);
initData();
}
/**
* 模拟数据
*/
private void initData(){
dataBeanList = new ArrayList<>();
for (int i = 1; i <= 50; i++) {
dataBean = new DataBean();
dataBean.setID(i+"");
dataBean.setType(0);
dataBean.setParentLeftTxt("父--"+i);
dataBean.setParentRightTxt("父内容--"+i);
dataBean.setChildLeftTxt("子--"+i);
dataBean.setChildRightTxt("子内容--"+i);
dataBean.setChildBean(dataBean);
dataBeanList.add(dataBean);
}
setData();
}
private void setData(){
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mAdapter = new RecyclerAdapter(this,dataBeanList);
mRecyclerView.setAdapter(mAdapter);
//滚动监听
mAdapter.setOnScrollListener(new RecyclerAdapter.OnScrollListener() {
@Override
public void scrollTo(int pos) {
mRecyclerView.scrollToPosition(pos);
}
});
}
}
再有就是自定义Item点击监听接口,一个展开,一个收起
/**
* Created by hbh on 2017/4/20.
* 父布局Item点击监听接口
*/
public interface ItemClickListener {
/**
* 展开子Item
* @param bean
*/
void onExpandChildren(DataBean bean);
/**
* 隐藏子Item
* @param bean
*/
void onHideChildren(DataBean bean);
}
下面就是最主要的Adapter适配器了,主要代码都在这个里面,不过很简单,都有注释,一看就明白
/**
* Created by hbh on 2017/4/20.
* 适配器
*/
public class RecyclerAdapter extends RecyclerView.Adapter<BaseViewHolder> {
private Context context;
private List<DataBean> dataBeanList;
private LayoutInflater mInflater;
private OnScrollListener mOnScrollListener;
public RecyclerAdapter(Context context, List<DataBean> dataBeanList) {
this.context = context;
this.dataBeanList = dataBeanList;
this.mInflater = LayoutInflater.from(context);
}
@Override
public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = null;
switch (viewType){
case DataBean.PARENT_ITEM:
view = mInflater.inflate(R.layout.recycleview_item_parent, parent, false);
return new ParentViewHolder(context, view);
case DataBean.CHILD_ITEM:
view = mInflater.inflate(R.layout.recycleview_item_child, parent, false);
return new ChildViewHolder(context, view);
default:
view = mInflater.inflate(R.layout.recycleview_item_parent, parent, false);
return new ParentViewHolder(context, view);
}
}
/**
* 根据不同的类型绑定View
* @param holder
* @param position
*/
@Override
public void onBindViewHolder(BaseViewHolder holder, int position) {
switch (getItemViewType(position)){
case DataBean.PARENT_ITEM:
ParentViewHolder parentViewHolder = (ParentViewHolder) holder;
parentViewHolder.bindView(dataBeanList.get(position), position, itemClickListener);
break;
case DataBean.CHILD_ITEM:
ChildViewHolder childViewHolder = (ChildViewHolder) holder;
childViewHolder.bindView(dataBeanList.get(position), position);
break;
}
}
@Override
public int getItemCount() {
return dataBeanList.size();
}
@Override
public int getItemViewType(int position) {
return dataBeanList.get(position).getType();
}
private ItemClickListener itemClickListener = new ItemClickListener() {
@Override
public void onExpandChildren(DataBean bean) {
int position = getCurrentPosition(bean.getID());//确定当前点击的item位置
DataBean children = getChildDataBean(bean);//获取要展示的子布局数据对象,注意区分onHideChildren方法中的getChildBean()。
if (children == null) {
return;
}
add(children, position + 1);//在当前的item下方插入
if (position == dataBeanList.size() - 2 && mOnScrollListener != null) { //如果点击的item为最后一个
mOnScrollListener.scrollTo(position + 1);//向下滚动,使子布局能够完全展示
}
}
@Override
public void onHideChildren(DataBean bean) {
int position = getCurrentPosition(bean.getID());//确定当前点击的item位置
DataBean children = bean.getChildBean();//获取子布局对象
if (children == null) {
return;
}
remove(position + 1);//删除
if (mOnScrollListener != null) {
mOnScrollListener.scrollTo(position);
}
}
};
/**
* 在父布局下方插入一条数据
* @param bean
* @param position
*/
public void add(DataBean bean, int position) {
dataBeanList.add(position, bean);
notifyItemInserted(position);
}
/**
*移除子布局数据
* @param position
*/
protected void remove(int position) {
dataBeanList.remove(position);
notifyItemRemoved(position);
}
/**
* 确定当前点击的item位置并返回
* @param uuid
* @return
*/
protected int getCurrentPosition(String uuid) {
for (int i = 0; i < dataBeanList.size(); i++) {
if (uuid.equalsIgnoreCase(dataBeanList.get(i).getID())) {
return i;
}
}
return -1;
}
/**
* 封装子布局数据对象并返回
* 注意,此处只是重新封装一个DataBean对象,为了标注Type为子布局数据,进而展开,展示数据
* 要和onHideChildren方法里的getChildBean()区分开来
* @param bean
* @return
*/
private DataBean getChildDataBean(DataBean bean){
DataBean child = new DataBean();
child.setType(1);
child.setParentLeftTxt(bean.getParentLeftTxt());
child.setParentRightTxt(bean.getParentRightTxt());
child.setChildLeftTxt(bean.getChildLeftTxt());
child.setChildRightTxt(bean.getChildRightTxt());
return child;
}
/**
* 滚动监听接口
*/
public interface OnScrollListener{
void scrollTo(int pos);
}
public void setOnScrollListener(OnScrollListener onScrollListener){
this.mOnScrollListener = onScrollListener;
}
}
还有两个对应的 ParentViewHolder 和 ChildViewHolder
/**
* Created by hbh on 2017/4/20.
* 父布局ViewHolder
*/
public class ParentViewHolder extends BaseViewHolder {
private Context mContext;
private View view;
private RelativeLayout containerLayout;
private TextView parentLeftView;
private TextView parentRightView;
private ImageView expand;
private View parentDashedView;
public ParentViewHolder(Context context, View itemView) {
super(itemView);
this.mContext = context;
this.view = itemView;
}
public void bindView(final DataBean dataBean, final int pos, final ItemClickListener listener){
containerLayout = (RelativeLayout) view.findViewById(R.id.container);
parentLeftView = (TextView) view.findViewById(R.id.parent_left_text);
parentRightView = (TextView) view.findViewById(R.id.parent_right_text);
expand = (ImageView) view.findViewById(R.id.expend);
parentDashedView = view.findViewById(R.id.parent_dashed_view);
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) expand
.getLayoutParams();
expand.setLayoutParams(params);
parentLeftView.setText(dataBean.getParentLeftTxt());
parentRightView.setText(dataBean.getParentRightTxt());
if (dataBean.isExpand()) {
expand.setRotation(90);
parentDashedView.setVisibility(View.INVISIBLE);
} else {
expand.setRotation(0);
parentDashedView.setVisibility(View.VISIBLE);
}
//父布局OnClick监听
containerLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (listener != null) {
if (dataBean.isExpand()) {
listener.onHideChildren(dataBean);
parentDashedView.setVisibility(View.VISIBLE);
dataBean.setExpand(false);
rotationExpandIcon(90, 0);
} else {
listener.onExpandChildren(dataBean);
parentDashedView.setVisibility(View.INVISIBLE);
dataBean.setExpand(true);
rotationExpandIcon(0, 90);
}
}
}
});
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void rotationExpandIcon(float from, float to) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
ValueAnimator valueAnimator = ValueAnimator.ofFloat(from, to);//属性动画
valueAnimator.setDuration(500);
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
expand.setRotation((Float) valueAnimator.getAnimatedValue());
}
});
valueAnimator.start();
}
}
}
/**
* Created by hbh on 2017/4/20.
* 子布局ViewHolder
*/
public class ChildViewHolder extends BaseViewHolder {
private Context mContext;
private View view;
private TextView childLeftText;
private TextView childRightText;
public ChildViewHolder(Context context, View itemView) {
super(itemView);
this.mContext = context;
this.view = itemView;
}
public void bindView(final DataBean dataBean, final int pos){
childLeftText = (TextView) view.findViewById(R.id.child_left_text);
childRightText = (TextView) view.findViewById(R.id.child_right_text);
childLeftText.setText(dataBean.getChildLeftTxt());
childRightText.setText(dataBean.getChildRightTxt());
}
}
OVER,以上就是主要的代码,大部分都贴上了,很简单。
总的来说就是 通过增加和删除 再加上动画效果 来模拟列表的展开和收起,在这里抛砖引玉,如有更好的实现方法或方式,还望各位猿友们不吝赐教,文章如有表述不当,还请指正。