本来不想自己造轮子的,但奈何没找动相应效果的轮子,所以只能自己写了,其实还是白嫖来的轻松,哈哈
先看效果
这个是完成的效果,还可以吧!关键也不难一个自定义View搞定
先说一下思路,继承一个RelativeLayout 在布局中加入两个卡片的位置View,至于里面放什么可以随意扩展,复写view的onTouchEvent方法以及onInterceptTouchEvent方法 ,onTouchEvent方法只是单纯的去区分手势的滑动处理,而onInterceptTouchEvent是为了对卡片页面进行操作的时候处理
onTouchEvent方法下需要记录按下的坐标点以及滑动距离,通过滑动距离的改变来给卡片的view设置topMargin,来达到让卡片进行移动的效果,当然了 如果仅仅是设置topMargin的方式还不能满足,还要对移动过程增加平移动画TranslateAnimation,这样就看起来更加流畅
看一下代码
package view;
import android.content.Context;
import android.graphics.Canvas;
import android.os.Build;
import android.os.Handler;
import android.support.annotation.RequiresApi;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.widget.RelativeLayout;
import cc.vv.scoring.mine.R;
//会员卡滑动效果
public class VipCardViewScllorView extends RelativeLayout implements View.OnClickListener {
/**
* 切换过程是否完成
*/
private boolean isSwitch = true;
/**
* 滑动过程中不能点击切换
*/
private boolean isScroll = true;
/**
* 是否展开了
*/
private boolean isOpen = false;
/**
* 是否第一次进来
*/
private boolean isFirst = true;
/**
* 触摸的起始Y坐标
*/
private int startY;
/**
* 滑动Y距离
*/
private int movedY;
/**
* 最终距离
*/
private int offsetY;
private Context context;
/**
* 一进来初始的margeTop值
*/
private int startMargeTop = 0;
//卡片总布局
private RelativeLayout card_content;
//底部布局
private RelativeLayout individual_layout;
//公司会员卡
public RelativeLayout card_view_company;
//个人会员卡
public RelativeLayout card_view_personal;
private Handler handler;
public VipCardViewScllorView(Context context) {
super(context);
this.context = context;
}
public VipCardViewScllorView(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
}
public VipCardViewScllorView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
}
public void initView(Handler handler) {
this.handler = handler;
removeAllViews();
View view = LayoutInflater.from(context).inflate(R.layout.view_vipcard_layout, null, false);
card_content = view.findViewById(R.id.card_content);
card_view_company = view.findViewById(R.id.card_view_company);
individual_layout = view.findViewById(R.id.individual_layout);
card_view_personal = view.findViewById(R.id.card_view_personal);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
addView(view, layoutParams);
}
//会员卡布局的优先级
//这个逻辑不用管是我的业务逻辑
public void vipLayoutisSwitch(int type) {
if (type == 2) {
//公司卡在前
final View childAt_OnClick = card_content.getChildAt(1);
final View childAt0_OnClick = card_content.getChildAt(0);
final View bot = card_content.getChildAt(2);
card_content.removeAllViews();
card_content.addView(childAt_OnClick, 0);
card_content.addView(childAt0_OnClick, 1);
card_content.addView(bot, 2);
card_content.requestLayout();
}
}
public void cardViewOnClick() {
card_content.getChildAt(0).setOnClickListener(this);
card_content.getChildAt(1).setOnClickListener(this);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startY = (int) event.getRawY();//获得按下时的Y坐标
Log.d("zgcMotionEvent", "startY===" + startY);
Log.d("zgcMotionEvent", "starttop===" + startMargeTop);
Log.d("zgcMotionEvent", "card_content===" + card_content.getMeasuredHeight());
if (startY - (startMargeTop * 2) >= card_content.getMeasuredHeight()) {
//把滑动区域只放在卡片上而不是整个页面
Log.d("zgcMotionEvent", "===条件不符合,拦截点击事件===");
return false;
}
break;
case MotionEvent.ACTION_MOVE:
movedY = (int) event.getRawY();//获得移动时候的Y坐标
//当手指移动的时候
View childAt_MOVE = card_content.getChildAt(1);
View childAt0_MOVE = card_content.getChildAt(0);
LayoutParams layoutParams_MOVE = (LayoutParams) childAt_MOVE.getLayoutParams();
offsetY = startY - movedY;//获得Y轴的偏移量
Log.d("zgcOffsetY", "offsetY====" + offsetY);
if (isOpen == false && Math.abs(offsetY) > startMargeTop && offsetY < 0) {
if (Math.abs(offsetY) >= childAt0_MOVE.getMeasuredHeight()) {
layoutParams_MOVE.topMargin = childAt0_MOVE.getMeasuredHeight();
childAt_MOVE.setLayoutParams(layoutParams_MOVE);
Log.d("ACTION_MOVE", "==滑动已经到达最大位置 设置展开=");
} else {
layoutParams_MOVE.topMargin = Math.abs(offsetY);
childAt_MOVE.setLayoutParams(layoutParams_MOVE);
Log.d("ACTION_MOVE", "==滑动没有到达最大位置 继续增加topMargin");
}
} else if (offsetY > 0 && isOpen == true) {
if (Math.abs(offsetY - childAt0_MOVE.getMeasuredHeight()) <= startMargeTop) {
layoutParams_MOVE.topMargin = startMargeTop;
childAt_MOVE.setLayoutParams(layoutParams_MOVE);
Log.d("ACTION_MOVE", "==是收缩的并且继续向上滑动");
} else {
if (childAt0_MOVE.getMeasuredHeight() - offsetY > 0) {
layoutParams_MOVE.topMargin = childAt0_MOVE.getMeasuredHeight() - offsetY;
childAt_MOVE.setLayoutParams(layoutParams_MOVE);
Log.d("ACTION_MOVE", "==没有收缩的并且继续向上滑动");
}
}
}
break;
case MotionEvent.ACTION_UP:
final View childAt_UP = card_content.getChildAt(1);
final View childAt0_UP = card_content.getChildAt(0);
final LayoutParams layoutParams_up = (LayoutParams) childAt_UP.getLayoutParams();
Log.d("zgcOffsetY", "offsetY====" + offsetY);
//收缩
if (isOpen == true) {
if (offsetY + startMargeTop >= childAt_UP.getMeasuredHeight() / 2) {
setTranslateAnimation(childAt_UP, layoutParams_up, 0, startMargeTop - layoutParams_up.topMargin, startMargeTop, 350);
isOpen = false;
Log.d("TranslateAnimation", "==滑动已经过半 卡片收缩");
} else if (Math.abs(offsetY) >= childAt_UP.getMeasuredHeight()) {
layoutParams_up.setMargins(0, childAt_UP.getMeasuredHeight(), 0, 0);
childAt_UP.setLayoutParams(layoutParams_up);
Log.d("TranslateAnimation", "=处于展开状态 状态下直接收缩");
isOpen = true;
} else {
setTranslateAnimation(childAt_UP, layoutParams_up, 0, childAt0_UP.getMeasuredHeight() - layoutParams_up.topMargin, childAt0_UP.getMeasuredHeight(), 350);
isOpen = true;
Log.d("TranslateAnimation", "==滑动没有过半 卡片继续展开");
}
} else if (isOpen == false) {
//展开
if (layoutParams_up.topMargin >= childAt_UP.getMeasuredHeight() / 2) {
Log.d("TranslateAnimation", "==滑动过半 卡片慢慢展开" + (childAt0_UP.getMeasuredHeight() - layoutParams_up.topMargin - startMargeTop));
setTranslateAnimation(childAt_UP, layoutParams_up, 0, childAt0_UP.getMeasuredHeight() - layoutParams_up.topMargin, childAt_UP.getMeasuredHeight(), 350);
isOpen = true;
} else {
setTranslateAnimation(childAt_UP, layoutParams_up, 0, -(layoutParams_up.topMargin - startMargeTop), startMargeTop, 350);
isOpen = false;
Log.d("TranslateAnimation", "==滑动没有过半 卡片继续收缩");
}
}
break;
}
return true;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.d("VipCardViewScllorView", "onMeasureY====");
if (isFirst) {
View childAt = card_content.getChildAt(1);
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) childAt.getLayoutParams();
startMargeTop = layoutParams.topMargin;
isFirst = false;
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
Log.d("VipCardViewScllorView", "onLayout====");
cardViewOnClick();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.d("VipCardViewScllorView", "onDraw====");
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startY = (int) event.getRawY();//获得按下时的Y坐标
break;
case MotionEvent.ACTION_MOVE:
movedY = (int) event.getRawY();//获得移动时候的Y坐标
if (isOpen == true) {
if (startY - movedY == 0) {
return false;
}
} else {
if (startY - movedY == 0 && isOpen == false) {
Log.d("zgcACTION", "ACTION_MOVE===" + (startY - movedY));
return false;
}
}
return true;
}
return false;
}
//设置滑动动画
public void setTranslateAnimation(final View view, final LayoutParams layoutParams_up, float fromYDelta, final float toYDelta, final int top, int
animationTime) {
final TranslateAnimation translateAnimation = new TranslateAnimation(0, 0, fromYDelta, toYDelta);
translateAnimation.setDuration(animationTime);
translateAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
Log.d("onAnimationEnd", "getRootView()1===" + layoutParams_up.topMargin);
isScroll = false;
}
@Override
public void onAnimationEnd(Animation animation) {
Log.d("onAnimationEnd", "top" + top);
translateAnimation.cancel();//解决界面闪动的关键代码
layoutParams_up.setMargins(0, top, 0, 0);
view.setLayoutParams(layoutParams_up);
view.clearAnimation();
individual_layout.clearAnimation();
Log.d("onAnimationEnd", "getRootView()2===" + layoutParams_up.topMargin);
isScroll = true;
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
view.startAnimation(translateAnimation);
individual_layout.startAnimation(translateAnimation);
}
//设置切换动画
public void setSwitchAnimation(final View view, final LayoutParams layoutParams_up, float fromYDelta, float toYDelta, final int top, int
animationTime) {
final TranslateAnimation translateAnimation = new TranslateAnimation(0, 0, fromYDelta, toYDelta);
translateAnimation.setDuration(animationTime);
translateAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
translateAnimation.cancel();//解决界面闪动的关键代码
layoutParams_up.setMargins(0, top, 0, 0);
view.setLayoutParams(layoutParams_up);
view.clearAnimation();
individual_layout.clearAnimation();
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
view.startAnimation(translateAnimation);
}
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public void onClick(final View v) {
//在收缩状态下点击 卡片切换位置
final View childAt_OnClick = card_content.getChildAt(1);
final View childAt0_OnClick = card_content.getChildAt(0);
final View bot = card_content.getChildAt(2);
float translationY = bot.getTranslationY();
float translationX = bot.getTranslationX();
if (v.getId() == card_content.getChildAt(0).getId()) {
if (isOpen == false && isSwitch == true && isScroll == true) {
isSwitch = false;
card_content.removeAllViews();
card_content.addView(childAt_OnClick, 0);
card_content.addView(childAt0_OnClick, 1);
card_content.addView(bot, 2);
View childAt = card_content.getChildAt(0);
View childAt0 = card_content.getChildAt(1);
View childAt1 = card_content.getChildAt(2);
childAt1.setTranslationY(translationY);
childAt1.setTranslationX(translationX);
LayoutParams layoutParams = (LayoutParams) childAt.getLayoutParams();
LayoutParams childAt0Params = (LayoutParams) childAt0.getLayoutParams();
setSwitchAnimation(childAt0, childAt0Params, 0, startMargeTop, startMargeTop, 500);
setSwitchAnimation(childAt, layoutParams, startMargeTop, -startMargeTop, 0, 500);
handler.post(new Runnable() {
@Override
public void run() {
if (v.getId() == R.id.card_view_company) {
((RelativeLayout.LayoutParams) bot.getLayoutParams()).addRule(RelativeLayout.BELOW, R.id.card_view_company);
}
if (v.getId() == R.id.card_view_personal) {
((RelativeLayout.LayoutParams) bot.getLayoutParams()).addRule(RelativeLayout.BELOW, R.id.card_view_personal);
}
}
});
isSwitch = true;
}
}
}
}
布局文件
这就是全部代码了 静态xml布局和自定义View代码
其中大部分的代码主要集中在了onTouchEvent的 ACTION_DOWN(手指按下)、ACTION_MOVE(手指滑动)、ACTION_UP(手指离开)、这三个手势监听上面,当手指按下(ACTION_DOWN)时记录当前坐标点Y(因为咱们是上下滑动,所以只有Y坐标会变x坐标是不会改变的),当手指开始滑动(ACTION_MOVE)我们把滑动距离设置给当前第一层View的topmargin,当然必须设置最大值不然滑的看不见影子了,当滑动距离大于等于最大值时 我们直接设置topmargin最大,这个时候千万不要设置break事件,如果加入了break终止事件当View的topmargin大于等于最大值时View就滑动停止了,所以想让view在手指不离开前随手指上下滑动就不要乱动
当手指离开时(ACTION_UP)这个时候需要判断一下滑动处于什么位置,我这里是判断滑动距离小于一半就自动回弹上去,大于了才展开,收缩的时候也是一样,这样上下滑动回弹效果就完成了,忘记说了回弹和展开统一使用的是平移动画TranslateAnimation
当然还有一个前后切换的动画效果也是TranslateAnimation,原理就是当点击的view是最底下的View时我们先把它换到第一层然后给他设置一个向下的平移动画,原来第一层的View换到最底层给他设置一个向上的平移动画,这样看起来就仿佛两个在前后切换一样
代码的注释 我已经写得很详细了,使用的话就是xml布局初始化 然后调用view里面的 initView方法