Android自定义控件(一)_2015-03-22

自定义控件教程:

1,http://blog.csdn.net/aigestudio/article/details/41212583
2,http://blog.csdn.net/aigestudio/article/details/41316141
3,http://blog.csdn.net/aigestudio/article/details/41447349
4,http://blog.csdn.net/aigestudio/article/details/41960507
5,http://blog.csdn.net/aigestudio/article/details/42677973
6,http://blog.csdn.net/aigestudio/article/details/42989325

1,Paint类

    private void init() {
        paint = new Paint();
        paint.setAntiAlias(true);//反锯齿
        paint.setColor(Color.RED);
        /*
         * 设置画笔样式为描边,圆环嘛……当然不能填充不然就么意思了
         *
         * 画笔样式分三种:
         * 1.Paint.Style.STROKE:描边
         * 2.Paint.Style.FILL_AND_STROKE:描边并填充
         * 3.Paint.Style.FILL:填充
         */
        paint.setStyle(Paint.Style.FILL);
        /*
         * 设置描边的粗细,单位:像素px
         * 注意:当setStrokeWidth(0)的时候描边宽度并不为0而是只占一个像素
         */
        paint.setStrokeWidth(paintStrokeWidth);
    }

Paint的其他方法:

  • setStrokeCap(Paint.Cap cap)
    方法,该方法用来设置我们画笔的笔触风格,上面的例子中我使用的是ROUND,表示是圆角的笔触,那么什么叫笔触呢,其实很简单,就像我们现实世界中的笔,如果你用圆珠笔在纸上戳一点,那么这个点一定是个圆,即便很小,它代表了笔的笔触形状,如果我们把一支铅笔笔尖削成方形的,那么画出来的线条会是一条弯曲的“矩形”,这就是笔触的意思。除了ROUND,Paint.Cap还提供了另外两种类型:SQUARE和BUTT

  • setStrokeJoin(Paint.Join join)
    这个方法用于设置结合处的形态,就像上面的代码中我们虽说是花了一条心电线,但是这条线其实是由无数条小线拼接成的,拼接处的形状就由该方法指定。

  • setShadowLayer(float radius, float dx, float dy, int shadowColor)
    该方法为我们绘制的图形添加一个阴影层效果:


TextPaint专门用户绘制文字的画笔

/* 
 * 初始化文字画笔 
 */
textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG | Paint.SUBPIXEL_TEXT_FLAG);
textPaint.setColor(Color.WHITE);
textPaint.setTextSize(30);
textPaint.setTextAlign(Paint.Align.CENTER);

与画笔颜色相关的类

详情见博客:2,http://blog.csdn.net/aigestudio/article/details/41316141

PorterDuffXfermode类,图形混合模式的意思

2,View类的invalidate()方法,和postInvalidate()方法

在Android中提供了一个叫invalidate()的方法来让我们重绘我们的View。poistInvalidate()方法和invaliadate方法相同,只是它可以在子线程中运行.
nvalidate()方法只能在主线程中调用,而postInvalidate()方法可以在子线程中调用。

3,Shader类(着色器)

Android自定义控件(一)_2015-03-22_第1张图片
Paste_Image.png

Shader类呢也是个灰常灰常简单的类,它有五个子类,像PathEffect一样每个子类都实现了一种Shader,Shader在三维软件中我们称之为着色器, 其作用嘛就像它的名字一样是来给图像着色的或者更通俗的说法是上色!这么说该懂了吧!再不懂去厕所哭去!这五个Shader里最异类的是BitmapShader,因为只有它是允许我们载入一张图片来给图像着色,那我们还是先来看看这个怪胎吧BitmapShader只有一个含参的构造方法BitmapShader (Bitmap bitmap, Shader.TileMode tileX, Shader.TileMode tileY)

BitmapShader (Bitmap bitmap, Shader.TileMode tileX, Shader.TileMode tileY)的第一个参数是位图这个很显然,而后两个参数则分别表示XY方向上的着色模式。
Shader.TileMode里有三种模式:CLAMP、MIRROR和REPETA。MIRROR镜像效果,REPETA重复,CLAMP的意思就是边缘拉伸的意思

4,Canvas 画布

  • canvas.save();锁定画布
  • canvas.restore(); 释放画布,即将画布释放到之前锁定的位置。先调用sava()方法,才能调用restore()方法进行释放。
    在Android中我们可以使用Canvas的saveXXX和restoreXXX方法来模拟图层的类似效果

Canvas我们一直称其为画布,其实更准确地说Canvas是一个容器,如果把Canvas理解成画板,那么我们的“层”就像张张夹在画板上的透明的纸,而这些纸对应到Android则是一个个封装在Canvas中的Bitmap。
除了save()方法Canvas还给我们提供了一系列的saveLayerXXX方法给我们保存画布,与save()方法不同的是,saveLayerXXX方法会将所有的操作存到一个新的Bitmap中而不影响当前Canvas的Bitmap,而save()方法则是在当前的Bitmap中进行操作,并且只能针对Bitmap的形变和裁剪进行操作,saveLayerXXX方法则无所不能,当然两者还有很多的不同.
save和saveLayerXXX方法有着本质的区别,saveLayerXXX方法会将所有操作在一个新的Bitmap中进行,而save则是依靠stack栈来进行
详情:http://blog.csdn.net/aigestudio/article/details/42677973

  • canvas.translate(x, y);平移画布,就相当于将画布的原点移到x,y的位置 ,即相当于坐标系移动了。
Android自定义控件(一)_2015-03-22_第2张图片
Paste_Image.png
  • canvas.rotate(degrees);旋转画布,将画布按照一定角度进行旋转,即相当于坐标系进行了旋转。
Android自定义控件(一)_2015-03-22_第3张图片
Paste_Image.png

注意:画布的平移旋转同样也会影响画布的自身坐标

5,Canvas类(画布)


Canvas所提供的各种方法根据功能来看大致可以分为几类,第一是以drawXXX为主的绘制方法,第二是以clipXXX为主的裁剪方法,第三是以scale、skew、translate和rotate组成的Canvas变换方法,最后一类则是以saveXXX和restoreXXX构成的画布锁定和还原。

  • drawBitmapMesh (Bitmap bitmap, int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, int colorOffset, Paint paint)
    很吊毛的方法,但是计算复杂度很高,所有被沉默了。(一般不推荐使用,因为计算难度很大)
    详细原理http://blog.csdn.net/aigestudio/article/details/41960507

drawBitmapMesh是个很屌毛的方法,为什么这样说呢?因为它可以对Bitmap做几乎任何改变,是的,你没听错,是任何,几乎无所不能,这个屌毛方法我曾一度怀疑谷歌那些逗比为何将它屈尊在Canvas下,因为它对Bitmap的处理实在在强大了。上一节我们在讲到Matrix的时候说过Matrix可以对我们的图像做多种变换,实际上drawBitmapMesh也可以,只不过需要一点计算,比如我们可以使用drawBitmapMesh来模拟错切skew的效果。

  • Canvas(Bitmap bitmap);含参数的构造。
    可以实现在一张Bitmap的基础上进行绘制。- canvas.setBitmap(Bitmap bitmap); 作用同含参数的构造.
    例如:
ivMain = (ImageView) findViewById(R.id.main_iv);
Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawColor(Color.RED);
ivMain.setImageBitmap(bitmap); 
  • canvas.translate()和rotate()方法:
    此类方法是对画布进行平移旋转等操作。其实可以理解为对坐标轴的旋转平移等操作。
    -canvas.clipXXX()方法:对画布的一种剪切方式,剪切后只能在剪切的部分显示需要绘制的内容。当前画布被“裁剪”了,只有剪裁的部分能够出现我们绘制的内容,如果我们所绘制的东西在该区域外部,即便绘制了你也看不到
clipPath(Path path, Region.Op op)
clipRect(Rect rect, Region.Op op)
clipRect(RectF rect, Region.Op op)
clipRect(float left, float top, float right, float bottom, Region.Op op)
clipRegion(Region region, Region.Op op) 

Canvas中有关裁剪的方法,你会发现有一大堆带有Region.Op参数的重载方法.要明白这些方法的Region.Op参数那么首先要了解Region为何物。Region的意思是“区域”,在Android里呢它同样表示的是一块封闭的区域。
详情请看 一下8中有关Region的简介
-canvas.drawPath(Path path, Paint paint) 绘制路径。
-canvas.drawTextOnPath (String text, Path path, float hOffset, float vOffset, Paint paint) ; 绘制文字在Path路径上。

6 Rect和RectF类

Rect和RectF是类似的,只不过RectF中涉及计算的时候数值类型均为float型,两者均表示一块规则矩形。重要的方法:

  • intersect(RectF rectF) / intersect (float left, float top, float right, float bottom) :此方法表示两个矩形相交的部分。取两个区域的相交区域作为最终区域。intersect方法的计算方式是相当有趣的,它不是单纯地计算相交而是去计算相交区域最近的左上端点和最近的右下端点
public class CanvasView extends View {  
    private Rect mRect;  
  
    public CanvasView(Context context, AttributeSet attrs) {  
        super(context, attrs);  
        mRect = new Rect(0, 0, 500, 500);  
  
        mRect.intersect(250, 250, 750, 750);  
    }  
  
    @Override  
    protected void onDraw(Canvas canvas) {  
        canvas.drawColor(Color.BLUE);  
  
        canvas.clipRect(mRect);  
  
        canvas.drawColor(Color.RED);  
    }  

Android自定义控件(一)_2015-03-22_第4张图片
图示.png

注意:黄色线框为后期加上的辅助线非程序生成

  • union(RectF rect) / union(float left, float top, float right, float bottom) 方法:union方法与intersect相反,取的是相交区域最远的左上端点作为新区域的左上端点,而取最远的右下端点作为新区域的右下端点
  • mRect.union(250, 250, 750, 750);

运行后我们会看到如下结果:

Android自定义控件(一)_2015-03-22_第5张图片
Paste_Image.png

是不是觉得不是我们想象中的那样单纯地两个区域相加。此方法是指的左上角的点和右下角的点的最远距离。

7 Path 类

此类表示一个路径

  • lineTo(float x, float y): 将路径连接至某个点坐标。(不适用moveTo方法时默认起点坐标为原点)。我们可以考虑多次调用lineTo方法来绘制更复杂的图形.
  • moveTo(float x, float y):将Path的起始点移动到某个特定的点。(Path默认起始点为原点)
  • close() 此方法含义师闭合曲线。
// 实例化路径  
mPath = new Path();    
// 移动点至[300,300]  
mPath.moveTo(100, 100);    
// 连接路径到点  
mPath.lineTo(300, 100);  
mPath.lineTo(400, 200);  
mPath.lineTo(200, 200);    
// 闭合曲线  
mPath.close(); 
Android自定义控件(一)_2015-03-22_第6张图片
Paste_Image.png
  • XXXTo() 贝塞尔曲线相关方法这些方法帮助我们绘制各类直线、曲线
    -- quadTo(float x1, float y1, float x2, float y2) 绘制二阶贝塞尔曲线。 quadTo的前两个参数为控制点的坐标,后两个参数为终点坐标, 当然起点坐标就是path的起点坐标
    -- cubicTo(float x1, float y1, float x2, float y2, float x3, float y3) 绘制三阶贝塞尔曲线。与quadTo类似,前四个参数表示两个控制点,最后两个参数表示终点:
// 实例化路径  
mPath = new Path();    
// 移动点至[100,100]  
mPath.moveTo(100, 100);    
// 连接路径到点  
mPath.cubicTo(200, 200, 300, 0, 400, 100);  
Android自定义控件(一)_2015-03-22_第7张图片
三阶贝塞尔曲线.png
  • arcTo (RectF oval, float startAngle, float sweepAngle) 用来生成弧线的方法。说白了就是从圆或者椭圆上截取一部分而已。
// 实例化路径  
mPath = new Path();    
// 移动点至[100,100]  
mPath.moveTo(100, 100);    
// 连接路径到点  
RectF oval = new RectF(100, 100, 200, 200);  
mPath.arcTo(oval, 0, 90); 

Android自定义控件(一)_2015-03-22_第8张图片
运行后.png

运行后效果跟我们想想的有点不一样, 这是因为使用Path生成的路径必定都是连贯的,虽然我们使用arcTo绘制的时一段狐,但是其最终都会与我们Path的起点链接起来。如果你不想要链接,那么path也提供了一种方法,而已设置是否强制链接起点。

  • arcTo (RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo) 该方法只是多了一个布尔值,值为true时将会把弧的起点作为Path的起点

  • Path中除了上面介绍的几个XXXTo方法外还有一套rXXXTo方法:

rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3)  
rLineTo(float dx, float dy)  
rMoveTo(float dx, float dy)  
rQuadTo(float dx1, float dy1, float dx2, float dy2)  

这一系列rXXXTo方法其实跟上面的那些XXXTo差不多的,唯一的不同是rXXXTo方法的参考坐标是相对的而XXXTo方法的参考坐标始终是参照画布原点坐标。相对的指的是,终点的坐标是相对于起点的坐标的,例如rMoveTo(100,100) ; rLineTo(200,200) ,表示这个线段的长度是200而不是100.

  • addXXX()方法,Path的这一系列的add方法允许我们直接往Path中添加一些曲线。
    比如:addArc(RectF oval, float startAngle, float sweepAngle) 方法允许我们将一段弧形添加至Path,注意这里我用到了“添加”这个词汇,也就是说,通过addXXX方法添加到Path中的曲线是不会和上一次的曲线进行连接的。
    -- 除了addArc(RectF oval, float startAngle, float sweepAngle)方法之外还有这一系列的add方法。
addCircle(float x, float y, float radius, Path.Direction dir)  
addOval(float left, float top, float right, float bottom, Path.Direction dir)  
addRect(float left, float top, float right, float bottom, Path.Direction dir)  
addRoundRect(float left, float top, float right, float bottom, float rx, float ry, Path.Direction dir)  

这些方法和addArc有很明显的区别,就是多了一个Path.Direction参数Path.Direction只有两个常量值CCW和CW分别表示逆时针方向闭合和顺时针方向闭合
mPath.addOval(oval, Path.Direction.CW);顺时针

Android自定义控件(一)_2015-03-22_第9张图片
Paste_Image.png

mPath.addOval(oval, Path.Direction.CCW); 逆时针
Android自定义控件(一)_2015-03-22_第10张图片
Paste_Image.png

public class PathView extends View {  
    private Path mPath;// 路径对象  
    private Paint mPaint;// 路径画笔对象  
  
    public PathView(Context context, AttributeSet attrs) {  
        super(context, attrs);    
        /* 
         * 实例化画笔并设置属性 
         */  
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);  
        mPaint.setStyle(Paint.Style.STROKE);  
        mPaint.setColor(Color.CYAN);  
        mPaint.setStrokeWidth(5);    
        // 实例化路径  
        mPath = new Path();    
        // 移动点至[100,100]  
        mPath.moveTo(100, 100);    
        // 连接路径到点  
        mPath.lineTo(200, 200);    
        // 添加一条弧线到Path中  
        RectF oval = new RectF(100, 100, 300, 400);  
        mPath.addArc(oval, 0, 90);  
    }    
    @Override  
    protected void onDraw(Canvas canvas) {  
        // 绘制路径  
        canvas.drawPath(mPath, mPaint);  
    }  
} 
Android自定义控件(一)_2015-03-22_第11张图片
Paste_Image.png

8 Region

回顾Canvas中有关裁剪的方法,你会发现有一大堆带有Region.Op参数的重载方法:
clipPath(Path path, Region.Op op) clipRect(Rect rect, Region.Op op) clipRect(RectF rect, Region.Op op) clipRect(float left, float top, float right, float bottom, Region.Op op) clipRegion(Region region, Region.Op op)
要明白这些方法的Region.Op参数那么首先要了解Region为何物。Region的意思是“区域”,在Android里呢它同样表示的是一块封闭的区域,Region中的方法都非常的简单,我们重点来瞧瞧Region.Op,Op是Region的一个枚举类,里面呢有六个枚举常量:

Android自定义控件(一)_2015-03-22_第12张图片
Paste_Image.png

那么Region.Op究竟有什么用呢?其实它就是个组合模式,我们曾学过一个叫图形混合模式的,而在本节开头我们也曾讲过Rect也有类似的组合方法,Region.Op灰常简单,如果你看过图形混合模式的话。这里我就给出一段测试代码,大家可以尝试去改变不同的组合模式看看效果

public class CanvasView extends View {  
    private Region mRegionA, mRegionB;// 区域A和区域B对象  
    private Paint mPaint;// 绘制边框的Paint  
  
    public CanvasView(Context context, AttributeSet attrs) {  
        super(context, attrs);    
        // 实例化画笔并设置属性  
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);  
        mPaint.setStyle(Paint.Style.STROKE);  
        mPaint.setColor(Color.WHITE);  
        mPaint.setStrokeWidth(2);    
        // 实例化区域A和区域B  
        mRegionA = new Region(100, 100, 300, 300);  
        mRegionB = new Region(200, 200, 400, 400);  
    }    
    @Override  
    protected void onDraw(Canvas canvas) {  
        // 填充颜色  
        canvas.drawColor(Color.BLUE);    
        canvas.save();    
        // 裁剪区域A  
        canvas.clipRegion(mRegionA);    
        // 再通过组合方式裁剪区域B  
        canvas.clipRegion(mRegionB, Region.Op.DIFFERENCE);    
        // 填充颜色  
        canvas.drawColor(Color.RED);    
        canvas.restore();    
        // 绘制框框帮助我们观察  
        canvas.drawRect(100, 100, 300, 300, mPaint);  
        canvas.drawRect(200, 200, 400, 400, mPaint);  
    }  
}  

以下是各种组合模式的效果
DIFFERENCE

Android自定义控件(一)_2015-03-22_第13张图片

最终区域为第一个区域与第二个区域不同的区域。
INTERSECT
Android自定义控件(一)_2015-03-22_第14张图片

最终区域为第一个区域与第二个区域相交的区域。
REPLACE
Android自定义控件(一)_2015-03-22_第15张图片

最终区域为第二个区域。
REVERSE_DIFFERENCE
Android自定义控件(一)_2015-03-22_第16张图片

最终区域为第二个区域与第一个区域不同的区域。
UNION
Android自定义控件(一)_2015-03-22_第17张图片

最终区域为第一个区域加第二个区域。
XOR
Android自定义控件(一)_2015-03-22_第18张图片

最终区域为第一个区域加第二个区域并减去两者相交的区域。
Region.Op就是这样,它和我们之前讲到的图形混合模式几乎一模一样换汤不换药……我在做示例的时候仅仅是使用了一个Region,实际上Rect、Cricle、Ovel等封闭的曲线都可以使用Region.Op,介于篇幅,而且也不难以理解就不多说了。
有些童鞋会问那么Region和Rect有什么区别呢?
首先最重要的一点,Region表示的是一个区域,而Rect表示的是一个矩形,这是最根本的区别之一,其次,Region有个很特别的地方是它不受Canvas的变换影响,Canvas的local不会直接影响到Region自身,
什么意思呢?我们来看一个simple你就会明白:

public class CanvasView extends View {  
    private Region mRegion;// 区域对象  
    private Rect mRect;// 矩形对象  
    private Paint mPaint;// 绘制边框的Paint  
  
    public CanvasView(Context context, AttributeSet attrs) {  
        super(context, attrs);  
  
        // 实例化画笔并设置属性  
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);  
        mPaint.setStyle(Paint.Style.STROKE);  
        mPaint.setColor(Color.DKGRAY);  
        mPaint.setStrokeWidth(2);  
  
        // 实例化矩形对象  
        mRect = new Rect(0, 0, 200, 200);  
  
        // 实例化区域对象  
        mRegion = new Region(200, 200, 400, 400);  
    }  
  
    @Override  
    protected void onDraw(Canvas canvas) {  
        canvas.save();  
  
        // 裁剪矩形  
        canvas.clipRect(mRect);  
        canvas.drawColor(Color.RED);  
  
        canvas.restore();  
  
        canvas.save();  
  
        // 裁剪区域  
        canvas.clipRegion(mRegion);  
        canvas.drawColor(Color.RED);  
  
        canvas.restore();  
  
        // 为画布绘制一个边框便于观察  
        canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), mPaint);  
    }  
}  

大家看到,我在[0, 0, 200, 200]和[200, 200, 400, 400]的位置分别绘制了Rect和Region,它们两个所占大小是一样的:


Android自定义控件(一)_2015-03-22_第19张图片

画布因为和屏幕一样大,so~~我们看不出描边的效果,这时,我们将Canvas缩放至75%大小,看看会发生什么:

@Override  
protected void onDraw(Canvas canvas) {  
    // 缩放画布  
    canvas.scale(0.75F, 0.75F);  
  
    canvas.save();  
  
    // 裁剪矩形  
    canvas.clipRect(mRect);  
    canvas.drawColor(Color.RED);  
  
    canvas.restore();  
  
    canvas.save();  
  
    // 裁剪区域  
    canvas.clipRegion(mRegion);  
    canvas.drawColor(Color.RED);  
  
    canvas.restore();  
  
    // 为画布绘制一个边框便于观察  
    canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), mPaint);  
}  

这时我们会看到,Rect随着Canvas的缩放一起缩放了,但是Region依旧泰山不动地淡定:


Android自定义控件(一)_2015-03-22_第20张图片

你可能感兴趣的:(Android自定义控件(一)_2015-03-22)