一.背景
正准备出去抽根烟的你突然被产品经理叫住了,“快来,快来,我告诉你一个好消息,前几天我不是说要给 app 的菜单入口这一截 UI 美化一下嘛,我昨天突发灵感,已经想好了一个很有特色的样式,这下我们的 UI 一定和别人的不一样,我们要让产品富有品牌表现力”。
这看起来是挺有特别的,不过感觉有违 android UI 排列常理呀,形状倒是没什么问题,关键是这无缝衔接就有点奇怪了,这形状最简单的就是直接切几个梯形图当背景就实现了,但是想让控件边缘两两完美契合就得让两个控件重叠一部分,那点击时不就会错乱了,那肯定不行。
二.思路分析
实际布局的宽是以较长边决定的,例如上图中,第二个控件和第一个控件有一部分是重叠的,这是为了让控件两两之间看上去是完美契合的。这里就要考虑一个点击区域问题了,上图中红圈部分实际上全部是第二个控件,但是当用户点击 1 的那个梯形右下角区域,应该让第一个控件响应,点击 2 的那个梯形左上角,应该让第二个控件响应。
不仅要控制点击和契合图形,最好还要能支持设置这个梯形控件是正梯形还是倒梯形(下边更宽还是上边更宽),能设置宽度数值,能设置左边或是右边是否垂直;所以总结如下:
1.支持设置宽度数值,支持设置两边是否垂直,这样用固定图片就不太合适了,干脆通过自定义 View 来实现;
2.实现两控件间完美契合,这可以在自定义 View 时让截好的梯形以外的部分透明显示,然后让右边的控件往左 margin 负的上边和下边宽度相差的一半就正好契合了。
3.支持代码动态创建布局,设置背景图片,设置文字。
三、具体实现
第一步先实现这个自定义形状的控件,大概需要这几个参数,上宽,下宽,高度,左边是否垂直,右边是否垂直
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PaintView, defStyleAttr, 0);
topWidth = ta.getDimensionPixelSize(R.styleable.PaintView_topWidth, 100);
bottomWidth = ta.getDimensionPixelSize(R.styleable.PaintView_bottomWidth, 100);
mHeight = ta.getDimensionPixelSize(R.styleable.PaintView_height, 100);
leftRect = ta.getBoolean(R.styleable.PaintView_leftRect, false);
rightRect = ta.getBoolean(R.styleable.PaintView_rightRect, false);
然后规划梯形路径
paint.setStyle(Paint.Style.FILL_AND_STROKE); //设置边框
paint.setStrokeWidth(strokWidth);
paint.setStrokeJoin(Paint.Join.ROUND); //设置圆角
mDrawPath = new Path();
int offset = strokWidth / 2;
if (topWidth > bottomWidth) {
mDrawPath.moveTo(offset, offset);
mDrawPath.rLineTo(topWidth, 0);
mDrawPath.lineTo(bottomWidth + (topWidth - bottomWidth) / (rightRect ? 1 : 2) + offset, mHeight + offset);
mDrawPath.lineTo((leftRect ? 0 : (topWidth - bottomWidth) / 2) + offset, mHeight + offset);
mDrawPath.close();
} else {
mDrawPath.moveTo((leftRect ? 0 : (bottomWidth - topWidth) / 2) + offset, offset);
mDrawPath.lineTo((bottomWidth - topWidth) / (rightRect ? 1 : 2) + topWidth + offset, offset);
mDrawPath.lineTo(bottomWidth + offset, mHeight + offset);
mDrawPath.lineTo(offset, mHeight + offset);
mDrawPath.close();
}
路径规划好后就开始测量绘制了
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(Math.max(topWidth, bottomWidth) + strokWidth, mHeight + strokWidth);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(mDrawPath, paint);
}
下面是布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.lcp.customeview.widget.PaintView
android:id="@+id/ldder1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginLeft="20dp"
android:layout_marginBottom="20dp"
android:focusableInTouchMode="true"
android:focusable="true"
app:bottomWidth="80dp"
app:height="100dp"
app:pcolor="#d78f8f"
app:topWidth="50dp"
app:leftRect="true"/>
<com.lcp.customeview.widget.PaintView
android:id="@+id/ldder2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:focusableInTouchMode="true"
android:focusable="true"
android:layout_marginLeft="-14dp"
android:layout_toRightOf="@id/ldder1"
app:bottomWidth="50dp"
app:height="100dp"
app:pcolor="#bc9747"
app:topWidth="80dp" />
<com.lcp.customeview.widget.PaintView
android:id="@+id/ldder3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:focusableInTouchMode="true"
android:focusable="true"
android:layout_marginLeft="-14dp"
android:layout_toRightOf="@id/ldder2"
app:bottomWidth="80dp"
app:height="100dp"
app:pcolor