背景
對(duì)于安卓卡片式交互,已有很多案例挤渐,前有“探探”卡片滑動(dòng)交互苹享,后有各種各樣的三方軟件,都在互相復(fù)制粘貼浴麻。今項(xiàng)目中也有類(lèi)似需求得问,特此記錄。
H砻狻9场!代碼鏈接在文末8嘞簟@焐А蝌衔!
演示gif
思路
實(shí)現(xiàn)這樣的效果,其實(shí)從宏觀上蝌蹂,就是實(shí)現(xiàn)了一個(gè)layoutmanger以及ItemTouchHelper噩斟。
(一)LayoutManager主要是實(shí)現(xiàn)recyclerview的布局
(二)ItemTouchHelper主要是實(shí)現(xiàn)用戶滑動(dòng)的時(shí)候,卡片的交互過(guò)程
實(shí)現(xiàn)
(一)重寫(xiě)LayoutManger
首先孤个,要確定的是剃允,繪制多少個(gè)層級(jí)的布局。目前需求是顯示三個(gè)疊加的item齐鲤,因此斥废,對(duì)于LayoutManager,我們只需要每次刷新的時(shí)候佳遂,繪制三次即可营袜。(ps:開(kāi)發(fā)過(guò)程應(yīng)當(dāng)把“層級(jí)”變量抽象全局化,適配“層級(jí)”變化的情況)
核心代碼如下:
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
super.onLayoutChildren(recycler, state);
detachAndScrapAttachedViews(recycler);
int maxCount = SlideConfig.SHOW_MAX_COUNT;
//獲取所有item(包括不可見(jiàn)的)個(gè)數(shù)
int count = getItemCount();
//由于我們是倒序擺放丑罪,所以初始索引從后面開(kāi)始
int initIndex = count - maxCount;
if (initIndex < 0) {
initIndex = 0;
}
//當(dāng)前順序
int currentIndex = 0;
for (int i = initIndex; i < count; i++) {
//從緩存中獲取view
View view = recycler.getViewForPosition(i);
//添加到recyclerView
addView(view);
//測(cè)量一下view
measureChild(view, 0, 0);
//居中擺放荚板,getDecoratedMeasuredWidth方法是獲取帶分割線的寬度,比直接使用view.getWidth()精確
int realWidth = getDecoratedMeasuredWidth(view);
int realHeight = getDecoratedMeasuredHeight(view);
int widthPadding = (int) ((getWidth() - realWidth) / 2f);
int heightPadding = (int) ((getHeight() - realHeight) / 2f);
//擺放child
layoutDecorated(view, widthPadding, heightPadding,
widthPadding + realWidth, heightPadding + realHeight);
//根據(jù)索引吩屹,來(lái)位移和縮放child
int measureHeight = view.getMeasuredHeight();
int trainY = SlideDpUtils.dp2px(SlideConfig.TRANSLATION_Y);
RecyclerView.LayoutParams viewParams = (RecyclerView.LayoutParams) view.getLayoutParams();
int paramsHeight = viewParams.height;
if (paramsHeight == -1) {
viewParams.height = (measureHeight - trainY * (maxCount + 1));
// viewParams.height = (500);
view.setLayoutParams(viewParams);
}
float translationY = (maxCount - currentIndex - 1) * trainY;
if (currentIndex != maxCount - 1) {
view.setTranslationY(translationY);
}
view.setScaleX(1 - (maxCount - currentIndex - 1) * SlideConfig.SCALE);
currentIndex++;
// view.setScaleY(1 - level * SlideConfig.SCALE);
}
}
可見(jiàn)跪另,,其實(shí)也沒(méi)啥煤搜,就是一些布局中免绿,寬高的適配,以及一些位置計(jì)算罷了擦盾。
其實(shí)這里的繪制邏輯嘲驾,是和recyclerview中的LinearLayoutManager中的處理手段,有著異曲同工之妙迹卢,所以辽故,這里就不在敘述了。
值得注意的是腐碱,在繪制的過(guò)程中誊垢,LayoutManager有個(gè)居中的邏輯,如果布局高度太小症见,則會(huì)出現(xiàn)上下距離過(guò)寬的問(wèn)題喂走。如果布局高度填充滿了,則會(huì)出現(xiàn)顯示不全堆疊效果的問(wèn)題谋作。
這里的解決思路就是芋肠,當(dāng)布局高度未填充滿的情況下,則進(jìn)行控件的高度進(jìn)行重新設(shè)置遵蚜,計(jì)算好誤差距離业栅,然后進(jìn)行Y軸方向的偏移秒咐。
(二)重寫(xiě)ItemTouchHelper
對(duì)于ItemTouchHelper,需要在swipe方法進(jìn)行數(shù)據(jù)的移除碘裕,界面刷新以及監(jiān)聽(tīng)的回調(diào)携取,核心代碼如下:
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
//通知adapter
if (mAdapter != null) {
int currentPosition = viewHolder.getLayoutPosition();
//由于是限定了永遠(yuǎn)只有三個(gè),所以帮孔,該current position的值永遠(yuǎn)少于等于
mAdapter.getDataList().remove(currentPosition);
// mAdapter.getDataList().add(0, s);
mAdapter.notifyDataSetChanged();
mAdapter.notifyOutSideRefresh();
mAdapter.notifyPageChange();
}
}
而對(duì)于觸摸的場(chǎng)景下雷滋,卡片的寬高,縮放等設(shè)置文兢,則在onChildDraw方法實(shí)現(xiàn)即可晤斩,核心代碼如下:
@Override
public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView
recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY,
int actionState, boolean isCurrentlyActive) {
try {
//計(jì)算移動(dòng)距離
float distance = (float) Math.hypot(dX, dY);
float maxDistance = recyclerView.getWidth() / 2f;
//比例
float fraction = distance / maxDistance;
if (fraction > 1) {
fraction = 1;
}
int maxCount = SlideConfig.SHOW_MAX_COUNT;
int trainY = SlideDpUtils.dp2px(SlideConfig.TRANSLATION_Y);
//為每個(gè)child執(zhí)行動(dòng)畫(huà)
int count = recyclerView.getChildCount();
int adapterCount = mAdapter.getDataList().size();
for (int i = 0; i < count; i++) {
if (i != count - 1 && adapterCount > maxCount) {
//不是第一層--且數(shù)量大于3
View view = recyclerView.getChildAt(i);
view.setScaleX(1 - (maxCount - i - 1) * SlideConfig.SCALE + fraction * SlideConfig.SCALE);
view.setTranslationY((maxCount - i - 1) * trainY - fraction * trainY);
}
// int level = SlideConfig.SHOW_MAX_COUNT - i - 1;
// if (level != SlideConfig.SHOW_MAX_COUNT)
// //獲取的view從下層到上層
// View view = recyclerView.getChildAt(i);
// int level = SlideConfig.SHOW_MAX_COUNT - i - 1;
// //level范圍(SlideConfig.SHOW_MAX_COUNT-1)-0,每個(gè)child最大只移動(dòng)一個(gè)SlideConfig.TRANSLATION_Y和放大SlideConfig.SCALE
//
// if (level == SlideConfig.SHOW_MAX_COUNT - 1) { // 最下層的不動(dòng)和最后第二層重疊
// view.setTranslationY(SlideConfig.TRANSLATION_Y * (level - 1));
// view.setScaleX(1 - SlideConfig.SCALE * (level - 1));
// view.setScaleY(1 - SlideConfig.SCALE * (level - 1));
// } else if (level > 0) {
// view.setTranslationY(level * SlideConfig.TRANSLATION_Y - fraction * SlideConfig.TRANSLATION_Y);
// view.setScaleX(1 - level * SlideConfig.SCALE + fraction * SlideConfig.SCALE);
// view.setScaleY(1 - level * SlideConfig.SCALE + fraction * SlideConfig.SCALE);
// }
}
} catch (Exception e) {
}
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
這里姆坚,就實(shí)現(xiàn)了recyclerview卡片堆疊效果了澳泵。
代碼地址--庫(kù)libslidrecyclerview
that's all---------------------------------------------------------------------