自定义控件---BitmapShader/Matrix仿CircleImageView

来源:互联网 发布:美团大数据 在哪看 编辑:程序博客网 时间:2024/05/17 09:13

前言

开发中头像的使用,我们会选择CircleImageView。实现圆角图片的方法应该很多,常见的就是利用Xfermode,Shader。下面我们就按照CircleImageView的实现方式,直接继承直接继承ImageView,使用BitmapShader实现圆角的绘制,进行简单的自定义控件

开发准备

BitmapShader

BitmapShader是Shader的子类,可以以下作用:

重复:就是横向、纵向不断重复这个bitmap镜像:横向不断翻转重复,纵向不断翻转重复;拉伸:这个和电脑屏保的模式应该有些不同,这个拉伸的是图片最后的那一个像素;横向的最后一个横行像素,不断的重复,纵项的那一列像素,不断的重复;

实现的方式,是通过构造传入参数确定的

mBitmapShader = new BitmapShader(bitmap, TileMode.CLAMP, TileMode.CLAMP);

TileMode的三种参数:

CLAMP 拉伸REPEAT 重复MIRROR 镜像

BitmapShader通过设置给mPaint,然后用这个mPaint绘图时,就会根据你设置的TileMode,对绘制区域进行着色。本例中设置为CLAMP:拉伸。同时为BitmapShader设置一个matrix,去适当的放大或者缩小图片。

开发之路

首先,我们选择继承ImageView进行自定义控件,紧接着我们设计一些自定义属性

<declare-styleable name="RoundImageView">    <attr name="borderRadius" format="dimension"/>    <attr name="type" >        <!-- 可以设置模式:圆形:圆角矩形 --!>        <enum name="circle" value="0"/>        <enum name="round" value="1"/>    </attr></declare-styleable>

代码获取自定义属性,并初始化画笔和图形处理的Matrix:

public class RoundImageView extends ImageView{    public static final int TYPE_CIRCLE = 0;    public static final int TYPE_ROUND = 1;    public static final String STATE_INSTANCE = "state_instance";    public static final String STATE_TYPE = "state_type";    public static final String STATE_BORDER_RADIUS = "state_border_radius";    /**     * 圆角大小的默认值     */    public static final int BODER_RADIUS_DEFAULT = 10;    private int mBorderRadius;    private int mType;    private int mWidth;    /**     * 圆角的半径     */    private int mRadius;    /**     * 渲染图像,使用图像为绘制图形着色     */    private BitmapShader mBitmapShader;    /**     * 3x3 矩阵,主要用于缩小放大     */    private Matrix mMatrix;    /**     * 绘图的Paint     */    private Paint mBitmapPaint;    private RectF mRoundRect;    public RoundImageView(Context context) {        this(context, null);    }    public RoundImageView(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public RoundImageView(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.RoundImageView,defStyle,0);        mBorderRadius = a.getDimensionPixelSize(R.styleable.RoundImageView_borderRadius, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, BODER_RADIUS_DEFAULT, getResources().getDisplayMetrics()));        mType = a.getInt(R.styleable.RoundImageView_type, TYPE_CIRCLE);        a.recycle();        mMatrix = new Matrix();        mBitmapPaint = new Paint();    }}

重写onMeasure方法,当类型为圆形时,让view的宽和高一致

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    super.onMeasure(widthMeasureSpec, heightMeasureSpec);    //如果类型是圆形,则强制改变view的宽高一致,以小值为准    if (mType == TYPE_CIRCLE){        mWidth = Math.min(getMeasuredHeight(),getMeasuredWidth());        mRadius = mWidth / 2;        setMeasuredDimension(mWidth,mWidth);    }}

接下来就是设置BitmapShader

private void setUpShader(){    Drawable drawable = getDrawable();    if (drawable == null){        return;    }    Bitmap bmp = drawableToBitmap(drawable);    // 将bmp作为着色器,就是在指定区域内绘制bmp    mBitmapShader = new BitmapShader(bmp, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);    float scale = 1.0f;    if (mType == TYPE_CIRCLE){        // 拿到bitmap宽或高的小值        int bSize = Math.min(bmp.getWidth(),bmp.getHeight());        scale = mWidth * 1.0f / bSize;    }else if (mType == TYPE_ROUND){        // 如果图片的宽或者高与view的宽高不匹配,计算出需要缩放的比例;缩放后的图片的宽高,一定要大于我们view的宽高;所以我们这里取大值        scale = Math.max(getWidth() * 1.0f / bmp.getWidth(),getHeight() * 1.0f / bmp.getHeight());    }    // shader的变换矩阵,我们这里主要用于放大或者缩小    mMatrix.setScale(scale,scale);    // 设置变换矩阵    mBitmapShader.setLocalMatrix(mMatrix);    // 设置shader      mBitmapPaint.setShader(mBitmapShader);}private Bitmap drawableToBitmap(Drawable drawable) {    if (drawable instanceof BitmapDrawable){        BitmapDrawable bd = (BitmapDrawable) drawable;        return bd.getBitmap();    }    int w = drawable.getIntrinsicWidth();    int h = drawable.getIntrinsicHeight();    Bitmap bitmap = Bitmap.createBitmap(w,h, Bitmap.Config.ARGB_8888);    Canvas canvas = new Canvas(bitmap);    drawable.setBounds(0,0,w,h);    drawable.draw(canvas);    return bitmap;}

代码比较简单,注释也详细,不再赘述了
在OnDraw中调用setUpShader(),进行绘制。

@Overrideprotected void onDraw(Canvas canvas) {    if (getDrawable() == null){        return;    }    setUpShader();    if (mType == TYPE_ROUND){        canvas.drawRoundRect(mRoundRect,mBorderRadius,mBorderRadius,mBitmapPaint);    }else{        canvas.drawCircle(mRadius,mRadius,mRadius,mBitmapPaint);    }}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {    super.onSizeChanged(w, h, oldw, oldh);    if (mType == TYPE_ROUND){        mRoundRect = new RectF(0,0,getWidth(),getHeight());    }}

最后,优化一下:遇到Activity重启,View应该也能尽可能的去保存自己的属性

@Overrideprotected Parcelable onSaveInstanceState() {    Bundle bundle = new Bundle();    bundle.putParcelable(STATE_INSTANCE,super.onSaveInstanceState());    bundle.putInt(STATE_TYPE,mType);    bundle.putInt(STATE_BORDER_RADIUS,mBorderRadius);    return bundle;}@Overrideprotected void onRestoreInstanceState(Parcelable state{    if (state instanceof Bundle){        Bundle bundle = (Bundle) state;        super.onRestoreInstanceState((Bundle) ((Bundle) state).getParcelable(STATE_INSTANCE));        this.mType = bundle.getInt(STATE_TYPE);        this.mBorderRadius = bundle.getInt(STATE_BORDER_RADIUS);    }else{        super.onRestoreInstanceState(state);    }}

暴露动态修改圆角大小和type的方法:

public void setBorderRadius(int borderRadius){    int pxVal = dp2px(borderRadius);    if (this.mBorderRadius != pxVal){        this.mBorderRadius = pxVal;        invalidate();    }}public void setType(int type){    if (this.mType != type){        this.mType = type;        if (this.mType != TYPE_ROUND && this.mType != TYPE_CIRCLE){            this.mType = TYPE_CIRCLE;        }        requestLayout();    }}private int dp2px(int dpVal) {    return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dpVal,getResources().getDisplayMetrics());}

自定义控件已经写完,下面就是如何使用:
xml布局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    android:layout_width="match_parent"    android:orientation="vertical"    android:layout_height="match_parent"    >    <ScrollView        android:layout_width="match_parent"        android:layout_height="match_parent">        <LinearLayout            android:layout_width="match_parent"            android:orientation="vertical"            android:layout_height="match_parent">            <com.zhang.customview.widget.RoundImageView                android:id="@+id/riv_first"                android:layout_margin="10dp"                android:src="@mipmap/lake"                android:layout_width="wrap_content"                android:layout_height="wrap_content" />            <com.zhang.customview.widget.RoundImageView                android:id="@+id/riv_second"                android:layout_margin="10dp"                android:src="@mipmap/lake"                android:layout_width="wrap_content"                app:borderRadius="20dp"                app:type="round"                android:layout_height="wrap_content" />            <com.zhang.customview.widget.RoundImageView                android:layout_margin="10dp"                android:src="@mipmap/lake"                android:layout_width="200dp"                android:layout_height="200dp" />        </LinearLayout>    </ScrollView></LinearLayout>

Activity代码:

public class MainActivity extends AppCompatActivity {    @BindView(R.id.riv_first)    RoundImageView mRivFirst;    @BindView(R.id.riv_second)    RoundImageView mRivSecond;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        ButterKnife.bind(this);    }    @OnClick({R.id.riv_first, R.id.riv_second})    public void onClick(View view) {        switch (view.getId()) {            case R.id.riv_first:                mRivFirst.setType(RoundImageView.TYPE_ROUND);                break;            case R.id.riv_second:                mRivSecond.setBorderRadius(90);                break;        }    }}

效果图如下
效果图
如还有问题,请查阅: