安卓自定义控件(三)实现自定义View
前面两篇博客,把View绘制的方法说了一下,但是,我们只在onDraw里面做文章,控件都是直接传入一个Context,还不能在布局文件里使用自定义View。这一篇博客,就不再讲绘制,在我们原先的基础上,真正实现一个可用的View。
工具类:ViewHelper(View处理常用方法封装)
安卓自定义控件(一)Canvas、Paint、Shader、Xfermode
安卓自定义控件(二)BitmapShader、ShapeDrawable、Shape
安卓自定义控件(三)实现自定义View
方案:
1、在attrs.xml文件中添加View的自定义属性。
2、继承一个已有的View,比如Button,这样就不要考虑onMeasure方法的内容,因为安卓自带的View肯定已经把它写好了。
3、继承一个View,我们自己设计,实现View的全部功能。
自定义属性
首先,学会自定义属性,两个步骤1、配置attrs.xml文件,写入我们自定义属性;2、在View中引用我们的自定义属性。
配置attrs.xml文件
在res/values包下新建attrs.xml文件,配置好自定义控件名称、自定义属性名称。
我自定义了一个View,名叫RectView,自定义两个属性,rect_hight和rect_width,然后配置文件如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="RectView">
<attr name="rect_hight" format="reference|dimension"/>
<attr name="rect_width" format="reference|dimension"/>
</declare-styleable>
</resources>
format的可选参数类型:
“reference” //引用
“color” //颜色
“boolean” //布尔值
“dimension” //尺寸值
“float” //浮点值
“integer” //整型值
“string” //字符串
“fraction” //百分数,比如200%
使用自定义属性
在自定义View中获取自定义属性:
//获取TypedArray对象
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RectView);
//获取自定义属性
int mWidth = typedArray.getDimensionPixelSize(R.styleable.RectView_rect_width, 300);
//回收TypedArray
typedArray.recycle();
或者使用for循环遍历
int n = typedArray.getIndexCount();
for (int i = 0; i < n; i++) {
switch (typedArray.getIndex(i)){
case R.styleable.RectView_rect_width:
int mWidth = typedArray.getDimensionPixelSize(R.styleable.RectView_rect_width, 300);
break;
}
}
Demo
使用上面给出的attrs.xml文件,在布局文件中使用我们的自定义View,在使用自定义属性的时候,注意在布局中写这一行:xmlns:app=”http://schemas.android.com/apk/res-auto”,在Android Studio下输入App回车即可。
布局文件:
<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:layout_height="match_parent"
android:orientation="vertical">
<main.com.example.administrator.demo.design.canvas.RectView
android:layout_width="200dp"
android:layout_height="200dp"
app:rect_width="150dp"
app:rect_hight="150dp"
android:background="@android:color/holo_blue_light"/>
</LinearLayout>
自定义View代码:
/**
* 在布局文件中使用自定义View
* Created by ChenSS on 2016/11/24.
*/
public class RectView extends View {
private int mWidth, mHeight;
private Paint mPaint;
public RectView(Context context) {
this(context, null);
}
public RectView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RectView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//获取所需的控件参数
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RectView);
mWidth = typedArray.getDimensionPixelSize(R.styleable.RectView_rect_width, 300);
mHeight = typedArray.getDimensionPixelSize(R.styleable.RectView_rect_hight, 500);
mPaint = new Paint();
mPaint.setAntiAlias(true);
//注意回收
typedArray.recycle();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRoundRect(30, 30, mWidth, mHeight, 40, 20, mPaint);
}
}
实现的效果:
简单地实现了一个自定义控件,绘制一个圆角矩形,从效果图可以看出,layout_width和layout_height决定了View的大小(蓝色区域),而我们自定义的rect_width和rect_hight用于绘制我们自己的图形(黑色区域)。
自定义View方式一:继承已有的View
- 继承Button,直接拷贝Button的所有构造函数;
- 注意:调用3个参数的构造函数时,第三个参数使用:R.attr.buttonStyle,这样使用wrap_content的时候,显示的大小和默认Button相同;
- 绘制思路:使用Xfermode,先绘制圆角矩形,然后设置PorterDuff.Mode,再绘制位图,将位图中圆角矩形的部分显示出来;
- 也可以使用Shape的子类绘制圆角矩形。
/**
* 自定义圆角矩形ImageView
* Created by ChenSS on 2016/11/24.
*/
public class RectView extends Button {
private Paint mPaint;
private Bitmap mBitmap;
private PorterDuffXfermode xfermode;
//边框
private static final int RECT_BORDER=5;
//圆角
private static final int RECT_RADIO=10;
public RectView(Context context) {
this(context, null);
}
public RectView(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.buttonStyle);
}
public RectView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public RectView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
xfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
mBitmap = ViewHelper.findBitmapById(context, R.mipmap.view_shape);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(Color.GRAY);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//位图改变大小
mBitmap=ViewHelper.zoomImage(mBitmap,getMeasuredWidth(),getMeasuredHeight());
//描边
canvas.drawRoundRect(0, 0, mBitmap.getWidth(), mBitmap.getHeight(),
RECT_BORDER+RECT_RADIO, RECT_BORDER+RECT_RADIO, mPaint);
//离屏缓存
int sc = canvas.saveLayer(0, 0, mBitmap.getWidth(), mBitmap.getHeight(), null, Canvas.ALL_SAVE_FLAG);
//图片遮罩
canvas.drawRoundRect(RECT_BORDER, RECT_BORDER,
mBitmap.getWidth()-RECT_BORDER, mBitmap.getHeight()-RECT_BORDER,
RECT_RADIO, RECT_RADIO, mPaint);
mPaint.setXfermode(xfermode);
canvas.drawBitmap(mBitmap,RECT_BORDER, RECT_BORDER, mPaint);
mPaint.setXfermode(null);
// 还原画布
canvas.restoreToCount(sc);
}
}
采用这种方式自定义View,实现起来就十分简单,直接在onDraw中调用getMeasuredWidth()、getMeasuredHeight()就可以了,不用我们多考虑什么。
自定义View方式二:继承View
如果是继承View,就要重新考虑一下View的大小了,在Xml布局中,会有3种情况:match_parent、wrap_content或者指定一个确切的大小,区分这些情况,我们就要考虑MeasureSpec的Mode了,Mode一共三种类型:
- EXACTLY:一般是设置了明确的值,或者是MATCH_PARENT
- AT_MOST:表示子布局限制在一个最大值内,一般为WARP_CONTENT
- UNSPECIFIED:表示子布局想要多大就多大,如果你想绘制Listview这样的东西,就考虑使用吧
对上面的代码做一些修改,把继承Button改成继承View,如果不重写onMeasure方法,你会发现match_parent、wrap_content效果是一样的,下面代码简单地重写了一下onMeasure方法。
/**
* 继承View实现自定义控件
* Created by ChenSS on 2016/11/24.
*/
public class RectView extends View {
private Paint mPaint;
private Bitmap mBitmap;
private PorterDuffXfermode xfermode;
//边框
private static final int RECT_BORDER = 5;
//圆角
private static final int RECT_RADIO = 10;
//默认宽度
private static final int DEFAULT_WIDTH = 600;
//默认高度
private static final int DEFAULT_HEIGHT = 200;
public RectView(Context context) {
this(context, null);
}
public RectView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RectView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public RectView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
xfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
mBitmap = ViewHelper.findBitmapById(context, R.mipmap.view_shape);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(Color.GRAY);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//位图改变大小
mBitmap = ViewHelper.zoomImage(mBitmap, getMeasuredWidth(), getMeasuredHeight());
//描边
canvas.drawRoundRect(0, 0, mBitmap.getWidth(), mBitmap.getHeight(),
RECT_BORDER + RECT_RADIO, RECT_BORDER + RECT_RADIO, mPaint);
//离屏缓存
int sc = canvas.saveLayer(0, 0, mBitmap.getWidth(), mBitmap.getHeight(), null, Canvas.ALL_SAVE_FLAG);
//图片遮罩
canvas.drawRoundRect(RECT_BORDER, RECT_BORDER,
mBitmap.getWidth() - RECT_BORDER, mBitmap.getHeight() - RECT_BORDER,
RECT_RADIO, RECT_RADIO, mPaint);
mPaint.setXfermode(xfermode);
canvas.drawBitmap(mBitmap, RECT_BORDER, RECT_BORDER, mPaint);
mPaint.setXfermode(null);
// 还原画布
canvas.restoreToCount(sc);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
width = DEFAULT_WIDTH;
}
if (heightMode == MeasureSpec.EXACTLY) {
height=heightSize;
} else {
height = DEFAULT_HEIGHT;
}
setMeasuredDimension(width, height);
}
}
自定义View进阶:Java代码修改View的显示
有时候,我们会有这样的需求,当我们点击图片的时候,会有一个新的图片替换掉旧的,其中的关键方法是postInvalidate(),在调用postInvalidate()之前,我们需要对参数进行一些设置,调用postInvalidate()之后,onDraw中的内容会重新执行一遍。自定义View的其它方法请参考:自定义View常用方法汇总
Demo:
/**
* 继承View实现自定义控件
* Created by ChenSS on 2016/11/24.
*/
public class RectView extends View {
private Paint mPaint;
private Bitmap mBitmap;
private PorterDuffXfermode xfermode;
private Context mContext;
//边框
private static final int RECT_BORDER = 5;
//圆角
private static final int RECT_RADIO = 10;
//默认宽度
private static final int DEFAULT_WIDTH = 600;
//默认高度
private static final int DEFAULT_HEIGHT = 200;
public RectView(Context context) {
this(context, null);
}
public RectView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RectView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public RectView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mContext=context;
xfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
mBitmap = ViewHelper.findBitmapById(context, R.mipmap.view_shape);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(Color.GRAY);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//位图改变大小
mBitmap = ViewHelper.zoomImage(mBitmap, getMeasuredWidth(), getMeasuredHeight());
//描边
canvas.drawRoundRect(0, 0, mBitmap.getWidth(), mBitmap.getHeight(),
RECT_BORDER + RECT_RADIO, RECT_BORDER + RECT_RADIO, mPaint);
//离屏缓存
int sc = canvas.saveLayer(0, 0, mBitmap.getWidth(), mBitmap.getHeight(), null, Canvas.ALL_SAVE_FLAG);
//图片遮罩
canvas.drawRoundRect(RECT_BORDER, RECT_BORDER,
mBitmap.getWidth() - RECT_BORDER, mBitmap.getHeight() - RECT_BORDER,
RECT_RADIO, RECT_RADIO, mPaint);
mPaint.setXfermode(xfermode);
canvas.drawBitmap(mBitmap, RECT_BORDER, RECT_BORDER, mPaint);
mPaint.setXfermode(null);
// 还原画布
canvas.restoreToCount(sc);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
width = DEFAULT_WIDTH;
}
if (heightMode == MeasureSpec.EXACTLY) {
height=heightSize;
} else {
height = DEFAULT_HEIGHT;
}
setMeasuredDimension(width, height);
}
/**
* 修改图片
* @param drawableId 图片资源ID
*/
public void setBitmap(int drawableId){
mBitmap.recycle();
mBitmap = ViewHelper.findBitmapById(mContext, drawableId);
if(mBitmap.isRecycled())
return;
postInvalidate();
}
}
上面的代码中,我们添加了一个setBitmap方法,这个方法用于获取本地的图片Id,然后修改自定义View中的mBitmap对象,然后调用postInvalidate()方法进行View的重新绘制。
在Activity中修改View的显示
public class RectActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_rect);
final RectView rectView= (RectView) findViewById(R.id.rect_id);
rectView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//替换背景
rectView.setBitmap(R.mipmap.view_robot);
}
});
}
}
点击前:
点击后:
安卓自定义控件(三)实现自定义View的更多相关文章
- Android绘图机制(三)——自定义View的实现方式以及半弧圆新控件
Android绘图机制(三)--自定义View的三种实现方式以及实战项目操作 在Android绘图机制(一)--自定义View的基础属性和方法 里说过,实现自定义View有三种方式,分别是 1.对现有 ...
- 安卓开发28:自定义View类
自定义View类 通过自定义View类,可以自定义复杂的,按照自己需求的控件. 一个简单的例子 mainActivity.java 这个里面就是最普通的代码,但是给自定义的控件加上了一个onclick ...
- 安卓自定义控件(二)BitmapShader、ShapeDrawable、Shape
第一篇博客中,我已经Canvas.Paint.Shader.Xfermode这些对象做了总结,而现在这篇文章主要介绍BitmapShader位图渲染,Xfermode如何实际应用,还有形状的绘制.不过 ...
- Android之自定义View的实现
对于学习Android开发的小童鞋对于自定义View一定不会陌生,相信大家对它是又爱又恨,爱它可以跟随我们的心意设计出漂亮的效果:恨它想要完全流畅掌握,需要一定的功夫.对于初学者来说确实很不容易,网上 ...
- 深入了解View实现原理以及自定义View详解
下面几篇文章对View的原理讲的非常详细. Android LayoutInflater原理分析,带你一步步深入了解View(一) Android视图绘制流程完全解析,带你一步步深入了解View(二) ...
- Android自定义view控件
转载自: http://blog.163.com/ppy2790@126/blog/static/103242241201382210910473/ 开发自定义控件的步骤: 1.了解View的工作原理 ...
- android 自定义view 前的基础知识
本篇文章是自己自学自定义view前的准备,具体参考资料来自 Android LayoutInflater原理分析,带你一步步深入了解View(一) Android视图绘制流程完全解析,带你一步步深入了 ...
- Android绘图机制(二)——自定义View绘制形, 圆形, 三角形, 扇形, 椭圆, 曲线,文字和图片的坐标讲解
Android绘图机制(二)--自定义View绘制形, 圆形, 三角形, 扇形, 椭圆, 曲线,文字和图片的坐标讲解 我们要想画好一些炫酷的View,首先我们得知道怎么去画一些基础的图案,比如矩形,圆 ...
- 安卓自定义View(一)自定义控件属性
自定义View增加属性第一步:定义属性资源文件 在/res/values 文件夹下建立"Values XML layout",按照如下定义一个textview的属性 <?xm ...
随机推荐
- python安装(python2.7)
1.下载python 进入官网下载安装 点击打开链接(官网地址:https://www.python.org/downloads/),进入官网后根据自己需求选择python2 或者 python3 2 ...
- win10 uwp 上传Nuget 让别人用我们的库
Nuget 我们的开发经常使用别人的dll,那么我们需要每次都从网上下载,然后复制到我们的项目, 而不知道我们的dll是否安全? 当我们的库更新的时候,我们又需要从网上搜索,这样不好,于是我们就用Nu ...
- openwrt下 samba设置
1. 增加用户: 可以手工直接修改 /etc/passwd, 增加一行: samba:x:102:100::/home/samba:#也可命令如下opkg updateopkg install sha ...
- JavaFX引入资源问题
描述 - 使用javafx 引入资源的时候 抛出异常 在swing引入资源 采取相对路径即可,而javafx不是 ImageView imageNode = (ImageView) root.look ...
- MySQL中字段类型为timestamp的小坑
之前遇到过一个MySQL的字段为timestamp类型的小坑. MySQL中一个字段存储时间类型数据的时候,该字段的类型如果为timestamp类型的话,最多只能存储到2038-01-19 11:14 ...
- 损失函数&经验函数
损失函数:度量模型一次预测的好坏 经验函数:度量模型平均意义下的预测好坏 输出预测值F(x)与实际值Y可能不一致也可能一致,损失函数(Loss function)可以度量一次预测,记作L(Y,F(x) ...
- Oracle参数设置之set与reset的实际案例
Oracle参数设置之set与reset的实际案例 环境:Oracle 10.2.0.5 RAC 需求:节点1的aq_tm_processes要求恢复默认,节点2设置要求保持不变 1.构建测试环境 2 ...
- JAVA基础知识总结:八
面向对象语言的三大特性;封装.继承.多态 一.面向对象语言特性之封装 1.什么是封装? 一个类中某些属性,如果不希望外界直接访问,我们可以将这个属性作为私有的,可以给外界暴露出来一个访问的方法 使用封 ...
- 微信公众号支付|微信H5支付|微信扫码支付|小程序支付|APP微信支付解决方案总结
最近负责的一些项目开发,都用到了微信支付(微信公众号支付.微信H5支付.微信扫码支付.APP微信支付).在开发的过程中,在调试支付的过程中,或多或少都遇到了一些问题,今天总结下,分享,留存. 先说注意 ...
- TinyOS编程思想和Nesc基础语法
TinyOS操作系统由nesc语言写成,从程序员角度看,它的基本作用就是提供了一组API接口以及一些编程规则. 具体来说,基于nesc语言的TinyOS编程行为具有以下特点: a.兼容C语言:使用ne ...