实现任意View的圆角化

前言:

通常我们实现某一特殊功能的View控件的时候,都会在当前View上动刀子,比如想要实现一个圆角矩形边框的图片控件,好,在ImageView上动动手,改一个RoundImageView; 想要实现一个下拉刷新的列表控件,好,在ListView上动动刀,改一个RefreshListView. 

以上思路不能说有问题,但动违反了任何事务应该抽象化的一般思想.问题不能只看眼前,要有更长远的规划.

近来研究Android 新增的一些控件,可以明显感觉到这些实现所带来的重大编程思想.
比如: RefreshLayout,将下拉刷新的逻辑抽象到一个布局中去,那么任何添加到这个布局中的控件都具有下拉刷新的行为.
CardView 可以让一切加入其中的View控件实现圆角化.而不仅仅局限于一张图片.
还有Metro的很多控件,都是基于这样一个思路–动刀子不要只顾着眼下,治病要治根.

正文

本文提供的一个新型控件是对CardView的一个改进, 用过CardView的同学应该都知道,它在低版本的手机上表示出来的缺陷–通过给内容增加padding只是背景圆角化了,内容仍然四四方方.
有没有办法实现在任意系统下都能够让内容圆角化的控件呢? 答案是肯定的.
思路是什么呢? 如果大家深入研究一下Canvas这个绘图画布就应该知道, 画布设置多大,内容就会显示多大. 另外这个画布还可以设置剪裁区.

剪裁区是个什么东东呢?剪裁区就是内容能够显示出来的窗口,剪裁区的形状也决定了内容最终显示出来的形状.例如我将剪裁区设置为一个圆形,那么画出来的内容,一定是个圆形,圆形以外的地方,则会被遮挡而没有绘制.
这就是我们今天要讲的这个控件的实现.

基于剪裁区的多边形布局实现.

不仅仅是圆角哦~你可以为它添加任意形状,圆形,椭圆形,圆角矩形,星形,5边形….形状随你控制.
下边直接贴出原码供大家参考:

package cn.andrewlu.app.customview;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.FrameLayout;

/**
 * Created by Andrewlu on 2016/4/6. 
 * CardView 在低版本上内容与边框之间会有空隙.并非真正的圆角布局.
 * RoundView用来构造一个真正的圆角布局.可选形状有: 圆角矩形, 圆,椭圆.矩形.矩形实际上是弧度为0的圆角矩形.
 */
public class RoundView extends FrameLayout {
    private float mRadius = 0;
    private float mStrokeWidth = 0;
    private int mStrokeColor = Color.TRANSPARENT;
    private Path mBoundPath = null;
    private Type mType = Type.Rect;// 默认为 圆角矩形,即未声明type属性时,默认当成圆角矩形.

    public RoundView(Context context) {
        this(context, null);
    }

    public RoundView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    /**
    *通过自定义属性,设置圆角大小,形状,边框宽度,边框颜色等.参考CardView的自定义属性.
    **/
    public RoundView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setWillNotDraw(false);
        TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.RoundView);
        mRadius = a.getDimension(R.styleable.RoundView_radius, mRadius);
        mStrokeWidth = a.getDimension(R.styleable.RoundView_strokeWidth,
                mStrokeWidth);
        mStrokeColor = a.getColor(R.styleable.RoundView_strokeColor,
                mStrokeColor);
        int shape = a.getInt(R.styleable.RoundView_shape, mType.getType());
        mType = Type.from(shape);
        a.recycle();
    }

    public void setRadius(float radius) {
        if (mRadius == radius)
            return;
        this.mRadius = radius;
        postInvalidate();
    }

    public float getRadius() {
        return mRadius;
    }

    public void setStrokeWidth(float strok) {
        this.mStrokeWidth = strok;
        postInvalidate();
    }

    public float getStrokeWidth() {
        return mStrokeWidth;
    }

    public void setStrokeColor(int strokeColor) {
        this.mStrokeColor = strokeColor;
    }

    public int getStrokeColor() {
        return mStrokeColor;
    }

    //draw()函数是任何View在绘制自己前都会被调用的方法,通过在绘制自己前设置裁剪区,可以实现任意形状的布局.
    public void draw(Canvas canvas) {
        beforeDraw(canvas);
        super.draw(canvas);
    }
    //dispatchDraw是任何布局在内容绘制结束时都会被调用的方法.可以做一些其他操作,如描边等.
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        afterDraw(canvas);
    }

    //在绘制前计算出可以剪裁的矩形区域,并根据矩形区域设置自己的形状Path. 
    private void beforeDraw(Canvas canvas) {
        Rect rect = new Rect();
        getLocalVisibleRect(rect);
        mBoundPath = onCaculatePath(rect);
        canvas.clipPath(mBoundPath);

        Log.i("RoundView", "beforeDraw");
    }

    //绘制结束后,可以给形状进行描边操作.
    private void afterDraw(Canvas canvas) {
        Rect rect = new Rect();
        getLocalVisibleRect(rect);
        // 进行描边操作.
        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
        p.setStyle(Paint.Style.STROKE);
        p.setColor(mStrokeColor);
        p.setStrokeWidth(mStrokeWidth);
        Path path = onGetPathStroke(rect, mBoundPath);
        if (path == null)
            return;
        canvas.drawPath(path, p);
    }

    protected Path onCaculatePath(Rect r) {
        switch (mType) {
        case Rect:
            return caculateRoundRectPath(r);
        case Circle:
            return caculateCirclePath(r);
        case Oval:
            return caculateOvalPath(r);
        }
        return caculateRoundRectPath(r);
    }

    protected Path onGetPathStroke(Rect r, Path boundPath) {
        switch (mType) {
        case Circle:
            return getCirclePathWithinStroke(r, boundPath);
        default:
            return getPathWithinStroke(r, boundPath);
        }
    }

    // 将path 进行变换,以容纳描边线宽.
    private Path getPathWithinStroke(Rect r, Path path) {
        if (mStrokeWidth <= 0)
            return path;

        // 防止边过宽,完全遮挡内容.
        int minWidth = r.width() > r.height() ? r.height() : r.width();
        if (minWidth <= 0)
            return null;

        if (mStrokeWidth >= minWidth / 2)
            mStrokeWidth = minWidth / 2.5f;

        Path p = new Path();
        Matrix matrix = new Matrix();
        float scaleX = (r.width() - mStrokeWidth / 2) / r.width();
        float scaleY = (r.height() - mStrokeWidth / 2) / r.height();

        matrix.setScale(scaleX, scaleY, r.centerX(), r.centerY());
        path.transform(matrix, p);
        return p;
    }

    private Path getCirclePathWithinStroke(Rect r, Path path) {
        if (mStrokeWidth <= 0)
            return path;
        // 防止边过宽,完全遮挡内容.
        int minWidth = r.width() > r.height() ? r.height() : r.width();
        if (minWidth <= 0)
            return null;

        if (mStrokeWidth >= minWidth / 2)
            mStrokeWidth = minWidth / 2.5f;

        Path p = new Path();
        Matrix matrix = new Matrix();
        float scale = (minWidth - mStrokeWidth / 2) / minWidth;

        matrix.setScale(scale, scale, r.centerX(), r.centerY());
        path.transform(matrix, p);
        return p;
    }

    // 以下方法留做备用,可用于生产各种外形的边框.仅供参考.
    private Path caculateRoundRectPath(Rect r) {
        Path path = new Path();
        float radius = getRadius();
        float elevation = 0;
        path.addRoundRect(new RectF(r.left + elevation, r.top + elevation,
                r.right - elevation, r.bottom - elevation), radius, radius,
                Path.Direction.CW);
        return path;
    }

    private Path caculateCirclePath(Rect r) {
        Path path = new Path();
        int radius = r.width() > r.height() ? r.height() / 2 : r.width() / 2;
        path.addCircle(r.left + radius, r.top + radius, radius,
                Path.Direction.CW);
        return path;
    }

    private Path caculateOvalPath(Rect r) {
        Path path = new Path();
        path.addOval(new RectF(r), Path.Direction.CW);
        return path;
    }

    public enum Type {
        Rect(0), Circle(1), Oval(2);
        private int type;

        Type(int type) {
            this.type = type;
        }

        public int getType() {
            return this.type;
        }

        public static Type from(int type) {
            switch (type) {
            case 0:
                return Rect;
            case 1:
                return Circle;
            case 2:
                return Oval;
            default:
                return Rect;
            }
        }
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值