通过分析CircleImageView掌握自定义View流程

自定义View的几种方式:

1.直接继承View

2.直接继承ViewGroup

3.继承现有的View如(TextView、ImageView等);今天要说的CircleImageView就直接继承ImageView

4.继承现有的ViewGroup(如LinearLayout、RelativeLayout、FrameLayout等)

自定义View的基本框架:

1.创建自定义View骨架(如extends View)

2.添加自定义属性 (在res/attrs 文件声明),并在构造函数中获取属性信息

3.初始化画笔

4.实现onMeasure()方法 -> 对View进行测量

5.实现onSizeChanged()方法-> 对View进行更新

6.实现onDraw()方法-> 绘制View

7.布局中使用自定义View


言归正传,我们开始分析CircleImageView源码:

step1 : 继承ImageView

public class CircleImageView extends ImageView {

step2 : 添加自定义属性 (在res/attrs 文件声明),并在构造函数中获取属性信息

a.添加自定义属性

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CircleImageView">
        <attr name="civ_border_width" format="dimension" />
        <attr name="civ_border_color" format="color" />
        <attr name="civ_border_overlay" format="boolean" />
        <attr name="civ_fill_color" format="color" />
    </declare-styleable>
</resources>
b.构造函数中获取属性信息

public CircleImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0);

        mBorderWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_civ_border_width, DEFAULT_BORDER_WIDTH);
        mBorderColor = a.getColor(R.styleable.CircleImageView_civ_border_color, DEFAULT_BORDER_COLOR);
        mBorderOverlay = a.getBoolean(R.styleable.CircleImageView_civ_border_overlay, DEFAULT_BORDER_OVERLAY);
        mFillColor = a.getColor(R.styleable.CircleImageView_civ_fill_color, DEFAULT_FILL_COLOR);

        a.recycle();

        init();
    }

step3 : 初始化画笔

//绘制头像
private final Paint mBitmapPaint = new Paint();
//绘制边框
private final Paint mBorderPaint = new Paint();
//绘制填充效果
private final Paint mFillPaint = new Paint();

step4 : 实现onMeasure()方法

复用ImageView的测量流程。

step5 :实现onSizeChanged()方法

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    setup();
}

注意:setup()对画笔信息进行设置、从新计算图片、边框等大小,然后调用invalidate方法对View进行重绘。

private void setup() {
        if (!mReady) {
            mSetupPending = true;
            return;
        }

        if (getWidth() == 0 && getHeight() == 0) {
            return;
        }

        if (mBitmap == null) {
            invalidate();
            return;
        }

        mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);

        mBitmapPaint.setAntiAlias(true);
        mBitmapPaint.setShader(mBitmapShader);

        mBorderPaint.setStyle(Paint.Style.STROKE);
        mBorderPaint.setAntiAlias(true);
        mBorderPaint.setColor(mBorderColor);
        mBorderPaint.setStrokeWidth(mBorderWidth);

        mFillPaint.setStyle(Paint.Style.FILL);
        mFillPaint.setAntiAlias(true);
        mFillPaint.setColor(mFillColor);

        mBitmapHeight = mBitmap.getHeight();
        mBitmapWidth = mBitmap.getWidth();

        mBorderRect.set(calculateBounds());
        mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2.0f, (mBorderRect.width() - mBorderWidth) / 2.0f);

        mDrawableRect.set(mBorderRect);
        if (!mBorderOverlay && mBorderWidth > 0) {
            mDrawableRect.inset(mBorderWidth - 1.0f, mBorderWidth - 1.0f);
        }
        mDrawableRadius = Math.min(mDrawableRect.height() / 2.0f, mDrawableRect.width() / 2.0f);

        applyColorFilter();
        updateShaderMatrix();
        invalidate();
    }

step6 : 实现onDraw()方法

@Override
protected void onDraw(Canvas canvas) {
	if (mDisableCircularTransformation) {
	    super.onDraw(canvas);
	    return;
	}

	if (mBitmap == null) {
	    return;
	}
	//如果填充颜色不是透明,绘制透明效果
	if (mFillColor != Color.TRANSPARENT) {
	    canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius, mFillPaint);
	}
	//绘制圆形图片
	canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius, mBitmapPaint);
	//如果有边框,绘制边框
	if (mBorderWidth > 0) {
	    canvas.drawCircle(mBorderRect.centerX(), mBorderRect.centerY(), mBorderRadius, mBorderPaint);
	}
}

step7 :  布局中使用自定义View

<de.hdodenhof.circleimageview.CircleImageView
            android:layout_width="160dp"
            android:layout_height="160dp"
            android:layout_centerInParent="true"
            android:src="@drawable/hugh"
            app:civ_border_width="2dp"
            app:civ_border_color="@color/dark" />
注:这里用到自定义属性,所以需要在布局文件中 引入对应的命名空间

xmlns:app="http://schemas.android.com/apk/res-auto"

CircleImageView源码地址: CircleImageView源码


最后想说一下自定义View几种方式之间的区别:

直接继承View或ViewGroup()更接近于底层实现,但是难度也更高,需要对View的测量(onMeasure())、布局(onLayout())、绘制(onDraw())等进行重写,同时需要解决view之间的事件冲突,这就需要对view的事件分发机制有一定的了解。

* 理解view的工作原理:测量、布局、绘制

* 理解view事件分发

关于这些知识我会在后续进行不断地补充。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值