废话不多说先上图
看到这样的设计图你会怎么实现呢,三秒钟思考…
指导思想
你可以用popupWindow去做,拿到当前任务View的位置,调用popupWindow的showAtLocation()设置popupWindow显示在指定View的位置,然后让其点击不消失基本就可以实现了。但是如果这个控件位于可滑动的列表中,你还要计算popWindow在竖直方向的位置,由于滑动时不断的计算位置,popupWindow还会出现闪烁的情况(这也是我放弃popwindow的原因),当然如果在不可滑动的页面,完全可以用popwindow去实现。
我这里用的是自定义ViewGroup实现的,
指导思想就是拿到当前任务View的位置后,将任务文案View摆放好就行了。
talk is cheap,show me the code
测量
由于的控件上面是一个列表,下面是一个文案,所以我们继承了ViewGroup。不论是自定义View还是自定义ViewGroup都需要测量你要自定义的View的大小,即宽高,在自定义ViewGroup时还要多测一步就是子View的宽高,因为只有子View的宽高确定了,ViewGroup的宽高才能确定。测量的宽高的时候要注意,控件的宽高不光是控件本身的宽高,还包括你在xml文件里设置的margin和padding。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int totalWidth = 0;
int totalHeight = 0;
measureChildren(widthMeasureSpec, heightMeasureSpec);
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
MarginLayoutParams mParams = (MarginLayoutParams) child.getLayoutParams();
if (i == 0) {
totalHeight += getPaddingTop();
totalWidth = getPaddingStart() + getPaddingEnd() + mParams.leftMargin +
mParams.rightMargin + child.getMeasuredWidth();
} else if (i == childCount - 1) {
totalHeight += getPaddingBottom();
}
int cHeight = child.getMeasuredHeight();
totalHeight += cHeight + mParams.topMargin + mParams.bottomMargin;
}
int measureWidth = widthMode == MeasureSpec.EXACTLY ? widthSize : totalWidth;
int measureHeight = heightMode == MeasureSpec.EXACTLY ? heightSize : totalHeight;
setMeasuredDimension(measureWidth, measureHeight);
}
复制代码
布局
测量完之后,就到了摆放子View的环节了,也就是onLayout,摆放的时候其实和测量宽高步骤差不多,需要拿到控件的宽高以及边距,这里提一嘴child.onlayout(left,top,right,bottom),left、top、right、bottom指的是距离ViewGroup的上下左右边界的距离。
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childCount = getChildCount();
int childLeft = 0;
int childRight = 0;
int childTop = 0;
int childBottom = 0;
int lastTotalHeight = 0;
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
MarginLayoutParams cParams = (MarginLayoutParams) child.getLayoutParams();
if (i != 0) {
childLeft = mPopLeft;
} else {
childLeft = cParams.leftMargin + getPaddingLeft();
}
childRight = childLeft + childWidth + getPaddingRight();
if (i == 0) {
childTop = cParams.topMargin + getPaddingTop();
} else {
childTop = lastTotalHeight + cParams.topMargin;
}
if (i == getChildCount() - 1) {
childBottom = childTop + childHeight + getPaddingBottom();
} else {
childBottom = childTop + childHeight;
}
lastTotalHeight = childBottom;
child.layout(childLeft, childTop, childRight, childBottom);
}
}
复制代码
到这里基本完成了自定义任务奖励控件,我们在拿到任务文案需要展示在第几个任务编号的位置后,重新layout的就好。
public void setPopPosition(int position) {
mPopLeft = position;
requestLayout();
}
复制代码
这里再提一个注意点,只有重写 generateLayoutParams(AttributeSet attrs) 才可以获取 MarginLayoutParams,从而获取在xml里如果设置了margin或padding,否则会报错。
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MyLayoutParams(getContext(), attrs);
}
@Override
protected LayoutParams generateLayoutParams(LayoutParams lp) {
return new MyLayoutParams(lp);
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new MyLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
public static class MyLayoutParams extends MarginLayoutParams {
public MyLayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
}
public MyLayoutParams(int width, int height) {
super(width, height);
}
public MyLayoutParams(LayoutParams lp) {
super(lp);
}
}
复制代码
最后在xml文件里,定义一下这个布局
<com.ziroom.housekeeperazeroth.mall.MallTaskView
android:id="@+id/mtv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dp_14">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_task"
android:layout_width="match_parrent"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/tv_pop_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dp_5"
android:background="@drawable/bg_pop_mid"
android:gravity="bottom|center_horizontal"
android:paddingBottom="@dimen/dp_6"
android:text="加油,还需4次!"
android:textColor="@color/black"
android:textSize="@dimen/dp_12" />
</com.ziroom.housekeeperazeroth.mall.MallTaskView>
复制代码
以上就是自定义任务组件的全流程,覆盖了自定义ViewGroup非常重要的点。自定义控件可以很简单也可很复杂,但万变不离其宗,用到的还是onMeasure()、onLayout()、onDraw()以及动画的展示和触摸反馈,多加练习自定义控件,会越来越得心应手。