自定义控件实现整个过程,从零到有!

前言:自定义控件这技术已经被技术大牛写的很明白了,可我相信还是有好多人当自己实现时还是一头雾水,现在我们来一起学习到底如何才能学会自定义控件!

自定义控件大多都会说三步走,1.onDraw();2.onMeasure();3.onLayout();,当然这是没错的,可大多都忽略了一点,技术大牛们的基础在那摆着,这样当然无可厚非,可他们却忽略了一些人(包括我)所所欠缺的眼界问题。

在这三步的基础上我们来说一下,之前我们所要做的工作。

举个例子:侧滑面板(偷笑中),实现效果不需赘述了吧,

看看图片:

首先声明这个效果实现方式有很多,不要抬杠,这只是个例子而已,那现在我们开始进行分析,这个自定义控件的实现效果来看这是两层页面,而能实现两侧页面效果的,我们首先想到的就是FrameLayout,为什么是Framelayout呢,在这里我们不需要自己去做测量和摆放,而FrameLayout 已经对三个方法进行了具体实现,所以继承FrameLayout 更加简单省事

接下来我们要开始今天的重要内容了,自定义控件到底怎么才能快速入门呢?答案:源码!

建议大家从LinearLayout的源码入手,简单,容易理解。

一般所有控件类的源码,都会从 measure, layout和draw3个方法入手,查看他们的回调函数onMeasure, onLayout和onDraw 
只要明白这3个流程,一般控件的整个实现也就明白了 

下面代码是为了更好的理解这3个方法 我写的一个自定义View 

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        LogUtil.e("onMeasure");
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        LogUtil.e("onDraw");
        float []pts={10,10,100,100,200,200,400,400};
        Paint paint = new Paint();//第一步新建画笔
        paint.setAntiAlias(true);//设置抗锯齿
        paint.setColor(Color.GREEN);//设置画笔颜色
//        paint.setStyle(Paint.Style.FILL_AND_STROKE);//设置填充风格   FILL为全部填充,FILL_AND_STROKE为填充内容和描边  这两者没有区别,STROKE为只描边
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(20);//设置画笔宽度
        canvas.drawRGB(200,200,200);//设置画布背景颜色
        canvas.drawCircle(190,200,150,paint);//画圆
        canvas.drawLine(50,50,300,600,paint);//画线
        canvas.drawLines(pts,paint);//画多条线
        canvas.drawPoint(800,800,paint);//画点
        RectF rectF = new RectF(10,300,400,800);
        canvas.drawRect(rectF,paint);//画矩形
        RectF rectF1 = new RectF(400,300,600,800);
        canvas.drawRoundRect(rectF1,10,10,paint);//画圆角矩形
        canvas.drawOval(rectF,paint);//画椭圆
        RectF rectF2= new RectF(100, 10, 300, 100);

        canvas.drawArc(rectF,0,90,false,paint);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        LogUtil.e("onLayout");
    }

这是打印日志 

08-29 14:06:52.903 10665-10665/com.view.zhaojian.view E/LogUtil: onMeasure
08-29 14:06:52.929 10665-10665/com.view.zhaojian.view E/LogUtil: onLayout
08-29 14:06:52.937 10665-10665/com.view.zhaojian.view E/LogUtil: onDraw
 

 从日志中可以看出是先执行onMeasure,再执行onLayout,最好onDraw,那么整个过程就很明朗了,先是onMeasure来测量高度,宽度(建议),在onLayout里确定布局,注意 在这里才是真正确定宽度高度的地方,最后在ondraw里进行绘制。

这里还是拿LineaLayout为例,

 

这里是onDraw里调用的一段代码 

void drawDividersVertical(Canvas canvas) {
    final int count = getVirtualChildCount();
    for (int i = 0; i < count; i++) {
        final View child = getVirtualChildAt(i);
        if (child != null && child.getVisibility() != GONE) {
            if (hasDividerBeforeChildAt(i)) {
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                final int top = child.getTop() - lp.topMargin - mDividerHeight;
                drawHorizontalDivider(canvas, top);
            }

        }
    }
注意加粗下划线的代码,这里先取得子View的参数然后进行其他操作,那么子View的参数怎么确定的呢?当然是onLayout()方法确定位置后传来的,这里我截取onLayout里一段代码

for (int i = 0; i < count; i++) {
    final View child = getVirtualChildAt(i);
    if (child == null) {
        childTop += measureNullChild(i);
    } else if (child.getVisibility() != GONE) {
       final int childWidth = child.getMeasuredWidth();
        final int childHeight = child.getMeasuredHeight();

        final LinearLayout.LayoutParams lp =
                (LinearLayout.LayoutParams) child.getLayoutParams();

        int gravity = lp.gravity;
        if (gravity < 0) {
            gravity = minorGravity;
        }
        final int layoutDirection = getLayoutDirection();
        final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
        switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
            case Gravity.CENTER_HORIZONTAL:
                childLeft = paddingLeft + ((childSpace - childWidth) / 2)
                        + lp.leftMargin - lp.rightMargin;
                break;

            case Gravity.RIGHT:
                childLeft = childRight - childWidth - lp.rightMargin;
                break;

            case Gravity.LEFT:
            default:
                childLeft = paddingLeft + lp.leftMargin;
                break;
        }

        if (hasDividerBeforeChildAt(i)) {
            childTop += mDividerHeight;
        }

        childTop += lp.topMargin;
        setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                childWidth, childHeight);
        childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

        i += getChildrenSkipCount(child, i);
    }
}

 从这段代码中可以看出View的位置由此而来,上面有加粗的两句代码注意,这两个变量的值怎么来的?当然是通过onMeasure测量后得来的,下面这段代码是我作为判断的依据

final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
        Math.max(0, childHeight), MeasureSpec.EXACTLY);
final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
        mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
        lp.width);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

// Child may now not fit in vertical dimension.
childState = combineMeasuredStates(childState, child.getMeasuredState()
        & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));

这是我在onMeasure里截取的,大家可以看到里面的代码正好对应上面我加粗的一段代码 ,到这里基本过程就结束了,我是以倒推的方式进行的分析,大家不要被我误导了

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值