不同手机的大小和像素密度都不同,为统一,定义了几个标准的DPI值
相同的像素,在不同密度的屏幕上显示,长度会不同,因为高密度的屏幕包含更多像素点
如下工具类提供了px、dp、sp的互相转换
public class DisplayUtil {
// dp = 像素/密度
public static int px2dip(Context context, float pxValue) {
float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}
public static int dip2px(Context context, float dipValue) {
float scale = context.getResources().getDisplayMetrics().density;
return (int) (dipValue * scale + 0.5f);
}
// sp = 像素/缩放密度
public static int px2sp(Context context, float pxValue) {
float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (pxValue / fontScale + 0.5f);
}
public static int sp2px(Context context, float spValue) {
float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * fontScale + 0.5f);
}
}
或可以使用TypedValue.applyDimension()方法
public int dp2px(int dp) {
return (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
dp,
getResources().getDisplayMetrics());
}
public int sp2px(int sp) {
return (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP,
sp,
getResources().getDisplayMetrics());
}
如下引用图片并转为Bitmap
Shape可实现不同类型的形状,如下实现渐变阴影
效果如图
Layer可实现类似PS中图层的概念
上面两个item分别为图片进行叠加,效果如图
Selector实现不同事件设置反馈,如下实现修改为圆角矩形,点击后切换颜色
-
-
下面为常用的方法
如下绘制一个时钟,通过平移、旋转坐标系简化实现
public class Clock extends View {
private int mWidth;
private int mHeight;
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 onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = getMeasuredWidth();
mHeight = getMeasuredHeight();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 绘制外圆,以(mWidth / 2, mHeight / 2)为圆形,以mWidth / 2为半径
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 paintDegree = new Paint();
for (int i = 0; i < 24; i++) {
if (i == 0 || i == 6 || i == 12 || i == 18) {
paintDegree.setStrokeWidth(5);
paintDegree.setTextSize(30);
canvas.drawLine(mWidth / 2, mHeight / 2 - mWidth / 2,
mWidth / 2, mHeight / 2 - mWidth / 2 + 60,
paintDegree);
String degree = String.valueOf(i);
canvas.drawText(degree,
mWidth / 2 - paintDegree.measureText(degree) / 2,
mHeight / 2 - mWidth / 2 + 90,
paintDegree);
} else {
paintDegree.setStrokeWidth(3);
paintDegree.setTextSize(15);
canvas.drawLine(mWidth / 2, mHeight / 2 - mWidth / 2,
mWidth / 2, mHeight / 2 - mWidth / 2 + 30,
paintDegree);
String degree = String.valueOf(i);
canvas.drawText(degree,
mWidth / 2 - paintDegree.measureText(degree) / 2,
mHeight / 2 - mWidth / 2 + 60,
paintDegree);
}
// 将画布以圆心旋转15°,简化坐标运算
canvas.rotate(15, mWidth / 2, mHeight / 2);
}
// 绘制指针
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();
}
}
图层基于栈结构,入栈后操作都发生在该图层上,出栈后则把图像绘制到上层Canvas
public class MyView extends View {
private static final String TAG = MyView.class.getSimpleName();
private Paint mPaint;
public MyView(Context context) {
this(context, null);
}
public MyView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mPaint = new Paint();
}
@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, 127);
mPaint.setColor(Color.RED);
canvas.drawCircle(200, 200, 100, mPaint);
canvas.restore();
}
}
如下图,透明度为127的红色圆叠加在蓝色圆上方
PorterDuffXfermod设置的是两个图层交集区域的显示方式,dst是先画的图形,而src是后画的图形,共有16种模式,如图
如下实现圆角图形,mOut为先画的图像,mBitmap为后画的图像,用SrcIn取交集
public class MyView extends View {
private static final String TAG = MyView.class.getSimpleName();
private Bitmap mBitmap;
private Bitmap mOut;
private Paint mPaint;
public MyView(Context context) {
this(context, null);
}
public MyView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
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);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(mOut, 0, 0, null);
}
}
效果如图
如下代码设置透明的画笔,实时获取坐标调用drawPath()涂抹上面的图层,实现刮刮乐
public class XfermodeView extends View {
private Bitmap mBgBitmap, mFgBitmap;
private Paint mPaint;
private Canvas mCanvas;
private Path mPath;
public XfermodeView(Context context) {
this(context, null);
}
public XfermodeView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public XfermodeView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPaint = new Paint();
mPaint.setAlpha(0);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeWidth(50);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPath = new Path();
mBgBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.b);
mFgBitmap = Bitmap.createBitmap(mBgBitmap.getWidth(), mBgBitmap.getHeight(), Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mFgBitmap);
mCanvas.drawColor(Color.GRAY);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mPath.reset();
mPath.moveTo(event.getX(), event.getY());
break;
case MotionEvent.ACTION_MOVE:
mPath.lineTo(event.getX(), event.getY());
break;
}
mCanvas.drawPath(mPath, mPaint);
invalidate();
return true;
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(mBgBitmap, 0, 0, null);
canvas.drawBitmap(mFgBitmap, 0, 0, null);
}
}
用于实现渐变、渲染效果,包括
上述渐变都有三种模式选择:
如下,将图片填充到BitmapShader,创建一支具有图像填充功能的Paint
public class MyView extends View {
private static final String TAG = MyView.class.getSimpleName();
private Bitmap mBitmap;
private Paint mPaint;
private BitmapShader mBitmapShader;
public MyView(Context context) {
this(context, null);
}
public MyView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
mPaint = new Paint();
mPaint.setShader(mBitmapShader);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(mBitmap.getWidth() / 2.0f, mBitmap.getHeight() / 2.0f, 50, mPaint);
}
}
利用Paint在图片中心绘制圆形,效果如下
public class MyView extends View {
private static final String TAG = MyView.class.getSimpleName();
private Bitmap mBitmap;
private Paint mPaint;
private BitmapShader mBitmapShader;
public MyView(Context context) {
this(context, null);
}
public MyView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
mPaint = new Paint();
mPaint.setShader(mBitmapShader);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(mBitmap.getWidth() / 2.0f, mBitmap.getHeight() / 2.0f, 500, mPaint);
}
}
如下利用PorterDuffXfermode和LinnerGradient实现倒影效果的图片
public class ReflectView extends View {
private Bitmap mSrcBitmap, mRefBitmap;
private Paint mPaint;
private PorterDuffXfermode mXfermode;
public ReflectView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
mSrcBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.b);
Matrix matrix = new Matrix();
matrix.setScale(1F, -1F);//垂直翻转
mRefBitmap = Bitmap.createBitmap(mSrcBitmap, 0, 0,
mSrcBitmap.getWidth(), mSrcBitmap.getHeight(), matrix, true);
mPaint = new Paint();
mPaint.setShader(new LinearGradient(0, mSrcBitmap.getHeight(),
0, mSrcBitmap.getHeight() * 2,
0xDD000000, 0x10000000, Shader.TileMode.REPEAT));
mXfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.BLACK);
//原图
canvas.drawBitmap(mSrcBitmap, 0, 0, null);
//倒影图
canvas.drawBitmap(mRefBitmap, 0, mSrcBitmap.getHeight(), null);
mPaint.setXfermode(mXfermode);
//绘制渐变效果层
canvas.drawRect(0, mRefBitmap.getHeight(), mRefBitmap.getWidth(), mSrcBitmap.getHeight() * 2, mPaint);
mPaint.setXfermode(null);
}
}
先将图片翻转,再添加黑色透明度递减的渐变,DST_IN是为了避免上面的图像被渐变效果遮挡
如上,分别为
public class MyView extends View {
private static final String TAG = MyView.class.getSimpleName();
private Path mPath;
private PathEffect[] mPathEffect;
private Paint mPaint;
public MyView(Context context) {
this(context, null);
}
public MyView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPath = new Path();
mPath.moveTo(0, 0);
for (int i = 0; i <= 30; i++) {
mPath.lineTo(i * 35, (float) (Math.random() * 100));
}
mPathEffect = new PathEffect[6];
mPathEffect[0] = null;
mPathEffect[1] = new CornerPathEffect(30);
mPathEffect[2] = new DiscretePathEffect(3.0F, 5.0F);
mPathEffect[3] = new DashPathEffect(new float[]{20, 10, 5, 10}, 0);
Path path = new Path();
path.addRect(0, 0, 8, 8, Path.Direction.CCW);
mPathEffect[4] = new PathDashPathEffect(path, 12, 0, PathDashPathEffect.Style.ROTATE);
mPathEffect[5] = new ComposePathEffect(mPathEffect[3], mPathEffect[1]);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (PathEffect pathEffect : mPathEffect) {
mPaint.setPathEffect(pathEffect);
canvas.drawPath(mPath, mPaint);
canvas.translate(0, 200);
}
}
}
Android通过VSYNC信号刷新View进行屏幕的重绘,时间间隔为16ms,若在需要频繁刷新的界面上执行太多的操作逻辑,则容易阻塞主线程,造成画面卡顿
当需要频繁刷新或刷新时数据处理量比较大时,可使用SurfaceView
SurfaceView的模板代码如下,主要原理是初始化时自行创建子线程并调用draw()方法绘制
public class SurfaceViewTemplate extends SurfaceView implements SurfaceHolder.Callback, Runnable {
//内部Holder,用于获取Canvas和提交绘制
private SurfaceHolder mHolder;
//用于控制线程的标志位
private boolean mIsDrawing;
//用于绘图的Canvas
private Canvas mCanvas;
public SurfaceViewTemplate(Context context) {
this(context, null);
}
public SurfaceViewTemplate(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SurfaceViewTemplate(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mHolder = getHolder();
mHolder.addCallback(this); //将自身作为回调
setFocusableInTouchMode(true);
setFocusable(true);
this.setKeepScreenOn(true);
}
//创建时开启线程,调用draw()
@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();
//获取Canvas开始绘制,获取的并非新的Canvas,而是上次的
} catch (Exception e) {
e.printStackTrace();
} finally {
if (mHolder != null) {
//绘制完后需提交
mHolder.unlockCanvasAndPost(mCanvas);
}
}
}
}
如下代码利用SurfaceView实现正弦函数的动态绘制
public class SurfaceViewTemplate extends SurfaceView implements SurfaceHolder.Callback, Runnable {
private SurfaceHolder mHolder;
private boolean mIsDrawing;
private Canvas mCanvas;
private int x;
private int y;
private Path mPath;
private Paint mPaint;
public SurfaceViewTemplate(Context context) {
this(context, null);
}
public SurfaceViewTemplate(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SurfaceViewTemplate(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPath = new Path();
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mHolder = getHolder();
mHolder.addCallback(this);
setFocusableInTouchMode(true);
setFocusable(true);
this.setKeepScreenOn(true);
}
@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();
x += 1;
y = (int) (100 * Math.sin(x * 2 * Math.PI / 180) + 400);
mPath.lineTo(x, y);
}
}
private void draw() {
try {
mCanvas = mHolder.lockCanvas();
mCanvas.drawColor(Color.WHITE); //设置背景
mCanvas.drawPath(mPath, mPaint);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (mHolder != null) {
mHolder.unlockCanvasAndPost(mCanvas);
}
}
}
}
效果如图
如下代码利用SurfaceView实现绘图板,根据手指移动绘制路径,在draw()中调用sleep()避免过度频繁绘制
public class SurfaceViewTemplate extends SurfaceView implements SurfaceHolder.Callback, Runnable {
private SurfaceHolder mHolder;
private boolean mIsDrawing;
private Canvas mCanvas;
private Path mPath;
private Paint mPaint;
public SurfaceViewTemplate(Context context) {
this(context, null);
}
public SurfaceViewTemplate(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SurfaceViewTemplate(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPath = new Path();
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mHolder = getHolder();
mHolder.addCallback(this);
setFocusableInTouchMode(true);
setFocusable(true);
this.setKeepScreenOn(true);
}
@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 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;
}
@Override
public void run() {
long start = System.currentTimeMillis();
while (mIsDrawing) {
draw();
}
long end = System.currentTimeMillis();
if (end - start < 100) {
SystemClock.sleep(100 - (end - start));
}
}
private void draw() {
try {
mCanvas = mHolder.lockCanvas();
mCanvas.drawColor(Color.WHITE);
mCanvas.drawPath(mPath, mPaint);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (mHolder != null) {
mHolder.unlockCanvasAndPost(mCanvas);
}
}
}
}
效果如图