目录
简易画板代码:https://github.com/liuchenyang0515/SimpleDrawingBoard
撕衣服代码:https://github.com/liuchenyang0515/ScrapeBeauty
表示图形的几种方式:
假设给定一张800*400像素的图片,即32万像素的图片,保存为bmp格式,分别按照单色,16色,256色,24位来保存
用单色保存:32W*1/8=40000byte,因为有一些额外信息,比如保存时间等,所以图片比40000byte要多一点
用16色保存:32W*1/2=160000byte,因为有一些额外信息,比如保存时间等,所以图片比160000byte要多一点
用256色保存:32W*1=32Wbyte,同理,比32Wbyte要多一点
用24位保存,24个2进制数是2的24次方为16777216,每个像素可以表示16777216种颜色,这种表示方法为RGB三种像素,每个字节表示3个像素,32W像素*3=96Wbyte,同理,比96Wbyte多一点
bmp文件比jpg的大,jpg把bmp格式图片进行压缩,相邻位图差不多的就合并了,而png也是将bmp格式的图片压缩,压缩算法和jpg不一样,并且更高级。
android采用的图片是png,采用的色彩模式是ARGB,其中A( Alpha )是透明度,RGB是红绿蓝。android的图片一个像素占4个byte。
缩放显示大图片原理:
一般是用在图片比屏幕大的情况,合理的加载出图片
相关阅读:
一张图片占用多少内存:https://www.cnblogs.com/popfisher/p/6959106.html
让图片占用尽可能少的内存:http://www.cnblogs.com/popfisher/p/6770018.html
android屏幕和尺寸单位:http://xiaoyaozjl.iteye.com/blog/2178415
缩放步骤:
1.获取图片分辨率,比如2400*3200(水平为宽,竖直为高)
2.获取手机分辨率,比如320*480
3.计算缩放比(图片的宽除以手机分辨率的宽,图片的高除以手机分辨率的高)
宽 7(整型除法)
高 6
4.按照大的比例去缩放
MainActivity.java
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Point;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private ImageView imageView;
private int width;
private int height;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView = (ImageView) findViewById(R.id.iv);
// 获取手机的分辨率windowManager
//WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
// 过时的方法,没有包含虚拟按键高度,1080*1794
/*int height = windowManager.getDefaultDisplay().getHeight(); // 过时方法获取高
int width = windowManager.getDefaultDisplay().getWidth(); // 过时方法获取宽*/
// 这种方法并没有包含底部虚拟按钮的高度,所以测试的高度会小于实际高度,1080*1794
/*Point point = new Point();
windowManager.getDefaultDisplay().getSize(point);
Log.d(TAG, "" + point.x + "----height:" + point.y);*/
Point point = new Point();
// API 17之后使用,获取的像素宽高包含虚拟键所占空间,在API 17之前通过反射获取
// 获取显示的实际大小,而不减去任何窗口装饰或应用任何兼容性缩放因子。
getWindowManager().getDefaultDisplay().getRealSize(point);
width = point.x;
height = point.y;
Log.d(TAG, "" + width + "----height:" + height); // 准确1080*1920
}
// 点击按钮加载一张大图片
public void click(View view) {
// 避免申请读权限,暂且放在这里的目录,而不用/mnt/sdcard/...
// 如果heapSize太小就会OOM,本模拟器是256M
// 本图片是500*364*4/1024/1024=0.69M,不会溢出
// Bitmap bitmap = BitmapFactory.decodeFile("/data/data/com.example.loadlargeimages/cache/mingren.jpg");
// 创建一个位图工厂配置参数
BitmapFactory.Options options = new BitmapFactory.Options();
// 解码器不去真正解析位图,但是还能够获取图片的宽高信息
// api如下:
/*如果设置为true,解码器将返回null(无位图),但仍将设置out ...字段,允许调用者查询位图而无需为其像素分配内存。*/
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile("/data/data/com.example.loadlargeimages/cache/datu.jpg",
options);
//Log.d(TAG, "bitmap:" + bitmap);// 是null,所以不需要保存返回值
int imgWidth = options.outWidth;
int imgHeight = options.outHeight;
Log.d(TAG, "图片的宽高:" + imgWidth + "----height:" + imgHeight);
// 计算缩放比
int scale = 1; // 默认为1
int scaleX = imgWidth / width;
int scaleY = imgHeight / height;
if (scaleX > scaleY && scaleX > 1) { // 用缩放比大的
scale = scaleX;
} else if (scaleY > scaleX && scaleY > 1) {
scale = scaleY;
}
Log.d(TAG, "缩放比==:" + scale);
// 按照缩放比显示
options.inSampleSize = scale;
// 按缩放比解析位图
options.inJustDecodeBounds = false; // 真正的解析位图
Bitmap bitmap = BitmapFactory.decodeFile("/data/data/com.example.loadlargeimages/cache/datu.jpg",
options);
// 把bitmap显示到imageview
imageView.setImageBitmap(bitmap);
}
}
既然已经设置了预加载,而不是直接加载图片。一定记住解码图片时的第二个参数BitmapFactory.Options,控制下采样和图像是否应该被完全解码的选项,或者只是返回大小。
一张图片3200*2000尺寸的图片放在1080*1920尺寸的手机上
运行结果:
缩放比向下取整,如果向上取整可能会出现图片本来能占满屏幕,却没有占满屏幕的情况,边缘留白。
这里宽度比为2,高度比为1,所以是缩放比是2.
当旋转之后,重新点击按钮显示
这里宽度比是1,高度比是1,所以缩放比是1
public static class BitmapFactory.Options
extends Object
public boolean inJustDecodeBounds
如果设置为true,解码器将返回null(无位图),但仍将设置outWidth、outHeight字段,允许调用者查询位图而无需为其像素分配内存。
public int inSampleSize
如果设置为> 1的值,请求解码器对原始图像进行二次采样,返回较小的图像以节省内存。样本大小是任一维度中与解码位图中的单个像素相对应的像素数。例如,inSampleSize == 4返回的图像是原始宽度/高度的1/4,像素数量的1/16。任何值<= 1都被视为1.注意:解码器使用基于2的幂的最终值,任何其他值将向下舍入到最接近的2的幂。
创建原图的副本:
MainActivity.java
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.TypedValue;
import android.widget.ImageView;
public class MainActivity extends AppCompatActivity {
private ImageView iv_src;
private ImageView iv_copy;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv_src = (ImageView) findViewById(R.id.iv_src);
iv_copy = (ImageView) findViewById(R.id.iv_copy);
// 把图片转成bitmap到imageview
// res目录下都是资源
Bitmap srcBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.mingren);
// 操作图片,修改某一点坐标颜色
// srcBitmap.setPixel(20, 30, Color.RED); // 报错java.lang.IllegalStateException
iv_src.setImageBitmap(srcBitmap);
// 创建原图的副本
// 首先创建一个模版,相当于创建了一个和原图一样大小的空白纸
Bitmap copyBitmap = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), srcBitmap.getConfig());
// 想作画需要一个画笔
Paint paint = new Paint();
// 创建一个画布,把白纸铺到画布上
Canvas canvas = new Canvas(copyBitmap);
// 开始作画,参考原图画矩阵
canvas.drawBitmap(srcBitmap, new Matrix(), paint);
// 操作画出来的图片
for (int i = 0; i < 200; ++i) {
copyBitmap.setPixel(20 + i, 30, Color.RED); // 一次修改一个像素看不出来,用循环
}
// 把copybitmap显示到iv_copy
iv_copy.setImageBitmap(copyBitmap);
}
}
批注:
drawBitmap重载方法较多,具体见官方api
public void drawBitmap (Bitmap bitmap, Matrix matrix, Paint paint)
使用指定的矩阵绘制位图。
参数 | |
---|---|
bitmap |
Bitmap :要绘制的位图
这个值绝不能是 |
matrix |
Matrix :用于在绘制位图时转换位图的矩阵。
这个值绝不能是 |
paint |
Paint: 可能为空。用于绘制位图的油漆 |
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_src"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
android:id="@+id/iv_copy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp" />
</LinearLayout>
运行结果
可以看到副本的图左上方多了一条线
对图形的处理(旋转、缩放、平移、镜面、倒影):
MainActivity.java
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ImageView;
public class MainActivity extends AppCompatActivity {
private ImageView iv_src;
private ImageView iv_copy;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv_src = (ImageView) findViewById(R.id.iv_src);
iv_copy = (ImageView) findViewById(R.id.iv_copy);
// 把图片转成bitmap到imageview
// res目录下都是资源
Bitmap srcBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.mingren);
// 操作图片,修改某一点坐标颜色
// srcBitmap.setPixel(20, 30, Color.RED); // 报错java.lang.IllegalStateException
iv_src.setImageBitmap(srcBitmap);
// 创建原图的副本
// 首先创建一个模版,相当于创建了一个和原图一样大小的空白纸
Bitmap copyBitmap = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), srcBitmap.getConfig());
// 想作画需要一个画笔
Paint paint = new Paint();
// 创建一个画布,把白纸铺到画布上
Canvas canvas = new Canvas(copyBitmap);
// 开始作画,参考原图画矩阵
Matrix matrix = new Matrix();
// 对图片进行旋转
// matrix.setRotate(30, srcBitmap.getWidth() >> 1, srcBitmap.getHeight() >> 1);
// 对图片进行缩放
// matrix.setScale(0.5f, 0.5f);
// matrix.setScale(0.5f, 0.5f,100f, 100f); // 此处缩小,如果是放大,记得将画布设置变大
// 对图片进行平移
// matrix.setTranslate(200, 0);
// 对图片进行镜面,就是使用缩放和平移的组合
// 不能setScale和setTranslate组合,应该setScale和postTrasetnslate组合
/*matrix.setScale(-1.0f, 1.0f);
// post是在上一次修改的基础上再次修改,set是每次操作都重新初始化再进行,多个setxxx方法会以最后为准
matrix.postTranslate(srcBitmap.getWidth(), 0);*/
// 对图片进行倒影效果
matrix.setScale(1.0f, -1.0f);
matrix.postTranslate(0, srcBitmap.getHeight());
canvas.drawBitmap(srcBitmap, matrix, paint);
// 操作画出来的图片
for (int i = 0; i < 200; ++i) {
// 这一条斜线不会因为图片旋转而改变
copyBitmap.setPixel(20 + i, 30 + i, Color.RED); // 一次修改一个像素看不出来,用循环
}
// 把copybitmap显示到iv_copy
iv_copy.setImageBitmap(copyBitmap);
}
}
批注:
因为里面修改像素点的有划线语句,这条线不会因为图片的旋转而改变。
setScale(float sx,float sy)
:设置Matrix进行缩放,sx,sy控制X,Y方向上的缩放比例;
setScale(float sx,float sy,float px,float py)
:设置Matrix以px,py为轴心进行缩放(此处有坑),默认以画布左上角的点(0,0)缩放,sx,sy控制X,Y方向上的缩放比例;
post是在上一次修改的基础上再次修改,set是每次操作都重新初始化再进行,多个setxxx方法会以最后为准,比如先缩放setScale(0.5f,0.5f);接着平移setTranslation(200, 0);那么就只会看到平移效果,缩放效果被覆盖。
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_src"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
android:id="@+id/iv_copy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp" />
</LinearLayout>
旋转效果图:
缩放效果图:
平移效果图:
镜面效果图:
倒影效果图:
实现简易画板:
MainActivity.java
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private ImageView imageView;
private Bitmap copyBitmap;
private Paint paint;
private Canvas canvas;
private long lastMillis;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 用来显示我们画的内容
imageView = (ImageView) findViewById(R.id.iv);
// 获取原图
Bitmap srcBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.drawablebg);
// 获取原图的副本,相当于空白纸
copyBitmap = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), srcBitmap.getConfig());
// 创建画笔
paint = new Paint();
// 创建画布
canvas = new Canvas(copyBitmap);
// 开始作画
canvas.drawBitmap(srcBitmap, new Matrix(), paint); // 此时copyBitmap就有内容了
//canvas.drawLine(20f, 30f, 50f, 80f, paint);
imageView.setImageBitmap(copyBitmap);
imageView.setOnTouchListener(new View.OnTouchListener() {
float startX = 0f;
float startY = 0f;
@Override
public boolean onTouch(View v, MotionEvent event) {
// 获取当前时间的类型
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
Log.d(TAG, "触摸: ");
// 获取开始位置(划线)
startX = event.getX();
startY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG, "移动: ");
// 获取结束位置
float stopX = event.getX();
float stopY = event.getY();
// 不停的划线
canvas.drawLine(startX, startY, stopX, stopY, paint);
// 再次显示到iv
imageView.setImageBitmap(copyBitmap);
// 获取开始位置(划线)
startX = event.getX();
startY = event.getY();
break;
case MotionEvent.ACTION_UP:
Log.d(TAG, "抬起: ");
break;
}
return true; // 如果为false,只能执行第一个处理的事件,认为还未处理完
}
});
}
public void click(View view) {
switch (view.getId()) {
case R.id.btn1:
paint.setColor(Color.RED);
break;
case R.id.btn2:
paint.setStrokeWidth(15);
break;
case R.id.btn3:
applyPermissions();
save();
break;
}
}
private void save() {
// 保存图片是耗时操作
new Thread() {
@Override
public void run() {
/*
* 第一个参数format:保存图片的格式
* 第二个参数quality:保存图片的质量
* 第三个参数是输出流
* 其中命名用了SystemClock.uptimeMillis()是当前手机已开机的时间
* */
// 简单防抖实现
long millis = SystemClock.uptimeMillis();
if (millis - lastMillis < 200) { // 200ms,防止快速点击,可以根须需要设置
return;
}
String fileName = millis + "test.png";
File file = new File(Environment.getExternalStorageDirectory().getPath(), fileName);
lastMillis = millis;
try {
FileOutputStream fos = new FileOutputStream(file);
copyBitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
// action为Intent.ACTION_MEDIA_SCANNER_SCAN_FILE扫描指定文件
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
Uri uri = Uri.fromFile(file);
intent.setData(uri);
sendBroadcast(intent);
}
}.start();
}
private void applyPermissions() {
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
} else {
save();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
save();
} else {
Toast.makeText(this, "权限被拒绝", Toast.LENGTH_SHORT).show();
}
break;
}
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:id="@+id/iv"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/btn1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="click"
android:text="红色" />
<Button
android:id="@+id/btn2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="click"
android:text="加粗" />
<Button
android:id="@+id/btn3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="click"
android:text="保存" />
</LinearLayout>
</LinearLayout>
运行结果(华为荣耀V9真机8.0.0系统测试):
批注:
画线的功能一定经历了3个动作,第一次点上去会识别为触摸,然后在不断移动,最后放手识别为抬起
Bitmap类里的一个方法
public boolean compress (Bitmap.CompressFormat format, int quality, OutputStream stream)
将位图的压缩版本写入指定的输出流。如果返回true,则可以通过将相应的输入流传递给BitmapFactory.decodeStream()来重建位图。注意:并非所有Formats都直接支持所有位图配置,因此BitmapFactory返回的位图可能位于不同的bitdepth中,和/或可能丢失了每像素alpha(例如,JPEG仅支持不透明像素)。
此方法可能需要几秒钟才能完成,因此只能从工作线程(辅助线程)调用它。
参数 | |
---|---|
format |
Bitmap.CompressFormat : 压缩图像的格式
|
quality |
int : 提示压缩器,0-100。 0表示压缩小尺寸,100表示压缩以获得最高质量。某些格式,如无损的PNG,将忽略质量设置
|
stream |
OutputStream : 输出流写入压缩数据。
|
Returns | |
---|---|
boolean |
如果成功压缩到指定的流,则为true。 |
图像的压缩格式有JPEG、PNG、WEBP
撕衣服小案例:
真机测试(华为荣耀V9,8.0.0系统)运行效果图:
Mainctivity.java
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Point;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private ImageView iv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv = (ImageView) findViewById(R.id.iv2);
//改变图片大小
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inSampleSize = 1;
// 获取要操作的原图
Bitmap srcBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.a);
// 创建一个副本,相当于和一个原图一样的白纸
final Bitmap alterBitmap = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), srcBitmap.getConfig());
// 创建画笔
Paint paint = new Paint();
// 创建画布 把白纸铺到画布
Canvas canvas = new Canvas(alterBitmap);
// 开始作画
canvas.drawBitmap(srcBitmap, new Matrix(), paint);
iv.setImageBitmap(alterBitmap);
// 给iv设置一个触摸事件
iv.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// 具体判断一下触摸事件
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE: // 移动的事件
for (int i = -20; i < 20; ++i) {
for (int j = -20; j < 20; ++j) {
if (Math.sqrt(i * i + j * j) < 20) {
int x = (int) event.getX();
int y = (int) event.getY();
if (x + i < 0 || x + i >= alterBitmap.getWidth()) // 0到width-1的闭区间
continue;
if (y + j < 0 || y + j >= alterBitmap.getHeight())
continue;
Log.d(TAG, "getX: " + x + " getY:" + y);
alterBitmap.setPixel(x + i, y + j, Color.TRANSPARENT); // 设置为透明
}
}
}
iv.setImageBitmap(alterBitmap);
break;
default:
break;
}
return true;
}
});
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/iv1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/b" />
<ImageView
android:id="@+id/iv2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/a" />
</RelativeLayout>
关于dp、dip、dpi、px、sp:
更详细的请参考:
android适配(一) 之dp、dip、dpi、px、sp简介及相关换算:https://blog.csdn.net/qq_23042121/article/details/53118853
Android屏幕适配(一)dp、px、dpi、sp的理解:https://blog.csdn.net/yuanmingxiang/article/details/51384420
更权威的介绍请参加官方文档:https://developer.android.google.cn/guide/practices/screens_support
屏幕密度的定义:密度是指屏幕单位面积内的像素数量,通常用 dpi(每英寸点数)来表示,屏幕密度划通常分为五大类,你需要熟悉这五大类:
六种通用的密度:(六种通用密度之间的缩放比率为3:4:6:8:12:16, 可以想象成40为基数去乘以比例)
- ldpi(低)~120dpi
- mdpi(中)~160dpi
- hdpi(高)~240dpi
- xhdpi(超高)~320dpi
- xxhdpi(超超高)~480dpi
- xxxhdpi(超超超高)~640dpi
mdpi 设备(中等密度设备)每英寸具有大约 160 个点
hdpi 设备(高密度设备)每英寸具有大约 240 个点
xhdpi 设备(超高密度设备)每英寸具有大约 320 个点
像素点的数量逐步增加
对于超超超高密度设备(即 xxx)来说,每英寸具有大约 640 个点,根据名称和数量可以判断,随着密度的增大,屏幕上的像素数量会随之增多,随着时间的推移,设备硬件越来越完善。设备分辨率越来越高,屏幕上具有的像素数量比这里显示的还要多,但是我们暂时先讨论这五大类:
密度独立像素来表示宽度和高度值,也就是说我们可以规定按钮的尺寸为 48 x 48 dip,实际上 Android 会根据设备的屏幕密度,将这一数值转换为不同的像素数量,那么对图片来说是如何操作的呢?
作为开发者,你应该用 dp 值来声明图片的宽度和高度,例如 48dp x 48dp。然后 Android 会确保所有这些图片在各种不同的设备上大概保持相同的尺寸,无论是每英寸的像素数量是多少。但是,如果我们只有一个版本的图片,则对于屏幕上具有更多像素的高密度设备来说,Android 可能需要拉伸图片,或者另一方面,如果图片上的像素很多,Android 可能需要针对低密度设备缩小图片。无论是哪种情况,图片都可能会看起来扭曲了或者变得模糊不清。我们肯定不希望出现这种情况,要解决该问题,我们可以针对每个密度类别,为同一图片提供不同尺寸的版本,这样会在所有设备上都能显示非常清晰的图片。应用运行时,Android 设备不会对图片做出太多的拉伸或缩小处理,设计师要知道提供哪些尺寸的资源,他们可以遵守一定的独立像素与像素转换比例。
我们来举个例子:
对中密度设备来说 1 dp = 1 px,所以如果我们希望图片的尺寸是 48dp x 48dp,那么该图片的 mdpi 版本应该是 48px x 48px
对于高密度设备来说 1 dp = 1.5 px,所以如果我们希望图片的尺寸为 48dp x 48dp,那么该图片的 hdpi 版本应该为 72px x 72px
我们可以根据提供的比例计算出高分辨率的尺寸,可以看出,图片的 xxxhdpi 版本要比 mdpi 版本大了很多,那是因为 xxxhdpi 设备的屏幕上有更多的像素,最终这些图片在用户的设备上看起来尺寸将保持相同
现在再仔细研究下我们提供的图片的像素尺寸
我们打开 drawable-mdpi 文件夹,看看某一图片,选中该文件 右击并选择“查看信息 (get info)”,在这里,对于该图片的 mdpi 版本,我们看到尺寸是 88 X 88 像素,所以在最终应用中,该图片的尺寸将显示为 88dp x 88dp,所以该版本的尺寸必须为 88px x 88px
因为对于 mdpi 设备来说 1dp=1px,那么对于同一图片的 xxxhdpi 版本来说,像素尺寸是多少?
转到 drawable-xxxhdpi 文件夹,找到同一名字的图片,同样选择“查看信息 (get info)”,我们发现这里的图片大多了,在这里列出尺寸是 352 x 352 像素(此时1dp=4px,88*4=352)
我们这里是在应用将需要的存储空间和应用将需要的处理能力之间找到一个折中,当我们提供多个版本的资源时,应用的确会占用更多的存储空间 但是却需要更少的处理能力,对于移动设备来说,这是一个非常值得达成的折中点,因为这意味着我们的设备将能够在处理能力更低的设备上运行而不会遇到性能问题。
dp和px之间的关系:1dp是屏幕密度为160dpi时的1px,也就是说在密度值为160dpi的情况下,1dp=1px。
上图中,以mdpi(160dpi)为基准,和其他密度的比例关系是:
3/4 : 1 : 1.5 : 2 : 3
ldpi:mdpi:hdpi:xhdpi : xxhdpi
以1920*1080(我认为是当前主流分辨率)为例:
在1920里,1dp=3px,上述规范中,状态栏高度是24dp,所以在设计稿中状态栏的高度就是72px。其他的以此类推。
最后我们来说一下怎么计算dpi
dpi就是一英寸显示多少像素点,也就是dpi=像素/英寸(对角线长度)
以1920*1080,5.0英寸的屏幕为例:
先利用勾股定理(A的平方=B的平方+C的平方),我们可以算出来对角线的像素是2203px,然后除以5等于440,所以,分辨率在1920*1080,5.0英寸下的dpi是440dpi。
Bitmap及缓存相关阅读:
Android 之 Bitmap:https://www.jianshu.com/p/98c88f9ceafa
其中计算采样率的时候可以有如下写法
// pixelW是期待的宽,pixelH是期待的高,前面两个参数也可以直接options传进来再获取也行
public static int getSimpleSize(int originalW, int originalH, int pixelW, int pixelH) {
int simpleSize = 1;
if (originalW > originalH && originalW > pixelW) {
simpleSize = originalW / pixelW;
} else if (originalW < originalH && originalH > pixelH) {
simpleSize = originalH / pixelH;
}
if (simpleSize <= 0) {
simpleSize = 1;
}
return simpleSize;
}
Android图片缓存之Bitmap详解:http://www.cnblogs.com/whoislcj/p/5547758.html
inScaled用法学习笔记:
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Point;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private ImageView imageView;
private int width;
private int height;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView = (ImageView) findViewById(R.id.iv);
// 获取手机的分辨率windowManager
//WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
// 过时的方法,没有包含虚拟按键高度,1080*1794
/*int height = windowManager.getDefaultDisplay().getHeight(); // 过时方法获取高
int width = windowManager.getDefaultDisplay().getWidth(); // 过时方法获取宽*/
// 这种方法并没有包含底部虚拟按钮的高度,所以测试的高度会小于实际高度,1080*1794
/*Point point = new Point();
windowManager.getDefaultDisplay().getSize(point);
Log.d(TAG, "" + point.x + "----height:" + point.y);*/
Point point = new Point();
// API 17之后使用,获取的像素宽高包含虚拟键所占空间,在API 17之前通过反射获取
// 获取显示的实际大小,而不减去任何窗口装饰或应用任何兼容性缩放因子。
getWindowManager().getDefaultDisplay().getRealSize(point);
width = point.x;
height = point.y;
Log.d(TAG, "" + width + "----height:" + height); // 准确1080*1920
}
// 点击按钮加载一张大图片
public void click(View view) {
// 避免申请读权限,暂且放在这里的目录,而不用/mnt/sdcard/...
// 如果heapSize太小就会OOM,本模拟器是256M
// 本图片是500*364*4/1024/1024=0.69M,不会溢出
// Bitmap bitmap = BitmapFactory.decodeFile("/data/data/com.example.loadlargeimages/cache/mingren.jpg");
// 创建一个位图工厂配置参数
BitmapFactory.Options options = new BitmapFactory.Options();
// 解码器不去真正解析位图,但是还能够获取图片的宽高信息
// api如下:
/*如果设置为true,解码器将返回null(无位图),但仍将设置out ...字段,允许调用者查询位图而无需为其像素分配内存。*/
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.drawable.datu, options);
//Log.d(TAG, "bitmap:" + bitmap);// 是null,所以不需要保存返回值
int imgWidth = options.outWidth;
int imgHeight = options.outHeight;
Log.d(TAG, "图片的宽高:" + imgWidth + "----height:" + imgHeight);
// 按照缩放比显示
options.inSampleSize = getSimpleSize(options, 200, 200);
options.inJustDecodeBounds = false; // 真正的解析位图
options.inScaled = false; // 保证drawable-任何dpi都是缩放后初始的尺寸,不会因为机型屏幕再次缩放bitmap对象的尺寸
// 比如inSampleSize=10,那么3200*2000分辨率的图就是bitmap.getWidth()=320. bitmap.getHeight()=200
// 如果drawable文件夹设置得不对,那么获取的值就不是这个,而是对应比例的缩放
// 但是还是会因为机型屏幕改变其大小去显示(因为dpi每英寸像素密度不同)
// 这就是可能出现获得bitmap.getwidth()和getHeight()的尺寸和显示的尺寸不同,因为图片放在drawable不同的目录
// 按缩放比解析位图
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.datu, options);
Log.d(TAG, "图片的宽高:" + bitmap.getWidth() + "----height:" + bitmap.getHeight());
// 测试结果
// 如果3200----height:2000,simpleSize:16,屏幕dpi为420,缩放后应该width=200,height=175
// 在对应的drawable-420dpi文件夹没有对应的图,而在drawable-640dpi有图,那么
// bitmap.getWidth()和getHeight()获取的值有两种情况,如果inScaled=false,那么就是width=200,height=175
// 如果没有设置inScaled,默认为true,那么width=131, height=82
// 但是那么设置了inScaled=false,获取的尺寸是我们想要的,但放在屏幕上却不是这个200*175的尺寸
// 因为图在drawable-640dpi文件夹下,而屏幕420dpi,会让这个图适应屏幕而进行自动缩放显示。
// inScale是真正改变bitmap的尺寸,而选择图放在哪个文件夹只是改变屏幕上的显示
// 把bitmap显示到imageview
imageView.setImageBitmap(bitmap);
}
static int getSimpleSize(BitmapFactory.Options options, int pixelW, int pixelH) {
int simpleSize = 1;
int originalW = options.outWidth;
int originalH = options.outHeight;
Log.d(TAG, "options.outWidth:" + originalW + " options.outHeight:" + originalH);
if (originalW > originalH && originalW > pixelW) {
simpleSize = originalW / pixelW;
} else if (originalW < originalH && originalH > pixelH) {
simpleSize = originalH / pixelH;
}
if (simpleSize <= 0) {
simpleSize = 1;
}
Log.d(TAG, "simpleSize:" + simpleSize);
return simpleSize;
}
}
Bitmap通过getWidth和getHeight获取尺寸不符请见:https://blog.csdn.net/renwudao24/article/details/47680789
===========================Talk is cheap, show me the code========================