自定义View的几种方式:
自定义View的基本框架:
言归正传,我们开始分析CircleImageView源码:
CircleImageView源码地址: CircleImageView源码
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事件分发
关于这些知识我会在后续进行不断地补充。