RecyclerView实现二级目录显示---item可伸展收缩的RecyclerVoew
2017-07-30 23:02
447 查看
布局文件代码:
MainActivity代码
GroupBean与ChildBean
两个代码
Steven封装的分割线类所有代码 4种分割线
两种布局的XML文件
<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"> <android.support.v7.widget.RecyclerView android:id="@+id/recyclerView_main" android:layout_width="match_parent" android:layout_height="match_parent"/> </RelativeLayout>
MainActivity代码
package com.steven.bk31_expandablerecyclerview; import android.content.Context; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import com.steven.bk31_expandablerecyclerview.adapter.MyAdapter; import com.steven.bk31_expandablerecyclerview.decoration.MyItemAnimator; import com.steven.bk31_expandablerecyclerview.decoration.SpacesItemDecoration; import com.steven.bk31_expandablerecyclerview.model.ChildModel; import com.steven.bk31_expandablerecyclerview.model.GroupModel; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { private Context mContext = this; private RecyclerView recyclerView_main; //private MyAdapter.ViewHolder holder; private MyAdapter adapter = null; private List<Object> totalList = new ArrayList<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initData(); initView(); } /** * / */ private void initView() { //初始化recyclerview recyclerView_main = ((RecyclerView) findViewById(R.id.recyclerView_main)); // 如果可以确定每个item的高度是固定的,设置这个选项可以提高性能 recyclerView_main.setHasFixedSize(true); //设置分割线或者分割空间 recyclerView_main.addItemDecoration(new SpacesItemDecoration(3)); //设置动画 MyItemAnimator animator = new MyItemAnimator(); //适配器创建 adapter = new MyAdapter(this, totalList, animator); //设置布局管理器 GridLayoutManager layoutManager = new GridLayoutManager(this, 1); //根据位置设置span layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { if (adapter.getItemViewType(position) == MyAdapter.GROUP) { return 1; } return 1; } }); //设置布局管理器 recyclerView_main.setLayoutManager(layoutManager); //设置适配器 recyclerView_main.setAdapter(adapter); // recyclerView_main.setItemAnimator(animator); } private void initData() { //编造假数据源 int numzero=0; for (int i = 0; i < 10; i++) { GroupModel group = new GroupModel(String.format("第%d组项目", i)); int radom =(int)(Math.random()*10); if (radom<=0) { numzero=numzero+1; } if (radom>0) { totalList.add(group); for (int j = 0; j <radom ; j++) { //组对象中放置各个组下的二级目录 group.getEntries().add(new ChildModel(String.format("第%d条消息", j),i-numzero)); } } } } }JavaBean包含两个
GroupBean与ChildBean
两个代码
package com.steven.bk31_expandablerecyclerview.model; /** * Created by StevenWang on 16/6/13. */ public class ChildModel { private String text; private int parent_position; public int getParent_position() { return parent_position; } public void setParent_position(int parent_position) { this.parent_position = parent_position; } public String getText() { return text; } public void setText(String text) { this.text = text; } public ChildModel(String text,int position) { this.text = text; this.parent_position =position; } @Override public String toString() { return "ChildModel{" + "text='" + text + '\'' + '}'; } }
package com.steven.bk31_expandablerecyclerview.model; import java.util.ArrayList; import java.util.List; /** * Created by StevenWang on 16/6/13. */ public class GroupModel { private String text; //组下实体集合 private List<ChildModel> entries; //是否展开 private boolean isExpand; public GroupModel(String text) { this.text = text; entries = new ArrayList<>(); } public String getText() { return text; } public void setText(String text) { this.text = text; } public boolean isExpand() { return isExpand; } public void setIsExpand(boolean isExpand) { this.isExpand = isExpand; } public List<ChildModel> getEntries() { return entries; } public void setEntries(List<ChildModel> entries) { this.entries = entries; } @Override public String toString() { return "GroupModel{" + "text='" + text + '\'' + ", entries=" + entries + ", isExpand=" + isExpand + '}'; } }Adapter代码
package com.steven.bk31_expandablerecyclerview.adapter; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.support.v7.app.AlertDialog; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import android.widget.Toast; import com.steven.bk31_expandablerecyclerview.R; import com.steven.bk31_expandablerecyclerview.decoration.MyItemAnimator; import com.steven.bk31_expandablerecyclerview.model.ChildModel; import com.steven.bk31_expandablerecyclerview.model.GroupModel; import java.util.ArrayList; import java.util.List; import java.util.concurrent.locks.LockSupport; public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private Context context; public static final int GROUP = 0; public static final int CHILD = 1; private List<Object> list; private MyItemAnimator animator; private RecyclerView recyclerView; private LayoutInflater mInflater = null; private List<Boolean> flags =new ArrayList<>(); private int last_position=-1; public MyAdapter(Context context, List<Object> list, MyItemAnimator animator) { this.context = context; this.list = list; this.animator = animator; mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); for (int i = 0; i < list.size(); i++) { flags.add(false); } } @Override public RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup parent, int viewType) { View view = null; switch (viewType) { case GROUP: view = mInflater.inflate(R.layout.item_recyclerview_group, parent, false); //增加点击监听器 view.setOnClickListener(new View.OnClickListener() { /** /* @param v */ @Override public void onClick(View v) { //获取view在adpter中的位置 int position = recyclerView.getChildAdapterPosition(v); //获取这个我位置上的数据源类型 switch (getItemViewType(position)) { //组类型 case GROUP: GroupModel group = (GroupModel) list.get(position); if (last_position==-1||last_position==position) { //是否展开 if (group.isExpand()) { group.setIsExpand(false); // animator.setXY(v.getLeft(), v.getTop()); list.removeAll(group.getEntries()); last_position=-1; notifyItemRangeRemoved(position + 1, group.getEntries().size()); } else { group.setIsExpand(true); // animator.setXY(v.getLeft(), v.getTop()); list.addAll(position + 1, group.getEntries()); last_position=position; notifyItemRangeInserted(position + 1, group.getEntries().size ()); } }else { if (position<last_position) { //删除掉上次特定位置数据 GroupModel groupModel= (GroupModel) list.get(last_position); Log.i("删除组的大小和内容:",groupModel.getEntries().size()+":::"+groupModel.toString()); Log.i("集合大小",list.size()+":::"+list.toString()); list.removeAll(groupModel.getEntries()); Log.i("集合大小",list.size()+":::"+list.toString()); groupModel.setIsExpand(false); // animator.setXY(v.getLeft(), v.getTop()); // notifyItemRangeRemoved(last_position+1, groupModel.getEntries().size()); GroupModel groupModel1 = (GroupModel) list.get(position); groupModel1.setIsExpand(true); //当前点击位置下面加入子目录数据 list.addAll(position + 1,(groupModel1.getEntries())); // animator.setXY(v.getLeft(), v.getTop()); notifyDataSetChanged(); //改变上次点击位置 last_position=position; // notifyItemRangeRemoved(position+1, groupModel1.getEntries().size()); }else if (position>last_position){ GroupModel groupModel = (GroupModel) list.get(last_position); int size = groupModel.getEntries().size(); list.removeAll(groupModel.getEntries()); groupModel.setIsExpand(false); GroupModel groupModel1 = (GroupModel) list.get(position-size); groupModel1.setIsExpand(true); groupModel1.setIsExpand(true); list.addAll(position + 1-size,(groupModel1.getEntries())); // animator.setXY(v.getLeft(), v.getTop()); notifyDataSetChanged(); //改变上次点击位置 last_position=position-size; } } break; case CHILD: break; } } }); return new ViewHolderGroup(view); case CHILD: view = mInflater.inflate(R.layout.item_recyclerview_child, parent, false); view.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(final View v) { // int position = recyclerView.getChildAdapterPosition(v); Toast.makeText(context,"点击位置"+position,Toast.LENGTH_LONG).show(); AlertDialog.Builder dialog=new AlertDialog.Builder(context); dialog.setTitle("是否删除"); dialog.setNegativeButton("取消", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); //子目录删除操作 dialog.setPositiveButton("是", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { int position = recyclerView.getChildAdapterPosition(v); ChildModel childModel = (ChildModel) list.get(position); int parent_position = childModel.getParent_position(); Log.i("parent_position腹肌目录的位置:",parent_position+""); GroupModel groupModel = (GroupModel) list.get(parent_position); groupModel.getEntries().remove(position-parent_position-1); list.remove(position); Log.i(":::::::::::::>>>",list.size()+"个数"); int size = list.size(); if ((position-1)>-1&&((position+1)<list.size()-1)) { if ((list.get(position-1) instanceof GroupModel)==true&&(list.get(position+1) instanceof GroupModel)==true) { //删除一个父级目录 后续的所有父级目录下面 的字目录的getParentositio()都得变化 list.remove(position-1); for (int i = position-1; i < list.size(); i++) { if (list.get(i) instanceof GroupModel) { GroupModel model = (GroupModel) list.get(i); List<ChildModel> entries = model.getEntries(); for (int j = 0; j < entries.size(); j++) { entries.get(j).setParent_position(i-1); } } } } Log.i(":::::",(list.get(position-1) instanceof GroupModel)+""); Log.i(":::::",(list.get(position+1) instanceof GroupModel)+""); } else if (position==size&&(list.get(position-1) instanceof GroupModel)) { Log.i("hhhhh::","////"+position); // list.remove(position-1); } notifyDataSetChanged(); // //有问题 当删除的子条目为父级目录中的最后一个时候 开始执行删除父级目录指令 // if ((list.get(position-1) instanceof GroupModel)&&(list.get(position+1) instanceof GroupModel)) // { // list.remove(position); // } // GroupModel groupModel = (GroupModel) list.get(parent_position); // if (groupModel.getEntries().size()==0) // { // list.remove(parent_position); // } } }); dialog.create().show(); return false; } }); return new ViewHolderChild(view); } return new ViewHolderGroup(view); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { Object obj = list.get(position); if (obj instanceof GroupModel) { ViewHolderGroup viewHolderGroup = (ViewHolderGroup) holder; viewHolderGroup.textView_item_group.setText(((GroupModel) obj).getText()); } if (obj instanceof ChildModel) { ViewHolderChild viewHolderChild = (ViewHolderChild) holder; viewHolderChild.textView_item_child.setText(((ChildModel) obj).getText()); } } @Override public int getItemCount() { return list.size(); } @Override public int getItemViewType(int position) { Object obj = list.get(position); if (obj instanceof GroupModel) { return GROUP; } if (obj instanceof ChildModel) { return CHILD; } return super.getItemViewType(position); } @Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { this.recyclerView = recyclerView; } //组对象ViewHolder class ViewHolderGroup extends RecyclerView.ViewHolder { private TextView textView_item_group; public ViewHolderGroup(View itemView) { super(itemView); textView_item_group = (TextView) itemView.findViewById(R.id.textView_item_group); } } //组对象下对应二级目录Viewholder class ViewHolderChild extends RecyclerView.ViewHolder { private TextView textView_item_child; public ViewHolderChild(View itemView) { super(itemView); textView_item_child = ((TextView) itemView.findViewById(R.id.textView_item_child)); } } }
Steven封装的分割线类所有代码 4种分割线
package com.steven.bk31_expandablerecyclerview.decoration; 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.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.LayoutManager; import android.support.v7.widget.RecyclerView.State; import android.support.v7.widget.StaggeredGridLayoutManager; import android.view.View; /** * 设置网格排列时item之间的分割线 */ public class DividerGridItemDecoration extends RecyclerView.ItemDecoration { private static final int[] ATTRS = new int[]{android.R.attr.listDivider}; private Drawable mDivider; public DividerGridItemDecoration(Context context) { final TypedArray a = context.obtainStyledAttributes(ATTRS); mDivider = a.getDrawable(0); a.recycle(); } @Override public void onDraw(Canvas c, RecyclerView parent, State state) { drawHorizontal(c, parent); drawVertical(c, parent); } private int getSpanCount(RecyclerView parent) { int spanCount = -1; LayoutManager layoutManager = parent.getLayoutManager(); if (layoutManager instanceof GridLayoutManager) { spanCount = ((GridLayoutManager) layoutManager).getSpanCount(); } else if (layoutManager instanceof StaggeredGridLayoutManager) { spanCount = ((StaggeredGridLayoutManager) layoutManager) .getSpanCount(); } return spanCount; } public void drawHorizontal(Canvas c, RecyclerView parent) { 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.getLeft() - params.leftMargin; final int right = child.getRight() + params.rightMargin + mDivider.getIntrinsicWidth(); final int top = child.getBottom() + params.bottomMargin; final int bottom = top + mDivider.getIntrinsicHeight(); mDivider.setBounds(left, top, right, bottom); mDivider.draw(c); } } public void drawVertical(Canvas c, RecyclerView parent) { 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 top = child.getTop() - params.topMargin; final int bottom = child.getBottom() + params.bottomMargin; final int left = child.getRight() + params.rightMargin; final int right = left + mDivider.getIntrinsicWidth(); mDivider.setBounds(left, top, right, bottom); mDivider.draw(c); } } private boolean isLastColum(RecyclerView parent, int pos, int spanCount, int childCount) { LayoutManager layoutManager = parent.getLayoutManager(); if (layoutManager instanceof GridLayoutManager) { if ((pos + 1) % spanCount == 0) { return true; } } else if (layoutManager instanceof StaggeredGridLayoutManager) { int orientation = ((StaggeredGridLayoutManager) layoutManager) .getOrientation(); if (orientation == StaggeredGridLayoutManager.VERTICAL) { if ((pos + 1) % spanCount == 0) { return true; } } else { childCount = childCount - childCount % spanCount; if (pos >= childCount) return true; } } return false; } private boolean isLastRaw(RecyclerView parent, int pos, int spanCount, int childCount) { LayoutManager layoutManager = parent.getLayoutManager(); if (layoutManager instanceof GridLayoutManager) { childCount = childCount - childCount % spanCount; if (pos >= childCount) return true; } else if (layoutManager instanceof StaggeredGridLayoutManager) { int orientation = ((StaggeredGridLayoutManager) layoutManager) .getOrientation(); // StaggeredGridLayoutManager if (orientation == StaggeredGridLayoutManager.VERTICAL) { childCount = childCount - childCount % spanCount; if (pos >= childCount) return true; } else // StaggeredGridLayoutManager { if ((pos + 1) % spanCount == 0) { return true; } } } return false; } @Override public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) { int spanCount = getSpanCount(parent); int childCount = parent.getAdapter().getItemCount(); if (isLastRaw(parent, itemPosition, spanCount, childCount))// { outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0); } else if (isLastColum(parent, itemPosition, spanCount, childCount))// { outRect.set(0, 0, 0, mDivider.getIntrinsicHeight()); } else { outRect.set(0, 0, mDivider.getIntrinsicWidth(), mDivider.getIntrinsicHeight()); } } }
package com.steven.bk31_expandablerecyclerview.decoration; /* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * limitations under the License. */ 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; /** * 设置线性排列时item之间的分割线 * This class is from the v7 samples of the Android SDK. * See the license above for details. */ 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); RecyclerView v = new 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); } } }
package com.steven.bk31_expandablerecyclerview.decoration; import android.support.annotation.NonNull; import android.support.v4.animation.AnimatorCompatHelper; import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewPropertyAnimatorCompat; import android.support.v4.view.ViewPropertyAnimatorListener; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.SimpleItemAnimator; import android.view.View; import java.util.ArrayList; import java.util.List; /** * Created by StevenWang on 16/6/13. */ public class MyDefaultItemAnimator extends SimpleItemAnimator { /* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * This implementation of {@link RecyclerView.ItemAnimator} provides basic * animations on remove, add, and move events that happen to the items in * a RecyclerView. RecyclerView uses a DefaultItemAnimator by default. * * @see RecyclerView#setItemAnimator(RecyclerView.ItemAnimator) */ private static final boolean DEBUG = false; private ArrayList<RecyclerView.ViewHolder> mPendingRemovals = new ArrayList<>(); private ArrayList<RecyclerView.ViewHolder> mPendingAdditions = new ArrayList<>(); private ArrayList<MoveInfo> mPendingMoves = new ArrayList<>(); private ArrayList<ChangeInfo> mPendingChanges = new ArrayList<>(); private ArrayList<ArrayList<RecyclerView.ViewHolder>> mAdditionsList = new ArrayList<>(); private ArrayList<ArrayList<MoveInfo>> mMovesList = new ArrayList<>(); private ArrayList<ArrayList<ChangeInfo>> mChangesList = new ArrayList<>(); private ArrayList<RecyclerView.ViewHolder> mAddAnimations = new ArrayList<>(); private ArrayList<RecyclerView.ViewHolder> mMoveAnimations = new ArrayList<>(); private ArrayList<RecyclerView.ViewHolder> mRemoveAnimations = new ArrayList<>(); private ArrayList<RecyclerView.ViewHolder> mChangeAnimations = new ArrayList<>(); private static class MoveInfo { public RecyclerView.ViewHolder holder; public int fromX, fromY, toX, toY; private MoveInfo(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) { this.holder = holder; this.fromX = fromX; this.fromY = fromY; this.toX = toX; this.toY = toY; } } private static class ChangeInfo { public RecyclerView.ViewHolder oldHolder, newHolder; public int fromX, fromY, toX, toY; private ChangeInfo(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder) { this.oldHolder = oldHolder; this.newHolder = newHolder; } private ChangeInfo(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromX, int fromY, int toX, int toY) { this(oldHolder, newHolder); this.fromX = fromX; this.fromY = fromY; this.toX = toX; this.toY = toY; } @Override public String toString() { return "ChangeInfo{" + "oldHolder=" + oldHolder + ", newHolder=" + newHolder + ", fromX=" + fromX + ", fromY=" + fromY + ", toX=" + toX + ", toY=" + toY + '}'; } } @Override public void runPendingAnimations() { boolean removalsPending = !mPendingRemovals.isEmpty(); boolean movesPending = !mPendingMoves.isEmpty(); boolean changesPending = !mPendingChanges.isEmpty(); boolean additionsPending = !mPendingAdditions.isEmpty(); if (!removalsPending && !movesPending && !additionsPending && !changesPending) { // nothing to animate return; } // First, remove stuff for (RecyclerView.ViewHolder holder : mPendingRemovals) { animateRemoveImpl(holder); } mPendingRemovals.clear(); // Next, move stuff if (movesPending) { final ArrayList<MoveInfo> moves = new ArrayList<>(); moves.addAll(mPendingMoves); mMovesList.add(moves); mPendingMoves.clear(); Runnable mover = new Runnable() { @Override public void run() { for (MoveInfo moveInfo : moves) { animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY, moveInfo.toX, moveInfo.toY); } moves.clear(); mMovesList.remove(moves); } }; if (removalsPending) { View view = moves.get(0).holder.itemView; ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration()); } else { mover.run(); } } // Next, change stuff, to run in parallel with move animations if (changesPending) { final ArrayList<ChangeInfo> changes = new ArrayList<>(); changes.addAll(mPendingChanges); mChangesList.add(changes); mPendingChanges.clear(); Runnable changer = new Runnable() { @Override public void run() { for (ChangeInfo change : changes) { animateChangeImpl(change); } changes.clear(); mChangesList.remove(changes); } }; if (removalsPending) { RecyclerView.ViewHolder holder = changes.get(0).oldHolder; ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration()); } else { changer.run(); } } // Next, add stuff if (additionsPending) { final ArrayList<RecyclerView.ViewHolder> additions = new ArrayList<>(); additions.addAll(mPendingAdditions); mAdditionsList.add(additions); mPendingAdditions.clear(); Runnable adder = new Runnable() { public void run() { for (RecyclerView.ViewHolder holder : additions) { animateAddImpl(holder); } additions.clear(); mAdditionsList.remove(additions); } }; if (removalsPending || movesPending || changesPending) { long removeDuration = removalsPending ? getRemoveDuration() : 0; long moveDuration = movesPending ? getMoveDuration() : 0; long changeDuration = changesPending ? getChangeDuration() : 0; long totalDelay = removeDuration + Math.max(moveDuration, changeDuration); View view = additions.get(0).itemView; ViewCompat.postOnAnimationDelayed(view, adder, totalDelay); } else { adder.run(); } } } @Override public boolean animateRemove(final RecyclerView.ViewHolder holder) { endAnimation(holder); mPendingRemovals.add(holder); return true; } private void animateRemoveImpl(final RecyclerView.ViewHolder holder) { final View view = holder.itemView; final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view); mRemoveAnimations.add(holder); animation.setDuration(getRemoveDuration()) .alpha(0).setListener(new VpaListenerAdapter() { @Override public void onAnimationStart(View view) { dispatchRemoveStarting(holder); } @Override public void onAnimationEnd(View view) { animation.setListener(null); ViewCompat.setAlpha(view, 1); dispatchRemoveFinished(holder); mRemoveAnimations.remove(holder); dispatchFinishedWhenDone(); } }).start(); } @Override public boolean animateAdd(final RecyclerView.ViewHolder holder) { endAnimation(holder); ViewCompat.setAlpha(holder.itemView, 0); mPendingAdditions.add(holder); return true; } private void animateAddImpl(final RecyclerView.ViewHolder holder) { final View view = holder.itemView; final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view); mAddAnimations.add(holder); animation.alpha(1).setDuration(getAddDuration()). setListener(new VpaListenerAdapter() { @Override public void onAnimationStart(View view) { dispatchAddStarting(holder); } @Override public void onAnimationCancel(View view) { ViewCompat.setAlpha(view, 1); } @Override public void onAnimationEnd(View view) { animation.setListener(null); dispatchAddFinished(holder); mAddAnimations.remove(holder); dispatchFinishedWhenDone(); } }).start(); } @Override public boolean animateMove(final RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) { final View view = holder.itemView; fromX += ViewCompat.getTranslationX(holder.itemView); fromY += ViewCompat.getTranslationY(holder.itemView); resetAnimation(holder); int deltaX = toX - fromX; int deltaY = toY - fromY; if (deltaX == 0 && deltaY == 0) { dispatchMoveFinished(holder); return false; } if (deltaX != 0) { ViewCompat.setTranslationX(view, -deltaX); } if (deltaY != 0) { ViewCompat.setTranslationY(view, -deltaY); } mPendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY)); return true; } private void animateMoveImpl(final RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) { final View view = holder.itemView; final int deltaX = toX - fromX; final int deltaY = toY - fromY; if (deltaX != 0) { ViewCompat.animate(view).translationX(0); } if (deltaY != 0) { ViewCompat.animate(view).translationY(0); } // TODO: make EndActions end listeners instead, since end actions aren't called when // vpas are canceled (and can't end them. why?) // need listener functionality in VPACompat for this. Ick. final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view); mMoveAnimations.add(holder); animation.setDuration(getMoveDuration()).setListener(new VpaListenerAdapter() { @Override public void onAnimationStart(View view) { dispatchMoveStarting(holder); } @Override public void onAnimationCancel(View view) { if (deltaX != 0) { ViewCompat.setTranslationX(view, 0); } if (deltaY != 0) { ViewCompat.setTranslationY(view, 0); } } @Override public void onAnimationEnd(View view) { animation.setListener(null); dispatchMoveFinished(holder); mMoveAnimations.remove(holder); dispatchFinishedWhenDone(); } }).start(); } @Override public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromX, int fromY, int toX, int toY) { if (oldHolder == newHolder) { // Don't know how to run change animations when the same view holder is re-used. // run a move animation to handle position changes. return animateMove(oldHolder, fromX, fromY, toX, toY); } final float prevTranslationX = ViewCompat.getTranslationX(oldHolder.itemView); final float prevTranslationY = ViewCompat.getTranslationY(oldHolder.itemView); final float prevAlpha = ViewCompat.getAlpha(oldHolder.itemView); resetAnimation(oldHolder); int deltaX = (int) (toX - fromX - prevTranslationX); int deltaY = (int) (toY - fromY - prevTranslationY); // recover prev translation state after ending animation ViewCompat.setTranslationX(oldHolder.itemView, prevTranslationX); ViewCompat.setTranslationY(oldHolder.itemView, prevTranslationY); ViewCompat.setAlpha(oldHolder.itemView, prevAlpha); if (newHolder != null) { // carry over translation values resetAnimation(newHolder); ViewCompat.setTranslationX(newHolder.itemView, -deltaX); ViewCompat.setTranslationY(newHolder.itemView, -deltaY); ViewCompat.setAlpha(newHolder.itemView, 0); } mPendingChanges.add(new ChangeInfo(oldHolder, newHolder, fromX, fromY, toX, toY)); return true; } private void animateChangeImpl(final ChangeInfo changeInfo) { final RecyclerView.ViewHolder holder = changeInfo.oldHolder; final View view = holder == null ? null : holder.itemView; final RecyclerView.ViewHolder newHolder = changeInfo.newHolder; final View newView = newHolder != null ? newHolder.itemView : null; if (view != null) { final ViewPropertyAnimatorCompat oldViewAnim = ViewCompat.animate(view).setDuration( getChangeDuration()); mChangeAnimations.add(changeInfo.oldHolder); oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX); oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY); oldViewAnim.alpha(0).setListener(new VpaListenerAdapter() { @Override public void onAnimationStart(View view) { dispatchChangeStarting(changeInfo.oldHolder, true); } @Override public void onAnimationEnd(View view) { oldViewAnim.setListener(null); ViewCompat.setAlpha(view, 1); ViewCompat.setTranslationX(view, 0); ViewCompat.setTranslationY(view, 0); dispatchChangeFinished(changeInfo.oldHolder, true); mChangeAnimations.remove(changeInfo.oldHolder); dispatchFinishedWhenDone(); } }).start(); } if (newView != null) { final ViewPropertyAnimatorCompat newViewAnimation = ViewCompat.animate(newView); mChangeAnimations.add(changeInfo.newHolder); newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration()). alpha(1).setListener(new VpaListenerAdapter() { @Override public void onAnimationStart(View view) { dispatchChangeStarting(changeInfo.newHolder, false); } @Override public void onAnimationEnd(View view) { newViewAnimation.setListener(null); ViewCompat.setAlpha(newView, 1); ViewCompat.setTranslationX(newView, 0); ViewCompat.setTranslationY(newView, 0); dispatchChangeFinished(changeInfo.newHolder, false); mChangeAnimations.remove(changeInfo.newHolder); dispatchFinishedWhenDone(); } }).start(); } } private void endChangeAnimation(List<ChangeInfo> infoList, RecyclerView.ViewHolder item) { for (int i = infoList.size() - 1; i >= 0; i--) { ChangeInfo changeInfo = infoList.get(i); if (endChangeAnimationIfNecessary(changeInfo, item)) { if (changeInfo.oldHolder == null && changeInfo.newHolder == null) { infoList.remove(changeInfo); } } } } private void endChangeAnimationIfNecessary(ChangeInfo changeInfo) { if (changeInfo.oldHolder != null) { endChangeAnimationIfNecessary(changeInfo, changeInfo.oldHolder); } if (changeInfo.newHolder != null) { endChangeAnimationIfNecessary(changeInfo, changeInfo.newHolder); } } private boolean endChangeAnimationIfNecessary(ChangeInfo changeInfo, RecyclerView.ViewHolder item) { boolean oldItem = false; if (changeInfo.newHolder == item) { changeInfo.newHolder = null; } else if (changeInfo.oldHolder == item) { changeInfo.oldHolder = null; oldItem = true; } else { return false; } ViewCompat.setAlpha(item.itemView, 1); ViewCompat.setTranslationX(item.itemView, 0); ViewCompat.setTranslationY(item.itemView, 0); dispatchChangeFinished(item, oldItem); return true; } @Override public void endAnimation(RecyclerView.ViewHolder item) { final View view = item.itemView; // this will trigger end callback which should set properties to their target values. ViewCompat.animate(view).cancel(); // TODO if some other animations are chained to end, how do we cancel them as well? for (int i = mPendingMoves.size() - 1; i >= 0; i--) { MoveInfo moveInfo = mPendingMoves.get(i); if (moveInfo.holder == item) { ViewCompat.setTranslationY(view, 0); ViewCompat.setTranslationX(view, 0); dispatchMoveFinished(item); mPendingMoves.remove(i); } } endChangeAnimation(mPendingChanges, item); if (mPendingRemovals.remove(item)) { ViewCompat.setAlpha(view, 1); dispatchRemoveFinished(item); } if (mPendingAdditions.remove(item)) { ViewCompat.setAlpha(view, 1); dispatchAddFinished(item); } for (int i = mChangesList.size() - 1; i >= 0; i--) { ArrayList<ChangeInfo> changes = mChangesList.get(i); endChangeAnimation(changes, item); if (changes.isEmpty()) { mChangesList.remove(i); } } for (int i = mMovesList.size() - 1; i >= 0; i--) { ArrayList<MoveInfo> moves = mMovesList.get(i); for (int j = moves.size() - 1; j >= 0; j--) { MoveInfo moveInfo = moves.get(j); if (moveInfo.holder == item) { ViewCompat.setTranslationY(view, 0); ViewCompat.setTranslationX(view, 0); dispatchMoveFinished(item); moves.remove(j); if (moves.isEmpty()) { mMovesList.remove(i); } break; } } } for (int i = mAdditionsList.size() - 1; i >= 0; i--) { ArrayList<RecyclerView.ViewHolder> additions = mAdditionsList.get(i); if (additions.remove(item)) { ViewCompat.setAlpha(view, 1); dispatchAddFinished(item); if (additions.isEmpty()) { mAdditionsList.remove(i); } } } // animations should be ended by the cancel above. //noinspection PointlessBooleanExpression,ConstantConditions if (mRemoveAnimations.remove(item) && DEBUG) { throw new IllegalStateException("after animation is cancelled, item should not be in " + "mRemoveAnimations list"); } //noinspection PointlessBooleanExpression,ConstantConditions if (mAddAnimations.remove(item) && DEBUG) { throw new IllegalStateException("after animation is cancelled, item should not be in " + "mAddAnimations list"); } //noinspection PointlessBooleanExpression,ConstantConditions if (mChangeAnimations.remove(item) && DEBUG) { throw new IllegalStateException("after animation is cancelled, item should not be in " + "mChangeAnimations list"); } //noinspection PointlessBooleanExpression,ConstantConditions if (mMoveAnimations.remove(item) && DEBUG) { throw new IllegalStateException("after animation is cancelled, item should not be in " + "mMoveAnimations list"); } dispatchFinishedWhenDone(); } private void resetAnimation(RecyclerView.ViewHolder holder) { AnimatorCompatHelper.clearInterpolator(holder.itemView); endAnimation(holder); } @Override public boolean isRunning() { return (!mPendingAdditions.isEmpty() || !mPendingChanges.isEmpty() || !mPendingMoves.isEmpty() || !mPendingRemovals.isEmpty() || !mMoveAnimations.isEmpty() || !mRemoveAnimations.isEmpty() || !mAddAnimations.isEmpty() || !mChangeAnimations.isEmpty() || !mMovesList.isEmpty() || !mAdditionsList.isEmpty() || !mChangesList.isEmpty()); } /** * Check the state of currently pending and running animations. If there are none * pending/running, call {@link #dispatchAnimationsFinished()} to notify any * listeners. */ private void dispatchFinishedWhenDone() { if (!isRunning()) { dispatchAnimationsFinished(); } } @Override public void endAnimations() { int count = mPendingMoves.size(); for (int i = count - 1; i >= 0; i--) { MoveInfo item = mPendingMoves.get(i); View view = item.holder.itemView; ViewCompat.setTranslationY(view, 0); ViewCompat.setTranslationX(view, 0); dispatchMoveFinished(item.holder); mPendingMoves.remove(i); } count = mPendingRemovals.size(); for (int i = count - 1; i >= 0; i--) { RecyclerView.ViewHolder item = mPendingRemovals.get(i); dispatchRemoveFinished(item); mPendingRemovals.remove(i); } count = mPendingAdditions.size(); for (int i = count - 1; i >= 0; i--) { RecyclerView.ViewHolder item = mPendingAdditions.get(i); View view = item.itemView; ViewCompat.setAlpha(view, 1); dispatchAddFinished(item); mPendingAdditions.remove(i); } count = mPendingChanges.size(); for (int i = count - 1; i >= 0; i--) { endChangeAnimationIfNecessary(mPendingChanges.get(i)); } mPendingChanges.clear(); if (!isRunning()) { return; } int listCount = mMovesList.size(); for (int i = listCount - 1; i >= 0; i--) { ArrayList<MoveInfo> moves = mMovesList.get(i); count = moves.size(); for (int j = count - 1; j >= 0; j--) { MoveInfo moveInfo = moves.get(j); RecyclerView.ViewHolder item = moveInfo.holder; View view = item.itemView; ViewCompat.setTranslationY(view, 0); ViewCompat.setTranslationX(view, 0); dispatchMoveFinished(moveInfo.holder); moves.remove(j); if (moves.isEmpty()) { mMovesList.remove(moves); } } } listCount = mAdditionsList.size(); for (int i = listCount - 1; i >= 0; i--) { ArrayList<RecyclerView.ViewHolder> additions = mAdditionsList.get(i); count = additions.size(); for (int j = count - 1; j >= 0; j--) { RecyclerView.ViewHolder item = additions.get(j); View view = item.itemView; ViewCompat.setAlpha(view, 1); dispatchAddFinished(item); additions.remove(j); if (additions.isEmpty()) { mAdditionsList.remove(additions); } } } listCount = mChangesList.size(); for (int i = listCount - 1; i >= 0; i--) { ArrayList<ChangeInfo> changes = mChangesList.get(i); count = changes.size(); for (int j = count - 1; j >= 0; j--) { endChangeAnimationIfNecessary(changes.get(j)); if (changes.isEmpty()) { mChangesList.remove(changes); } } } cancelAll(mRemoveAnimations); cancelAll(mMoveAnimations); cancelAll(mAddAnimations); cancelAll(mChangeAnimations); dispatchAnimationsFinished(); } void cancelAll(List<RecyclerView.ViewHolder> viewHolders) { for (int i = viewHolders.size() - 1; i >= 0; i--) { ViewCompat.animate(viewHolders.get(i).itemView).cancel(); } } @Override public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, @NonNull List<Object> payloads) { return !payloads.isEmpty() || super.canReuseUpdatedViewHolder(viewHolder, payloads); } private static class VpaListenerAdapter implements ViewPropertyAnimatorListener { @Override public void onAnimationStart(View view) { } @Override public void onAnimationEnd(View view) { } @Override public void onAnimationCancel(View view) { } } private int x; private int y; public void setXY(int x, int y) { this.x = x; this.y = y; } }
/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.steven.bk31_expandablerecyclerview.decoration; import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewPropertyAnimatorCompat; import android.support.v4.view.ViewPropertyAnimatorListener; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.ViewHolder; import android.support.v7.widget.SimpleItemAnimator; import android.view.View; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; /** * This implementation of {@link RecyclerView.ItemAnimator} provides basic * animations on remove, add, and move events that happen to the items in * a RecyclerView. RecyclerView uses a DefaultItemAnimator by default. * * @see RecyclerView#setItemAnimator(RecyclerView.ItemAnimator) */ public class MyItemAnimator extends SimpleItemAnimator { private static final boolean DEBUG = false; private ArrayList<ViewHolder> mPendingRemovals = new ArrayList<ViewHolder>(); private ArrayList<ViewHolder> mPendingAdditions = new ArrayList<ViewHolder>(); private ArrayList<MoveInfo> mPendingMoves = new ArrayList<MoveInfo>(); private ArrayList<ChangeInfo> mPendingChanges = new ArrayList<ChangeInfo>(); private ArrayList<ArrayList<ViewHolder>> mAdditionsList = new ArrayList<ArrayList<ViewHolder>>(); private ArrayList<ArrayList<MoveInfo>> mMovesList = new ArrayList<ArrayList<MoveInfo>>(); private ArrayList<ArrayList<ChangeInfo>> mChangesList = new ArrayList<ArrayList<ChangeInfo>>(); private ArrayList<ViewHolder> mAddAnimations = new ArrayList<ViewHolder>(); private ArrayList<ViewHolder> mMoveAnimations = new ArrayList<ViewHolder>(); private ArrayList<ViewHolder> mRemoveAnimations = new ArrayList<ViewHolder>(); private ArrayList<ViewHolder> mChangeAnimations = new ArrayList<ViewHolder>(); private int x; private int y; private static class MoveInfo { public ViewHolder holder; public int fromX, fromY, toX, toY; private MoveInfo(ViewHolder holder, int fromX, int fromY, int toX, int toY) { this.holder = holder; this.fromX = fromX; this.fromY = fromY; this.toX = toX; this.toY = toY; } } private static class ChangeInfo { public ViewHolder oldHolder, newHolder; public int fromX, fromY, toX, toY; private ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder) { this.oldHolder = oldHolder; this.newHolder = newHolder; } private ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder, int fromX, int fromY, int toX, int toY) { this(oldHolder, newHolder); this.fromX = fromX; this.fromY = fromY; this.toX = toX; this.toY = toY; } @Override public String toString() { return "ChangeInfo{" + "oldHolder=" + oldHolder + ", newHolder=" + newHolder + ", fromX=" + fromX + ", fromY=" + fromY + ", toX=" + toX + ", toY=" + toY + '}'; } } @Override public void runPendingAnimations() { boolean removalsPending = !mPendingRemovals.isEmpty(); boolean movesPending = !mPendingMoves.isEmpty(); boolean changesPending = !mPendingChanges.isEmpty(); boolean additionsPending = !mPendingAdditions.isEmpty(); if (!removalsPending && !movesPending && !additionsPending && !changesPending) { // nothing to animate return; } // First, remove stuff Collections.sort(mPendingRemovals, new Comparator<ViewHolder>() { @Override public int compare(ViewHolder lhs, ViewHolder rhs) { return rhs.getAdapterPosition() - lhs.getAdapterPosition(); } }); int i = 0; for (ViewHolder holder : mPendingRemovals) { animateRemoveImpl(holder, i++); } mPendingRemovals.clear(); // Next, move stuff if (movesPending) { final ArrayList<MoveInfo> moves = new ArrayList<MoveInfo>(); moves.addAll(mPendingMoves); mMovesList.add(moves); mPendingMoves.clear(); Runnable mover = new Runnable() { @Override public void run() { for (MoveInfo moveInfo : moves) { animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY, moveInfo.toX, moveInfo.toY); } moves.clear(); mMovesList.remove(moves); } }; if (removalsPending) { View view = moves.get(0).holder.itemView; ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration() + (i - 1) * 100); } else { mover.run(); } } // Next, change stuff, to run in parallel with move animations if (changesPending) { final ArrayList<ChangeInfo> changes = new ArrayList<ChangeInfo>(); changes.addAll(mPendingChanges); mChangesList.add(changes); mPendingChanges.clear(); Runnable changer = new Runnable() { @Override public void run() { for (ChangeInfo change : changes) { animateChangeImpl(change); } changes.clear(); mChangesList.remove(changes); } }; if (removalsPending) { ViewHolder holder = changes.get(0).oldHolder; ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration()); } else { changer.run(); } } // Next, add stuff if (additionsPending) { final ArrayList<ViewHolder> additions = new ArrayList<ViewHolder>(); additions.addAll(mPendingAdditions); mAdditionsList.add(additions); mPendingAdditions.clear(); Runnable adder = new Runnable() { public void run() { int i = 0; Collections.sort(additions, new Comparator<ViewHolder>() { @Override public int compare(ViewHolder lhs, ViewHolder rhs) { return lhs.getAdapterPosition() - rhs.getAdapterPosition(); } }); for (ViewHolder holder : additions) { animateAddImpl(holder, i++); } additions.clear(); mAdditionsList.remove(additions); } }; if (removalsPending || movesPending || changesPending) { long removeDuration = removalsPending ? getRemoveDuration() : 0; long moveDuration = movesPending ? getMoveDuration() : 0; long changeDuration = changesPending ? getChangeDuration() : 0; long totalDelay = removeDuration + Math.max(moveDuration, changeDuration); View view = additions.get(0).itemView; ViewCompat.postOnAnimationDelayed(view, adder, totalDelay); } else { adder.run(); } } } /** * 准备删除 * @param holder * @return */ @Override public boolean animateRemove(final ViewHolder holder) { endAnimation(holder); mPendingRemovals.add(holder); return true; } /** * 执行删除动画 * @param holder */ private void animateRemoveImpl(final ViewHolder holder, int i) { final View view = holder.itemView; final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view); mRemoveAnimations.add(holder); animation.setStartDelay(i * 100).setDuration(getRemoveDuration()) .translationX(x - view.getLeft()) .translationY(y - view.getTop()) // .rotationXBy(90) .setListener(new VpaListenerAdapter() { @Override public void onAnimationStart(View view) { dispatchRemoveStarting(holder); } @Override public void onAnimationEnd(View view) { animation.setListener(null); ViewCompat.setTranslationX(view, 0); ViewCompat.setTranslationY(view, 0); // ViewCompat.setRotationX(view, 0); dispatchRemoveFinished(holder); mRemoveAnimations.remove(holder); dispatchFinishedWhenDone(); } }).start(); } /** * 插入前的准备 * @param holder * @return */ @Override public boolean animateAdd(final ViewHolder holder) { endAnimation(holder); ViewCompat.setTranslationX(holder.itemView, x - holder.itemView.getLeft()); ViewCompat.setTranslationY(holder.itemView, y - holder.itemView.getTop()); holder.itemView.setVisibility(View.INVISIBLE); mPendingAdditions.add(holder); return true; } /** * 执行插入动画 * @param holder */ private void animateAddImpl(final ViewHolder holder, int i) { final View view = holder.itemView; final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view); mAddAnimations.add(holder); animation.setStartDelay(i * 100) .translationX(0) .translationY(0) .setDuration(getAddDuration()). setListener(new VpaListenerAdapter() { @Override public void onAnimationStart(View view) { dispatchAddStarting(holder); view.setVisibility(View.VISIBLE); } @Override public void onAnimationCancel(View view) { ViewCompat.setTranslationX(view, 0); ViewCompat.setTranslationY(view, 0); } @Override public void onAnimationEnd(View view) { animation.setListener(null); dispatchAddFinished(holder); mAddAnimations.remove(holder); dispatchFinishedWhenDone(); } }).start(); } @Override public boolean animateMove(final ViewHolder holder, int fromX, int fromY, int toX, int toY) { final View view = holder.itemView; fromX += ViewCompat.getTranslationX(holder.itemView); fromY += ViewCompat.getTranslationY(holder.itemView); endAnimation(holder); int deltaX = toX - fromX; int deltaY = toY - fromY; if (deltaX == 0 && deltaY == 0) { dispatchMoveFinished(holder); return false; } if (deltaX != 0) { ViewCompat.setTranslationX(view, -deltaX); } if (deltaY != 0) { ViewCompat.setTranslationY(view, -deltaY); } mPendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY)); return true; } private void animateMoveImpl(final ViewHolder holder, int fromX, int fromY, int toX, int toY) { final View view = holder.itemView; final int deltaX = toX - fromX; final int deltaY = toY - fromY; if (deltaX != 0) { ViewCompat.animate(view).translationX(0); } if (deltaY != 0) { ViewCompat.animate(view).translationY(0); } // TODO: make EndActions end listeners instead, since end actions aren't called when // vpas are canceled (and can't end them. why?) // need listener functionality in VPACompat for this. Ick. final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view); mMoveAnimations.add(holder); animation.setStartDelay(0).setDuration(getMoveDuration()).setListener(new VpaListenerAdapter() { @Override public void onAnimationStart(View view) { dispatchMoveStarting(holder); } @Override public void onAnimationCancel(View view) { if (deltaX != 0) { ViewCompat.setTranslationX(view, 0); } if (deltaY != 0) { ViewCompat.setTranslationY(view, 0); } } @Override public void onAnimationEnd(View view) { animation.setListener(null); dispatchMoveFinished(holder); mMoveAnimations.remove(holder); dispatchFinishedWhenDone(); } }).start(); } @Override public boolean animateChange(ViewHolder oldHolder, ViewHolder newHolder, int fromX, int fromY, int toX, int toY) { final float prevTranslationX = ViewCompat.getTranslationX(oldHolder.itemView); final float prevTranslationY = ViewCompat.getTranslationY(oldHolder.itemView); final float prevAlpha = ViewCompat.getAlpha(oldHolder.itemView); endAnimation(oldHolder); int deltaX = (int) (toX - fromX - prevTranslationX); int deltaY = (int) (toY - fromY - prevTranslationY); // recover prev translation state after ending animation ViewCompat.setTranslationX(oldHolder.itemView, prevTranslationX); ViewCompat.setTranslationY(oldHolder.itemView, prevTranslationY); ViewCompat.setAlpha(oldHolder.itemView, prevAlpha); if (newHolder != null && newHolder.itemView != null) { // carry over translation values endAnimation(newHolder); ViewCompat.setTranslationX(newHolder.itemView, -deltaX); ViewCompat.setTranslationY(newHolder.itemView, -deltaY); ViewCompat.setAlpha(newHolder.itemView, 0); } mPendingChanges.add(new ChangeInfo(oldHolder, newHolder, fromX, fromY, toX, toY)); return true; } private void animateChangeImpl(final ChangeInfo changeInfo) { final ViewHolder holder = changeInfo.oldHolder; final View view = holder == null ? null : holder.itemView; final ViewHolder newHolder = changeInfo.newHolder; final View newView = newHolder != null ? newHolder.itemView : null; if (view != null) { final ViewPropertyAnimatorCompat oldViewAnim = ViewCompat.animate(view).setDuration( getChangeDuration()); mChangeAnimations.add(changeInfo.oldHolder); oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX); oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY); oldViewAnim.alpha(0).setListener(new VpaListenerAdapter() { @Override public void onAnimationStart(View view) { dispatchChangeStarting(changeInfo.oldHolder, true); } @Override public void onAnimationEnd(View view) { oldViewAnim.setListener(null); ViewCompat.setAlpha(view, 1); ViewCompat.setTranslationX(view, 0); ViewCompat.setTranslationY(view, 0); dispatchChangeFinished(changeInfo.oldHolder, true); mChangeAnimations.remove(changeInfo.oldHolder); dispatchFinishedWhenDone(); } }).start(); } if (newView != null) { final ViewPropertyAnimatorCompat newViewAnimation = ViewCompat.animate(newView); mChangeAnimations.add(changeInfo.newHolder); newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration()). alpha(1).setListener(new VpaListenerAdapter() { @Override public void onAnimationStart(View view) { dispatchChangeStarting(changeInfo.newHolder, false); } @Override public void onAnimationEnd(View view) { newViewAnimation.setListener(null); ViewCompat.setAlpha(newView, 1); ViewCompat.setTranslationX(newView, 0); ViewCompat.setTranslationY(newView, 0); dispatchChangeFinished(changeInfo.newHolder, false); mChangeAnimations.remove(changeInfo.newHolder); dispatchFinishedWhenDone(); } }).start(); } } private void endChangeAnimation(List<ChangeInfo> infoList, ViewHolder item) { for (int i = infoList.size() - 1; i >= 0; i--) { ChangeInfo changeInfo = infoList.get(i); if (endChangeAnimationIfNecessary(changeInfo, item)) { if (changeInfo.oldHolder == null && changeInfo.newHolder == null) { infoList.remove(changeInfo); } } } } private void endChangeAnimationIfNecessary(ChangeInfo changeInfo) { if (changeInfo.oldHolder != null) { endChangeAnimationIfNecessary(changeInfo, changeInfo.oldHolder); } if (changeInfo.newHolder != null) { endChangeAnimationIfNecessary(changeInfo, changeInfo.newHolder); } } private boolean endChangeAnimationIfNecessary(ChangeInfo changeInfo, ViewHolder item) { boolean oldItem = false; if (changeInfo.newHolder == item) { changeInfo.newHolder = null; } else if (changeInfo.oldHolder == item) { changeInfo.oldHolder = null; oldItem = true; } else { return false; } ViewCompat.setAlpha(item.itemView, 1); ViewCompat.setTranslationX(item.itemView, 0); ViewCompat.setTranslationY(item.itemView, 0); dispatchChangeFinished(item, oldItem); return true; } @Override public void endAnimation(ViewHolder item) { final View view = item.itemView; // this will trigger end callback which should set properties to their target values. ViewCompat.animate(view).cancel(); // TODO if some other animations are chained to end, how do we cancel them as well? for (int i = mPendingMoves.size() - 1; i >= 0; i--) { MoveInfo moveInfo = mPendingMoves.get(i); if (moveInfo.holder == item) { ViewCompat.setTranslationY(view, 0); ViewCompat.setTranslationX(view, 0); dispatchMoveFinished(item); mPendingMoves.remove(i); } } endChangeAnimation(mPendingChanges, item); if (mPendingRemovals.remove(item)) { ViewCompat.setAlpha(view, 1); dispatchRemoveFinished(item); } if (mPendingAdditions.remove(item)) { ViewCompat.setAlpha(view, 1); dispatchAddFinished(item); } for (int i = mChangesList.size() - 1; i >= 0; i--) { ArrayList<ChangeInfo> changes = mChangesList.get(i); endChangeAnimation(changes, item); if (changes.isEmpty()) { mChangesList.remove(i); } } for (int i = mMovesList.size() - 1; i >= 0; i--) { ArrayList<MoveInfo> moves = mMovesList.get(i); for (int j = moves.size() - 1; j >= 0; j--) { MoveInfo moveInfo = moves.get(j); if (moveInfo.holder == item) { ViewCompat.setTranslationY(view, 0); ViewCompat.setTranslationX(view, 0); dispatchMoveFinished(item); moves.remove(j); if (moves.isEmpty()) { mMovesList.remove(i); } break; } } } for (int i = mAdditionsList.size() - 1; i >= 0; i--) { ArrayList<ViewHolder> additions = mAdditionsList.get(i); if (additions.remove(item)) { ViewCompat.setAlpha(view, 1); dispatchAddFinished(item); if (additions.isEmpty()) { mAdditionsList.remove(i); } } } // animations should be ended by the cancel above. if (mRemoveAnimations.remove(item) && DEBUG) { throw new IllegalStateException("after animation is cancelled, item should not be in " + "mRemoveAnimations list"); } if (mAddAnimations.remove(item) && DEBUG) { throw new IllegalStateException("after animation is cancelled, item should not be in " + "mAddAnimations list"); } if (mChangeAnimations.remove(item) && DEBUG) { throw new IllegalStateException("after animation is cancelled, item should not be in " + "mChangeAnimations list"); } if (mMoveAnimations.remove(item) && DEBUG) { throw new IllegalStateException("after animation is cancelled, item should not be in " + "mMoveAnimations list"); } dispatchFinishedWhenDone(); } @Override public boolean isRunning() { return (!mPendingAdditions.isEmpty() || !mPendingChanges.isEmpty() || !mPendingMoves.isEmpty() || !mPendingRemovals.isEmpty() || !mMoveAnimations.isEmpty() || !mRemoveAnimations.isEmpty() || !mAddAnimations.isEmpty() || !mChangeAnimations.isEmpty() || !mMovesList.isEmpty() || !mAdditionsList.isEmpty() || !mChangesList.isEmpty()); } /** * Check the state of currently pending and running animations. If there are none * pending/running, call {@link #dispatchAnimationsFinished()} to notify any * listeners. */ private void dispatchFinishedWhenDone() { if (!isRunning()) { dispatchAnimationsFinished(); } } @Override public void endAnimations() { int count = mPendingMoves.size(); for (int i = count - 1; i >= 0; i--) { MoveInfo item = mPendingMoves.get(i); View view = item.holder.itemView; ViewCompat.setTranslationY(view, 0); ViewCompat.setTranslationX(view, 0); dispatchMoveFinished(item.holder); mPendingMoves.remove(i); } count = mPendingRemovals.size(); for (int i = count - 1; i >= 0; i--) { ViewHolder item = mPendingRemovals.get(i); dispatchRemoveFinished(item); mPendingRemovals.remove(i); } count = mPendingAdditions.size(); for (int i = count - 1; i >= 0; i--) { ViewHolder item = mPendingAdditions.get(i); View view = item.itemView; ViewCompat.setAlpha(view, 1); dispatchAddFinished(item); mPendingAdditions.remove(i); } count = mPendingChanges.size(); for (int i = count - 1; i >= 0; i--) { endChangeAnimationIfNecessary(mPendingChanges.get(i)); } mPendingChanges.clear(); if (!isRunning()) { return; } int listCount = mMovesList.size(); for (int i = listCount - 1; i >= 0; i--) { ArrayList<MoveInfo> moves = mMovesList.get(i); count = moves.size(); for (int j = count - 1; j >= 0; j--) { MoveInfo moveInfo = moves.get(j); ViewHolder item = moveInfo.holder; View view = item.itemView; ViewCompat.setTranslationY(view, 0); ViewCompat.setTranslationX(view, 0); dispatchMoveFinished(moveInfo.holder); moves.remove(j); if (moves.isEmpty()) { mMovesList.remove(moves); } } } listCount = mAdditionsList.size(); for (int i = listCount - 1; i >= 0; i--) { ArrayList<ViewHolder> additions = mAdditionsList.get(i); count = additions.size(); for (int j = count - 1; j >= 0; j--) { ViewHolder item = additions.get(j); View view = item.itemView; ViewCompat.setAlpha(view, 1); dispatchAddFinished(item); additions.remove(j); if (additions.isEmpty()) { mAdditionsList.remove(additions); } } } listCount = mChangesList.size(); for (int i = listCount - 1; i >= 0; i--) { ArrayList<ChangeInfo> changes = mChangesList.get(i); count = changes.size(); for (int j = count - 1; j >= 0; j--) { endChangeAnimationIfNecessary(changes.get(j)); if (changes.isEmpty()) { mChangesList.remove(changes); } } } cancelAll(mRemoveAnimations); cancelAll(mMoveAnimations); cancelAll(mAddAnimations); cancelAll(mChangeAnimations); dispatchAnimationsFinished(); } void cancelAll(List<ViewHolder> viewHolders) { for (int i = viewHolders.size() - 1; i >= 0; i--) { ViewCompat.animate(viewHolders.get(i).itemView).cancel(); } } public void setXY(int x, int y){ this.x = x; this.y = y; } private static class VpaListenerAdapter implements ViewPropertyAnimatorListener { @Override public void onAnimationStart(View view) {} @Override public void onAnimationEnd(View view) {} @Override public void onAnimationCancel(View view) {} }; }
package com.steven.bk31_expandablerecyclerview.decoration; import android.graphics.Rect; import android.support.v7.widget.RecyclerView; import android.view.View; /** * 设置item之间的分割空间 */ public class SpacesItemDecoration extends RecyclerView.ItemDecoration { private int space; public SpacesItemDecoration(int space) { this.space=space; } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { outRect.left = space; outRect.right = space; outRect.bottom = space; if(parent.getChildPosition(view) == 0 || parent.getChildPosition(view) == 1 || parent.getChildPosition(view) == 2 || parent.getChildPosition(view) == 3){ outRect.top = space; } } }
两种布局的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:gravity="center" android:orientation="horizontal" android:background="#efe" android:layout_marginBottom="1dp" > <ImageView android:id="@+id/imageView_item_icon" android:layout_width="50dp" android:layout_height="50dp" android:background="@drawable/ic_stub"/> <TextView android:id="@+id/textView_item_child" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="5dp" android:textColor="#00f" android:textSize="23sp" android:text="New Text"/> </LinearLayout>
<?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:background="#eee" android:orientation="vertical"> <TextView android:id="@+id/textView_item_group" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#eee" android:text="Large Text" android:textSize="25sp" /> </LinearLayout>
相关文章推荐
- 用RecyclerView实现新闻列表页,包括头部的图片轮播,两种Item显示方式,下拉刷新和上拉加载以及限制列表的加载条目数
- Android笔记(六):RecyclerView实现上/下滑隐藏/显示顶部搜索栏(item可点击)
- Android如何实现RecyclerView的item的展开及显示并让item位于头部
- Android中RecyclerView的item中控件的点击事件添加删除一行、上移下移一行的代码实现
- RecyclerView长按拖动效果,用ItemTouchHelper实现
- RecyclerView实现条目Item拖拽排序与滑动删除
- RecyclerView实现Item滑动加载进入动画效果
- Android使用ItemTouchHelper实现RecyclerView的item拖动位置交换
- RecyclerView嵌套CheckBox实现单选全选反选操作每条Item添加分割线
- 使用ItemTouchHelper轻松实现RecyclerView拖拽排序和滑动删除
- MVP+RXJAVA+RecyclerView实现sd卡根目录下的所有文件中的照片加载并显示
- BaseRecyclerViewAdapterHelper开源项目之BaseMultiItemQuickAdapter 实现多类型源码学习
- ItemTouchHelper实现RecyclerView拖动排序和滑动删除
- RecyclerView替代Listview,实现滚动列表的显示
- Android RecyclerView单击、长按事件标准实现:基于OnItemTouchListener + GestureDetector
- Android开发之实现滑动RecyclerView,浮动按钮的显示和隐藏(二)
- 在listview的item中使用其他通过适配器传值的控件如Recyclerview不显示的问题
- recyclerview隐藏item但位置还在,显示空白
- RecyclerView 分组 item显示不同view
- ListView实现隐藏,显示Item的部分View.以及部分问题