Android 使用popuwindow仿drawLayout实
2019-03-11 本文已影响0人
When_Young
Android 使用popuwindow仿drawLayout实现侧滑隐藏
背景:
最近公司有一个需求,点击列表项展示列表项详情,我使用popuwindow实现之后公司又要求要实现侧滑返回功能。本着懒的态度我在网上度娘goole一番之后并无头绪。只有撸起袖子自己干。最后功夫不负有心人,最终实现侧滑隐藏,还有很多不足的地方希望大家指正。
优势:
使用popuwindow相比drawLayout更大程度的解耦,方便复用。在任何地方都可以弹出popuwindow
实现思路:
1、先获取popuwindow的触摸事件
2、通过触摸事件改变popuwindow的位置
3、判断滑动位置是否超过阈值,超过就执行消失动画,否者就执行回弹动画
具体代码
1、先获取popuwindow的触摸事件
//先获取 触摸事件
setTouchInterceptor(this);
2、通过触摸事件改变popuwindow的位置
如何改变popuwindow的位置,这个问题我在官方api没有找到对应方式,就通过查看popuwindow的源码找到了灵感,下面是源码一部分,通过查看源码不难发现 mDecorView这个成员变量activity的activity.getWindow().getDecorView()所获取的到的类似,就是popuwindow的根view,
/** View that handles event dispatch and content transitions. */
private PopupDecorView mDecorView;
之后就是通过反射获取到这个view
private View getDecorView() {//获取popudowindow 的根view
try {
Field field = PopupWindow.class.getDeclaredField("mDecorView");
field.setAccessible(true);
return (View) field.get(this);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
try {//适配api 19
Field field = PopupWindow.class.getDeclaredField("mPopupView");
field.setAccessible(true);
return (View) field.get(this);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
之后就是处理触摸事件来改变mDecorView的位置来实现改变popuwindow的显示位置
public static final int EVENT_INSIDE=0x001;//初始点在范围外
public static final int EVENT_OUTSIDE=0x002;//初始点在范围内
public static final int EVENT_END = 0x003;//结束
public static final int SLIDE_TYPE_VERTICAL = 0x004;//垂直滑动
public static final int SLIDE_TYPE_HORIZONTAL = 0x005;//水平滑动
int eventStatu;
int slideType;
float eventStartX;
float eventStartY;
float viewStartX;
float viewstartY;
float damp = 0.7f;
float slideTypeXThreshold = 5;//判断滑动方向的阈值
float slideTypeYThreshold = 5;//判断滑动方向的阈值
float actionThreshold = 0.25f;//判断当滑动整个弹窗的多少时关闭弹窗
float distance;
boolean isAnimation=false;
int allAnimation = 500;//动画执行最大时间
//处理触摸事件
private boolean onTouchEvent(MotionEvent ev){
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
if(isAnimation){
return true;
}
//判断按下的点是否在弹窗范围内,如果不是就不处理这个事件了
float x = ev.getX();
float y = ev.getY();
eventStartX = x;
eventStartY = y;
//获取弹窗的范围
float popuStartX = rootView.getX();
float popuStartY = rootView.getY();
viewStartX = popuStartX;
viewstartY = popuStartY;
float popuEndX = popuStartX+rootView.getWidth();
float popuEndY = popuStartY+rootView.getHeight();
if(x>=popuStartX&&x<=popuEndX&&y>=popuStartY&&y<=popuEndY){//按下的点在弹窗返回内需要处理本次触摸事件
eventStatu = EVENT_INSIDE;
}else {
eventStatu = EVENT_OUTSIDE;
}
slideType = 0;//初始化滑动状态
distance = 0;
mDecorView = null;
break;
case MotionEvent.ACTION_UP:
if(isAnimation){
return true;
}
eventStatu = EVENT_END;
//判断是否需要关闭弹窗
if(distance/actionThreshold/damp>=rootView.getWidth()){
setDismissAnimation();
}else {//回弹效果
setSpringbackAnimation();
}
break;
case MotionEvent.ACTION_MOVE:
if(isAnimation){
return true;
}
if(eventStatu==EVENT_INSIDE){
//如果在竖直滑动之后不处理水平滑动了
float newX = ev.getX();
float relXDistance = newX - eventStartX;//真实位移
float newY = ev.getY();
float relYDistance = newY - eventStartY;//真实位移
if(slideType!=SLIDE_TYPE_HORIZONTAL&&Math.abs(relYDistance)>DensityUtils.dip2px(context,slideTypeYThreshold)){//如果大于阈值
slideType = SLIDE_TYPE_VERTICAL;
}
if(slideType!=SLIDE_TYPE_VERTICAL&&Math.abs(relXDistance)>DensityUtils.dip2px(context,slideTypeXThreshold)){//如果大于阈值
slideType = SLIDE_TYPE_HORIZONTAL;
}
if(slideType==SLIDE_TYPE_VERTICAL){//如果是竖直方向就继续处理了
return false;
}
if(slideType==SLIDE_TYPE_HORIZONTAL){
if(distance<0){
return true;
}
distance = relXDistance*damp;//乘以阻尼之后的距离
setMDecorView(viewStartX+distance);
return true;
}
}
break;
}
return false;
}
private void setMDecorView(float x) {
//改变view的位置
if(mDecorView==null){
mDecorView = getDecorView();
}
if(mDecorView!=null){
if(x>=viewStartX){//避免移动位置越过popuwindow的初始位置
mDecorView.setX(x);
}
}
}
最后就是在触摸事件结束时判断是否滑动距离超过阈值,如果超过就执行隐藏动画,否者就执行回弹
//判断是否需要关闭弹窗
if(distance/actionThreshold/damp>=rootView.getWidth()){
setDismissAnimation();
}else {//回弹效果
setSpringbackAnimation();
}
下面是消失动画和回弹动画
private void setDismissAnimation() {
//改变view的位置
if(mDecorView==null){
mDecorView = getDecorView();
}
if(mDecorView==null){
return;
}
float width =Utils.getScreenWidth(context);
float offSet= width-mDecorView.getX();
if(offSet<0){
return;
}
int duration = (int) (300*(offSet/(mDecorView.getWidth()*(1-actionThreshold))));
ObjectAnimator animator = ObjectAnimator.ofFloat(mDecorView,"translationX",mDecorView.getX(),width);
animator.setDuration(duration);
animator.start();
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
isAnimation=false;
dismiss();
}
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
isAnimation=true;
}
});
}
private void setSpringbackAnimation() {
//改变view的位置
if(mDecorView==null){
mDecorView = getDecorView();
}
if(mDecorView==null){
return;
}
float offSet= mDecorView.getX()-viewStartX;
if(offSet<0){
return;
}
int duration = (int) (allAnimation*(offSet/(mDecorView.getWidth()*actionThreshold)));
ObjectAnimator animator = ObjectAnimator.ofFloat(mDecorView,"translationX",mDecorView.getX(),viewStartX);
animator.setDuration(duration);
animator.start();
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
isAnimation=false;
}
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
isAnimation=true;
}
});
}