Android ImageView长按保存图片及截屏相关知识

在日常开发中,可能会需要做长按保存图片这个功能,又或者需要做个截屏分享功能。最近正好在研究这些东西,写篇博客整理一下。

view长按保存图片的几种方式

如果是网络图片,我们可以直接选择将图片下载下来后保存,这种方式,简单暴力,可以直接获得原图,本质其实就是下载文件。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public static boolean  downloadBitmap(String urlString, File fileDir) {
URL url = null;
InputStream is = null;
FileOutputStream fos = null;
HttpURLConnection conn=null;
boolean isSuccess=false;
try {

url = new URL(urlString);
//开启连接
conn = (HttpURLConnection) url.openConnection();
//设置超时的时间,5000毫秒即5秒
conn.setConnectTimeout(5000);
//设置获取图片的方式为GET
conn.setRequestMethod("GET");
conn.connect();
//响应码为200,则访问成功
if (conn.getResponseCode() == 200) {
//获取连接的输入流,这个输入流就是图片的输入流
is = conn.getInputStream();
if (!fileDir.exists()) {
fileDir.mkdirs();
}
String fileName = urlString.substring(urlString.lastIndexOf("/") + 1); //根据链接获取文件名
File file = new File(fileDir, fileName);
fos = new FileOutputStream(file);
int len;
byte[] buffer = new byte[1024];
//将输入流写入到我们定义好的文件中
while ((len = is.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
//将缓冲刷入文件
fos.flush();
isSuccess=true;

}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if(fos!=null){
fos.close();
}
if(is!=null){
is.close();
}

if(conn!=null){
conn.disconnect();
}

} catch (IOException e) {
e.printStackTrace();
}
}

return isSuccess;

}

上面的方法用于网络图片,好处是可以直接获得原图,缺点也显而易见,必须依赖于网络,并且可能会耗费用户的流量。那么问题来了,有没有其他办法可以直接保存ImageView里面的图片呢?幸运的是,的确可以做到,而且不止一种方式,下面我将逐一介绍。在介绍前,我们先来认识一下view里面的几个API:

  • setDrawingCacheEnabled

    Enables or disables the drawing cache. When the drawing cache is enabled, the next call to {@link #getDrawingCache()} or {@link #buildDrawingCache()} will draw the view in a bitmap. Calling {@link #draw(android.graphics.Canvas)} will not draw from the cache when the cache is enabled. To benefit from the cache, you must request the drawing cache by calling {@link #getDrawingCache()} and draw it on screen if the returned bitmap is not null.

    由API可知,setDrawingCacheEnabled为true后,使用getDrawingCache会将该view绘制成bitmap并返回,setDrawingCacheEnabled为false后,会清空缓存。

  • getDrawingCache

    Returns the bitmap in which this view drawing is cached. The returned bitmap is null when caching is disabled. If caching is enabled and the cache is not ready, this method will create it. Calling {@link #draw(android.graphics.Canvas)} will not draw from the cache when the cache is enabled. To benefit from the cache, you must request the drawing cache by calling this method and draw it on screen if the returned bitmap is not null.

    由API可知,只要开启绘画缓存之后,如果缓存还没有准备好,就先创建缓存(将view绘制到bitmap中保存起来),创建缓存使用buildDrawingCache,可以手动调用,或者直接 getDrawingCache 则系统会自动创建缓存,并返回。

  • buildDrawingCache

    如果缓存无效,则开始构建缓存(将view绘制成bitmap)

  • destroyDrawingCache

    清空缓存,释放缓存的资源占用

在了解上面API,思路就已经很清晰了。以下是我整理的保存图片的几种方式。

  1. 开启缓存保存图片,适用于所有view。但是这种方式保存的图片有个致命的缺点,所见即所得,比如ImageView的缩放类型为ScaleType.CENTER_CROP,此时保存的也是缩放过后的图片,这种效果可能有时无法满足我们的实际需求。但也不是一无是处,在后面的截屏介绍中,这种方法非常强大。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    public static void saveBitmap(View view, String filePath){
    view.setDrawingCacheEnabled(true);//开启绘制缓存
    Bitmap imageBitmap = view.getDrawingCache();
    FileOutputStream outStream = null;
    File file=new File(filePath);
    if(file.isDirectory()){//如果是目录不允许保存
    return;
    }
    try {
    outStream = new FileOutputStream(file);
    imageBitmap.compress(Bitmap.CompressFormat.PNG, 100, outStream);
    outStream.flush();
    } catch (IOException e) {
    e.printStackTrace();
    }finally {
    try {
    if(outStream!=null){
    outStream.close();
    }

    } catch (IOException e) {
    e.printStackTrace();
    }
    imageBitmap.recycle();
    view.setDrawingCacheEnabled(false);//关闭绘制缓存
    }

    }
  2. 上面的方式无法保存一张完整的图片,所以可能无法满足实际工作需求,这时就需要用到下面这种方法,这种方法可以获取整个图片,但是只适用于ImageView。其他view只能使用getBackground来获得Drawable对象,不满足实际需求,而且Background也不一定是BitmapDrawable

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    public static void saveBitmap(ImageView view, String filePath) {
    Drawable drawable = view.getDrawable();
    if (drawable == null) {
    return;
    }
    FileOutputStream outStream = null;
    File file = new File(filePath);
    if (file.isDirectory()) {//如果是目录不允许保存
    return;
    }
    try {
    outStream = new FileOutputStream(file);
    Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
    bitmap.compress(Bitmap.CompressFormat.PNG, 100, outStream);
    outStream.flush();
    bitmap.recycle();
    } catch (IOException e) {
    e.printStackTrace();
    } finally {
    try {
    if (outStream != null) {
    outStream.close();
    }

    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    }
  3. 这种方式适用于所有的view,可以将view里的全部内容绘制成bitmap并保存。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    public static void saveBitmap(View view, String filePath){

    // 创建对应大小的bitmap
    Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(),
    Bitmap.Config.RGB_565);
    Canvas canvas = new Canvas(bitmap);
    view.draw(canvas);

    //存储
    FileOutputStream outStream = null;
    File file=new File(filePath);
    if(file.isDirectory()){//如果是目录不允许保存
    return;
    }
    try {
    outStream = new FileOutputStream(file);
    bitmap.compress(Bitmap.CompressFormat.PNG, 100, outStream);
    outStream.flush();
    } catch (IOException e) {
    e.printStackTrace();
    }finally {
    try {
    bitmap.recycle();
    if(outStream!=null){
    outStream.close();
    }

    } catch (IOException e) {
    e.printStackTrace();
    }
    }

    }

关于长按保存图片功能已经介绍完了,下面来看看常见的截屏保存图片的方式。

常见的截屏保存图片分享方式

  • 获取当前屏幕截图,包含状态栏

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
       public static Bitmap snapShotWithStatusBar(Activity activity) {
    View view = activity.getWindow().getDecorView();
    view.setDrawingCacheEnabled(true);
    view.buildDrawingCache();
    Bitmap bitmap= view.getDrawingCache();
    int width = getScreenWidth(activity);//获取屏幕的宽
    int height = getScreenHeight(activity);//获取屏幕的高
    Bitmap bm = null;
    bm = Bitmap.createBitmap(bitmap, 0, 0, width, height);
    view.destroyDrawingCache();
    return bm;

    }
  • 获取当前屏幕截图,不包含状态栏

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
       public static Bitmap snapShotWithoutStatusBar(Activity activity) {
    View view = activity.getWindow().getDecorView();
    view.setDrawingCacheEnabled(true);
    view.buildDrawingCache();
    Bitmap bitmap= view.getDrawingCache();
    Rect frame = new Rect();
    activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
    int statusBarHeight = frame.top;//获取状态栏的高
    int width = getScreenWidth(activity);//获取屏幕的宽
    int height = getScreenHeight(activity);//获取屏幕的高
    Bitmap bm = Bitmap.createBitmap(bitmap, 0, statusBarHeight, width, height - statusBarHeight);
    view.destroyDrawingCache();
    return bm;

    }
  • 获取当前屏幕截图,不包含状态栏以及标题栏(ActionBar)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public static Bitmap snapShotWithoutStatusBarAndTitle(Activity activity) {
    View view = activity.getWindow().getDecorView();
    view.setDrawingCacheEnabled(true);
    view.buildDrawingCache();
    Bitmap bitmap= view.getDrawingCache();
    Rect frame = new Rect();
    activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
    int statusBarHeight = frame.top;//获取状态栏的高
    int titleBarHeight = activity.getWindow().findViewById(Window.ID_ANDROID_CONTENT).getTop();//获取标题栏的高
    int width = getScreenWidth(activity);//获取屏幕的宽
    int height = getScreenHeight(activity);//获取屏幕的高
    int totalBarHeight=titleBarHeight+statusBarHeight;
    Bitmap bm= Bitmap.createBitmap(bitmap, 0, totalBarHeight , width, height - totalBarHeight);
    view.destroyDrawingCache();
    return bm;
    }
  • 截取Scrollview等ViewGroup里面的内容,常用于生成长微博,效果可参照简书生成图片分享

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public static Bitmap snapShotForViewGroup(ViewGroup view) {
    int totalHeight = 0;
    // 获取ViewGroup实际高度
    for (int i = 0; i < view.getChildCount(); i++) {
    totalHeight += view.getChildAt(i).getHeight();
    //view.getChildAt(i).setBackgroundColor(0xffffffff);//这里可以设置背景颜色,以免背景透明看不出效果
    }
    Bitmap bitmap= Bitmap.createBitmap(view.getWidth(), totalHeight,
    Bitmap.Config.RGB_565);
    Canvas canvas = new Canvas(bitmap);
    view.draw(canvas);
    return bitmap;
    }
  • 截取WebView里面的全面内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    /**
    * Android 5.0以上 在Activity的setContentView之前要进行如下处理
    * if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    * WebView.enableSlowWholeDocumentDraw();
    * }
    * @param view
    * @return
    */

    public static Bitmap snapShotForWebView(WebView view) {
    float scale = view.getScale(); //获取webview缩放率
    int webViewHeight = (int) (view.getContentHeight() * scale);//得到缩放后webview内容的高度
    Bitmap bitmap = Bitmap.createBitmap(view.getWidth(),webViewHeight,Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(bitmap);
    view.draw(canvas);
    return bitmap;
    }
  • 自由截屏,即手指滑动区域截屏

  1. 首先针对整个DecorView开启绘制缓存,然后根据传入的Rect进行区域截屏。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public static Bitmap snapShotByFinger(Activity activity,Rect rect) {
    View view = activity.getWindow().getDecorView();
    view.setDrawingCacheEnabled(true);
    view.buildDrawingCache();
    Bitmap bitmap = view.getDrawingCache();
    Bitmap bm = Bitmap.createBitmap(bitmap, rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top);
    bitmap.recycle();
    view.destroyDrawingCache();
    return bm;

    }
  2. Rect需要通过TouchEvent来获得

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    private int left,top;
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
    switch (event.getAction()){
    case MotionEvent.ACTION_DOWN://手指按下时,记录当前坐标
    left = (int) event.getRawX();
    top = (int) event.getRawY();
    break;
    case MotionEvent.ACTION_UP://手指抬起时,记录当前坐标,然后进行换算,计算Rect;
    int right = (int) event.getRawX();
    int bottom = (int) event.getRawY();
    Rect rect=new Rect();
    if(right>left){
    if(bottom>top){
    rect.set(left,top,right,bottom);
    }else{
    rect.set(left,bottom,right,top);
    }
    }else{
    if(bottom>top){
    rect.set(right,top,left,bottom);
    }else{
    rect.set(right,bottom,left,top);
    }
    }

    //截屏,保存图片
    String path = Environment.getExternalStorageDirectory().toString()+"/12345.png";
    Util.saveBitmap(Util.snapShotByFinger(WebViewActivity.this,rect),path);
    break;
    }

    return super.dispatchTouchEvent(event);
    }

暂时就整理这么多东西,关于手指滑动截屏,实际应用中可能还需要处理滑动冲突,或者手指滑动过程中显示路径,这些东西大家自行研究吧。