自定义View-Paint和Canvas

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_32113133/article/details/64920662
Paint类:
1)和颜色相关
颜色是指绘图时使用的颜色,在 Android 中颜色可以指定透明度,使用 16 进制来表示颜色时,格式通常为#AARRGGBB,其中,AA 表示透明度、RR 表示红色、GG 表示绿色、BB 表示蓝色,Color 类定义了颜色信息,内置了常用颜色的 int 型常量,比如 Color.RED 是红色,Color.BLUE 是蓝色……如果您习惯了 16 进制的颜色,Color 类的静态方法 parseColor(String colorString)可以将 16进制颜色转换成 Color 类型。

下面的代码将 16 进制颜色转换成 Android 认可的颜色值:
int color = Color.parseColor("#66FF0000");

Paint 类中与颜色相关的方法有:

设置颜色

public native void setColor(int color);
设置透明度,a 的范围取 0~255 之间的整数
public native void setAlpha(int a);
指定透明度、红、绿、蓝定义一种颜色
public void setARGB(int a,int r,int g,int b);

2)和文本相关

绘制文本时,可以指定文本的大小、对齐方式、文本样式等属性,文本样式主要是为文本指定粗体、下划线、删除线等修饰性属性。


Paint 类与文本相关的方法如下:

设置文本大小,单位是 px,这个和我们平时使用的字体单位 sp 不同,所以最好进行转换。

public native void setTextSize(float textSize)

设置文本的对齐方式,可选值有 Paint.Align.LEFT、Paint.Align.CENTER、Paint.Align.RIGHT等。

 public void setTextAlign(Paint.Align align)

设置文本的倾斜程度,skewx 取值于 0~1 之间,正负表示倾斜的方向。

public native void setTextSkewX(float skewx)
给文本添加下载线,underline 为 true 表示添加。

public native void setUnderlineText(boolean underline)

设置文本的粗体样式,bold 为 true 表示粗体。

public native void setFakeBoldText(boolean bold)

strike 为 true 时为文本添加删除线。

public native void setStrikeThruText(boolean strike)

3)Paint 类与图形样式相关
图形样式包含绘制的图形是空心样式还是实心样式,同时还能指定落笔和收笔时的笔触效果。绘制直线或折线时,图形样式能影响到一些绘制细节。

Paint 类与图形样式相关的方法有:
public void setStyle(Paint.Style style)
public native void setStrokeWidth(float width)

Canvas类
Canvas 类封装了大量的绘图方法,绘制的图形受到 Paint 对象的影响。一般情况下,在绘图之前,要先创建 Paint 对象,定义绘制的颜色、样式。Paint 对象是一个轻量级对象,在程序运行过程中可以创建多个,我们也可以从头到尾只创建一个,最终取决于绘图需求。不过,Paint 类有一个 reset()方法,能重置 Paint 参数,所以,除非有必要,我们并不推荐大量创建Paint 对象,而是调用 reset()方法重置后重复使用,不然这对有限的手机资源来说是一种考验。

Canvas 类定义的绘图方法主要分成以下类型:位图 点 线 矩形 圆 路径 文字
1)绘制位图
Canvas 中定义的绘制位图的方法有:
public void drawBitmap(Bitmap bitmap,floatleft,floattop,Paint paint)

该方法是最为简单,将 bitmap 绘制在画布上,同时指定位图左上角相对于画布的坐
标,大小与原位置相同,不进行任何缩放。
绘制位图时,除非需要进行位图运算,否则,并不需要指定 paint 对象,直接传递
null 即可。
public void drawBitmap(Bitmap bitmap,Rect src,Rect dst,Paint paint)
public void drawBitmap(Bitmap bitmap,Rect src,RectF dst,Paint paint)
这两个方法从 bitmap 中抠出一块大小区域为 src 的图片并绘制到 canvas 的 dst 处。src

和 dst 的大小与比例关系影响到最终的绘制效果。


MainActivity:

public class MainActivity extends Activity {
    private ImageView iv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        iv = (ImageView) findViewById(R.id.iv);
        Bitmap bmpBuffer = Bitmap.createBitmap(100, 300,
                Bitmap.Config.ARGB_8888);//设置一个宽100,高300的空的图片
        Canvas canvas = new Canvas(bmpBuffer);
        Bitmap bmp = BitmapFactory.decodeResource(getResources()
                , R.mipmap.tiger);//拿到Bitmap
        //原大小绘制
        canvas.drawBitmap(bmp, 0, 0, null);//画图
        iv.setImageBitmap(bmpBuffer);//将图片设置到ImageView中
    }
}


tiger.png



结果:



2)绘制点
点的大小取决于 setStrokeWidth()方法的参数,参数值越大,点也就越大。所以,不要以为
一个点就是屏幕上的一个像素。如果将 stroke 的宽度设置为足够大,我们发现最终绘制出来的点其实是一个正方形。
绘制点的方法一共有三个:
public void drawPoint(float x,float y,Paint paint)
该方法在(x,y)处绘制一个点。
public void drawPoints(float[] pts,Paint paint)
该方法的参数 pts 是一个数组,从下标 0 开始每 2 个数确定一个点,连续绘制多个点。
多余的元素会忽略。
public void drawPoints(float[] pts,int offset,int count,Paint paint)

从 pts 数组中的第 offset 处开始取出 count 个数字,以 2 个数为一组确实一个点,连续绘制若干个点。忽略多余的元素。

MainActivity:

public class MainActivity extends Activity {
    private ImageView iv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        iv = (ImageView) findViewById(R.id.iv);

        Paint paint = new Paint();
        paint.setColor(Color.BLUE);
        paint.setStrokeWidth(4);

        Bitmap bitmap = Bitmap.createBitmap(500, 600, Bitmap.Config.ARGB_8888);//画一个空的位图
        Canvas canvas = new Canvas(bitmap);
        canvas.drawPoint(50, 50, paint);//画一个蓝色的点

        paint.setColor(Color.RED);
        float[] floats = new float[]{100, 60, 100, 70, 100, 80};//两个数一组画 4 个红色的点
        canvas.drawPoints(floats, paint);


        paint.setColor(Color.BLACK);
        canvas.drawPoints(floats, 1, 4, paint);//从floats中取出下标1-4的数据,每两个画一个点(包含1和4)

        iv.setImageBitmap(bitmap);

    }
}

结果:


3)绘制线
两个点确定一条直线,所以,绘制线条时,需要指定两个点的坐标。同画点一样,绘制线条
也有 3 个重载的方法:
public void drawLine(float startX,float startY,float stopX,float stopY,Paint paint)
在(startX, startY)和(stopX, stopY)两个点之间绘制一条直线。
public void drawLines(float[] pts,Paint paint)
pts 数组中每 4 个数一组绘制一条直线,多余的元素会忽略。
public void drawLines(float[] pts,int offset,int count,Paint paint)

从 pts 数组中的 offset 索引处开始,取出 count 个元素,并以 4 个数一组绘制直线,忽略多余的元素。


MainActivity:

public class MainActivity extends Activity {
    private ImageView iv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        iv = (ImageView) findViewById(R.id.iv);

        Paint paint = new Paint();
        paint.setColor(Color.BLUE);
        paint.setStrokeWidth(4);

        Bitmap bitmap = Bitmap.createBitmap(500,600, Bitmap.Config.ARGB_8888);//画一个空的位图
        Canvas canvas = new Canvas(bitmap);
        canvas.drawLine(50,50,200,50,paint);	 //画一条蓝色的线

        paint.setColor(Color.RED);
        float floats[] = new float[]{100,80,200,80,300,100,400,100};//画两条线  每4个数据绘制一条线,多余的元素会忽略
        canvas.drawLines(floats,paint);

        paint.setColor(Color.BLACK);
        canvas.drawLines(floats,2,5,paint);//每4个数据绘制一条线,多余的元素会忽略
        iv.setImageBitmap(bitmap);
    }
}


结果:


4)绘制矩形
矩形包含直角矩形和圆角矩形,正方形也是矩形的一种,所以 Canvas 并没有ᨀ供绘制正方形的方法。绘制矩形时,参数分为两种:一种是指定 left、top、right、bottom 等 4 个参数,另一种直接指定一个 Rect 对象或 RectF 对象。
绘制直角矩形的三个重载方法如下:
public void drawRect(float left,float top,float right,float bottom,Paint paint)
public void drawRect(Rect r,Paint paint)
public void drawRect(RectF r,Paint paint)

本质上这三个绘制直角矩形的方法是完全一样的,只是提供了调用的多种形态,使用哪一个完全由开发者自己决定。圆角矩形的几何形状比直角矩形相对复杂一些,我们需要指定 4 个拐角的弧度,4 个角的弧度不能单独设置,而是统一设置为相同的值。拐角弧度实际上是圆或椭圆的一段弧线,
绘制圆角矩形一共有 2 个重载的方法:
public void drawRoundRect(float left,float top,float right,float bottom,float rx,float ry,Paint paint)
该方法用于绘制一个圆角矩形,left、top、right、bottom 构建一个矩形,rx、ry 分别是
圆角处的水平半径和垂直半径。rx 和 ry 不一定相同,如果不同,则是椭圆上的一段弧
线。
public void drawRoundRect(RectF rect,float rx,float ry,Paint paint)
该方法是上面方法的另一种重载形式。

MainActivity:

public class MainActivity extends Activity {
    private ImageView iv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        iv = (ImageView) findViewById(R.id.iv);

        Bitmap bmpBuffer = Bitmap.createBitmap(500, 800,
                Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bmpBuffer);
        Paint paint = new Paint();
        paint.setColor(Color.RED);
        canvas.drawRoundRect(10, 10, 400, 300, 50, 30, paint);

        paint.setStyle(Paint.Style.STROKE);
        canvas.drawRoundRect(new RectF(10, 320, 400, 620), 30, 50, paint);
        iv.setImageBitmap(bmpBuffer);
    }
}

结果:


5)绘制圆
在对图形进行分类时,我将圆、椭圆、扇形、弧线统一归类到“圆”这一类中,
扇形和弧线可以认为是圆或椭圆的一部分,椭圆的大小是由他的外切矩形来决定的。


绘制椭圆的方法如下:
public void drawOval(float left,float top,float right,float bottom,Paint paint)
public void drawOval(RectF oval,Paint paint)

上面两个方法的参数定义了一个矩形结构,绘制的结果是该矩形的内切椭圆。
绘制椭圆时,如果外切矩形的长和宽相等,即为正方形,绘制出来的图形就是一个圆,但
是 Cavnas 类ᨀ供了另一个更加简单实用的方法,ᨀ供圆点的坐标和半径即可。
public void drawCircle(float cx,float cy,float radius,Paint paint)

(cx、cy)为圆心坐标,radius 为圆的半径。

弧线和扇形本质上更是相似,弧线是椭圆上的一段,而扇形则是将弧线的两个端点和椭圆中心点使用线条连接形成的闭合区域。理解弧线和扇形的演变过程便很容易明白方法中的参数意义。
绘制弧线和扇形的方法如下:
public void drawArc(RectF oval,float startAngle,float sweepAngle,boolean useCenter,Paint paint)
public void drawArc(float left,float top,float right,float bottom,float startAngle,float sweepAngle,boolean useCenter,Paint paint)

以上两个方法中,参数 startAngle 表示起始角度,sweepAngle 表示扇形或弧线所占的
角度,正数表示顺时针,负数表示逆时针。useCenter 参数询问是否要使用中心点,为
true 表示扇形,为 false 表示弧线。

6)绘制路径
Path 是 Graphics2D 中一个非常重要的概念,表示“路径”,理解该概念时保持“路径”的本色就好。路径可以是直的、也可以是弯的,可以是闭合的、也可以是非闭合的,可以是圆形的、也可以是方形的,可以是单个的、也可以是多个的,可以是简单的、也可以是复杂的……总的来说,路径是基于普通图形但是功能比普通图形更强的一种复杂图形。
Path 是一个类,用于绘制复杂图形,创建之初什么也没有,只有往 Path 中添加了具体的形状,Path 才会清晰可见。绘制 Path 时,所有信息都存储在 Path 对象中,Canvas 根据 Path 对象来绘制相应的图形。

我们将 Path 的功能归纳成以下几类:
往 Path 中添加线条
往 Path 中添加矩形、椭圆、弧
往 Path 中添加曲线和贝塞尔曲线
将 Path 中的图形进行运算

通过 Path 可以绘制出奇形怪状的线条,并能将线条组合在一起变成折线,闭合后就是一个多边形了。这就是 Path 的厉害之处。为此,Path 类中定义了 5 个方法:
public void moveTo(float x,float y)

将画笔移动到点(x,y)的位置,使用的是绝对定位。

public void rMoveTo(float dx,float dy)
将画笔移动到一个新点,新点在上一个点的基础上偏移(dx,dy),也就是说,新点的坐标为(x + dx,y + dy)。这里使用的是相对定位。首字母“r”就是“relative(相对)”的意思。
public void lineTo(float x,float y)
将画笔移动到点(x,y)的位置,并在上一个点与当前点之前画一条直线。使用的是绝对定位。
public void rLineTo(float dx,float dy)

将画笔移动到一个新点,新点在上一个点的基础上偏移(dx,dy),新点的坐标为(xdx,y+ dy),同时,在新点与上一个点之间画一条直线。这里使用的是相对定位。

public void close()
在第一个点和最后一个点之前画一条直线,形成闭合区域。


MainActivity:

public class MainActivity extends Activity {
    private ImageView iv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        iv = (ImageView) findViewById(R.id.iv);

        Bitmap bmpBuffer = Bitmap.createBitmap(500, 800,
                Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bmpBuffer);
        Paint paint = new Paint();
        paint.setColor(Color.RED);
        paint.setStyle(Paint.Style.STROKE);

        Path path = new Path();
        path.moveTo(0,200);
        path.rLineTo(300,0);
        path.rLineTo(-300,150);
        path.rLineTo(150,-300);
        path.rLineTo(150,300);
        path.close();

        canvas.drawPath(path,paint);

        iv.setImageBitmap(bmpBuffer);
    }
}

结果:



6)绘制文字
绘制图形也包括文字的绘制,对于刚刚接触 Graphics2D 的读者朋友来说,可能会觉得有点不可思议,好在绘制文字并不复杂,Canvas为我们提供了两组方法,一组直接从指定的位置开始绘制文字,另一组沿着 Path 绘制文字:
public void drawText(char[] text,int index,int count,float x,float y,Paint paint)
public void drawText(String text,float x,float y,Paint paint)
public void drawText(String text,int start,int end,float x,float y,Paint paint)
public void drawText(CharSequence text,int start,int end,float x,float y,Paint paint)

上面这一组方法是从指定的位置(坐标)开始绘制文字,虽然都是字符串,但是供了三种形式:char[]、String 和 CharSequence,本质上并没有什么不同,参数 index 和count、start 和 end 可以从字符串中取出子串,而参数 x、y 就是文字绘制的坐标位置,其中 y 是文字的 baseline 的值。


public void drawTextOnPath(String text,Path path,float hOffset,float vOffset,Paint paint)
public void drawTextOnPath(char[] text,int index,int count,Path path,float hOffset,float vOffset,Paint paint)
这两个重载的 drawTextOnPath()方法用于沿着 Path 定义好的路径绘制文字,这是一个很在趣的功能,文字在 Path 的带领下龙飞凤舞,灵活多变。参数 hOffset 和 vOffset 用于定义文字离 Path 的水平偏移量和垂直偏移量,正数和负数影响文字与路径的相对位置。同样的,也支持绘制从字符数组中截取的子串,index 表示起始索引,count 表示要截取的长度。

public void drawText(String text,float x,float y,Paint paint)
得到文字的x,y坐标:
int x = (viewWidth - rect.width()) / 2;//x坐标
int y = (int) (viewHeight / 2 + (fontMetrics.descent - fontMetrics.ascent) / 2 - fontMetrics.descent);//y坐标
            
从技术层面上来说,字符由下面几个部分构成,从文字上理解可能比较晦涩,
简单来说,常用字符的高度是 ascent 和 descent 的
和,但是,一些特殊字符比如拼音的音调等则会延伸到 top 的位置。
baseline:基准点;
ascent:baseline 之上至字符最高处的距离;
descent:baseline 之下至字符最低处的距离;
top:字符可达最高处到 baseline 的值,即 ascent 的最大值;
bottom:字符可达最低处到 baseline 的值,即 descent 的最大值。
在 Android 中,字体的信息使用 Paint.FontMetrics 类来表示,该类源码如下:
public  static class FontMetrics {
public float top;
public float ascent;
public float descent;
public float bottom;
public float leading;

}


1)不通过x,y绘制

MainActivity:

public class MainActivity extends Activity {
    private ImageView iv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        iv = (ImageView) findViewById(R.id.iv);

        Bitmap bmpBuffer = Bitmap.createBitmap(500, 800,
                Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bmpBuffer);

        Paint paint = new Paint();
        paint.setAntiAlias(true);//指定是否使用抗锯齿功能,如果使用,会使绘图速度变慢
        paint.setColor(Color.RED);//设置画笔颜色
        paint.setTextSize(14);//设置文字大小
        paint.setStrokeWidth(4);//设置画笔宽度
        paint.setStyle(Paint.Style.FILL);//绘图样式,对于设文字和几何图形都有效
        paint.setTextAlign(Paint.Align.LEFT);//设置文字对齐方式,取值:align.CENTER、align.LEFT或align.RIGHT

        //样式设置
        paint.setFakeBoldText(true);//设置是否为粗体文字
        paint.setUnderlineText(true);//设置下划线
        paint.setTextSkewX((float) -0.25);//设置字体水平倾斜度,普通斜体字是-0.25
        paint.setStrikeThruText(true);//设置带有删除线效果

        //其它设置
        paint.setTextScaleX(2);//只会将水平方向拉伸,高度不会变

        String text = "滚滚长江东逝水浪花淘尽英雄";
        canvas.drawText(text, 100, 200, paint);//一般用这个
        //文字截取一般用不到
        canvas.drawText(text, 2, 5, 100, 300, paint);
        canvas.drawText(text.toCharArray(), 2, 5, 100, 400, paint);


        iv.setImageBitmap(bmpBuffer);
    }
}

结果:



2)通过确定x,y绘制文字

MainActivity:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

CustomText:

public class CustomText extends View {
    private static final String TEXT = "dsfsdfgfhggdfg!周瑜";
    private Paint paint;
    private Rect rect;
    private Paint.FontMetrics fontMetrics;

    public CustomText(Context context) {
        super(context);
    }

    public CustomText(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public CustomText(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public void init() {
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setTextSize(25);
        paint.setColor(Color.RED);
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.FILL);

        rect = new Rect();
        paint.getTextBounds(TEXT, 0, TEXT.length(), rect);

        fontMetrics = paint.getFontMetrics();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int viewWidth = getMeasuredWidth();
        int viewHeight = getMeasuredHeight();
        int x = (viewWidth - rect.width()) / 2;//x坐标
        int y = (int) (viewHeight / 2 +
                (fontMetrics.descent - fontMetrics.ascent) / 2
                - fontMetrics.descent);//y坐标
        canvas.drawText(TEXT, x, y, paint);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int textWidth = rect.width();
        int textHeight = rect.height();

        int width = measureWidht(widthMeasureSpec, textWidth);
        int height = measureHeight(heightMeasureSpec, textHeight);

        setMeasuredDimension(width, height);
    }

    private int measureWidht(int widthMeasureSpec, int textWidth) {
        int measureMode = MeasureSpec.getMode(widthMeasureSpec);
        int mearureWidht = MeasureSpec.getSize(widthMeasureSpec);

        int width = 0;

        if (measureMode == MeasureSpec.EXACTLY) {//当宽度为match_content或固定值时
            width = mearureWidht;
        } else if (measureMode == MeasureSpec.AT_MOST) {
            width = textWidth;
        }

        return width;
    }

    private int measureHeight(int heightMeasureSpec, int textHeight) {
        int measureMode = MeasureSpec.getMode(heightMeasureSpec);
        int mearureHeight = MeasureSpec.getSize(heightMeasureSpec);

        int height = 0;

        if (measureMode == MeasureSpec.EXACTLY) {//当宽度为match_content或固定值时
            height = mearureHeight;
        } else if (measureMode == MeasureSpec.AT_MOST) {
            height = textHeight;
        }

        return height;
    }
}

结果:


猜你喜欢

转载自blog.csdn.net/qq_32113133/article/details/64920662
今日推荐