RecyclerView item 可展開動畫效果的實現
前文提要:
Android list 列表裏面空間的顯示和 隱藏,基本都是用的View.VISIBLE 和 View.GONE 實現的,展示的效果有點突兀,看了ios 同事做的相同的效果,他們的很順暢,所以決定做一個相同的效果.
已經上傳到github 上面地址是 demo的項目地址 :https://github.com/luhui2014/ExpandableViewHolder/tree/master
1.相關說明:
參考資料:Android—RecyclerView之動畫(工具類)實現可展開列表
1-1.佈局文件:
將需要展開收縮的那部分佈局的透明度在xml文件裏默認設置爲0,在代碼中設置一樣
1-2.動畫工具類說明(代碼我基本上都添加了註釋):
這裏我就不贅述了,請參考原文 Android—RecyclerView之動畫(工具類)實現可展開列表
相關原理就是:利用屬性動畫,動態計算view展開後的高度,實現動畫效果。中間插了一段alpha 的動畫,爲了過渡顯示,關鍵代碼:
//OpenHolder中動畫的具體操作方法
public static Animator ofItemViewHeight(RecyclerView.ViewHolder holder) {
View parent = (View) holder.itemView.getParent();
if (parent == null)
throw new IllegalStateException("Cannot animate the layout of a view that has no parent");
//測量擴展動畫的起始高度和結束高度
int start = holder.itemView.getMeasuredHeight();
holder.itemView.measure(View.MeasureSpec.makeMeasureSpec(parent.getMeasuredWidth(),
View.MeasureSpec.AT_MOST), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
int end = holder.itemView.getMeasuredHeight();
final Animator animator = LayoutAnimator.ofHeight(holder.itemView, start, end); //具體的展開動畫
//設定該Item在動畫開始結束和取消時能否被recycle
animator.addListener(new ViewHolderAnimatorListener(holder));
//設定結束時這個Item的寬高
animator.addListener(new LayoutParamsAnimatorListener(holder.itemView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
return animator;
}
還有一段比較有意思的地方是 處理了recyclerView 的的回收,增加了一個動畫監聽,在動畫結束的時候,讓recyclerView 自身去處理是否回收的問題
public static class ViewHolderAnimatorListener extends AnimatorListenerAdapter {
private final RecyclerView.ViewHolder mHolder; //holder對象
//設定在動畫開始結束和取消狀態下是否可以被回收
public ViewHolderAnimatorListener(RecyclerView.ViewHolder holder) {
mHolder = holder;
}
@Override
public void onAnimationStart(Animator animation) { //開始時
mHolder.setIsRecyclable(false);
}
@Override
public void onAnimationEnd(Animator animation) { //結束時
mHolder.setIsRecyclable(true);
}
@Override
public void onAnimationCancel(Animator animation) { //取消時
mHolder.setIsRecyclable(true);
}
}
1-3.問題:
但是原作裏沒有處理好展開和收縮緩存的問題,已經解耦的問題。對應展開item之後是否需要其他的動畫,這裏應該開放出來,自行去實現,所以我這裏就改了一下:
/**
* 響應ViewHolder的點擊事件
*
* @param holder holder對象
*/
@SuppressWarnings("unchecked")
public void toggle(VH holder) {
int position = holder.getPosition();
if (explanedList.contains(position + "")) {
opened = -1;
deletePositionInExpaned(position);
holder.doCustomAnim(true);
ExpandableViewHoldersUtil.getInstance().closeHolder(holder, holder.getExpandView(), true);
} else {
preOpen = opened;
opened = position;
addPositionInExpaned(position);
holder.doCustomAnim(false);
ExpandableViewHoldersUtil.getInstance().openHolder(holder, holder.getExpandView(), true);
//是否要關閉上一個
if (needExplanedOnlyOne && preOpen != position) {
final VH oldHolder = (VH) ((RecyclerView) holder.itemView.getParent()).findViewHolderForPosition(preOpen);
if (oldHolder != null) {
Log.e("KeepOneHolder", "oldHolder != null");
ExpandableViewHoldersUtil.getInstance().closeHolder(oldHolder, oldHolder.getExpandView(), true);
deletePositionInExpaned(preOpen);
}
}
}
}
}
開放出來一個回調接口處理:holder.doCustomAnim(true);,根據需要自行增加相關的動畫;
對於記錄展開和收縮狀態的問題,定義了一個全局的變量用於存儲,初始化的時候,進行判斷。在用戶點擊動畫的時候,進行相應的增加和刪除處理:
這裏用String 記錄是爲了處理刪除的時候數據越界的問題:
private void deletePositionInExpaned(int pos) {
//remove Object 直接寫int,會變成index,造成數組越界
explanedList.remove(pos + "");
}
源碼中有這麼一段,直接刪除int 會存在數值越界的問題;
2.如何使用:
2-1.viewHoler 需要實現 ExpandableViewHoldersUtil.Expandable 接口
回調的view,就是處理展開動畫的view,
不要忘記初始化 keepOne = ExpandableViewHoldersUtil.getInstance().getKeepOneHolder();
class ViewHolder extends RecyclerView.ViewHolder implements ExpandableViewHoldersUtil.Expandable {
TextView tvTitle;
ImageView arrowImage;
LinearLayout lvArrorwBtn;
LinearLayout lvLinearlayout;
public ViewHolder(@NonNull View itemView) {
super(itemView);
tvTitle = itemView.findViewById(R.id.item_user_concern_title);
lvLinearlayout = itemView.findViewById(R.id.item_user_concern_link_layout);
lvArrorwBtn = itemView.findViewById(R.id.item_user_concern_arrow);
arrowImage = itemView.findViewById(R.id.item_user_concern_arrow_image);
keepOne = ExpandableViewHoldersUtil.getInstance().getKeepOneHolder();
lvLinearlayout.setVisibility(View.GONE);
lvLinearlayout.setAlpha(0);
}
@Override
public View getExpandView() {
return lvLinearlayout;
}
@Override
public void doCustomAnim(boolean isOpen) {
if (isOpen) {
ExpandableViewHoldersUtil.getInstance().rotateExpandIcon(arrowImage, 180, 0);
} else {
ExpandableViewHoldersUtil.getInstance().rotateExpandIcon(arrowImage, 0, 180);
}
}
}
2-2.adapter
在 onBindViewHolder 的時候需要綁定對應的view,初始展開和收縮的狀態,自然點擊效果就是 keepOne.toggle(viewHolder);
@Override
public void onBindViewHolder(@NonNull final ViewHolder viewHolder, int position) {
viewHolder.tvTitle.setText("中美經貿磋商 po=" + position);
keepOne.bind(viewHolder, position);
viewHolder.tvTitle.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
keepOne.toggle(viewHolder);
}
});
viewHolder.lvArrorwBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
keepOne.toggle(viewHolder);
}
});
}
2-3.ExpandableViewHoldersUtil
1.setNeedExplanedOnlyOne
//true 點擊第二個會收縮前一個
//false 不會
ExpandableViewHoldersUtil.getInstance().init().setNeedExplanedOnlyOne(false);
2.//清空記錄展開還是關閉的緩存數據,這個每次在下拉刷新的時候,是否清空根據需求自行處理
ExpandableViewHoldersUtil.getInstance().resetExpanedList();
3.結束:
參照的相關的代碼並根據自己在使用過程中遇到的問題,做了相關處理,原文鏈接已貼在開始。特意寫了一個簡單demo 上傳到git上,
還有問題請參考具體的實現demo :https://github.com/luhui2014/ExpandableViewHolder/tree/master