[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 | publicclassMainActivity extendsAppCompatActivity {    @Override    protectedvoidonCreate(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 | publicclassMyView  extendsView{        privateintwidth;//设置高        privateintheight;//设置高        privatePaint mPaintLine;//定义一个绘制直线的画笔        privatePaint mPaintSecondLine;//定义一个绘制直线的画笔        privatePaint mPaintInterCircle;//定义一个绘制圆的画笔        privatePaint mPaintOutSideCircle;//定义一个绘制圆的画笔        privatePaint mPaintText;//定义一个绘制文字的画笔        privateCalendar mCalendar;//创建一个时间类        privatestaticfinalintNEED_INVALIDATE = 0X6666;    publicMyView(Context context) {        super(context);    }    publicMyView(Context context, AttributeSet attrs) {        super(context, attrs);        //初始化画直线的画笔        mPaintLine = newPaint();        mPaintLine.setAntiAlias(true);//消除锯齿        mPaintLine.setColor(Color.GRAY);//设置画笔颜色        mPaintLine.setStyle(Paint.Style.STROKE);//设置为空心        mPaintLine.setStrokeWidth(10);//设置宽度// 初始化秒针的画笔        mPaintSecondLine = newPaint();        mPaintSecondLine.setAntiAlias(true);//消除锯齿        mPaintSecondLine.setColor(Color.GRAY);//设置画笔颜色        mPaintSecondLine.setStyle(Paint.Style.STROKE);//设置为空心        mPaintSecondLine.setStrokeWidth(7);//设置宽度//初始化内圆的画笔        mPaintInterCircle = newPaint();        mPaintInterCircle.setAntiAlias(true);//消除锯齿        mPaintInterCircle.setColor(Color.BLACK);        mPaintInterCircle.setStyle(Paint.Style.STROKE);//设置为空心        mPaintInterCircle.setStrokeWidth(5);        //初始化外圆的画笔        mPaintOutSideCircle = newPaint();        mPaintOutSideCircle.setAntiAlias(true);//消除锯齿        mPaintOutSideCircle.setColor(Color.BLACK);        mPaintOutSideCircle.setStyle(Paint.Style.STROKE);//设置为空心        mPaintOutSideCircle.setStrokeWidth(10);        //绘制文字的画笔        mPaintText = newPaint();        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 = newHandler() {            @Override            publicvoidhandleMessage(Message msg) {                super.handleMessage(msg);                switch(msg.what) {                    caseNEED_INVALIDATE:                        //跟新时间                        mCalendar = Calendar.getInstance();                        invalidate();                        sendEmptyMessageDelayed(NEED_INVALIDATE, 1000);                        break;                }            }        };        handler.sendEmptyMessageDelayed(NEED_INVALIDATE, 2000);    }    @Override    protectedvoidonMeasure(intwidthMeasureSpec, intheightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        width = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);        height = getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec);        setMeasuredDimension(width, height);//设置宽和高    }    @Override    protectedvoidonDraw(Canvas canvas) {        super.onDraw(canvas);        // 主线程自动调用        canvas.drawCircle(width / 2, height / 2, 300, mPaintInterCircle);        canvas.drawCircle(width / 2, height / 2, 320, mPaintOutSideCircle);        for(inti = 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()方法保存的状态        }        //绘制分针        intminute=  mCalendar.get(Calendar.MINUTE);        floatminuteDegree = 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();        //绘制时针        inthour=  mCalendar.get(Calendar.HOUR);        floathourDegree = (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();        //绘制秒针        intsecond =  mCalendar.get(Calendar.SECOND);        floatsecondDegree = 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 | publicclassMainActivity extendsAppCompatActivity {    DrawView drawView;    @Override    protectedvoidonCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        LinearLayout line = newLinearLayout(this);        DisplayMetrics displayMetrics = newDisplayMetrics();        getWindowManager().getDefaultDisplay().getRealMetrics(displayMetrics);        drawView = newDrawView(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 | publicclassDrawView extendsView{    floatprex;    floatprey;    privatePath path;    publicPaint paint = null;    Bitmap CacheBitmap = null;    Canvas CacheCanvas = null;    publicDrawView(Context context, intwidthPixels, intheightPixels) {        super(context);        CacheBitmap = Bitmap.createBitmap(widthPixels,heightPixels,Bitmap.Config.ARGB_8888);        CacheCanvas = newCanvas();        path = newPath();        CacheCanvas.setBitmap(CacheBitmap);        paint = newPaint(Paint.DITHER_FLAG);        paint.setColor(Color.RED);        paint.setStyle(Paint.Style.STROKE);        paint.setStrokeWidth(5);        paint.setAntiAlias(true);        paint.setDither(true);    }    @Override    publicbooleanonTouchEvent(MotionEvent event) {        floatx = event.getX();        floaty = event.getY();        switch(event.getAction())        {            caseMotionEvent.ACTION_DOWN:                path.moveTo(x,y);                prex = x;                prey = y;                break;            caseMotionEvent.ACTION_MOVE:                path.quadTo(prex, prey, x, y);                prex = x;                prey = y;                break;            caseMotionEvent.ACTION_UP:                CacheCanvas.drawPath(path,paint);                path.reset();                break;        }        invalidate();        returntrue;    }    @Override    publicvoidonDraw(Canvas canvas) {        Paint bmPaint = newPaint();        canvas.drawBitmap(CacheBitmap,0,0,bmPaint);        canvas.drawPath(path,paint);    }} | 
为了让view绘制的图形发生改变,需要程序记住一些状态数据:采用变量或者采用事件监听器,在监听器中修改这些数据。不管使用哪种方式,每次VIew组件上的图形状态发生改变时都应该通知View组件重新调用OnDraw()方法重绘该控件,通知可以调用invalidate()。
 
                     
                    
                 
                    
                
 
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号