如何自定义VIew

1. 有哪些自定义View的手段

  1. 继承View重写OnDraw方法
  2. 继承ViewGroup派生特殊的Layout
  3. 继承现有特殊的View(例如:TextView)
  4. 继承现有特殊的ViewGroup(例如:LeanerLayout)

2. 自定义View的难点

  • 需要比较清楚的了解View的绘制过程
    也就是:onMeasure、onLayout、onDraw 这三个过程。这三个过程涉及东西会比较多,会单独的出一篇文档来讲解这个过程。

  • 直接继承View的自定义View的需要重写onMeasure、onDraw方法。
    需要考虑 wrap_content、padding 的情况。由于View自身在绘制过程中的默认实现,如果不处理这两种情况,设置的值就不会生效。原因可以看下面View中的 getDefaultSize() 源码:

    public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
    
        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }
    

    从上面的代码,可以看到设置 wrap_content 的效果和设置了 match_parent的效果是一样的。因为 View 自身默认实现的原因,所以使得 wrap_content 与 match_parent 效果一样。那为啥padding也会有影响呢?

    第一,ViewGroup 中 margin 和 padding 对绘制过程的影响。如下 ViewGroup 的 measureChildWithMargins 的过程:

    protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);
    
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
    

    从上我们可以知道,View/ViewGroup设置的 padding 得自己处理,而设置的 margin 是由父View来处理的。即,ViewGroup 在计算子View的时候需要把自身的Padding给考虑进去,而子View只有在 onDraw 的时候需要,因为View不需要考虑自身的padding对子View的可用size造成的影响。

    其次,在 padding 表示的是内容与边框之间的距离。它也属于 View 自身宽度的一个组成部分。如果在 onDraw 绘制的时候,不针对这部分进行处理,padding也不会生效。

3. 举个栗子

自定义一个可以自定义圆角和边框的FrameLayout

class RoundFrameLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0): FrameLayout(context, attrs, defStyleAttr) {

    init {
    	// 允许重写onDraw
        setWillNotDraw(false)
    }

    var radius = 0f // 自定义传入的角度
    var borderColor: Int = Color.WHITE // 边框颜色

    /**
     * 单位:px
     */
    var borderWidth: Int = 0

    private var mPath: Path? = null
    private val mPaint by lazy {
        Paint()
    }

    override fun onDraw(canvas: Canvas?) {
        DebugManager.logError("${canvas == null}, $radius, $borderWidth, $borderColor")
        drawRectBorder(canvas)
        drawRoundRect(canvas)
        super.onDraw(canvas)
    }

    /**
     * 通过剪切,给矩形绘制指定的圆角
     */
    private fun drawRoundRect(canvas: Canvas?) {
        canvas?.let { cav ->
            DebugManager.logError("$radius, $borderWidth, $borderColor")
            if (radius != 0f) {
                if (mPath == null && radius > 0f) {
                    // 获取 宽高
                    val w = this.width
                    val h = this.height
                    if (w > 0 && h > 0) {
                        val radiis = floatArrayOf(radius, radius, radius, radius, radius, radius, radius, radius)
                        // 创建path
                        mPath = Path()
                        mPath?.addRoundRect(RectF(0f + borderWidth, 0f + borderWidth, w.toFloat() - borderWidth, h.toFloat() - borderWidth), radiis, Path.Direction.CW)
                    }
                }
                mPath?.let {
                    cav.clipPath(it)
                }
            }
        }
    }

    /**
     * 绘制边框
     */
    private fun drawRectBorder(canvas: Canvas?) {
        canvas?.let { cav ->
            // 获取 宽高
            val w = this.width
            val h = this.height
            if (borderWidth > 0 && w > 0 && h > 0) {
                mPaint.strokeWidth = borderWidth.toFloat()
                mPaint.color = borderColor
                mPaint.style = Paint.Style.STROKE
                val rect = RectF(0f + borderWidth, 0f + borderWidth, w.toFloat() - borderWidth, h.toFloat() - borderWidth)
                cav.drawRoundRect(rect, radius, radius, mPaint)
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值