[android]Android中图形图片及处理相关Api的小总结

开发应用中图片的使用是必不可少的,Android系统提供了丰富的图片支持功能。我们除了可以使Drawable资源库,还可以使用Bitmap、Picture类去创建图片,也可以使用Canvas、Paint、Path类等去绘制我们满意的图片。在自定义控件时,这些API使用尤为常见。因此,小编觉得有必要简单的做个小总结。

那就先从Bitmap和BitmapFactory开始吧

Bitmap和BitmapFactory

BitmapFactory

Bitmap代表一张位图。BitmapDrawable中封装的图片就是一个Bitmap对象。

可以调用BitmapDrawable的构造器将一个Bitmap对象封装成为一个BitmapDrawable对象,方法如下:

BitmapDrawable drawable = new BitmapDrawable(bitmap);

如果想要获取BitmapDrawable中封装的Bitmap对象,可以采用如下方法:

Bitmap bitmap = drawable.getBitmap();

BitmapFactory中提供了多个方法来解析、创建Bitmap的对象:
decodeByteArray(byte[] data, int offset, int length) :将制定字节数组从offset字节开始length长度的字节解析成Bitmap对象。

decodeFile(String pathName) :将指定路径下的文件解析成Bitmap对象。

decodeFileDescriptor(FileDescriptor fd) :将FileDescriptor对应文件中解析,创建Bitmap对象。

decodeResource(Resources res, int id) :将给定的资源ID解析成Bitmap对象。

decodeStream(InputStream is) 将指定的字节流解析成Bitmap对象。

另外,需要注意的是Android为Bitmap提供了两种方法判断它是否已经回收,以及强制Bitmap回收自己。分别为Boolean isRecycled() 和void recycle()方法

Canvas

  Canvas, 我们称之为“画布“,主要适用于绘制View的。 Canvas中提供了大量绘制图形的方法:

绘制扇形

drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint): 第一个参数RectF对象,指定扇形的区域;二个参数是起始角度;第三个参数是旋转角度,顺时针旋转;第四个参数是是否填充,true为填充,false为不填充,也就是为一条弧线;第五个参数是绘制图形的画笔对象Paint。

RectF:通过RectF(float left, float top, float right, float bottom)构造器创建RectF对象。

Paint:是绘制所有图形所用到的一个画笔,我们在稍后讲解。

drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, Paint paint) :这个是将扇形区域左,上,右,下边的坐标直接输入,而不是通过RectF对象。其他参数同上。

绘制圆形:

drawCircle(float cx, float cy, float radius, Paint paint): 第一、二个参数是指圆形的x, y坐标; 第三个参数是半径; 第四个参数是画笔Paint对象。

绘制直线:

drawLine(float startX, float startY, float stopX, float stopY, Paint paint) :两点确定一条直线,第一、二参数是起始点的坐标;第三、四参数是结束点的坐标;第五个参数画笔Paint对象。

drawLines(float[] pts, Paint paint) :多个点确定一条直线,第一个参数是点的数组;第二个参数是画笔Paint对象。

drawLines(float[] pts, int offset, int count, Paint paint)

绘制椭圆

drawOval(float left, float top, float right, float bottom, Paint paint):前四个参数是椭圆的左,上,右,下边的坐标,第五个是画笔Paint对象。

drawOval(RectF oval, Paint paint):第一个参数是RectF对象, 第二个参数是画笔Paint对象。

绘制矩形:

drawRect(RectF rect, Paint paint) :第一个参数是RectF对象, 第二个参数是画笔Paint对象。

绘制点:

drawPoint(float x, float y, Paint paint) :第一、二个参数点的坐标,第三个参数为Paint对象。

渲染文本:

drawText(String text, float x, floaty, Paint paint)

drawText(CharSequence text, int start, int end, float x, float y, Paint paint)

drawText(char[] text, int index, int count, float x, float y, Paint paint)

drawText(String text, int start, int end, float x, float y, Paint paint)

Canvas中还给我们提供了很多绘制其他图形的方法,这里我们不在一一列举。我们来看一下Paint”画笔“。

Paint:

  Paint是用于绘制的画笔,Canvas就像是我们的画纸,我们需要笔才可以完成一整幅图。Paint中为我们提供了很多设置的方法(我们这里只列举常用的方法):

setARGB(int a, int r, int g, int b) :设置 Paint对象颜色,参数一为alpha透明值

setAlpha(int a) :设置alpha不透明度,范围为0~255

setAntiAlias(boolean aa) :是否抗锯齿,这个一般是都要设置的。

setColor(int color) :设置颜色,这里Android内部定义的有Color类包含了一些常见颜色定义

setTextScaleX(float scaleX) :设置文本缩放倍数,1.0f为原始

setTextSize(float textSize) :设置字体大小

setUnderlineText(booleanunderlineText) :设置下划线

setStrokeCap(Paint.Cap cap) :当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的图形样式,如圆形样式 Cap.ROUND,或方形样式Cap.SQUARE

setSrokeJoin(Paint.Join join) :设置绘制时各图形的结合方式,如平滑效果等

Path

Path, 轨迹,路径。Path可以沿着多个点绘制一条路径, 在Canvas中可以根据Path绘制不同的图形。

我们在使用Path绘制路径,一般要使用到以下几个方法:

moveTo(float x, float y): 移动到(x, y)坐标点。绘制路径时,路径的第一个点一般我们通过moveTo()来决定,否则默认为(0, 0)点。
lineTo(float x, float y): 从当前点绘制直线到(x, y)点。这个与moveTo()不同,moveTo()是指跳转到(x, y)点,而不绘制连线。
close(): 将路径封闭。举例来说,我们绘制一个三角形,三角形的三个点是(0, 0,),(100, 0),(0, 100)我们使用lineTo将(0, 0,),(100, 0)连接,(100, 0),(0, 100)连接,这样还差(0, 0,),(0, 100)之间的连线,这时我们可以直接调用close()方法,这样就会直接将图形封闭构成三角形。
quadTo(float x1, float y1, float x2, float y2): 绘制贝塞尔曲线,贝塞尔曲线是由三个点控制的:起始点,终止点,控制点。在该方法中,前两个参数是控制点的坐标,后两个参数是终止点坐标。
  
  Path中我们可以通过点来绘制路径也可以通过addXXX()方法来绘制,Path中给我们提供了很多这样的方法来添加不同的路径:

方法用途
addArc(RectF oval, float startAngle, float sweepAngle) 添加圆弧轨迹
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) 添加椭圆角矩形轨迹

下面主要通过两个简单的小例子加深对知识点的理解。当然,在文章开始我们已经提到这些Api在自定义控件中比较常见,下面也涉及到一些自定义View方面的知识。(~!~如果时间希望能够系统的学习下自定义View以及ViewGroup方面的知识以及写一篇这方面的总结)

自定义时钟的Demo主要用到了Canvas以及Paint方面的知识,来看看代码吧:

1
2
3
4
5
6
7
8
public class MainActivity extends AppCompatActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

主活动中仅仅加载了一个布局。

1
2
3
4
<linearlayout android:layout_height="match_parent" android:layout_width="match_parent" android:paddingbottom="@dimen/activity_vertical_margin" android:paddingleft="@dimen/activity_horizontal_margin" android:paddingright="@dimen/activity_horizontal_margin" android:paddingtop="@dimen/activity_vertical_margin" tools:context=".MainActivity" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
 
    <com.example.zjn.clock.myview android:layout_height="match_parent" android:layout_width="match_parent">
</com.example.zjn.clock.myview></linearlayout>

在布局文件中放置我们自定义View,比较简单不需要太多介绍。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
public class MyView  extends View{
        private int width;//设置高
        private int height;//设置高
        private Paint mPaintLine;//定义一个绘制直线的画笔
        private Paint mPaintSecondLine;//定义一个绘制直线的画笔
        private Paint mPaintInterCircle;//定义一个绘制圆的画笔
        private Paint mPaintOutSideCircle;//定义一个绘制圆的画笔
        private Paint mPaintText;//定义一个绘制文字的画笔
        private Calendar mCalendar;//创建一个时间类
        private static final int NEED_INVALIDATE = 0X6666;
 
    public MyView(Context context) {
        super(context);
    }
 
    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        //初始化画直线的画笔
        mPaintLine = new Paint();
        mPaintLine.setAntiAlias(true);//消除锯齿
        mPaintLine.setColor(Color.GRAY);//设置画笔颜色
        mPaintLine.setStyle(Paint.Style.STROKE);//设置为空心
        mPaintLine.setStrokeWidth(10);//设置宽度// 初始化秒针的画笔
        mPaintSecondLine = new Paint();
        mPaintSecondLine.setAntiAlias(true);//消除锯齿
        mPaintSecondLine.setColor(Color.GRAY);//设置画笔颜色
        mPaintSecondLine.setStyle(Paint.Style.STROKE);//设置为空心
        mPaintSecondLine.setStrokeWidth(7);//设置宽度//初始化内圆的画笔
        mPaintInterCircle = new Paint();
        mPaintInterCircle.setAntiAlias(true);//消除锯齿
        mPaintInterCircle.setColor(Color.BLACK);
        mPaintInterCircle.setStyle(Paint.Style.STROKE);//设置为空心
        mPaintInterCircle.setStrokeWidth(5);
        //初始化外圆的画笔
        mPaintOutSideCircle = new Paint();
        mPaintOutSideCircle.setAntiAlias(true);//消除锯齿
        mPaintOutSideCircle.setColor(Color.BLACK);
        mPaintOutSideCircle.setStyle(Paint.Style.STROKE);//设置为空心
        mPaintOutSideCircle.setStrokeWidth(10);
 
        //绘制文字的画笔
        mPaintText = new Paint();
        mPaintText.setAntiAlias(true);//消除锯齿
        mPaintText.setColor(Color.GRAY);
        mPaintText.setStyle(Paint.Style.STROKE);//设置为空心
        mPaintText.setTextAlign(Paint.Align.CENTER);
        mPaintText.setTextSize(40);
        mPaintText.setStrokeWidth(6);
 
        //初始化日历
        mCalendar = Calendar.getInstance();
        //发送一个消息给UI主线程
        Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                switch (msg.what) {
                    case NEED_INVALIDATE:
                        //跟新时间
                        mCalendar = Calendar.getInstance();
                        invalidate();
                        sendEmptyMessageDelayed(NEED_INVALIDATE, 1000);
                        break;
                }
 
            }
        };
        handler.sendEmptyMessageDelayed(NEED_INVALIDATE, 2000);
    }
 
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        width = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);
        height = getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec);
        setMeasuredDimension(width, height);//设置宽和高
    }
 
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 主线程自动调用
        canvas.drawCircle(width / 2, height / 2, 300, mPaintInterCircle);
        canvas.drawCircle(width / 2, height / 2, 320, mPaintOutSideCircle);
        for (int i = 1; i <= 12; i++) {
            canvas.save();//保存当前状态
            canvas.rotate(360 / 12 * i, width / 2, height / 2);//根据点width/2,height/2旋转
            canvas.drawLine(width / 2, height / 2 - 300, width / 2, height / 2 - 270, mPaintLine);
            canvas.drawText( + i, width / 2, height / 2 - 240, mPaintText);
            canvas.restore();//回到save()方法保存的状态
        }
 
        //绘制分针
        int minute=  mCalendar.get(Calendar.MINUTE);
        float minuteDegree = minute / 60f * 360;
        canvas.save();
        canvas.rotate(minuteDegree, width / 2, height / 2);
        canvas.drawLine(width / 2, height / 2 - 200, width / 2, height / 2 + 40, mPaintLine);
        canvas.restore();
        //绘制时针
        int hour=  mCalendar.get(Calendar.HOUR);
        float hourDegree = (hour * 60 + minute);//(12f*60)*360;
        canvas.save();
        canvas.rotate(hourDegree, width / 2, height / 2);
        canvas.drawLine(width / 2, height / 2 - 170, width / 2, height / 2 + 30, mPaintLine);
        canvas.restore();
        //绘制秒针
        int second =  mCalendar.get(Calendar.SECOND);
        float secondDegree = second * 6;//一秒是6度。
        canvas.save();
        canvas.rotate(secondDegree, width / 2, height / 2);
        canvas.drawLine(width / 2, height / 2 - 220, width / 2, height / 2 + 50, mPaintSecondLine);
        canvas.restore();
    }
}

onDraw是UI主线程不断调用重绘界面的,因此我们需要使用到Handler,通过发送一个消息给Handler对象,让Handler对象在每一秒重绘一次MyView控件。这里重绘不能调用onDraw()方法额,而要调用的是invalidate()方法,invalidate()方法中调用了onDraw()方法。

下面马上来看看效果吧:



采用双缓冲实现画图板用到了以上提到的各类知识,主要原理是:当程序需要在指定View上进行绘制时,程序并不直接绘制到该View组建上,而是先绘制到内存中的一个Bitmap图片上,等内存中的Bitmap绘制好之后,再一次性将Bitmap绘制到View上面,还是直接看代码吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MainActivity extends AppCompatActivity {
 
    DrawView drawView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        LinearLayout line = new LinearLayout(this);
        DisplayMetrics displayMetrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getRealMetrics(displayMetrics);
        drawView = new DrawView(this, displayMetrics.widthPixels,displayMetrics.heightPixels);
        line.addView(drawView);
        setContentView(line);
    }
}

主活动中主要获取了穿件的宽和高,同是创建DrawView,让DrawView的宽和高保持与该Activity相同。来看看DrawView中的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class DrawView extends View{
    float prex;
    float prey;
    private Path path;
    public Paint paint = null;
    Bitmap CacheBitmap = null;
    Canvas CacheCanvas = null;
    public DrawView(Context context, int widthPixels, int heightPixels) {
        super(context);
        CacheBitmap = Bitmap.createBitmap(widthPixels,heightPixels,Bitmap.Config.ARGB_8888);
        CacheCanvas = new Canvas();
        path = new Path();
        CacheCanvas.setBitmap(CacheBitmap);
        paint = new Paint(Paint.DITHER_FLAG);
        paint.setColor(Color.RED);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(5);
        paint.setAntiAlias(true);
        paint.setDither(true);
    }
 
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        switch (event.getAction())
        {
            case MotionEvent.ACTION_DOWN:
                path.moveTo(x,y);
                prex = x;
                prey = y;
                break;
            case MotionEvent.ACTION_MOVE:
                path.quadTo(prex, prey, x, y);
                prex = x;
                prey = y;
                break;
            case MotionEvent.ACTION_UP:
                CacheCanvas.drawPath(path,paint);
                path.reset();
                break;
        }
        invalidate();
        return true;
    }
 
    @Override
    public void onDraw(Canvas canvas) {
        Paint bmPaint = new Paint();
        canvas.drawBitmap(CacheBitmap,0,0,bmPaint);
        canvas.drawPath(path,paint);
    }
}

为了让view绘制的图形发生改变,需要程序记住一些状态数据:采用变量或者采用事件监听器,在监听器中修改这些数据。不管使用哪种方式,每次VIew组件上的图形状态发生改变时都应该通知View组件重新调用OnDraw()方法重绘该控件,通知可以调用invalidate()。

原文地址:https://www.cnblogs.com/pbq-dream/p/5364571.html