开头必水,说是做弧形布局,不如说是Draw绘制这块踩坑,因为会对绘制结果进行裁剪(clipPath),而Path则是绘制贝塞尔曲线的结果。裁剪的通病用过的基本都知道,那就是抗锯齿,非常恶心,各种抗锯齿的办法基本GG。所以...我不打算用clip系列的办法(滑稽.jpg),而是Xfermode。Xfermode用起来问题也不大,链接是别人的,基本知道是个啥就行了,没必要全部看完。
弧形布局大概长这样:
基本就是一个 FrameLayout 里边重写dispatchDraw()方法,通过绘制贝塞尔曲线 + Xfermode实现。具体方法是先在一个空白画布上绘制好一个弧形,通过Xfermode实现最终效果。
基本的初始化
private Paint mPaint;
private PaintFlagsDrawFilter mDrawFilter;
private PorterDuffXfermode mXfermode;
private Path mPath;
private void init() {
mPaint = new Paint();
//绘制贝塞尔曲线
mPath = new Path();
//启用硬件加速,否则会出现一些异常(比如黑边,设计器和模拟器可能依旧会存在黑边)
setLayerType(View.LAYER_TYPE_HARDWARE, mPaint);
//消除画布的抗锯齿
mDrawFilter = new PaintFlagsDrawFilter(
0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG
);
/* 绘制时根据位置消除上层/下层部分的图层
* 因为处于bottom时,弧形会遮住整个布局,如果采用上层(DST_OUT)则会消除弧形和布局交集的部分,
* 所以bottom时采用下层(DST_IN) */
mXfermode = new PorterDuffXfermode(
arcDirection == 0 ? PorterDuff.Mode.DST_OUT : PorterDuff.Mode.DST_IN
);
}
绘制弧形路径
/**
* 绘制弧形路径
*/
private void drawArcPath() {
float x1 = (getWidth() / 2F);
mPath.lineTo(0, getDirectionHeight());
mPath.quadTo(x1, getArcYHeight(), getWidth(), getDirectionHeight());
mPath.lineTo(getWidth(), 0);
mPath.close();
}
如果参考链接依旧不理解,可以看看这个示意图,可能比较难理解的就是quadTo()方法,通俗点讲就是前两个参数主要确定弧形的顶点坐标(凸起的中间点坐标),后两个参数则是宽高(其中getDirectionHeight()接下来会讲到)。
因为我们有上弧和下弧,上下通过高度决定,所以获取高度这块我们独立写个方法getDirectionHeight()以及控制弧形的朝向(内/外)getArcYHeight()。至于为什么通过高度决定上下弧,看看接下的示意图就知道了。
//控制朝向。0:朝内,1:朝外
private int arcOrientation;
//控制上下弧。0:上弧,1:下弧
private int arcDirection;
控制朝向
/**
* 弧形Y轴高度(贝塞尔曲线凸/凹起部分的Y轴)
* @return 根据绘制位置返回Y轴高度
*/
private int getArcYHeight() {
//决定朝内还是朝外
int defHeight = (arcOrientation == 1 ? -arcHeight : arcHeight * 2);
switch ( arcDirection ) {
case 0: //顶部绘制弧形
return defHeight;
case 1: //底部绘制弧形
return getHeight() - defHeight;
}
return 0;
}
上下弧高度
/**
* 绘制弧形的高度
* @return 根据绘制位置返回绘制高度
*/
private int getDirectionHeight() {
boolean isIn = arcOrientation == 0;
int defHeight = arcHeight + arcOffsetY;
switch ( arcDirection ) {
case 0: //顶部绘制弧形
return isIn ? 0 : defHeight;
case 1: //底部绘制弧形
return isIn ? getHeight() : getHeight() - defHeight;
}
return 0;
}
有了上边的准备工作,接下来就可以绘制弧形了。
绘制弧形
/**
* 绘制弧形部分的图片。
* 因为用的一个paint,所以需要在设置{@link Paint#setXfermode(Xfermode)}之前获取弧形图片
*/
private Bitmap getDrawArcBitmp() {
//建立一个空白图片,在里边绘制弧形路径
Bitmap arcBmp = Bitmap.createBitmap(
getWidth(), getHeight(), Bitmap.Config.ARGB_8888
);
//建立画布
Canvas canvas = new Canvas( arcBmp );
//消除画布的抗锯齿
canvas.setDrawFilter( mDrawFilter );
//绘制弧形路径
drawArcPath();
//启用抗锯齿
mPaint.setAntiAlias( true );
//绘制路径
canvas.drawPath(mPath, mPaint);
return arcBmp;
}
最后,通过重写onDraw在布局中绘制弧形
重写onDraw方法
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
//获取绘制弧形部分的图片
Bitmap arcBmp = getDrawArcBitmp();
//消除画布的抗锯齿
canvas.setDrawFilter( mDrawFilter );
//绘制时消除绘制的弧形画布 (或者注释掉看效果就明白了)
mPaint.setXfermode( mXfermode );
//绘制最终的结果
canvas.drawBitmap(arcBmp, 0, 0, mPaint);
mPaint.setXfermode( null );
}