Android进阶-View系列(三)-Android绘图机制与处理技巧(上)

屏幕的尺寸信息

屏幕参数:
屏幕大小:值屏幕对角线的长度,通常使用“寸”来度量。
分辨率:分辨率是指手机屏幕的像素点个数。
PPI:每英寸的像素,又被称为DPI,它是由对角线的像素点数除以屏幕的大小得到的。

系统屏幕密度:


独立像素密度dp:

由于各种屏幕密度的不同,导致同样像素大小的长度,在不同密度的屏幕上显示长度不同。因为相同长度的屏幕,高密度的屏幕包含更多的像素点。Android系统使用mdpi即密度值为160的屏幕作为标准,在这个屏幕上1px=1dp。其他屏幕则可以通过比例进行换算,ldpi:mdpi:hdpi:xdpi:xxdpi=3:4:6:8:12。

单位换算:
在程序中,也可以非常方便的对这些单位进行换算。

/**
 * dp、sp转换成px的工具类
 * 
 */
public class DisplayUtils {
    /**
     * 将px值转换为dpi或者dp值,保持尺寸大小不变
     *
     * @param content
     * @param pxValus
     * 
     * @return
     */
    public static int px2dip(Context content, float pxValus) {
        final float scale = content.getResources().getDisplayMetrics().density;
        return (int) (pxValus / scale + 0.5f);
    }
    /**
     * 将dip和dp值转换为px值,保证尺寸大小不变
     *
     * @param content
     * @param dipValus
     * 
     * @return
     */
    public static int dip2px(Context content, float dipValus) {
        final float scale = content.getResources().getDisplayMetrics().density;
        return (int) (dipValus / scale + 0.5f);
    }
    /**
     * 将px值转换为sp值,保证文字大小不变
     *
     * @param content
     * @param pxValus
     * @return
     */
    public static int px2sp(Context content, float pxValus) {
        final float fontScale = content.getResources().getDisplayMetrics().scaledDensity;
        return (int) (pxValus / fontScale + 0.5f);
    }
    /**
     * 将sp值转换为px值,保证文字大小不变
     *
     * @param content
     * @param spValus
     * @return
     */
    public static int sp2px(Context content, float spValus) {
        final float fontScale = content.getResources().getDisplayMetrics().scaledDensity;
        return (int) (spValus / fontScale + 0.5f);
    }
}

同时,系统也提供了TypedValue类帮助我们转换:
/**
 * dp2px
 */
protected int dp2px(int dp){
    return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dp,getResources

().getDisplayMetrics());
}
/**
 * sp2px
 */
protected int sp2px(int sp){
    return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,sp,getResources

().getDisplayMetrics());
}





Android绘图技巧
系统通过提供的Canvas对象来提供绘图方法。它提供了各种绘制图像的API,如drawPoint(点)、drawLine(线)、drawRect(矩形)、drawVertices(多边形)、drawArc(弧)、drawCircle(圆),等等。通过他们的名字,我们基本上可以打字了解他们的功能。
当然,Paint作为一个非常重要的元素,功能也是非常强大的,这里简单的列举一些他的属性和对应的功能。

setAntiAlias()//设置画笔的锯齿效果
setColor()//设置画笔的颜色
setARGB()//设置画笔的A、R、G、B值
setAlpha()//设置画笔的Alpha值
setTextSize()//设置字体的尺寸
setStyle()//设置画笔的风格(空心或实心)
setStrokeWidth()//设置空心边框的宽度

设置Paint的Style就可以画出空心或者实心的矩形:

paint.setStyle(Paint.Style.STROKE)//空心效果
paint.setStyle(Paint.Style.FILL)//实心效果


canvas.drawPoint(x, y, paint)//绘制点
canvas.drawLine(startX, startY ,endX, endY, paint)//绘制直线


canvas.drawLines(new float[]{startX1, startY1, endX1, endY1,……,startXn, startYn, endXn, endYn}, paint)//绘制多条直线
canvas.drawRect(left, top, right, bottom, paint)//绘制矩形


canvas.drawRoundRect(left, top, right, bottom, radiusX, radiusY, paint)//绘制圆角矩形
canvas.drawCircle(circleX, circleY, radius, paint)//绘制圆


canvas.drawOval(left, top, right, bottom, paint)//绘制椭圆
canvas.drawText(text, startX, startY, paint)//绘制文本


canvas.drawPosText(text, new float[]{X1,Y1,X2,Y2,……Xn,Yn}, paint)//在指定位置绘制文本
Path path = new Path();
path.moveTo(50, 50);
path.lineTo(100, 100);
path.lineTo(100, 300);
path.lineTo(300, 50);
canvas.drawPath(path, paint)//绘制路径


paint.setStyle(Paint.Style.STROKE);
drawArc(left, top, right,bottom, startAngle, sweepAngle, true, paint)//绘制空心扇形
paint.setStyle(Paint.Style.STROKE);
drawArc(left, top, right,bottom, startAngle, sweepAngle, false, paint)//绘制弧线
paint.setStyle(Paint.Style.FILL);
drawArc(left, top, right,bottom, startAngle, sweepAngle, true, paint)//绘制实心扇形
paint.setStyle(Paint.Style.FILL);
drawArc(left, top, right,bottom, startAngle, sweepAngle, false, paint)//绘制实心弧形
注意drawArc()的倒数第二个参数的布尔值,产生的效果。


Android XML绘图
xml在Android系统中可不仅仅是Java中的一个布局文件、配置列表。在Android开发者的手上,它甚至可以变成一张画、一幅图。Android的开发者给xml提供了几个强大的技能来帮助我们实现这一功能。


Bitmap
在xml中使用Bitmap非常简单:

<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
    android:src="@mipmap/ic_launcher">
</bitmap>


Shape
通过Shape可以在XML中绘制各种形状,下面展示Shape所支持的参数

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:android="http://schemas.android.com/apk/res/android"
    //默认为rectangle
    android:shape=["rectangle" | "oval" | "line" | "ring"] >
    <corners   //当shape="rectangle"时使用
	    //半径,会被后面的单个半径属性覆盖,默认为1dp
        android:radius="integer"
        android:topLeftRadius="integer"
        android:topRightRadius="integer"
        android:bottomLeftRadius="integer"
        android:bottomRightRadius="integer" />
    <gradient   //渐变
        android:angle="integer"
        android:centerX="integer"
        android:centerY="integer"
        android:centerColor="integer"
        android:endColor="color"
        android:gradientRadius="integer"
        android:startColor="color"
        android:type=["linear" | "radial" | "sweep"]
        android:useLevel=["true" | "false"] />
    <padding
        android:left="integer"
        android:top="integer"
        android:right="integer"
        android:bottom="integer" />
    <size   //指定大小,一般在imageview配合scaleType属性使用
        android:width="integer"
        android:height="integer" />
    <solid   //填充颜色
        android:color="color" />
    <stroke   //指定边框
        android:width="integer"
        android:color="color"
        //虚线宽度
        android:dashWidth="integer"
        //虚线间隔宽度
        android:dashGap="integer" />
</shape>


下面就是一个简单的通过渐变来实现的阴影效果:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <gradient
        android:angle="45"
        android:endColor="#805FBBFF"
        android:startColor="#FF5DA2FF" />
    <padding
        android:bottom="7dp"
        android:left="7dp"
        android:right="7dp"
        android:top="7dp" />
    <corners android:radius="8dp" />
</shape>

运行结果:


Layer:
Layer是在Photoshop中非常常用的功能,在Android中,同样可以通过Layer来实现类似Photoshop中图层的概念:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <!--图片1-->
    <item android:drawable="@drawable/ic_launcher"/>
    <!--图片2-->
    <item
        android:bottom="10dp"
        android:top="10dp"
        android:right="10dp"
        android:left="10dp"
        android:drawable="@drawable/ic_launcher"
        />
</layer-list>

运行结果:


Selector:
Selector的作用在于帮助开发者实现静态绘图中的事件反馈,通过给不同的事件设置不同的图像,从而在程序中根据用户输入,返回不同的效果

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 默认时的背景图片-->
    <item android:drawable="@mipmap/ic_launcher" />
    <!-- 没有焦点时的背景图片-->
    <item android:drawable="@mipmap/ic_launcher" android:state_window_focused="false" />
    <!-- 非触摸模式下获得焦点并单击时的背景图片-->
    <item android:drawable="@mipmap/ic_launcher" android:state_pressed="true" 


android:state_window_focused="true" />
    <!-- 触摸模式下单击时的背景图片-->
    <item android:drawable="@mipmap/ic_launcher" android:state_focused="false" android:state_pressed="true" />
    <!--选中时的图片背景-->
    <item android:drawable="@mipmap/ic_launcher" android:state_selected="true" />
    <!--获得焦点时的图片背景-->
    <item android:drawable="@mipmap/ic_launcher" android:state_focused="true" />
</selector>

下面这个例子就展示在一个Selector中使用Shape作为它的Item的例子,实现一个具有点击反馈效果的、圆角矩形的Selector:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true">
        <shape android:shape="rectangle">
            <!--填充的颜色-->
            <solid android:color="#33444444" />
            <!--设置按钮的四个角为弧形-->
            <!--android:radius 弧形的半径-->
            <corners android:radius="5dp" />
            <!--padding:Button里面的文字与Button边界的间隔-->
            <padding android:bottom="10dp" android:left="10dp" android:right="10dp" android:top="10dp" />
        </shape>
    </item>
    <item>
        <shape android:shape="rectangle">
            <!--填充的颜色-->
            <solid android:color="#FFFFFF" />
            <!--设置按钮的四个角为弧形-->
            <!--android:radius 弧形的半径-->
            <corners android:radius="5dp" />
            <!--padding:Button里面的文字与Button边界的间隔-->
            <padding android:bottom="10dp" android:left="10dp" android:right="10dp" android:top="10dp" />
        </shape>
    </item>
</selector>

运行结果:



Canvas
Canvas.save():保存画布。它的作用就是将之前的所有已绘制图像保存起来,让后续的操作就好像在一个新的图层上操作一样。
Canvas.restore():合并图层,它的作用是将我们在save()之后绘制的所有图像与save()之前的图像进行合并。
Canvas.translate():画布平移,或者坐标系的平移。
Canvas.roate():画布翻转,或坐标系的翻转。

通过一个仪表盘的案例来了解这几个方法,这个仪表盘可以分解成以下几个元素:
仪表盘:外面的大圆盘
刻度线:包含四个长的刻度线和其他短的刻度线
刻度值:包含长刻度线对应的大的刻度值和其他小的刻度值
指针:中间的指针,一粗一细两根指针
代码案例:

 @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();
    }

运行结果:



Layer图层
一张复杂的画可以由很多歌图层叠加起来,形成一个复杂的图像。在Android中,使用saveLayer()方法来创建一个图层,图层同样是基于栈的结构进行管理。
当saveLayerAlpha()方法的倒数第二个参数就是透明度参数
当透明度为127时,就是半透明
当透明度为255时,就是完全不透明

当透明度为127时,就是完全透明

代码案例:

public class MyLayer extends View {
	private Paint mPaint;
	private static final int LAYER_FLAGS = Canvas.MATRIX_SAVE_FLAG
			| Canvas.CLIP_SAVE_FLAG | Canvas.HAS_ALPHA_LAYER_SAVE_FLAG
			| Canvas.FULL_COLOR_LAYER_SAVE_FLAG
			| Canvas.CLIP_TO_LAYER_SAVE_FLAG;

	public MyLayer(Context context) {
		super(context);
		mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
	}

	@Override
	protected void onDraw(Canvas canvas) {
		canvas.drawColor(Color.WHITE);
		mPaint.setColor(Color.BLUE);
		canvas.drawCircle(150, 150, 100, mPaint);

		canvas.saveLayerAlpha(0, 0, 400, 400, 0, LAYER_FLAGS);
		mPaint.setColor(Color.RED);
		canvas.drawCircle(200, 200, 100, mPaint);
		canvas.restore();
	}
}

运行结果:


源码下载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值