参考资料
目录
- 1)屏幕尺寸信息
- 2)2D绘图基础
- 3)XML绘图
- 4)绘图技巧
- 5)图像处理-色彩特效处理
- 6)图像处理-图形特效处理
- 7)图像处理-画笔特效处理
- 8)SurfaceView
1)屏幕尺寸信息
ldpi | mdpi | hdpi | xhdpi | xxhdpi |
---|---|---|---|---|
120 | 160 | 240 | 320 | 480 |
240x320 | 320x480 | 480x800 | 720x1280 | 1080x1920 |
1dp=1px | 1dp=1.5px | 1dp=2px | 1dp=3px |
//单位转换工具
public class DisplayUtil {
public static int px2dip(Context context, float pxValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}
public static int dip2px(Context context, float dipValue) {
// final float scale = context.getResources().getDisplayMetrics().density;
// return (int) (dipValue * scale + 0.5f);
//或者使用系统的TypedValue类来转换
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dipValue, context.getResources().getDisplayMetrics());
}
public static int px2sp(Context context, float pxValue) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (pxValue / fontScale + 0.5f);
}
public static int sp2px(Context context, float spValue) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * fontScale + 0.5f);
}
}
TypedValue applyDimension源码
public static float applyDimension(int unit, float value,
DisplayMetrics metrics)
{
switch (unit) {
case COMPLEX_UNIT_PX:
return value;
case COMPLEX_UNIT_DIP:
return value * metrics.density;
case COMPLEX_UNIT_SP:
return value * metrics.scaledDensity;
case COMPLEX_UNIT_PT:
return value * metrics.xdpi * (1.0f/72);
case COMPLEX_UNIT_IN:
return value * metrics.xdpi;
case COMPLEX_UNIT_MM:
return value * metrics.xdpi * (1.0f/25.4f);
}
return 0;
}
2)2D绘图基础(参见自定义View)
3)XML绘图
- Bitmap,这样引用可以将图片直接转成Bitmap使用
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/xxx"/>
- Shape
新建文件res/drawable/shape_test.xml
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
//默认为rectangle矩形,oval椭圆,line横线,ring圆环
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:type=["linear" | "radial" | "sweep"] //共有3中渐变类型,线性渐变(默认)/放射渐变/扫描式渐变
android:angle="integer" //渐变角度,必须为45的倍数,0为从左到右,90为从上到下
android:centerX="float" //渐变中心X的相当位置,范围为0~1
android:centerY="float" //渐变中心Y的相当位置,范围为0~1
android:startColor="color" //渐变开始点的颜色
android:centerColor="color" //渐变中间点的颜色,在开始与结束点之间
android:endColor="color" //渐变结束点的颜色
android:gradientRadius="float" //渐变的半径,只有当渐变类型为radial时才能使用
android:useLevel=["true" | "false"] /> //使用LevelListDrawable时就要设置为true。设为false时才有渐变效果
/>
<solid //填充颜色,与gradient互相排斥
android:color="color"/>
<stroke //描边属性
android:width="dimension" //描边的宽度
android:color="color" //描边的颜色
// 以下两个属性设置虚线
android:dashWidth="dimension" //虚线的宽度,值为0时是实线
android:dashGap="dimension" /> //虚线的间隔
//下面2个一般不怎么用,因为他们的功能控件本身也有
<padding
android:left="integer"
android:top="integer"
android:right="integer"
android:bottom="integer" />
<size //指定大小 一般在imageview配合scaleType
android:width="integer"
android:height="integer" />
</shape>
- Layer图层
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/xxx1">
<item android:drawable="@drawable/xxx2">
</layer-list>
- Selector可实现静态绘图的事件反馈,可针对事件设置不同图像。Selector中也可以使用Shape作为它的Item。
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 默认时的背景图片 -->
<item android:drawable="@drawable/pic1" />
<!-- 没有焦点时的背景图片 -->
<item android:state_window_focused="false"
android:drawable="@drawable/pic1" />
<!-- 非触摸模式下获得焦点并单击时的背景图片 -->
<item android:state_focused="true" android:state_pressed="true"
android:drawable= "@drawable/pic2" />
<!-- 触摸模式下单击时的背景图片 -->
<item android:state_focused="false" android:state_pressed="true"
android:drawable="@drawable/pic3" />
<!--选中时的图片背景 -->
<item android:state_selected="true"
android:drawable="@drawable/pic4" />
<!--获得焦点时的图片背景 -->
<item android:state_focused="true"
android:drawable="@drawable/pic5" />
</selector>
4)绘图技巧(参见自定义View)
5)图像处理-色彩特效处理
- ColorMatrix
Android对于图片的处理,常使用数据结构是位图,即Bitmap,它包含了图片所有的数据。整个图片是由点阵和颜色值组成。每个点阵就是一个包含像素的矩阵。
public class MainActivity extends AppCompatActivity implements SeekBar.OnSeekBarChangeListener{
private static final String TAG = "MainActivity";
private ImageView img;
private Bitmap bm;
private int mHue=1,mSaturation=1,mLum=1;
private SeekBar seek_hue,seek_saturation,seek_lum;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
seek_hue = (SeekBar) findViewById(R.id.seek_hue);
seek_saturation = (SeekBar) findViewById(R.id.seek_saturation);
seek_lum = (SeekBar) findViewById(R.id.seek_lum);
seek_hue.setOnSeekBarChangeListener(this);
seek_saturation.setOnSeekBarChangeListener(this);
seek_lum.setOnSeekBarChangeListener(this);
bm = BitmapFactory.decodeResource(getResources(),R.drawable.def);
img = (ImageView) findViewById(R.id.img);
}
private Bitmap handleImageEffect(Bitmap bm,float hue,float saturation,float lum){
//原始图片是不能修改的,可根据原始图片生成新图进行修改
Bitmap bmp = Bitmap.createBitmap(bm.getWidth(),bm.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bmp);
Paint paint = new Paint();
//色调
ColorMatrix hueMatrix = new ColorMatrix();
hueMatrix.setRotate(0,hue);
hueMatrix.setRotate(1,hue);
hueMatrix.setRotate(2,hue);
//保饱和度
ColorMatrix saturationMatrix = new ColorMatrix();
saturationMatrix.setSaturation(saturation);
//亮度
ColorMatrix lumMatrix = new ColorMatrix();
lumMatrix.setScale(lum,lum,lum,1);
ColorMatrix imageMatrix = new ColorMatrix();
imageMatrix.postConcat(hueMatrix);
imageMatrix.postConcat(saturationMatrix);
imageMatrix.postConcat(lumMatrix);
paint.setColorFilter(new ColorMatrixColorFilter(imageMatrix));
canvas.drawBitmap(bm,0,0,paint);
return bmp;
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
Log.i(TAG, "============================ ");
switch (seekBar.getId()){
case R.id.seek_hue:
mHue = progress;
break;
case R.id.seek_saturation:
mSaturation = progress;
break;
case R.id.seek_lum:
mLum = progress;
break;
}
Log.i(TAG, "mHue= "+mHue+" mSaturation= "+mSaturation+" mLum= "+mLum);
img.setImageBitmap(handleImageEffect(bm,mHue,mSaturation,mLum));
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
}
- 颜色矩阵
通过调整颜色矩阵可以改变一副图像的色彩效果,图像处理很大程度上就是寻找处理图像的颜色矩阵,除了使用系统提供的ColorMatrix,也可以精确的修改矩阵值来实现颜色效果处理。
4x5 最右侧为偏移量,左侧4x4为颜色系数RGBA
public class Main2Activity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "Main2Activity";
private GridLayout mGroup;
private EditText[] edts = new EditText[20];
private Bitmap bitmap;
private float[] mColorMatrix = new float[20];
private ImageView img;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.def);
img = (ImageView) findViewById(R.id.img);
img.setImageBitmap(bitmap);
mGroup = (GridLayout) findViewById(R.id.group);
Button btn1 = (Button) findViewById(R.id.btn1);
Button btn2 = (Button) findViewById(R.id.btn2);
btn1.setOnClickListener(this);
btn2.setOnClickListener(this);
initEdts();
initMatrix();
}
//初始化EditText
private void initEdts(){
Log.i(TAG, "mGroup.getHeight(): "+mGroup.getHeight());
for (int i=0;i<20;i++){
EditText editText = new EditText(this);
edts[i] = editText;
mGroup.addView(editText,getResources().getDisplayMetrics().widthPixels/5,100);
}
}
//初始化矩阵值
private void initMatrix(){
for (int i=0; i<20; i++){
if (i%6 == 0){
edts[i].setText("1");
}else {
edts[i].setText("0");
}
}
}
//获取矩阵值
private void getMatrix(){
for (int i=0;i<20;i++){
mColorMatrix[i] = Float.parseFloat(edts[i].getText().toString());
}
}
//将矩阵设置到图像
private void setImageMatrix(){
//原始图片是不能修改的,可根据原始图片生成新图进行修改
Bitmap bmp = Bitmap.createBitmap(bitmap.getWidth(),bitmap.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bmp);
Paint paint = new Paint();
paint.setColorFilter(new ColorMatrixColorFilter(mColorMatrix));
canvas.drawBitmap(bitmap,0,0,paint);
img.setImageBitmap(bmp);
}
@Override
public void onClick(View v) {
switch (v.getId()){
//设置值
case R.id.btn1:
getMatrix();
setImageMatrix();
break;
//重置
case R.id.btn2:
initMatrix();
getMatrix();
setImageMatrix();
break;
}
}
}
常用图像颜色矩阵处理效果
-
灰度效果
-
图像反转
-
怀旧效果
-
去色效果
-
高饱和度
像素点分析
Android中还提供了Bitmao.getPixels()方法来帮我们提取整个Bitmap的像素点。
public class PixelsActivity extends AppCompatActivity {
private Bitmap bitmap;
private ImageView img;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pixels);
bitmap= BitmapFactory.decodeResource(getResources(),R.drawable.def);
img = (ImageView) findViewById(R.id.img);
handleImage();
}
private void handleImage(){
//创建临时bitmap
Bitmap bmp = Bitmap.createBitmap(bitmap.getWidth(),bitmap.getHeight(), Bitmap.Config.ARGB_8888);
int[] oldPx = new int[bitmap.getWidth()*bitmap.getHeight()];
int[] newPx = new int[bitmap.getWidth()*bitmap.getHeight()];
//pixels - 接收位图颜色值的数组
//offset - 写入到pixels[]中的第一个像素索引值
//stride - pixels[]中的行间距
// x - 从位图中读取的第一个像素x坐标值
// y - 从位图中读取的第一个像素y坐标值
//width - 从每一行中读取的像素宽度
//height - 读取的行数
bitmap.getPixels(oldPx,0,bitmap.getWidth(),0,0,bitmap.getWidth(),bitmap.getHeight());
for (int i=1;i<bitmap.getWidth()*bitmap.getHeight();i++){
int color = oldPx[i-1];
int r = Color.red(color);
int g = Color.green(color);
int b = Color.blue(color);
int a = Color.alpha(color);
int color2 = oldPx[i];
int r2 = Color.red(color2);
int g2 = Color.green(color2);
int b2 = Color.blue(color2);
int a2 = Color.alpha(color2);
//底片效果
// r = 255-r;
// g = 255-g;
// b = 255-b;
//老照片效果
// r = (int) (0.393*r+0.769*g+0.189*b);
// g = (int) (0.349*r+0.686*g+0.168*b);
// b = (int) (0.272*r+0.534*g+0.131*b);
//浮雕效果
r = r-r2+127;
g = g-g2+127;
b = b-b2+127;
if (r>255){
r=255;
}else if (r<0){
r=0;
}
if (g>255){
g=255;
}else if (g<0){
g=0;
}
if (b>255){
b=255;
}else if (b<0){
b=0;
}
newPx[i] = Color.argb(a,r,g,b);
}
bmp.setPixels(newPx,0,bitmap.getWidth(),0,0,bitmap.getWidth(),bitmap.getHeight());
img.setImageBitmap(bmp);
}
}
6)图像处理-图形特效处理
7)图像处理-画笔特效处理
- PorterDuffXfermode
PorterDuffXfermode设置的是两个图层交集区域的显示方式,dst是先画的图形,而src为后画的图形。
先用一个普通画笔画一个mask遮罩层,再用带PorterDuffXfermode的画笔将图像花在遮罩层上。
-
例:使用PorterDuffXfermode实现的圆头像
ImageView img = (ImageView) findViewById(R.id.img);
Bitmap mBitmap = BitmapFactory.decodeResource(getResources(),R.drawable.def);
Bitmap bmp = Bitmap.createBitmap(mBitmap.getWidth(),mBitmap.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bmp);
Paint mPaint = new Paint();
mPaint.setAntiAlias(true);
RectF rectF = new RectF(0,0,mBitmap.getWidth(),mBitmap.getHeight());
//画圆
canvas.drawRoundRect(rectF,mBitmap.getWidth()/2,mBitmap.getHeight()/2,mPaint);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(mBitmap,0,0,mPaint);
img.setImageBitmap(bmp);
还有其他类型可以自己尝试:
SRC,SRC_IN,SRC_OVER,SRC_OUT,SRC_ATOP
DST,DST_IN,DST_OVER,DST_OUT,DST_ATOP
XOR,DARKEN,LIGHTEN,MULTIPLY,SCREEN
2)例:实现刮刮卡效果
public class XfermodeView extends View {
private Paint mPaint;
private Canvas canvas;
private Bitmap mBgBitmap, mFgBitmap;
private Path mPath;
public XfermodeView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
mPaint = new Paint();
//重要! 用于绘制透明path
mPaint.setAlpha(0);
//遮罩层类型
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
mPaint.setStrokeWidth(50);
mPaint.setStrokeCap(Paint.Cap.ROUND); //笔触风格
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND); // 接合处形态
mBgBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.def);
mFgBitmap = Bitmap.createBitmap(mBgBitmap.getWidth(), mBgBitmap.getHeight(), Bitmap.Config.ARGB_8888);
canvas = new Canvas(mFgBitmap);
canvas.drawColor(Color.GRAY);
mPath = new Path();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
mPath.moveTo(event.getX(),event.getY());
break;
case MotionEvent.ACTION_MOVE:
mPath.lineTo(event.getX(),event.getY());
break;
}
//其实就是绘制透明路径, 因mPaint设置的Alpha为0
canvas.drawPath(mPath,mPaint);
invalidate();
return true;
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(mBgBitmap,0,0,null);
canvas.drawBitmap(mFgBitmap,0,0,null);
}
}
- Shader
类 | 说明 |
---|---|
BitmapShader | 位图 |
LinearGradient | 线性 |
RadialGradient | 光束 |
SweepGradient | 梯度 |
ComposeShader | 混合 |
Shader.TileMode | 说明 |
---|---|
Shader.TileMode.CLAMP | 拉伸。的是图片最后的那一个元素,不断重复 |
REPEAT | 重复。横向、纵向不断重复 |
MIRROR | 镜像。横向不断翻转重复,纵向不断翻转重复 |
1)例:线性渐变着色器
public class MyTextView1 extends TextView {
private Paint mPaint = new Paint();
private int mViewWidth;
private LinearGradient mLinearGradient;
private Matrix mGradientMatrix;
private int mTranslate;
public MyTextView1(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (mViewWidth == 0){
mViewWidth = getMeasuredWidth();
if (mViewWidth > 0){
//重要!获取当前绘制用的Paint对象
mPaint = getPaint();
//线性渐变着色器
mLinearGradient = new LinearGradient(0,0,mViewWidth,0,new int[]{Color.RED,Color.GREEN,Color.CYAN},null, Shader.TileMode.CLAMP);
mPaint.setShader(mLinearGradient);
mGradientMatrix= new Matrix();
}
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mGradientMatrix != null){
mTranslate += mViewWidth/5;
if (mTranslate >2*mViewWidth){
mTranslate = -mViewWidth;
}
mGradientMatrix.setTranslate(mTranslate,0);
mLinearGradient.setLocalMatrix(mGradientMatrix);
postInvalidateDelayed(100);
}
}
}
2)例:使用Shader实现圆形头像
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.def);
Bitmap bmp = Bitmap.createBitmap(bitmap.getWidth(),bitmap.getHeight(), Bitmap.Config.ARGB_8888);
BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP,Shader.TileMode.CLAMP);
Paint mPaint = new Paint();
mPaint.setShader(bitmapShader);
canvas.drawCircle(100,100,100,mPaint);
8)SurfaceView
View通过刷新来重绘视图,刷新的间隔为16ms,如果在16ms内完成了需要操作的逻辑,则用户在视觉上不会产生卡顿的感觉。但逻辑复杂会卡顿,阻塞主线程。
View | SurfaceView |
---|---|
适用主动刷新 | 适用被动更新 |
主线程中刷新 | 子线程中刷新 |
无双缓冲 | 有双缓冲 |
若自定义View需要频繁刷新,或刷新时数据量大,可以用SurfaceView取代View。
Android 屏幕刷新机制
android屏幕刷新显示机制
-
例1)正弦曲线
public class SurfaceViewDemo extends SurfaceView implements SurfaceHolder.Callback,Runnable {
private int x;
private int y;
private SurfaceHolder mHolder;
private Canvas mCanvas;
private boolean mIsDrawing; //控制子线程
private Path mPath;
private Paint mPaint;
public SurfaceViewDemo(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
//初始化
private void init(){
mHolder = getHolder(); //初始化SurfaceHolder
mHolder.addCallback(this); //注册SurfaceHolder的回调方法
mPath = new Path();
mPaint = new Paint();
mPaint.setColor(Color.BLUE);
mPaint.setStyle(Paint.Style.STROKE);
}
//开启子线程进行绘制
@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不断绘制
while (mIsDrawing){
draw();
x += 1;
y = (int) (Math.sin(x*2*Math.PI/180)*100)+400;
mPath.lineTo(x,y);
}
}
private void draw(){
try {
mCanvas = mHolder.lockCanvas(); //获得canvas对象
mCanvas.drawColor(Color.WHITE);
mCanvas.drawPath(mPath,mPaint);
}catch (Exception e){
}finally {
if (mCanvas != null){
mHolder.unlockCanvasAndPost(mCanvas); //提交画布内容
}
}
}
}
-
例2)绘图板
public class SurfaceViewDemo2 extends SurfaceView implements SurfaceHolder.Callback,Runnable {
private static final String TAG = "SurfaceViewDemo2";
private SurfaceHolder mHolder;
private Canvas mCanvas;
private boolean mIsDrawing;
private Path mPath;
private Paint mPaint;
public SurfaceViewDemo2(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init(){
mHolder = getHolder();
mHolder.addCallback(this);
mPath = new Path();
mPaint = new Paint();
mPaint.setColor(Color.BLUE);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(6);
// setFocusable(true);
// setFocusableInTouchMode(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();
}
}
@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;
}
return true;
}
private void draw(){
try {
mCanvas = mHolder.lockCanvas();
mCanvas.drawColor(Color.WHITE);
mCanvas.drawPath(mPath,mPaint);
}catch (Exception e){
}finally {
mHolder.unlockCanvasAndPost(mCanvas);
}
}
}