经过上一篇的介绍,大家对于自定义View一定有了一定的认识,接下来我们就以实现一个图片下显示文字的自定义View来练习一下。废话不多说,下面进入我们的正题,首先看一下我们的思路,1、我们需要通过在values文件夹下添加一个attrs的文件,里面设置我们的自定义属性;2、通过重写View类,来获得我们设置的自定义属性的参数,并进行绘制;3、在我们的视图文件中进行引用。好了到这里我们的基本思路就已经形成,下面我们开始进行我们的实战编码操作。

  第一步:在res目录下,values文件夹下,新建一个attrs.xml文件:

<?xml version="1.0" encoding="utf-8"?>
<resources> <attr name="TitleText" format="string" />
<attr name="TitleColor" format="color" />
<attr name="TitleSize" format="dimension" /> <attr name="image" format="reference" />
<attr name="imageScaleType">
<enum name="fillXY" value="" />
<enum name="center" value="" />
</attr> <declare-styleable name="CustomImageView">
<attr name="TitleText" />
<attr name="TitleColor" />
<attr name="TitleSize" />
<attr name="image" />
<attr name="imageScaleType" />
</declare-styleable> </resources>

  提示一下:format对应的是该参数的值类型

  第二步:重写我们的View类:

public class MySelfImageView extends View {

    /*
* 图片区域
*/
Rect imageRect;
/*
* 文字区域
*/
Rect titleRect;
/*
* 画笔对象
*/
Paint mPaint;
/*
* 图片标题文字
*/
String titleText;
/*
* 图片标题文字颜色
*/
int titleColor;
/*
* 图片标题文字大小
*/
int titleSize;
/*
* 图片资源
*/
Bitmap image;
/*
* 图片资源显示样式
*/
int imageFillXY; int mWidth = 0;
int mHeight = 0; public MySelfImageView(Context context) {
this(context, null);
} public MySelfImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
} public MySelfImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//获取自定义设置的属性
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomImageView, defStyleAttr, 0);
int n = typedArray.getIndexCount();
for(int i=0; i<n; i++){
int att = typedArray.getIndex(i);
//分别取出自定义属性设置的值
switch (att) {
case R.styleable.CustomImageView_TitleText:
titleText = typedArray.getString(att);
break;
case R.styleable.CustomImageView_TitleColor:
titleColor = typedArray.getColor(att, Color.RED);
break;
case R.styleable.CustomImageView_TitleSize:
titleSize = typedArray.getDimensionPixelSize(att, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
16, getResources().getDisplayMetrics()));
break;
case R.styleable.CustomImageView_image:
image = BitmapFactory.decodeResource(getResources(), typedArray.getResourceId(att, 0));
break;
case R.styleable.CustomImageView_imageScaleType:
imageFillXY = typedArray.getInt(att, 0);
break;
}
}
typedArray.recycle(); imageRect = new Rect();
mPaint = new Paint();
titleRect = new Rect();
mPaint.setTextSize(titleSize);
// 计算描绘字体需要的范围
mPaint.getTextBounds(titleText, 0, titleText.length(), titleRect);
} @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
/**
* 设置宽度
*/
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec); if(specMode == MeasureSpec.EXACTLY){
mWidth = specSize;
}else{
// 由图片决定的宽
int desireByImg = getPaddingLeft() + getPaddingRight() + image.getWidth();
// 由字体决定的宽
int desireByTitle = getPaddingLeft() + getPaddingRight() + titleRect.width(); if (specMode == MeasureSpec.AT_MOST){// wrap_content
int desire = Math.max(desireByImg, desireByTitle);
mWidth = Math.min(desire, specSize);
}
} /**
* 设置高度
*/
specMode = MeasureSpec.getMode(heightMeasureSpec);
specSize = MeasureSpec.getSize(heightMeasureSpec); if(specMode == MeasureSpec.EXACTLY){
mHeight = specSize;
}else{
int desire = getPaddingTop() + getPaddingBottom() + image.getHeight() + titleRect.height();
if (specMode == MeasureSpec.AT_MOST){// wrap_content
mHeight = Math.min(desire, specSize);
}
}
setMeasuredDimension(mWidth, mHeight);
} @Override
protected void onDraw(Canvas canvas) {
/**
* 边框
*/
mPaint.setStrokeWidth(4);//设置空心线宽
mPaint.setStyle(Paint.Style.STROKE);//设置画笔为空心
mPaint.setColor(Color.CYAN);//设置画笔颜色
canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint); imageRect.left = getPaddingLeft();
imageRect.right = mWidth - getPaddingRight();
imageRect.top = getPaddingTop();
imageRect.bottom = mHeight - getPaddingBottom(); mPaint.setColor(titleColor);
mPaint.setStyle(Style.FILL);
/**
* 当前设置的宽度小于字体需要的宽度,将字体改为xxx...
*/
if (titleRect.width() > mWidth) {
TextPaint paint = new TextPaint(mPaint);
String msg = TextUtils.ellipsize(titleText, paint, (float) mWidth - getPaddingLeft() - getPaddingRight(),
TextUtils.TruncateAt.END).toString();
canvas.drawText(msg, getPaddingLeft(), mHeight - getPaddingBottom(), mPaint);
} else {
//正常情况,将字体居中
canvas.drawText(titleText, mWidth / 2 - titleRect.width() * 1.0f / 2, mHeight - getPaddingBottom(), mPaint);
} //取消使用掉的块
imageRect.bottom -= titleRect.height(); if (imageFillXY == 0) {
canvas.drawBitmap(image, null, imageRect, mPaint);
} else {
//计算居中的矩形范围
imageRect.left = mWidth / 2 - image.getWidth() / 2;
imageRect.right = mWidth / 2 + image.getWidth() / 2;
imageRect.top = (mHeight - titleRect.height()) / 2 - image.getHeight() / 2;
imageRect.bottom = (mHeight - titleRect.height()) / 2 + image.getHeight() / 2; canvas.drawBitmap(image, null, imageRect, mPaint);
}
} }

  第三步:布局文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:zhy="http://schemas.android.com/apk/res/com.example.myselfview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" > <com.example.myselfview.view.MySelfImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:padding="10dp"
zhy:image="@drawable/ic_launcher"
zhy:imageScaleType="center"
zhy:TitleText="hello andorid ! "
zhy:TitleColor="#ff0000"
zhy:TitleSize="30sp" /> <com.example.myselfview.view.MySelfImageView
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:padding="10dp"
zhy:image="@drawable/ic_launcher"
zhy:imageScaleType="center"
zhy:TitleText="helloworldwelcome"
zhy:TitleColor="#00ff00"
zhy:TitleSize="20sp" /> <com.example.myselfview.view.MySelfImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:padding="10dp"
zhy:image="@drawable/im"
zhy:imageScaleType="center"
zhy:TitleText="山水美景"
zhy:TitleColor="#ff0000"
zhy:TitleSize="12sp" /> </LinearLayout>

  最后效果图:

  

  相对第一、三步,第二步相对更复杂一些,下面我就对第二步里面的具体内容进行一下解析:

//获取自定义设置的属性
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomImageView, defStyleAttr, 0);
int n = typedArray.getIndexCount();
for(int i=0; i<n; i++){
int att = typedArray.getIndex(i);
//分别取出自定义属性设置的值
switch (att) {
case R.styleable.CustomImageView_TitleText:
titleText = typedArray.getString(att);
break;
case R.styleable.CustomImageView_TitleColor:
titleColor = typedArray.getColor(att, Color.RED);
break;
case R.styleable.CustomImageView_TitleSize:
titleSize = typedArray.getDimensionPixelSize(att, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
16, getResources().getDisplayMetrics()));
break;
case R.styleable.CustomImageView_image:
image = BitmapFactory.decodeResource(getResources(), typedArray.getResourceId(att, 0));
break;
case R.styleable.CustomImageView_imageScaleType:
imageFillXY = typedArray.getInt(att, 0);
break;
}
}
typedArray.recycle();

  作用是获取我们在attrs中设置的自定义参数,在布局文件中进行的赋值。

  onMeasure()方法用于设置控件的宽度,控件的长宽值如何取得呢?下面我们就来进行一下解析:

  

  最后是我们绘图方法onDraw(Canvas canvas):

@Override
protected void onDraw(Canvas canvas) {
/**
* 空心矩形绘制
*/
mPaint.setStrokeWidth(4);//设置空心线宽
mPaint.setStyle(Paint.Style.STROKE);//设置画笔为空心
mPaint.setColor(Color.CYAN);//设置画笔颜色
canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint); imageRect.left = getPaddingLeft();//获取图片左上角坐标
imageRect.right = mWidth - getPaddingRight();//获取图片右上角坐标
imageRect.top = getPaddingTop();//获取图片距控件顶部距离
imageRect.bottom = mHeight - getPaddingBottom();//确定图片底部坐标 mPaint.setColor(titleColor);//设置文本的颜色
mPaint.setStyle(Style.FILL);//设置文本内容的填充方式
/**
* 当前设置的宽度小于字体需要的宽度,将字体改为xxx...
*/
if (titleRect.width() > mWidth) {
TextPaint paint = new TextPaint(mPaint);
String msg = TextUtils.ellipsize(titleText, paint, (float) mWidth - getPaddingLeft() - getPaddingRight(),
TextUtils.TruncateAt.END).toString();
canvas.drawText(msg, getPaddingLeft(), mHeight - getPaddingBottom(), mPaint);
} else {
//正常情况,将字体居中
canvas.drawText(titleText, mWidth / 2 - titleRect.width() * 1.0f / 2, mHeight - getPaddingBottom(), mPaint);
} //取消使用掉的块
imageRect.bottom -= titleRect.height();//因为文字内容占用一定的高度,所以图片的底部坐标需要上移。 if (imageFillXY == 0) {
canvas.drawBitmap(image, null, imageRect, mPaint);
} else {
//计算居中的矩形范围
imageRect.left = mWidth / 2 - image.getWidth() / 2;
imageRect.right = mWidth / 2 + image.getWidth() / 2;
imageRect.top = (mHeight - titleRect.height()) / 2 - image.getHeight() / 2;
imageRect.bottom = (mHeight - titleRect.height()) / 2 + image.getHeight() / 2; canvas.drawBitmap(image, null, imageRect, mPaint);
}
}

  好了,到这里关于自定义View的初步学习就介绍完毕,如果你有更好的关于自定义View的文章,欢迎留言交流。

Android自定义View初步的更多相关文章

  1. Android自定义View(LineBreakLayout-自动换行的标签容器)

      最近一段时间比较忙,都没有时间更新博客,今天公司的事情忙完得空,继续为我的自定义控件系列博客添砖加瓦.本篇博客讲解的是标签自动换行的布局容器,正好前一阵子有个项目中需要,想了想没什么难度就自己弄了 ...

  2. Android自定义View实战(SlideTab-可滑动的选择器)

    转载请标明出处: http://blog.csdn.net/xmxkf/article/details/52178553 本文出自:[openXu的博客] 目录: 初步分析重写onDraw绘制 重写o ...

  3. Android自定义View 画弧形,文字,并增加动画效果

    一个简单的Android自定义View的demo,画弧形,文字,开启一个多线程更新ui界面,在子线程更新ui是不允许的,但是View提供了方法,让我们来了解下吧. 1.封装一个抽象的View类   B ...

  4. (转)[原] Android 自定义View 密码框 例子

    遵从准则 暴露您view中所有影响可见外观的属性或者行为. 通过XML添加和设置样式 通过元素的属性来控制其外观和行为,支持和重要事件交流的事件监听器 详细步骤见:Android 自定义View步骤 ...

  5. Android 自定义View合集

    自定义控件学习 https://github.com/GcsSloop/AndroidNote/tree/master/CustomView 小良自定义控件合集 https://github.com/ ...

  6. Android 自定义View (五)——实践

    前言: 前面已经介绍了<Android 自定义 view(四)-- onMeasure 方法理解>,那么这次我们就来小实践下吧 任务: 公司现有两个任务需要我完成 (1)监测液化天然气液压 ...

  7. Android 自定义 view(四)—— onMeasure 方法理解

    前言: 前面我们已经学过<Android 自定义 view(三)-- onDraw 方法理解>,那么接下我们还需要继续去理解自定义view里面的onMeasure 方法 推荐文章: htt ...

  8. Android 自定义 view(三)—— onDraw 方法理解

    前言: 上一篇已经介绍了用自己定义的属性怎么简单定义一个view<Android 自定义view(二) -- attr 使用>,那么接下来我们继续深究自定义view,下一步将要去简单理解自 ...

  9. Android 自定义view(二) —— attr 使用

    前言: attr 在前一篇文章<Android 自定义view -- attr理解>已经简单的进行了介绍和创建,那么这篇文章就来一步步说说attr的简单使用吧 自定义view简单实现步骤 ...

随机推荐

  1. In-Memory:内存优化表的事务处理

    内存优化表(Memory-Optimized Table,简称MOT)使用乐观策略(optimistic approach)实现事务的并发控制,在读取MOT时,使用多行版本化(Multi-Row ve ...

  2. CoreCRM 开发实录 —— Profile

    再简单的功能,也需要一坨代码的支持.Profile 的编辑功能主要就是修改个人的信息.比如用户名.头像.性别.电话--虽然只是一个编辑界面,但添加下来,涉及了6个文件的修改和7个新创建的文件.各种生成 ...

  3. 一起学微软Power BI系列-使用技巧(4)Power BI中国版企业环境搭建和帐号问题

    千呼万唤的Power BI中国版终于落地了,相信12月初的微软技术大会之后已经铺天盖地的新闻出现了,不错,Power BI中国版真的来了,但还有些遗憾,国际版的一些重量级服务如power bi emb ...

  4. 菜鸟学Struts2——Actions

    在对Struts2的工作原理学习之后,对Struts2的Action进行学习.主要对Struts2文档Guides中的Action分支进行学习,如下图: 1.Model Driven(模型驱动) St ...

  5. MVC5 网站开发之九 网站设置

    网站配置一般用来保存网站的一些设置,写在配置文件中比写在数据库中要合适一下,因为配置文件本身带有缓存,随网站启动读入缓存中,速度更快,而保存在数据库中要单独为一条记录创建一个表,结构不够清晰,而且读写 ...

  6. 从netty-example分析Netty组件续

    上文我们从netty-example的Discard服务器端示例分析了netty的组件,今天我们从另一个简单的示例Echo客户端分析一下上个示例中没有出现的netty组件. 1. 服务端的连接处理,读 ...

  7. android 两种实现计时器时分秒的实现,把时间放在你的手中~

    可能我们在开发中会时常用到计时器这玩意儿,比如在录像的时候,我们可能需要在右上角显示一个计时器.这个东西其实实现起来非常简单. 只需要用一个控件Chronometer,是的,就这么简单,我都不好意思讲 ...

  8. ASP.NET MVC5----常见的数据注解和验证

    只要一直走,慢点又何妨. 在使用MVC模式进行开发时,数据注解是经常使用的(模型之上操作),下面是我看书整理的一些常见的用法. 什么是验证,数据注解 验证 从全局来看,发现逻辑仅是整个验证的很小的一部 ...

  9. Effective java笔记(二),所有对象的通用方法

    Object类的所有非final方法(equals.hashCode.toString.clone.finalize)都要遵守通用约定(general contract),否则其它依赖于这些约定的类( ...

  10. Linux常用命令

    命令格式与目录处理命令 ls 命令格式与目录处理命令 ls 命令格式:命令 [-选项][参数] 例:ls -la /etc 说明: 1)个别命令使用不遵循格式 2)当有多个选项时,可以写在一起 3)简 ...