前面两篇博客,把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

  1. 继承Button,直接拷贝Button的所有构造函数;
  2. 注意:调用3个参数的构造函数时,第三个参数使用:R.attr.buttonStyle,这样使用wrap_content的时候,显示的大小和默认Button相同;
  3. 绘制思路:使用Xfermode,先绘制圆角矩形,然后设置PorterDuff.Mode,再绘制位图,将位图中圆角矩形的部分显示出来;
  4. 也可以使用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的更多相关文章

  1. Android绘图机制(三)——自定义View的实现方式以及半弧圆新控件

    Android绘图机制(三)--自定义View的三种实现方式以及实战项目操作 在Android绘图机制(一)--自定义View的基础属性和方法 里说过,实现自定义View有三种方式,分别是 1.对现有 ...

  2. 安卓开发28:自定义View类

    自定义View类 通过自定义View类,可以自定义复杂的,按照自己需求的控件. 一个简单的例子 mainActivity.java 这个里面就是最普通的代码,但是给自定义的控件加上了一个onclick ...

  3. 安卓自定义控件(二)BitmapShader、ShapeDrawable、Shape

    第一篇博客中,我已经Canvas.Paint.Shader.Xfermode这些对象做了总结,而现在这篇文章主要介绍BitmapShader位图渲染,Xfermode如何实际应用,还有形状的绘制.不过 ...

  4. Android之自定义View的实现

    对于学习Android开发的小童鞋对于自定义View一定不会陌生,相信大家对它是又爱又恨,爱它可以跟随我们的心意设计出漂亮的效果:恨它想要完全流畅掌握,需要一定的功夫.对于初学者来说确实很不容易,网上 ...

  5. 深入了解View实现原理以及自定义View详解

    下面几篇文章对View的原理讲的非常详细. Android LayoutInflater原理分析,带你一步步深入了解View(一) Android视图绘制流程完全解析,带你一步步深入了解View(二) ...

  6. Android自定义view控件

    转载自: http://blog.163.com/ppy2790@126/blog/static/103242241201382210910473/ 开发自定义控件的步骤: 1.了解View的工作原理 ...

  7. android 自定义view 前的基础知识

    本篇文章是自己自学自定义view前的准备,具体参考资料来自 Android LayoutInflater原理分析,带你一步步深入了解View(一) Android视图绘制流程完全解析,带你一步步深入了 ...

  8. Android绘图机制(二)——自定义View绘制形, 圆形, 三角形, 扇形, 椭圆, 曲线,文字和图片的坐标讲解

    Android绘图机制(二)--自定义View绘制形, 圆形, 三角形, 扇形, 椭圆, 曲线,文字和图片的坐标讲解 我们要想画好一些炫酷的View,首先我们得知道怎么去画一些基础的图案,比如矩形,圆 ...

  9. 安卓自定义View(一)自定义控件属性

    自定义View增加属性第一步:定义属性资源文件 在/res/values 文件夹下建立"Values XML layout",按照如下定义一个textview的属性 <?xm ...

随机推荐

  1. C# 相对路径转绝对路径

    如果是路径相对路径,使用 Path 转换 System.IO.Path.Combine(文件夹, relativePath); 文件夹就是相对的文件夹. 这样就可以把相对路径转绝对. 参见:http: ...

  2. 解析 .Net Core 注入 (1) 注册服务

    在学习 Asp.Net Core 的过程中,注入可以说是无处不在,对于 .Net Core 来说,它是独立的一个程序集,没有复杂的依赖项和配置文件,所以对于学习 Asp.Net Core 源码的朋友来 ...

  3. Oracle学习笔记之存储过程

                                                                                                        ...

  4. 如何设置App的启动图

    如何设置App的启动图,也就是Launch Image? Step1 1.点击Image.xcassets 进入图片管理,然后右击,弹出"New Launch Image" 2.如 ...

  5. Java并发编程之显式锁机制

    我们之前介绍过synchronized关键字实现程序的原子性操作,它的内部也是一种加锁和解锁机制,是一种声明式的编程方式,我们只需要对方法或者代码块进行声明,Java内部帮我们在调用方法之前和结束时加 ...

  6. LeetCode 53. Maximum Subarray(最大的子数组)

    Find the contiguous subarray within an array (containing at least one number) which has the largest ...

  7. Visual paradigm软件介绍

    Visual paradigm软件介绍 说起Visual Paradigm你可能并不陌生,因为此前有一款功能强大的UML软件叫Visual Paradigm for UML,在这款软件在v11.1的时 ...

  8. J - Scarily interesting! URAL - 2021

    This year at Monsters University it is decided to arrange Scare Games. At the Games all campus gathe ...

  9. 前端安全之CSRF攻击

    前端安全之CSRF攻击 转载请注明出处:unclekeith: 前端安全之CSRF攻击 CSRF定义 CSRF,即(Cross-site request forgery), 中文名为跨站请求伪造.是一 ...

  10. JsDoc脚本注释文档生成

    使用jsDoc可使用特定注释,将注释的内容生成文档,可用于生成脚本库的API文档 jsdoc 文档:   http://usejsdoc.org/