【Android学习】自定义View的实现----以圆形图片控件为例

在Android开发过程中,系统提供的控件有时并不能满足要求,往往需要通过自定义控件来实现需求。今天我们以圆形图片控件为例,简单介绍下它的具体实现方式


自定义控件的三种实现方式
  1. 组合控件
    组合控件,简单说来就是将系统的一些控件组合成一个新的控件便于使用。譬如常见的标题栏控件,如图
    这里写图片描述

最上一排中左边的返回按钮,中间的标题(自己的手机厚码)以及右边的其余功能按钮(弹出一个消息框)就是一个典型的组合控件
3. 自绘控件
自绘控件就是自己绘制的控件,主要是在View的onDraw方法中实现
3. 继承控件
继承控件继承系统已有的控件,在原有控件的属性上,引入新的属性以满足需求

我们今天的圆形图片控件就是结合上述的方法2.3实现。


定义自定义控件步骤
  1. 自定义View属性
  2. 从构造方法中获得自定义的View属性
  3. 重写onDraw以及onMeasure
  4. 使用自定义控件

自定义View属性

首先,在res/values中声明我们这个控件样式以及相关属性
这里写图片描述
一般说来的话attr.xml这个文件声明的都是view的相关信息,如果没有可以新建,也可以在别的xml文件中声明。

我这边的项目是要开发一个登录功能的界面,需要这个控件作为头像。因此,需要自定义的一些属性是这个头像的边框颜色以及宽度。具体代码如下。

<?xml version="1.0" encoding="utf-8"?>  
<resources>  
    <attr name="borderColor" format="color" />
    <attr name="borderWidth" format="dimension" />

    <declare-styleable name="RoundImageView">  
        <attr name="borderColor" />
        <attr name="borderWidth" />
    </declare-styleable>  
</resources>  

View attrs_imageviewplus.xml

format中color表示颜色值,dimension表示尺寸值,其他的格式不再赘述。


从构造方法中获得自定义View的属性

新建一个RoundImageView类,继承ImageView,正如我们上面所述的这是一种继承控件(好像由于版本原因,单单继承ImageView无法正常显示,Android Studio自身给我提供了这样一个新的继承android.support.v7.widget.AppCompatImageView,可以使用)

public class RoundImageView extends android.support.v7.widget.AppCompatImageView{
    private Paint mPaintBitmap = new Paint(Paint.ANTI_ALIAS_FLAG);
    private Paint mPaintBorder = new Paint(Paint.ANTI_ALIAS_FLAG);
    private Bitmap mRawBitmap;
    private BitmapShader mShader;
    private Matrix mMatrix = new Matrix();
    ...

之后是在构造方法中获取我们刚刚定义的新的属性

    private static final int DEFAULT_BORDER_COLOR = Color.TRANSPARENT;
    private static final int DEFAULT_BORDER_WIDTH = 0;

    public ImageViewPlus(Context context, AttributeSet attrs) {
        super(context, attrs);
        // 从attrs.xml文件中读取我们对RoundImageView的配置信息
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ImageViewPlus);
        mBorderColor = ta.getColor(R.styleable.ImageViewPlus_borderColor, DEFAULT_BORDER_COLOR);
        mBorderWidth = ta.getDimensionPixelSize(R.styleable.ImageViewPlus_borderWidth, dip2px(DEFAULT_BORDER_WIDTH));
        ta.recycle();
    }
    ...

重写onDraw以及onMeasure

在View实现之前,View会先做一次测量,算出自己需要占用多大的面积,是一个Measure过程。View给我们提供了onMeasure的接口去实现测量方法,这个重写是可选的,今天这个空间并不需要,我们需要重写的是绘制接口onDraw,代码如下。

@Override
    protected void onDraw(Canvas canvas) {
        Bitmap rawBitmap = getBitmap(getDrawable());
        if (rawBitmap != null){
            int viewWidth = getWidth();
            int viewHeight = getHeight();
            int viewMinSize = Math.min(viewWidth, viewHeight);
            float dstWidth = viewMinSize;
            float dstHeight = viewMinSize;
            if (mShader == null || !rawBitmap.equals(mRawBitmap)){
                mRawBitmap = rawBitmap;
                mShader = new BitmapShader(mRawBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
            }
            if (mShader != null){
                mMatrix.setScale((dstWidth - mBorderWidth * 2) / rawBitmap.getWidth(), (dstHeight - mBorderWidth * 2) / rawBitmap.getHeight());
                mShader.setLocalMatrix(mMatrix);
            }
            mPaintBitmap.setShader(mShader);
            mPaintBorder.setStyle(Paint.Style.STROKE);
            mPaintBorder.setStrokeWidth(mBorderWidth);
            mPaintBorder.setColor(mBorderColor);
            float radius = viewMinSize / 2.0f;
            canvas.drawCircle(radius, radius, radius - mBorderWidth / 2.0f, mPaintBorder);
            canvas.translate(mBorderWidth, mBorderWidth);
            canvas.drawCircle(radius - mBorderWidth, radius - mBorderWidth, radius - mBorderWidth, mPaintBitmap);
        } else {
            super.onDraw(canvas);
        }
    }

    private Bitmap getBitmap(Drawable drawable){
        if (drawable instanceof BitmapDrawable){
            return ((BitmapDrawable)drawable).getBitmap();
        } else if (drawable instanceof ColorDrawable){
            Rect rect = drawable.getBounds();
            int width = rect.right - rect.left;
            int height = rect.bottom - rect.top;
            int color = ((ColorDrawable)drawable).getColor();
            Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
            Canvas canvas = new Canvas(bitmap);
            canvas.drawARGB(Color.alpha(color), Color.red(color), Color.green(color), Color.blue(color));
            return bitmap;
        } else {
            return null;
        }
    }

    private int dip2px(int dipVal)
    {
        float scale = getResources().getDisplayMetrics().density;
        return (int)(dipVal * scale + 0.5f);
    }
}

关于代码中的一些解释
mMatrix.setScale与mShader.setLocalMatrix主要是为了保证图片在不同尺寸的手机大小下,固定大小的头像能够自适应缩放来吻合背景。
mPaintBitmap.setShader决定了圆形中的具体内容,而canvas.drawCircle 决定了画出来的形状是圆形
边框的实现则是先用实心纯色的Paint画了一个圆边,再在其中画圆形图片


使用自定义控件

最后就可以在我们的布局中使用我们自定义的控件拉,贴一下我自己的布局与效果图,大家可以做一下参考


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_weight="10" />

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_weight="1">

    <com.example.aaron_zrrrrr.gcms.view.RoundImageView
        android:id="@+id/roundImageView"
        android:layout_centerInParent="true"
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:src="@drawable/login"
        />
</RelativeLayout>

<LinearLayout
    android:id="@+id/accountRel"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:clickable="true"
    android:orientation="horizontal"
    android:layout_weight="2">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_weight="1" />

    <EditText
        android:id="@+id/account"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_weight="2"
        android:hint="account"
        android:textSize="15sp" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_weight="1" />
</LinearLayout>

<LinearLayout
    android:id="@+id/pwdRel"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:clickable="true"
    android:orientation="horizontal"
    android:layout_weight="2">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_weight="1" />

    <EditText
        android:id="@+id/pwd"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_weight="2"
        android:hint="account"
        android:textSize="15sp" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_weight="1" />
</LinearLayout>

<LinearLayout
    android:id="@+id/loginRel"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:clickable="true"
    android:orientation="horizontal"
    android:layout_weight="2">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_weight="1" />

    <Button
        android:id="@+id/login"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_weight="2"
        android:hint="Login"
        android:textSize="15sp" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_weight="1" />
</LinearLayout>

<TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_weight="30" />
</LinearLayout>

丑丑的登录界面OuO
这里写图片描述


参考源码:http://www.cnblogs.com/snser/p/5159126.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值