在很多时候,原生控件不能满足我们的需求,就需要我们自定义View,下面以ImageView圆角为例讲一下个人自定义View经验。我们在自定义View的过程中,需要搞清楚下面几点:
- 首先想想生活中画圆的过程:
我们需要工具:圆规,一张纸,圆规里面还必须要有笔,工具有了就可以画圆了。
代码开发出来的功能肯定是为了服务生活,那就必须来源于生活。对应起来,就不那么麻烦了。
-
然后就是将图片圆角的思路:
(1)我们要画出一个圆
(2)我们用一张图片去填充这个圆就行了
(3)细节,我们需要控制圆心坐标,计算圆的半径等等问题
-
再然后接下来就是该清楚系统绘制过程,和系统绘制的机制
(1)用什么去画?
(2)怎么画?
(3)我们怎么控制,让他画出我们想要的东西?
(4)在每个生命周期方法中都做了些什么?
带着思路上面的上面的四个问题,我们往下面看,先看完理论的讲解,再去看源代码,分析源代码,分析源代码的log,去慢慢搞懂这些问题就差不多了。
1.自定义View的过程或者说系统绘制的机制一般如下:
(1)有一支画笔(Paint),对这个对象决定最终的效果,比如颜色(颜色就是用于填充我们画出来的形状,也可以用图片去填充),风格等等渲染问题。
(2)有一张画布(Canvas),在画布上按照我们想要的效果绘制视图,就是用canvas这个对象去画画,canvas对象决定了画出来的图形的形状,坐标等等,这些都需要我们调用API去进行
2.系统将视图绘制到屏幕的整个过程中,ImageView的几个关键生命周期方法调用过程:
(1)调用setImageDrawable:setImageResource.....这几个设置图片的方法,在这几个方法中就能拿到ImageView中的bitmap或者resourceId,或者地址反正在这儿能得到你设置的图片
(2)调用构造方法:我们需要自定义属性,都需要在这儿先关联attr.xml文件,并且拿到布局文件中我们设置的属性值,在后面绘制的时候用。需要注意的是在这儿系统还拿不到view的宽高,所以初始化画笔等等的事儿,别在这儿做,有坑
(3)onSizeChanged方法:在这儿,我们就能拿到view的宽高了,所以在这儿初始化画笔等等,时机正好
(4)onDraw方法:这个方法才是真的让视图绘制出来,显示在我们的眼前。但是这儿要注意需不需要调用父类的onDraw方法,有可能你不想有的视图出现,完全是希望自己绘制,那就不要调用了,不然会出现双层视图
3.上源代码,来点实际的
在values目录下创建,自定义属性,attr.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- 如果是用Android studio开发,注意styleable中的name需要跟自定义的View类名相同,否则,在布局文件中无法引用下面的属性 -->
<declare-styleable name="CircleImage">
<!-- 是否有外边框 -->
<attr name="hasBorder" format="boolean" />
<!-- 有外边框的情况下,外边框的颜色 -->
<attr name="borderColor" format="color" />
<!-- 有外边框的情况下,外边框的厚度 -->
<attr name="borderWidth" format="dimension" />
</declare-styleable>
</resources>
编写xml布局文件文件引用
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:dragon="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#0000FF">
<!-- xmlns:dragon="http://schemas.android.com/apk/res-auto" 注意看上面的这句话,是用于引用自定义的属性,不可丢 -->
<com.picovr.animatordemo.CircleImage
android:id="@+id/iv_animator"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_centerInParent="true"
android:src="@mipmap/timgs"
dragon:borderColor="#000000"
dragon:borderWidth="3dp"
dragon:hasBorder="true" />
</RelativeLayout>
创建类,继承ImageView编写代码
package com.picovr.animatordemo;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.ImageView;
/**
* Created by PICO-USER dragon on 2017/2/28.
*/
public class CircleImage extends ImageView {
// 缩放类型
private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
private static final int COLORDRAWABLE_DIMENSION = 2;
private float circleRadius;
private Bitmap bitmap;
private Paint bitmapPaint;
private BitmapShader bitmapShader;
private RectF drawableRect;
private int bitmapWidth;
private int bitmapHeight;
private Matrix mShaderMatrix;
private Paint borderPaint;
private int borderColor;
private int borderWidth;
private RectF borderRect;
private float borderRadius;
private boolean hasBorder;
public CircleImage(Context context) {
super(context);
}
public CircleImage(Context context, AttributeSet attrs) {
super(context, attrs);
Log.i("CircleImage3", "CircleImage3 构造方法 :");
//自定义属性,关联attr.xml中<declare-styleable name="CircleImage">中的属性
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleImage);
//获取布局文件中的自定义属性
hasBorder = typedArray.getBoolean(R.styleable.CircleImage_hasBorder, false);
if (hasBorder) {
borderWidth = typedArray.getDimensionPixelSize(R.styleable.CircleImage_borderWidth, 0);
borderColor = typedArray.getColor(R.styleable.CircleImage_borderColor, Color.TRANSPARENT);
}
typedArray.recycle();
}
/**
* 构造方法中还不能拿到View的宽高,在这个方法中可以得到,所以在这儿实例化画圆所需要的一切配置
*
* @param w
* @param h
* @param oldw
* @param oldh
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
initPaint();
}
@Override
protected void onDraw(Canvas canvas) {
//这儿一定不要调用父类的onDraw方法,调用这个方法系统会直接将ImageView绘画出来,这就会出现两张图片
// super.onDraw(canvas);
Log.i("CircleImage3", "onDraw :");
canvas.drawCircle(getWidth() / 2, getHeight() / 2, circleRadius, bitmapPaint);
if (hasBorder) {
canvas.drawCircle(getWidth() / 2, getHeight() / 2, borderRadius, borderPaint);
}
}
/**
* Drawable转Bitmap
*
* @param drawable
* @return
*/
private Bitmap getBitmapFromDrawable(Drawable drawable) {
if (drawable == null) {
return null;
}
if (drawable instanceof BitmapDrawable) {
// 通常来说 我们的代码就是执行到这里就返回了。返回的就是我们最原始的bitmap
return ((BitmapDrawable) drawable).getBitmap();
}
try {
Bitmap bitmap;
if (drawable instanceof ColorDrawable) {
bitmap = Bitmap.createBitmap(COLORDRAWABLE_DIMENSION,
COLORDRAWABLE_DIMENSION, BITMAP_CONFIG);
} else {
bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(), BITMAP_CONFIG);
}
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
} catch (OutOfMemoryError e) {
return null;
}
}
/**
* 关键代码就在这儿,画笔初始化,圆半径计算,图片压缩,构建Shader,用Bitmap填充圆
*/
private void initPaint() {
if (getDrawable() != null) {
bitmap = getBitmapFromDrawable(getDrawable());
}
if (bitmap == null) {
throw new IllegalArgumentException("the bitmap of imageView is null !");
}
if (hasBorder) {
/**
* 初始化外圆画笔的属性,外圆半径的计算
*/
borderPaint = new Paint();
//外圆所在矩形:这儿用一个矩形去控制外圆,Android中的View都是矩形,所以画圆也是在一个矩形里面画内心圆
borderRect = new RectF();
borderPaint.setStyle(Paint.Style.STROKE);
borderPaint.setAntiAlias(true);
//外圆不需要用图片去填充,而是用想要的颜色去填充
borderPaint.setColor(borderColor);
borderPaint.setStrokeWidth(borderWidth);
//RectF类中计算,矩形的宽 = right坐标-left坐标,高 = bottom坐标-top坐标,
// 所以这儿只需要将本ImageView的宽值赋给矩形的right坐标,高赋值给bottom坐标即可
borderRect.set(0, 0, getWidth(), getHeight());
//外圆的半径是算法(外圆所在矩形的宽和高这两边的短边的一半减去外圆到内圆的距离)
borderRadius = Math.min((borderRect.width() - borderWidth) / 2, (borderRect.height() - borderWidth) / 2);
}
//初始化内圆的画笔属性,半径计算
bitmapPaint = new Paint();
//同外圆一样,画一个矩形的内心圆
drawableRect = new RectF();
//赋给shader的矩阵,用于压缩图片,让图片的中心部位去填充内圆
mShaderMatrix = new Matrix();
bitmapWidth = bitmap.getWidth();
bitmapHeight = bitmap.getHeight();
// 构建渲染器,用mBitmap位图来填充绘制区域 ,参数值代表如果图片太小的话 就直接拉伸
bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
bitmapPaint.setAntiAlias(true);
// 设置图片画笔渲染器
bitmapPaint.setShader(bitmapShader);
if (hasBorder) {
//内圆所属矩形的宽和高应该分别是外圆所属矩形的宽减去外圆到内圆的距离和高减去外圆到内圆的距离
drawableRect.set(0, 0, borderRect.width() - borderWidth, borderRect.height() - borderWidth);
} else {
drawableRect.set(0, 0, getWidth(), getHeight());
}
circleRadius = Math.min(drawableRect.height() / 2, drawableRect.width() / 2);
Log.i("CircleImage3", "initPaint drawable Width :" + drawableRect.width() + " Height " + drawableRect.height() + " View width :" + getWidth() + " height :" + getHeight());
//压缩并位移图片用于填充内部圆
setBitmapShaderMtrix();
}
private void setBitmapShaderMtrix() {
float scale;
float dx = 0;
float dy = 0;
Log.i("CircleImage3", "setBitmapShaderMtrix drawable Width :" + drawableRect.width() + " Height " + drawableRect.height());
Log.i("CircleImage3", "setBitmapShaderMtrix bitmap width :" + bitmap.getWidth() + " height : " + bitmap.getHeight());
mShaderMatrix.set(null);
//x方向压缩还是Y方向压缩判断
if (bitmapWidth * drawableRect.height() > drawableRect.width() * bitmapHeight) {
// y轴缩放 x轴平移 使得图片的y轴方向的边的尺寸缩放到图片显示区域(mDrawableRect)一样)
scale = drawableRect.height() / (float) bitmapHeight;
dx = (drawableRect.width() - bitmapWidth * scale) * 0.5f;
Log.i("CircleImage3", "setBitmapShaderMtrix scale :" + scale + " dx :" + dx);
} else {
// x轴缩放 y轴平移 使得图片的x轴方向的边的尺寸缩放到图片显示区域(mDrawableRect)一样)
scale = drawableRect.width() / (float) bitmapWidth;
dy = (drawableRect.height() - bitmapHeight * scale) * 0.5f;
Log.i("CircleImage3", "setBitmapShaderMtrix scale :" + scale + " dy :" + dy);
}
// shaeder的变换矩阵,我们这里主要用于放大或者缩小。
mShaderMatrix.setScale(scale, scale);
// 平移
mShaderMatrix.postTranslate((int) (dx + 0.5f) + drawableRect.left, (int) (dy + 0.5f) + drawableRect.top);
// 设置变换矩阵
bitmapShader.setLocalMatrix(mShaderMatrix);
}
@Override
public void setImageDrawable(Drawable drawable) {
super.setImageDrawable(drawable);
}
}
MainActivity
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv_animator = ((CircleImage) findViewById(R.id.iv_animator));
}
效果附上:
本文介绍如何通过自定义View实现带有圆角及边框的ImageView,包括创建自定义属性、初始化画笔、计算圆半径及图片压缩等关键步骤。
525

被折叠的 条评论
为什么被折叠?



