【安卓Android】自定义控件之弧形布局及clip系列方法抗锯齿

it2025-07-26  6

开头必水,说是做弧形布局,不如说是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 ); }

完整代码在我的码云仓库里边,XML参数中只有ArcLayout才是,剩下的都是其他控件的参数。

完整代码传送门

XML中的参数传送门(ArcLayout)

疯狂暗示!!!

 

最新回复(0)