转载自:http://www.gcssloop.com/gebug/rclayout

1.前言

之前,我在GitHub分享了一个开源库:rclayout,这个库的主要目的是快速实现Android中的圆角需求,例如这样的效果。

分享这个库的时候只是觉得可能有用而已,但没有想到居然有了800多个Star,看来有不少人像我一样,对圆角这一需求比较苦恼。

圆角算是一种比较常见的需求了,最常用于图片,因此可以找到大量的自定义圆角ImageView,不仅如此,一些比较流行的图片加载框架也都对圆角进行了支持,像Fresco和Glide很容易就能实现圆角效果。

除了图片之外,另一种比较常见的就是圆角背景了,例如TextView的背景或者按钮的背景,针对于背景的圆角也很容易实现,一般用图片或者写一个shape就行了,

<?xml version="1.0" encoding="utf-8"?>    <shape xmlns:android="http://schemas.android.com/apk/res/android">          <solid android:color="#66CCFF" />          <corners android:topLeftRadius="6dp"                  android:topRightRadius="6dp"                   android:bottomRightRadius="6dp"                  android:bottomLeftRadius="6dp"/>      shape>   

上面的这些都是很容易实现的常见需求,但也有一种稍微特殊的常见需求,像下面这样:

由多个控件组合而成的条目,包括左上角一个角标,文字,文字背景和图片。

从上图可以看出啦,图片已经是圆角了,可是角标和文字背景依旧是方的,这显然不是我们想要的效果。

我之前对这种组合控件的处理方案是这样的:

角标:让设计重新切成带圆角的图,或者用支持圆角的ImageView的进行展示。

文字背景:做一张带圆角的图,或者写一个底部是圆角的形状。

这样展示是没有问题的,唯一比较麻烦的是改需求的时候,如果需要调整圆角的大小,需要分别调整三个地方:图片的圆角,角标的圆角,以及文字背景的圆角如果一个地方忘记了调整,那么就会出现圆角无法对齐的情况。

正因如此,才想到开发一个圆角布局,将它们包裹起来,这样在不仅省去了处理角标和文字背景的麻烦,在修改需求的时候也只用修改一个地方就可以了。

2.圆角布局原理

所谓圆角布局,本质上还是一个方块,只不过是让圆角之外的部分不显示而已,简单来说就是对画布进行裁剪,也可以理解为设置一个遮罩,让目标区域外的内容不显示。

2.1 clipPath

在最初设计的时候,使用了canvas中clipPath的方法,该方法实现起来简单快速,原理如下:

2.1.1构造一个带圆角的路径

初始化圆角信息和路径:

// 1. 定义圆角信息 和 pathprivate float[] radii = new float[8];   // top-left, top-right, bottom-right, bottom-leftprivate Path mPath;// 2. 通过自定义属性获取圆角信息(以左上角为例)TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.RCRelativeLayout);mRoundCorner = ta.getDimensionPixelSize(R.styleable.RCRelativeLayout_round_corner, 0);radii[0] = mRoundCornerTopLeft;radii[1] = mRoundCornerTopLeft;// 3. 创建空的PathmPath = new Path();

在onSizeChanged方法中根据查看大小创建圆角路径,在这里要注意对padding值的处理。

@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {    super.onSizeChanged(w, h, oldw, oldh);    mRectF.left = getPaddingLeft();    mRectF.top = getPaddingTop();    mRectF.right = w - getPaddingRight();    mRectF.bottom = h - getPaddingBottom();    mPath.addRoundRect(mRectF, radii, Path.Direction.CW);}

2.1.2剪裁画布

直接使用canvas的clipPath方法进行剪裁。

@Overrideprotected boolean drawChild(Canvas canvas, View child, long drawingTime) {    // 绘制圆角    canvas.clipPath(mPath);    return super.drawChild(canvas, child, drawingTime);}

这样一个圆角布局就实现了。

2.2 setXfermode

由于clipPath方法不支持抗锯齿,因此在一些分辨率较低的设备上边缘看起来非常粗糙,所以换了一种实现方案,利用画笔的模式来实现对内容区域的剪裁。

有关于画笔模式可以参考:HTTP://www.gcssloop.com/customview/Color

2.2.1油漆

创建画笔并设置其模式:

private Paint mPaint;   // 画笔 mPaint = new Paint();mPaint.setColor(Color.WHITE);mPaint.setAntiAlias(true);// 绘制模式为填充mPaint.setStyle(Paint.Style.FILL);// 混合模式为 DST_IN, 即仅显示当前绘制区域和背景区域交集的部分,并仅显示背景内容。mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));

2.2.2绘制

创建带有圆角路径的内容不变,只不过这次将绘制部分移动到了dispatchDraw里面,其实放在drawChild里面也是可以的,只要注意一下绘制顺序即可。

由于画笔模式是SDT_IN,所以会显示原有内容区域和圆角路径区域交集的部分,并仅显示原有内容

@Override protected void dispatchDraw(Canvas canvas) {    canvas.saveLayer(new RectF(0, 0, canvas.getWidth(), canvas.getHeight()), null, Canvas            .ALL_SAVE_FLAG);  // 绘制子控件    super.dispatchDraw(canvas);  // 绘制带有圆角的 Path    canvas.drawPath(mPath, mPaint);    canvas.restore();}

2.3支持圆形

知道了绘制原理想要支持圆形就很简单了,其实就是将带有圆角矩形的路径更换为带有圆形的路径即可。

为了支持圆形,我定义了一个roundAsCircle属性,只要检测到这个属性,就在Path中添加一个圆形,否则添加一个圆角矩形。

在支持圆形的时候有一个坑需要注意一下,就是控件长宽比不一致的情况下,由于是按照最短的边计算的,所以在长宽比不一致的情况下,直接向Path添加圆形,Path是无法填充满画布的,在绘制的时候可能会出现圆形之外依旧有内容被绘制出来,所以这里使用了两个moveTo操作来让路径填充满画画(下文代码中注释部分)。

@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {    super.onSizeChanged(w, h, oldw, oldh);    RectF areas = new RectF();    areas.left = getPaddingLeft();    areas.top = getPaddingTop();    areas.right = w - getPaddingRight();    areas.bottom = h - getPaddingBottom();    mClipPath.reset();    if (mRoundAsCircle) {        float d = areas.width() >= areas.height() ? areas.height() : areas.width();        float r = d / 2;        PointF center = new PointF(w / 2, h / 2);        mClipPath.addCircle(center.x, center.y, r, Path.Direction.CW);        mClipPath.moveTo(0, 0);  // 通过空操作让Path区域占满画布        mClipPath.moveTo(w, h);    } else {        mClipPath.addRoundRect(areas, radii, Path.Direction.CW);    }    Region clip = new Region((int) areas.left, (int) areas.top,                             (int) areas.right, (int) areas.bottom);    mAreaRegion.setPath(mClipPath, clip);}

2.4支持描边

支持描边就更简单了,就是基础的绘制操作,如下:

@Override protected void dispatchDraw(Canvas canvas) {    canvas.saveLayer(new RectF(0, 0, canvas.getWidth(), canvas.getHeight()), null, Canvas            .ALL_SAVE_FLAG);    super.dispatchDraw(canvas);    // 描边    if (mStrokeWidth > 0) {        mPaint.setXfermode(null);        mPaint.setStrokeWidth(mStrokeWidth * 2);        mPaint.setColor(mStrokeColor);        mPaint.setStyle(Paint.Style.STROKE);        canvas.drawPath(mStrokePath, mPaint);    }    // 剪裁    mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));    mPaint.setStrokeWidth(0);    mPaint.setStyle(Paint.Style.FILL);    canvas.drawPath(mClipPath, mPaint);    canvas.restore();}

2.5限定点击区域

上面虽然实现了的圆角,圆形的显示,但是没有被绘制出来的部分依旧是可以被点击的,因此需要限定点击区域,对超出显示区域的部分不进行响应。

这个原理也不复杂,主要是利用事件分发机制和Region的contains方法。

2.5.1获取可点击区域

// 定义 Region,即内容区域private Region mAreaRegion;mAreaRegion = new Region();// 根据内容区域 Path 创建 Region,clip 为整个画布大小Region clip = new Region((int) areas.left, (int) areas.top,                       (int) areas.right, (int) areas.bottom);mAreaRegion.setPath(mStrokePath, clip);

2.5.2对超出区域外的事件不处理

使用包含方法判断事件的位置是否在区域内,如果不在区域内,就直接返回false,表示不处理。

@Override public boolean dispatchTouchEvent(MotionEvent ev) {    if (!mAreaRegion.contains((int) ev.getX(), (int) ev.getY())) {        return false;    }    return super.dispatchTouchEvent(ev);}

到此为止,有关通用圆角布局的核心内容就结束了。

3.结语

通用圆角布局的原理并不复杂,而且代码实现起来也非常简单,感兴趣的可以在Github上看一下源码,核心代码不过100行左右。

RCLayout:https://github.com/GcsSloop/rclayout

更多相关文章

  1. Android(安卓)不规则封闭区域填充 手指秒变油漆桶
  2. 【eoe教程】Android中自定义视图的绘制方法
  3. Android(安卓)View的测量、布局、绘制过程详解(上)
  4. android绘图 报表----aChartEngine图表显示(1)
  5. android 之view的测量和绘制(群英传读书笔记1)
  6. 百度地图 Android(安卓)SDK - 新的版本号(v3.2.0)正式上线
  7. Android(安卓)UI优化之OverDraw
  8. android中.9png
  9. 某android平板项目开发笔记----aChartEngine图表显示(1)

随机推荐

  1. Android用Intent启动Activity的方法
  2. android 在分享时判断是否安装QQ,微信客
  3. Android(安卓)自定义类库打包jar! 谁说不
  4. Android(安卓)编译系统(三)Main.mk分析
  5. android kill process 杀死进程的方法
  6. [置顶] MTK Android(安卓)编译小结
  7. 谈谈Binder
  8. Android(安卓)开发建立经验分享...
  9. Android开源之仿微信UI
  10. 2016年 Android(安卓)必火技术