精彩推荐
Android ViewDragHelper完全解析 自定义ViewGroup神器
模仿探探首页卡片左右滑动效果,滑动流畅,卡片view无限重生
案例效果图
案例注释
public class SlideLayout extends ViewGroup {
private List<CardItemView> viewList = new ArrayList<>(); // 存放的是每一层的view,从顶到底
private List<View> releasedViewList = new ArrayList<>(); // 手指松开后存放的view列表
private final ViewDragHelper mDragHelper; // 拖拽工具类
private int initCenterViewX = 0, initCenterViewY = 0; // 最初时,中间View的x位置,y位置
private int allWidth = 0; // 面板的宽度
private int allHeight = 0; // 面板的高度
private int childWith = 0; // 每一个子View对应的宽度
private static final float SCALE_STEP = 0.08f; // view叠加缩放的步长
private static final int MAX_SLIDE_DISTANCE_LINKAGE = 400; // 水平距离+垂直距离,超过这个值,则下一层view完成向上一层view的过渡
private View bottomLayout; // 卡片下边的三个按钮布局
private int bottomMarginTop = 40;//底部按钮和图片的间距
private int yOffsetStep = 40; // view叠加垂直偏移量的步长
private static final int X_VEL_THRESHOLD = 900;
private static final int X_DISTANCE_THRESHOLD = 300;
public static final int VANISH_TYPE_LEFT = 0;//左侧按钮点击事件
public static final int VANISH_TYPE_RIGHT = 1;//右侧按钮点击事件
private Object obj1 = new Object();
private CardSwitchListener cardSwitchListener; // 回调接口
private List<CardDataItem> dataList; // 存储的数据链表
private int isShowing = 0; // 当前正在显示的小项
private View leftBtn, rightBtn;
private boolean btnLock = false;
private GestureDetectorCompat moveDetector;
private OnClickListener btnListener;
public SlideLayout(Context context) {
this(context, null);
}
public SlideLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SlideLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.card);
bottomMarginTop = (int) array.getDimension(R.styleable.card_bottomMarginTop, bottomMarginTop);
yOffsetStep = (int) array.getDimension(R.styleable.card_yOffsetStep, yOffsetStep);
array.recycle();
mDragHelper = ViewDragHelper.create(this, 10, new DragHelperCallback());
moveDetector = new GestureDetectorCompat(context, new MoveDetector());
btnListener = new View.OnClickListener() {
@Override
public void onClick(View view) {
if (view instanceof ImageView) {
// 点击的是卡片
if (null != cardSwitchListener && view.getScaleX() > 1 - SCALE_STEP) {
cardSwitchListener.onItemClick(view, isShowing);
}
} else {
// 点击的是bottomLayout里面的一些按钮
btnLock = true;
int type = -1;
if (view == leftBtn) {
type = VANISH_TYPE_LEFT;
} else if (view == rightBtn) {
type = VANISH_TYPE_RIGHT;
}
vanishOnBtnClick(type);
}
}
};
}
//手势监听
class MoveDetector extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float dx, float dy) {
// 拖动了,touch不往下传递
return Math.abs(dy) + Math.abs(dx) > 5;
}
}
//拖动事件处理核心方法
public class DragHelperCallback extends ViewDragHelper.Callback {
//是否可以拖动view
@Override
public boolean tryCaptureView(View child, int pointerId) {
// 如果数据List为空,或者子View不可见,则不予处理
if (child == bottomLayout || dataList == null || dataList.size() == 0
|| child.getVisibility() != View.VISIBLE || child.getScaleX() <= 1.0f - SCALE_STEP) {
// 一般来讲,如果拖动的是第三层、或者第四层的View,则直接禁止
// 此处用getScale的用法来巧妙回避
return false;
}
if (btnLock) {
return false;
}
// 只捕获顶部view(rotation=0)
int childIndex = viewList.indexOf(child);
if (childIndex > 0) {
return false;
}
return true;
}
//松手之后View的处理
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
animToSide(releasedChild, xvel, yvel);
}
//View位置发生变化时
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
// 调用offsetLeftAndRight导致viewPosition改变,会调到此处,所以此处对index做保护处理
int index = viewList.indexOf(changedView);
if (index + 2 > viewList.size()) {
return;
}
processLinkageView(changedView);
}
//View滑动的X坐标
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return left;
}
//View滑动的Y坐标
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
return top;
}
}
//重测View的大小
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
measureChildren(widthMeasureSpec, heightMeasureSpec);
int maxWidth = MeasureSpec.getSize(widthMeasureSpec);
int maxHeight = MeasureSpec.getSize(heightMeasureSpec);
//设置模式和大小
setMeasuredDimension(
resolveSizeAndState(maxWidth, widthMeasureSpec, 0),
resolveSizeAndState(maxHeight, heightMeasureSpec, 0));
allWidth = getMeasuredWidth();
allHeight = getMeasuredHeight();
}
//放置布局
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
// 布局卡片view
int size = viewList.size();
for (int i = 0; i < size; i++) {
View viewItem = viewList.get(i);
int childHeight = viewItem.getMeasuredHeight();
viewItem.layout(left, top, right, top + childHeight);
int offset = yOffsetStep * i;
float scale = 1 - SCALE_STEP * i;
if (i > 2) {
// 备用的view
offset = yOffsetStep * 2;
scale = 1 - SCALE_STEP * 2;
}
viewItem.offsetTopAndBottom(offset);
viewItem.setScaleX(scale);
viewItem.setScaleY(scale);
}
// 布局底部按钮的View
if (null != bottomLayout) {
int layoutTop = viewList.get(0).getMeasuredHeight() + bottomMarginTop;
bottomLayout.layout(left, layoutTop, right, layoutTop
+ bottomLayout.getMeasuredHeight());
}
// 初始化一些中间参数
initCenterViewX = viewList.get(0).getLeft();
initCenterViewY = viewList.get(0).getTop();
childWith = viewList.get(0).getMeasuredWidth();
}
//设置卡片操作回调
public void setCardSwitchListener(CardSwitchListener cardSwitchListener) {
this.cardSwitchListener = cardSwitchListener;
}
//顶层卡片View位置改变,底层的位置需要调整
private void processLinkageView(View changedView) {
int changeViewLeft = changedView.getLeft();
int changeViewTop = changedView.getTop();
int distance = Math.abs(changeViewTop - initCenterViewY)
+ Math.abs(changeViewLeft - initCenterViewX);
float rate = distance / (float) MAX_SLIDE_DISTANCE_LINKAGE;
float rate1 = rate;
float rate2 = rate - 0.2f;
if (rate > 1) {
rate1 = 1;
}
if (rate2 < 0) {
rate2 = 0;
} else if (rate2 > 1) {
rate2 = 1;
}
ajustLinkageViewItem(changedView, rate1, 1);
ajustLinkageViewItem(changedView, rate2, 2);
}
//由index对应view变成index-1对应的view
private void ajustLinkageViewItem(View changedView, float rate, int index) {
int changeIndex = viewList.indexOf(changedView);
int initPosY = yOffsetStep * index;
float initScale = 1 - SCALE_STEP * index;
int nextPosY = yOffsetStep * (index - 1);
float nextScale = 1 - SCALE_STEP * (index - 1);
int offset = (int) (initPosY + (nextPosY - initPosY) * rate);
float scale = initScale + (nextScale - initScale) * rate;
View ajustView = viewList.get(changeIndex + index);
ajustView.offsetTopAndBottom(offset - ajustView.getTop()
+ initCenterViewY);
ajustView.setScaleX(scale);
ajustView.setScaleY(scale);
}
//处理是否拦截滑动事件
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean shouldIntercept = mDragHelper.shouldInterceptTouchEvent(ev);
boolean moveFlag = moveDetector.onTouchEvent(ev);
if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
// ACTION_DOWN的时候就对view重新排序
orderViewStack();
// 保存初次按下时arrowFlagView的Y坐标
// action_down时就让mDragHelper开始工作,否则有时候导致异常
mDragHelper.processTouchEvent(ev);
}
return shouldIntercept && moveFlag;
}
//处理是否处理滑动事件
@Override
public boolean onTouchEvent(MotionEvent event) {
mDragHelper.processTouchEvent(event);
return true;
}
//对View重新排序
private void orderViewStack() {
synchronized (obj1) {
if (releasedViewList.size() == 0) {
return;
}
CardItemView changedView = (CardItemView) releasedViewList.get(0);
if (changedView.getLeft() == initCenterViewX) {
releasedViewList.remove(0);
return;
}
// 1. 消失的卡片View位置重置,由于大多手机会重新调用onLayout函数,所以此处大可以不做处理,不信你注释掉看看
changedView.offsetLeftAndRight(initCenterViewX - changedView.getLeft());
changedView.offsetTopAndBottom(initCenterViewY - changedView.getTop() + yOffsetStep * 2);
float scale = 1.0f - SCALE_STEP * 2;
changedView.setScaleX(scale);
changedView.setScaleY(scale);
// 2. 卡片View在ViewGroup中的顺次调整
int num = viewList.size();
for (int i = num - 1; i > 0; i--) {
View tempView = viewList.get(i);
tempView.bringToFront();//调整到前面来
}
// 3. changedView填充新数据
int newIndex = isShowing + 4;
if (newIndex < dataList.size()) {
CardDataItem dataItem = dataList.get(newIndex);
changedView.fillData(dataItem);
} else {
changedView.setVisibility(View.INVISIBLE);
}
// 4. viewList中的卡片view的位次调整
viewList.remove(changedView);
viewList.add(changedView);
releasedViewList.remove(0);
// 5. 更新showIndex、接口回调
if (isShowing + 1 < dataList.size()) {
isShowing++;
}
if (null != cardSwitchListener) {
cardSwitchListener.onShow(isShowing);
}
}
}
@Override
public void computeScroll() {
if (mDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
} else {
// 动画结束
synchronized (this) {
if (mDragHelper.getViewDragState() == ViewDragHelper.STATE_IDLE) {
orderViewStack();
btnLock = false;
}
}
}
}
//松手时处理滑动到边缘的动画
private void animToSide(View changedView, float xvel, float yvel) {
int finalX = initCenterViewX;
int finalY = initCenterViewY;
int flyType = -1;
// 1. 下面这一坨计算finalX和finalY,要读懂代码需要建立一个比较清晰的数学模型才能理解,不信拉倒
int dx = changedView.getLeft() - initCenterViewX;
int dy = changedView.getTop() - initCenterViewY;
if (dx == 0) {
// 由于dx作为分母,此处保护处理
dx = 1;
}
if (xvel > X_VEL_THRESHOLD || dx > X_DISTANCE_THRESHOLD) {
finalX = allWidth;
finalY = dy * (childWith + initCenterViewX) / dx + initCenterViewY;
flyType = VANISH_TYPE_RIGHT;
} else if (xvel < -X_VEL_THRESHOLD || dx < -X_DISTANCE_THRESHOLD) {
finalX = -childWith;
finalY = dy * (childWith + initCenterViewX) / (-dx) + dy
+ initCenterViewY;
flyType = VANISH_TYPE_LEFT;
}
// 如果斜率太高,就折中处理
if (finalY > allHeight) {
finalY = allHeight;
} else if (finalY < -allHeight / 2) {
finalY = -allHeight / 2;
}
// 如果没有飞向两侧,而是回到了中间,需要谨慎处理
if (finalX != initCenterViewX) {
releasedViewList.add(changedView);
}
// 2. 启动动画
if (mDragHelper.smoothSlideViewTo(changedView, finalX, finalY)) {
ViewCompat.postInvalidateOnAnimation(this);
}
// 3. 消失动画即将进行,listener回调
if (flyType >= 0 && cardSwitchListener != null) {
cardSwitchListener.onCardVanish(isShowing, flyType);
}
}
//卡片回调接口
public interface CardSwitchListener {
/**
* 新卡片显示回调
*
* @param index 最顶层显示的卡片的index
*/
public void onShow(int index);
/**
* 卡片飞向两侧回调
*
* @param index 飞向两侧的卡片数据index
* @param type 飞向哪一侧{@link #VANISH_TYPE_LEFT}或{@link #VANISH_TYPE_RIGHT}
*/
public void onCardVanish(int index, int type);
/**
* 卡片点击事件
*
* @param cardImageView 卡片上的图片view
* @param index 点击到的index
*/
public void onItemClick(View cardImageView, int index);
}
//本来想写成Adapter适配,想想还是算了,这种比较简单
public void fillData(List<CardDataItem> dataList) {
this.dataList = dataList;
int num = viewList.size();
for (int i = 0; i < num; i++) {
CardItemView itemView = viewList.get(i);
itemView.fillData(dataList.get(i));
itemView.setVisibility(View.VISIBLE);
}
if (null != cardSwitchListener) {
cardSwitchListener.onShow(0);
}
}
//布局加载完成
@Override
protected void onFinishInflate() {
super.onFinishInflate();
// 渲染完成,初始化卡片view列表
viewList.clear();
int num = getChildCount();
for (int i = num - 1; i >= 0; i--) {
View childView = getChildAt(i);
if (childView.getId() == R.id.card_bottom_layout) {
bottomLayout = childView;
initBottomLayout();
} else {
CardItemView viewItem = (CardItemView) childView;
viewItem.setTag(i + 1);
viewItem.imageView.setOnClickListener(btnListener);
viewList.add(viewItem);
}
}
}
//初始化底部按钮
private void initBottomLayout() {
leftBtn = bottomLayout.findViewById(R.id.card_left_btn);
rightBtn = bottomLayout.findViewById(R.id.card_right_btn);
leftBtn.setOnClickListener(btnListener);
rightBtn.setOnClickListener(btnListener);
} //点击按钮消失动画
//按钮事件处理
private void vanishOnBtnClick(int type) {
synchronized (obj1) {
View animateView = viewList.get(0);
if (animateView.getVisibility() != View.VISIBLE || releasedViewList.contains(animateView)) {
return;
}
int finalX = 0;
if (type == VANISH_TYPE_LEFT) {
finalX = -childWith;
} else if (type == VANISH_TYPE_RIGHT) {
finalX = allWidth;
}
if (finalX != 0) {
releasedViewList.add(animateView);
if (mDragHelper.smoothSlideViewTo(animateView, finalX, initCenterViewY + allHeight)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
if (type >= 0 && cardSwitchListener != null) {
cardSwitchListener.onCardVanish(isShowing, type);
}
}
}
}