1.介绍
最近项目遇到一个需要在指定View上面覆盖一个大小、位置都相同的自定义View,并提供显示和隐藏的方法。想到这个场景需求还是挺常见的,所以写下这篇博客,这个需求的实现方式比较多,下面我就分享下我的实现方式,希望可以给那些没有思路的童鞋些许帮助吧!我的大体思路是先获取锚点View的父布局,并获取锚点View相对于该父布局的左边界和上边界,按照这些边界值将我们的自定义View添加到锚点View的父布局上来实现我们需要的界面效果!
2. 实现步骤
- 显示
这里CoverView继承于RelativeLayout,客官可以按照自己的实际需求自行更改,并提供
void showTargetView(View viewTarget)
用于在锚点viewTarget上面覆盖自定义View
public void showTargetView(View viewTarget) {
ViewGroup vpParent = (ViewGroup) getParent();
if (vpParent != null) {
return;
}
if (viewTarget == null) {
return;
}
this.viewTarget = viewTarget;
ViewGroup vpTargetViewParent = (ViewGroup) viewTarget.getParent();
if(vpTargetViewParent == null) {
return;
}
this.vpContainerParent = vpTargetViewParent;
int viewTargetLeft = viewTarget.getLeft();
int viewTargetTop = viewTarget.getTop();
int viewTargetWidth = viewTarget.getWidth();
int viewTargetHeight = viewTarget.getHeight();
MarginLayoutParams lpMargin = null;
if (vpContainerParent instanceof RelativeLayout) {
lpMargin = new RelativeLayout.LayoutParams(viewTargetWidth, viewTargetHeight);
lpMargin.topMargin = viewTargetTop;
lpMargin.leftMargin = viewTargetLeft;
vpContainerParent.addView(this, lpMargin);
} else if (vpContainerParent instanceof FrameLayout) {
lpMargin = new FrameLayout.LayoutParams(viewTargetWidth, viewTargetHeight);
lpMargin.topMargin = viewTargetTop;
lpMargin.leftMargin = viewTargetLeft;
vpContainerParent.addView(this, lpMargin);
} else if (vpContainerParent instanceof LinearLayout) {
index = vpContainerParent.indexOfChild(viewTarget);
android.view.ViewGroup.LayoutParams lpViewTarget = viewTarget.getLayoutParams();
vpContainerParent.removeView(viewTarget);
FrameLayout flContainer = new FrameLayout(getContext());
flContainer.setLayoutParams(lpViewTarget);
vpContainerParent.addView(flContainer, index);
flContainer.addView(viewTarget, new FrameLayout.LayoutParams(viewTargetWidth, viewTargetHeight));
flContainer.addView(this, new FrameLayout.LayoutParams(viewTargetWidth, viewTargetHeight));
}
}
其中1 - 16行是进行一些基本的空指针判断用来防止空指针异常和多次连续调用showTargetView()方法也引起的错误。
其中18 - 21行获取锚点View的左边界、上边界、宽高等信息
其中23 - 43针对不同的ViewGroup提供不同的实现,这里只给出三种实现,其他客官自己可以自行添加。当锚点View的父布局是FrameLayout和RelativeLayout的时候,只要把自定义的topMargin和leftMargin的值设置为锚点View的上边界和左边界即可。当锚点View的父布局是LinearLayout的时候,需要先把锚点View从其父布局中删除,然后创建一个Framelayout,并将锚点View和自定义View添加到这个FrameLayout容器中,最后将该FrameLayout添加到原来锚点View的父布局中的原来位置。
- 隐藏
隐藏过程和显示过程是一个相对的,同样也需要判断锚点View父布局的类型。
public void hide() {
if (vpContainerParent == null) {
return;
}
if (vpContainerParent instanceof RelativeLayout || vpContainerParent instanceof FrameLayout) {
vpContainerParent.removeView(this);
} else if (vpContainerParent instanceof LinearLayout) {
ViewGroup viewContainer = (ViewGroup) vpContainerParent.getChildAt(index);
if (viewContainer.indexOfChild(viewTarget) != -1) {
viewContainer.removeView(viewTarget);
}
if (viewContainer.indexOfChild(this) != -1) {
viewContainer.removeView(this);
}
if (viewContainer != null) {
android.view.ViewGroup.LayoutParams lpViewContainer = viewContainer.getLayoutParams();
vpContainerParent.removeView(viewContainer);
vpContainerParent.addView(viewTarget, index, new LinearLayout.LayoutParams(lpViewContainer));
}
}
}
当锚点View父布局为RelativeLayout和FrameLayout类型的时候比较简单,只要从该父布局中删除响应的自定义View即可, 当父布局为LinearLayout类型的时候,操作过程和显示的过程是互逆的。需要先把包裹锚点View和自定义View的FrameLayout从锚点原父布局中删除,并将锚点View从FrameLayout删除,最后将锚点View添加到它在父布局原来的位置。