《Android群英传》---读书笔记4

    xiaoxiao2021-12-14  17

    《Android群英传》—读书笔记4

    标签: android中级 读书笔记


    Android绘图机制与处理技巧

    Android屏幕相关知识

    2D绘图基础

    系统通过提供的Canvans对象来提供绘图,如下所示: drawPoint; drawRect; drawRoundRect;//绘制圆角矩形 drawLine;//绘制直线 drawLines;//绘制多条直线 drawVertices;绘制多边形 drawArc;绘制弧 drawOval;//绘制椭圆 drawCircle; drawText;//绘制文本 drawPosText;//在指定位置绘制文本 drawPath 等 Canvans通常配合Paint来使用 Paint相关的属性和方法如下: setAntiAlias();//设置画笔锯齿效果 setColor(); setARGB(); setAlpha(); setTextSize(); setStyle();//设置画笔是空心(FILL)还是实心(STROKE); setStrokeWidth();//设置空心边框的宽度

    Android XML绘图

    在XML使用BitMap < ?xml version=”1.0” encoding=”utf-8”?> < bitmap xmlns:android=”http://schemas.android.com/apk/res/android” android:src=”@drawable/ic_lanubcher”/>

    Shape 通过Shape可以在XML中绘制各种形状,Shape功能十分强大,可以实现扁平化,拟物化,渐变等

    Layer 类似于PS中的图层概念,非常容易实现叠加效果

    Selector 可以帮助实现静态绘图中的事件反馈

    Android绘图技巧

    6.4.1 Canvas

    (所有的绘制操作的默认原点是屏幕的左上角) Canvas.save();//将之前所有的绘制保存起来,让后续的操作就像在一个新的图层上操作 Canvas.restore();//类似于PS中图层合并操作 Canvas.translate();//画布平移,或者叫做坐标系的平移,例如translate(x,y)之后,就将原点(0,0)移动到了(x,y)位置 Canvas.rorate();//画布旋转,或者叫做坐标系的旋转 例如下面的代码就绘制一个带指针和刻度的Clock

    import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.util.AttributeSet; import android.view.View; public class Clock extends View { private int mHeight, mWidth; public Clock(Context context) { super(context); } public Clock(Context context, AttributeSet attrs) { super(context, attrs); } public Clock(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onDraw(Canvas canvas) { // 获取宽高参数 mWidth = getMeasuredWidth(); mHeight = getMeasuredHeight(); // 画外圆 Paint paintCircle = new Paint(); paintCircle.setStyle(Paint.Style.STROKE); paintCircle.setAntiAlias(true); paintCircle.setStrokeWidth(5); canvas.drawCircle(mWidth / 2, mHeight / 2, mWidth / 2, paintCircle); // 画刻度 Paint painDegree = new Paint(); paintCircle.setStrokeWidth(3); for (int i = 0; i < 24; i++) { // 区分整点与非整点 if (i == 0 || i == 6 || i == 12 || i == 18) { painDegree.setStrokeWidth(5); painDegree.setTextSize(30); canvas.drawLine(mWidth / 2, mHeight / 2 - mWidth / 2, mWidth / 2, mHeight / 2 - mWidth / 2 + 60, painDegree); String degree = String.valueOf(i); canvas.drawText(degree, mWidth / 2 - painDegree.measureText(degree) / 2, mHeight / 2 - mWidth / 2 + 90, painDegree); } else { painDegree.setStrokeWidth(3); painDegree.setTextSize(15); canvas.drawLine(mWidth / 2, mHeight / 2 - mWidth / 2, mWidth / 2, mHeight / 2 - mWidth / 2 + 30, painDegree); String degree = String.valueOf(i); canvas.drawText(degree, mWidth / 2 - painDegree.measureText(degree) / 2, mHeight / 2 - mWidth / 2 + 60, painDegree); } // 通过旋转画布简化坐标运算 canvas.rotate(15, mWidth / 2, mHeight / 2); } // 画圆心 Paint paintPointer = new Paint(); paintPointer.setStrokeWidth(30); canvas.drawPoint(mWidth / 2, mHeight / 2, paintPointer); // 画指针 Paint paintHour = new Paint(); paintHour.setStrokeWidth(20); Paint paintMinute = new Paint(); paintMinute.setStrokeWidth(10); canvas.save(); canvas.translate(mWidth / 2, mHeight / 2); canvas.drawLine(0, 0, 100, 100, paintHour); canvas.drawLine(0, 0, 100, 200, paintMinute); canvas.restore(); } }

    6.4.2 Layer图层 在Android中使用saveLayer()方法创建一个图层,图层的管理是基于栈的 Android中通过Canvas的saveLayer(),saveLayerAlpha()将一个图层入栈,使用restore(),restoreToCount()将一个图层出栈。

    Android图像处理之色彩特效处理

    在Android中,系统使用一个颜色矩阵—ColorMatrix,来处理图像的色调,饱和度,亮度,Android颜色矩阵是一个4X5的数字矩阵:第一行决定R–红色,第二行决定G–绿色,第三行决定B–蓝色,第四行决定A–透明度

    通过ColorMatrix改变色光属性 1获取实例 ColorMatrix colorMatrix=new ColorMatrix(); 2通过颜色矩阵改变属性 colorMatrix.setRotate(int axis,float degree);//改变色调 colorMatrix.setSaturation(float sat);//改变饱和度 colorMatrix.setScale();//改变亮度

    也可以通过postConcat()方法来将矩阵的作用效果混合: ColorMatrix imageMatrix=new ColorMatrix(); imageMatrix.postConcat(hueMatrix); imageMatrix.postConcat(saturationMatrix); imageMatrix.postConcat(lumMatrix); 3使用Paint类的setColorFilter()方法,将ColorMatrix构造的的ColorMatrixColorFilter对象传递进去。然后使用这个画笔绘制原来的图像

    Paint paint = new Paint(); paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix)); canvas.drawBitmap(bitmap, 0, 0, paint); (注意Android系统不允许直接修改原图,必须通过原图创建一个同样大小的Bitmap,并将原图绘制到该Bitmap中,以一个副本的形式来修改图像,代码如下: Bitmap bmp = Bitmap.createBitmap( bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bmp); Paint paint = new Paint(); canvas.drawBitmap(bitmap, 0, 0, paint); (关于构造Canvas传入的Bitmap对象,可以理解为bitmap为Paint作用的画布,以后在该canvas上调用的绘制方法都是绘制在这个画布上的) )

    当然也可以直接通过修改矩阵的值来实现颜色效果的处理:实现一个包含20个float值的一位数组,然后通过ColorMatrix的set方法,将一个一维数组的效果设置给该ColorMatrix的实例

    android.graphics.ColorMatrix colorMatrix = new android.graphics.ColorMatrix(); colorMatrix.set(mColorMatrix);

    6.5.3 常用的图像颜色矩阵的效果处理

    灰度效果 图像反转 怀旧效果 去色效果 高饱和度 (每个效果都对应一个特定的颜色矩阵,这里各自详细的内容略)

    6.5.4 像素点分析

    该方法可以实现更加精确的图像处理方式,步骤如下 (注意原图不能直接修改,需要更据原始图片生成新的图片来修改)

    1 Android中可以通过Bitmap.getPixels()来提取整个Bitmap中的像素点,并保存到一个数组中

    bitmap.getPixels(pixels,offset,stride,x,y,width,height); 通常可以使用如下代码 bitmap.getPixels(oldPx,0,bm.getWidth(),0,0,width,height);

    2 提取每个像素具体的ARGB值 color=oldPx[i]; r=Color.red(color); g=Color.green(color); h=Color.blue(color); a=Colot.alpha(color);

    3 更据相应算法修改它的ARGB,从而来重构一张新的图像

    r1 = (int) (0.393 * r + 0.769 * g + 0.189 * b); g1 = (int) (0.349 * r + 0.686 * g + 0.168 * b); b1 = (int) (0.272 * r + 0.534 * g + 0.131 * b);

    4 在新的ARGB()合成新的像素点

    newPx[i]=Color.argb(a,r1,g1,b1);

    5 最后将处理后的像素点重新set给我们的Bitmap, bmp.setPixels(newPx,0,width,0,0,width,height);

    Android图像处理之图形特效处理

    6.6.1 Android变形矩阵—Matrix 是一个3X3的矩阵: a b c d e f g h f 其中a和e控制Scale—缩放变换 b和d控制Skew—错切变换 c和f控制Trans—平移变换 a ,b, d, e 共同控制Rotate—旋转变换

    即规律如下 Scale_x Skew_x Trans_x Skew_x Scale_y Trans_y 0 0 1

    在图形变形矩阵中,同样可以通过一个一维数组,并通过setValues()方法将一个一维数组转换为图形变换矩阵,代码示例如下

    private float[] mImageMatrix=new float[9]; Matrix matrix=new Matrix(); matrix.setValues(mImageMatrix); 然后绘制 canvas.drawBitmap(mBitmap,matrix,null);

    Android系统也提供了API来简化矩阵的运算,Android提供Matrix类来简化矩阵的运算

    setRotate()---旋转变换 setTranslate()---平移变换 setScale()---缩放变换 setSkew()---错切变换 pre()和post()---提供矩阵的前乘和后乘运算 (set方法会重置矩阵中的所有矩阵中的所有值,而post(即后乘)和pre(即前乘)方法不会,这两个方法常用来实现矩阵的混合作用,例如 先平移到(300,100) 再旋转45度 最后平移到(200,200) 则若采用后乘: matrix.setRotate(45); matrix.postTranslate(200,200); 若采用前乘: matrix.setTranslate(200,200); matrix.setRotate(45); )

    像素块分析 在进行图效的特效处理时也有两种方式来处理:即矩阵运算和像素块分析(即drawBitmapMesh(),为Canvas类的方法) 方法参数如下:

    drawBitmapMesh(Bitmap bitmap,int meshWidth,int meshHeight,float[] verts,int vertOffset,int[] colors,int colorOffset,Paint paint); 其中 bitmap:将要扭曲的图像 meshWidth:需要的横向网格数目 meshHeight:需要的纵向网格数目 verts:网格交叉坐标数组 vertOffset:verts数组中开始跳过的(x,y)坐标对的数目

    drawBitmapMesh()就是讲图像分成了一个个的小块,然后通过改变每一个图像来修改整个图像。此方法可以实现许多复杂的效果,不过对算法要求较高

    以下代码实现了一个旗帜飘扬的效果 import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.util.AttributeSet; import android.view.View; public class FlagBitmapMeshView extends View { private final int WIDTH = 200; private final int HEIGHT = 200; private int COUNT = (WIDTH + 1) * (HEIGHT + 1); private float[] verts = new float[COUNT * 2]; private float[] orig = new float[COUNT * 2]; private Bitmap bitmap; private float A; private float k = 1; public FlagBitmapMeshView(Context context) { super(context); initView(context); } public FlagBitmapMeshView(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } public FlagBitmapMeshView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context); } private void initView(Context context) { setFocusable(true); bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.test); float bitmapWidth = bitmap.getWidth(); float bitmapHeight = bitmap.getHeight(); int index = 0; for (int y = 0; y <= HEIGHT; y++) { float fy = bitmapHeight * y / HEIGHT; for (int x = 0; x <= WIDTH; x++) { float fx = bitmapWidth * x / WIDTH; orig[index * 2 + 0] = verts[index * 2 + 0] = fx; orig[index * 2 + 1] = verts[index * 2 + 1] = fy + 100; index += 1; } } A = 50; } @Override protected void onDraw(Canvas canvas) { flagWave(); k += 0.1F; canvas.drawBitmapMesh(bitmap, WIDTH, HEIGHT, verts, 0, null, 0, null); invalidate(); } private void flagWave() { for (int j = 0; j <= HEIGHT; j++) { for (int i = 0; i <= WIDTH; i++) { verts[(j * (WIDTH + 1) + i) * 2 + 0] += 0; float offsetY = (float) Math.sin((float) i / WIDTH * 2 * Math.PI + Math.PI * k);//利用三角函数的周期来使图像动起来 verts[(j * (WIDTH + 1) + i) * 2 + 1] = orig[(j * WIDTH + i) * 2 + 1] + offsetY * A; } } } }

    6.7 画笔特效处理

    画笔的一些高级属性:

    6.7.1 PorterDuffXfermode 即设置两个图层交集区域的显示方式,dst是先画的图形,src是后画的图形(常用的模式有DST_IN,SRC_IN) 使用PoterDuffXfermode非常简单,只需要让画面拥有这个属性就可以了

    mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test1); mOut = Bitmap.createBitmap(mBitmap.getWidth(), mBitmap.getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(mOut); mPaint = new Paint(); mPaint.setAntiAlias(true); canvas.drawRoundRect(0, 0, mBitmap.getWidth(), mBitmap.getHeight(), 80, 80, mPaint); mPaint.setXfermode(new PorterDuffXfermode( PorterDuff.Mode.SRC_IN)); canvas.drawBitmap(mBitmap, 0, 0, mPaint); 以上代码绘制了一个圆角图片

    6.7.2 Shader Shader又被称之为着色器,渲染器,它用来一系列的渐变,渲染效果。Android中的Shader包括以下几种 BitmapShader—位图Shader LinearGradient—线性Shader RadialGradient—光束Shader SweepGradient—梯度Shader ComposeShader—混合Shader

    注意BitmapShader产生的是一个图像,它的作用就是通过Paint对画布进行指定的Bitmap的填充,填充时有以下几种模式 CLAMP—拉伸,拉伸的是图片最后的那一个像素,不断重复 REPEAT—重复,横向,纵向不断重复 MIRROR—镜像,横向和纵向不断翻转重复

    mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher); mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); mPaint = new Paint(); mPaint.setShader(mBitmapShader); canvas.drawCircle(500, 250, 200, mPaint);

    6.7.3 PathEffect

    PathEffect就是指用各种笔触效果来绘制一个路径

    CornerPathEffect:使拐弯处变得圆滑

    DiscretePathEffect:使用这个效果之后,线段上就会产生许多杂点 DashPathEffect:绘制虚线

    PathDashPathEffect:与前面的DashPathEffect类似,不过功能更 加强大

    ComposePathEffect:可以组合PathEffect效果

    6.8 View之孪生兄弟—SurfaceView

    SurfaceView与View的区别:SurfaceView更加适合频繁刷新界面的情况,更加适合处理刷新时更大数据量的处理,此时用SurfaceView取代View就比较合适

    使用Surface的步骤

    1创建SurfaceView 继承SurfaceView,并实现SurfaceHolder.callback和Runnable接口

    2初始化SurfaceView 在自定义SurfaceView的构造方法中,对SurfaceView进行初始化,同时需要定义三个成员变量

    private SurfaceHolder mHolder; private Canvas mCanvas; private boolean mIsDrawing//子线程标志位,SurfaceView是通过子线程来进行绘制的

    初始化的的方式: mHodler=getHolder(); mHolder.addCallback(this);

    3使用SurfaceView 使用lockCanvas()可以获得当前的Canvas对象,接下来就可以像在View中绘制的方式一样绘制了。(获得的Canvas对象是上次绘制的对象,之前绘制的所有绘图操作都会保留,若需要擦除,可以调用drawColor()进行清屏操作). 在绘制的时候,充分利用SurfaceView的三个回调方法,在surfaceCreated()中开启子线程进行绘制,在绘制的具体逻辑中,通过lockCanvas()获得的Canvas进行绘制,并通过unlockCanvasAndPost(mCanvas)对画布内容进行提交。 整个的模版示例代码如下:

    import android.content.Context; import android.graphics.Canvas; import android.util.AttributeSet; import android.view.SurfaceHolder; import android.view.SurfaceView; public class SurfaceViewTemplate extends SurfaceView implements SurfaceHolder.Callback, Runnable { // SurfaceHolder private SurfaceHolder mHolder; // 用于绘图的Canvas private Canvas mCanvas; // 子线程标志位 private boolean mIsDrawing; public SurfaceViewTemplate(Context context) { super(context); initView(); } public SurfaceViewTemplate(Context context, AttributeSet attrs) { super(context, attrs); initView(); } public SurfaceViewTemplate(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initView(); } private void initView() { mHolder = getHolder(); mHolder.addCallback(this); setFocusable(true); setFocusableInTouchMode(true); this.setKeepScreenOn(true); //mHolder.setFormat(PixelFormat.OPAQUE); } @Override public void surfaceCreated(SurfaceHolder holder) { mIsDrawing = true; new Thread(this).start(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { mIsDrawing = false; } @Override public void run() { while (mIsDrawing) { draw(); } } private void draw() { try { mCanvas = mHolder.lockCanvas(); // draw sth } catch (Exception e) { } finally { if (mCanvas != null) mHolder.unlockCanvasAndPost(mCanvas); } } }

    下面的代码可以绘制一个正弦曲线

    import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.util.AttributeSet; import android.view.SurfaceHolder; import android.view.SurfaceView; public class SinView extends SurfaceView implements SurfaceHolder.Callback, Runnable { private SurfaceHolder mHolder; private Canvas mCanvas; private boolean mIsDrawing; private int x = 0; private int y = 0; private Path mPath; private Paint mPaint; public SinView(Context context) { super(context); initView(); } public SinView(Context context, AttributeSet attrs) { super(context, attrs); initView(); } public SinView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initView(); } private void initView() { mHolder = getHolder(); mHolder.addCallback(this); setFocusable(true); setFocusableInTouchMode(true); this.setKeepScreenOn(true); mPath = new Path(); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(Color.RED); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(10); mPaint.setStrokeCap(Paint.Cap.ROUND); mPaint.setStrokeJoin(Paint.Join.ROUND); } @Override public void surfaceCreated(SurfaceHolder holder) { mIsDrawing = true; mPath.moveTo(0, 400); new Thread(this).start(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { mIsDrawing = false; } @Override public void run() { while (mIsDrawing) { draw(); x += 1; y = (int) (100*Math.sin(x * 2 * Math.PI / 180) + 400); mPath.lineTo(x, y); } } private void draw() { try { mCanvas = mHolder.lockCanvas(); // SurfaceView背景 mCanvas.drawColor(Color.WHITE); mCanvas.drawPath(mPath, mPaint); } catch (Exception e) { } finally { if (mCanvas != null) mHolder.unlockCanvasAndPost(mCanvas); } } }

    以下代码可以实现绘图板

    import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; public class SimpleDraw extends SurfaceView implements SurfaceHolder.Callback, Runnable { private SurfaceHolder mHolder; private Canvas mCanvas; private boolean mIsDrawing; private Path mPath; private Paint mPaint; public SimpleDraw(Context context) { super(context); initView(); } public SimpleDraw(Context context, AttributeSet attrs) { super(context, attrs); initView(); } public SimpleDraw(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initView(); } private void initView() { mHolder = getHolder(); mHolder.addCallback(this); setFocusable(true); setFocusableInTouchMode(true); this.setKeepScreenOn(true); mPath = new Path(); mPaint = new Paint(); mPaint.setColor(Color.RED); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(40); } @Override public void surfaceCreated(SurfaceHolder holder) { mIsDrawing = true; new Thread(this).start(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { mIsDrawing = false; } @Override public void run() { long start = System.currentTimeMillis(); while (mIsDrawing) { draw(); } long end = System.currentTimeMillis(); // 50 - 100,通过判断draw()方法所使用的逻辑时长来确定sleep的时长,这是一个非常通用的解决方案,代码中100ms是一个大致的经验图 if (end - start < 100) { try { Thread.sleep(100 - (end - start)); } catch (InterruptedException e) { e.printStackTrace(); } } } private void draw() { try { mCanvas = mHolder.lockCanvas(); mCanvas.drawColor(Color.WHITE); mCanvas.drawPath(mPath, mPaint); } catch (Exception e) { } finally { if (mCanvas != null) mHolder.unlockCanvasAndPost(mCanvas); } } @Override public boolean onTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mPath.moveTo(x, y); break; case MotionEvent.ACTION_MOVE: mPath.lineTo(x, y); break; case MotionEvent.ACTION_UP: break; } return true; } }
    转载请注明原文地址: https://ju.6miu.com/read-971456.html

    最新回复(0)