1. 程式人生 > >Android對圖片常用(拍照,相簿,拖拉,壓縮上傳)操作

Android對圖片常用(拍照,相簿,拖拉,壓縮上傳)操作

本篇部落格內容:

  • android app拍照功能

  • 部分手機上拍照圖片旋轉問題

  • android相簿獲取圖片功能

  • 檢視圖片,進行縮放,拖拉功能

  • 壓縮圖片,上傳功能

Android 拍照功能:
一般的拍照功能是呼叫系統中相機來實現,即通過Intent來呼叫其他運用程式(相機運用程式)來實現。眾所周知,通過Intent呼叫手機上的其他運用程式,都應該先檢查手機上是否裝有能Intent開啟的其他程式。程式碼如下:

  //MediaStore.ACTION_IMAGE_CAPTURE開啟手機中相機
  Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
         //檢查手機上是否裝有Intent對應開啟的程式
if (intent.resolveActivity(getPackageManager()) != null) { startActivityForResult(intent, requestCode); } }

從程式碼可知,呼叫開啟相機是通過startActivityForResult(), 那返回的是什麼呢?
一個壓縮後的Bitmap,還是一個圖片的path. ?以上程式碼產生的是一個系統壓縮後返回的Bitmap。在onActivityResult()中Intent#Bundle,Bundle中data(key)對應的值(value).

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
        Bundle extras = data.getExtras();
        Bitmap imageBitmap = (Bitmap) extras.get("data");
        mImageView.setImageBitmap(imageBitmap);
    }
}

一個系統壓縮後的Bitmap實際作用不大,適合做一個圖示。 若是需要按尺寸來載入圖片,然後將圖片壓縮上傳,利用以上程式碼是完全行不通的。 這時候,便需要知道相機拍照產生的原始圖片的Path,根據path來實現專案需求。

實際開發中,會指定圖片的路徑,即圖片產生到指定檔案下,且指定圖片格式為.png 。將圖片儲存路徑通過Intent#putExtra()告訴系統。

   private String imagePath1;
  /**
     * 開啟相機
     */
    public void openCamera(int requestCode) {
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        if (intent.resolveActivity(getPackageManager()) != null) {
            File bitmapCacheFile = BitmapUtils.getBitmapDiskFile(BaseApplication.getAppContext());
            if (bitmapCacheFile != null) {
                imagePath1 = bitmapCacheFile.getAbsolutePath();
                //指定拍照儲存路徑
                intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(bitmapCacheFile));
                startActivityForResult(intent, requestCode);
            }
        }
    }  

    /**
     * 獲得儲存bitmap的檔案
     * getExternalFilesDir()提供的是私有的目錄,在app解除安裝後會被刪除
     *
     * @param context
     * @param
     * @return
     */
    public static File getBitmapDiskFile(Context context) {
        String cachePath;
        if (Environment.MEDIA_MOUNTED.equals(Environment
                .getExternalStorageState())
                || !Environment.isExternalStorageRemovable()) {

            cachePath = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES).getPath();
        } else {
            cachePath = context.getFilesDir().getPath();
        }
        return new File(cachePath + File.separator + getBitmapFileName());
    }

    public static final String bitmapFormat = ".png";

    /**
     * 生成bitmap的檔名:日期,md5加密
     *
     * @return
     */
    public static String getBitmapFileName() {
        StringBuilder stringBuilder = new StringBuilder();
        try {
            final MessageDigest mDigest = MessageDigest.getInstance("MD5");
            String currentDate = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
            mDigest.update(currentDate.getBytes("utf-8"));
            byte[] b = mDigest.digest();
            for (int i = 0; i < b.length; ++i) {
                String hex = Integer.toHexString(0xFF & b[i]);
                if (hex.length() == 1) {
                    stringBuilder.append('0');
                }
                stringBuilder.append(hex);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        String fileName = stringBuilder.toString() + bitmapFormat;
        return fileName;
    }

向sd-card寫入資訊,許可權是少不了的:

 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

在onActivityResult中獲取拍照成功的標示,這裡系統將不會返回有任何資料,這時候需要自己開啟非同步執行緒去按適屏計算載入圖片:

 @Override
 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == CAREMA) {
            if (resultCode == RESULT_OK) {
                //開啟工作執行緒去載入,拍照產生的圖片
                new Thread(loadBitmapRunnable).start();
            }
        }
  } 

      /**
     * 根據path路徑,找到file,從而生成bitmap
     */
    private Runnable loadBitmapRunnable = new Runnable() {
        @Override
        public void run() {
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            try {
                Thread.sleep(500);
                Bitmap bitmap = BitmapUtils.decodeFileCreateBitmap(imagePath1 
                               , iv.getWidth(), iv.getHeight());
                handler.obtainMessage(CAREMA, bitmap).sendToTarget();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }; 

     public synchronized static Bitmap decodeFileCreateBitmap(String path, int targetWith, int targerHeight) {
        try {
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeFile(path, options);
            options.inSampleSize = calculateScaleSize(options, targetWith, targerHeight);
            options.inJustDecodeBounds = false;
            Bitmap bitmap = BitmapFactory.decodeFile(path, options);
            return getNormalBitamp(bitmap, path);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }  

    /**
     * 採用向上取整的方式,計算壓縮尺寸
     *
     * @param options
     * @param targetWith
     * @param targerHeight
     * @return
     */
    private static int calculateScaleSize(BitmapFactory.Options options, int targetWith, int targerHeight) {
        int simpleSize;
        if (targetWith > 0&&targerHeight>0) {
            int scaleWith = (int) Math.ceil((options.outWidth * 1.0f) / targetWith);
            int scaleHeight = (int) Math.ceil((options.outHeight * 1.0f) / targerHeight);
            simpleSize = Math.max(scaleWith, scaleHeight);
        } else {
            simpleSize = 1;
        }
        return simpleSize;
    }

部分手機上回遇到圖片旋轉問題,這時候需要使用到ExifInterface這類,手動回覆圖片原本方向:

 /**
     * 根據儲存的bitamp中旋轉角度,來建立正常的bitamp
     *
     * @param bitmap
     * @param path
     * @return
     */
    public static Bitmap getNormalBitamp(Bitmap bitmap, String path) {
        int rotate = getBitmapRotate(path);
        Bitmap normalBitmap=null;
        switch (rotate) {
            case 90:
            case 180:
            case 270:
                try {
                    Matrix matrix = new Matrix();
                    matrix.postRotate(rotate);
                    normalBitmap = bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth() 
                           , bitmap.getHeight(), matrix, true);
                    if (bitmap != null && !bitmap.isRecycled()) {
                        bitmap.recycle();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    normalBitmap=bitmap;
                }
                break;
            default:
                normalBitmap=bitmap;
                break;
        }
        return normalBitmap;
    }  

  /**
     * ExifInterface :這個類為jpeg檔案記錄一些image 的標記
     * 這裡,獲取圖片的旋轉角度
     *
     * @param path
     * @return
     */
    public static int getBitmapRotate(String path) {
        int degree = 0;
        try {
            ExifInterface exifInterface = new ExifInterface(path);
            int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION 
                               , ExifInterface.ORIENTATION_NORMAL);
            switch (orientation) {
                case ExifInterface.ORIENTATION_ROTATE_90:
                    degree = 90;
                    break;
                case ExifInterface.ORIENTATION_ROTATE_180:
                    degree = 180;
                    break;
                case ExifInterface.ORIENTATION_ROTATE_270:
                    degree = 270;
                    break;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return degree;
    }

最後建立一個主執行緒捆綁的Handler,進行載入適屏處理後的bitmap:


    private Handler handler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            if (msg.what == CAREMA) {
                if (msg.obj instanceof Bitmap) {
                    iv.setImageBitmap((Bitmap) msg.obj);
                }
            } 
            return false;
        }
    });

拍照效果如下:
1.選擇拍照功能:

這裡寫圖片描述

2.相機運用進行拍照:

這裡寫圖片描述

3.載入拍照產生的圖片:

這裡寫圖片描述

android相簿獲取圖片功能

獲取相簿中相片,也是通過Intent來開啟相簿運用來實現的。

    /**
     * 開啟相簿
     */
    public void openMapStorage(int requestCode) {
        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.setType("image/*");
        if (intent.resolveActivity(getPackageManager()) != null) {
            startActivityForResult(intent, requestCode);
        }
    }

在 onActivityResult中接收相簿返回的資訊,這裡是返回一個Uri。


    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
       if (requestCode == PHOTO) {
            if (resultCode == RESULT_OK) {
                uri = data.getData();
                new Thread(loadUriCreateBitmapRunnable).start();
            }
        }
    }

根據Uri來查詢相簿的資料庫,找到圖片路徑,從而載入圖片:

/**
     * 根據Uri,查詢到path,找到對應的file,生成bitmap
     */
    private Runnable loadUriCreateBitmapRunnable = new Runnable() {
        @Override
        public void run() {
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            Cursor cursor = null;
            try {
                Thread.sleep(500);
                if (uri != null) {
                    cursor = getContentResolver().query(uri,  
                             new String[]{MediaStore.Images.Media.DATA}, null, null, null);
                    if (cursor != null && cursor.moveToFirst()) {
                        imagePath1 = cursor.getString( 
                                cursor.getColumnIndex(MediaStore.Images.Media.DATA));
                        if (imagePath1 != null) {
                            Bitmap bitmap = BitmapUtils.decodeFileCreateBitmap(imagePath1,  
                                      iv.getWidth(), iv.getHeight());
                            handler.obtainMessage(CAREMA, bitmap).sendToTarget();

                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (cursor != null) {
                    cursor.close();
                }
            }
        }
    };

最後建立一個主執行緒捆綁的Handler,進行載入相簿選中的bitmap:


    private Handler handler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            if (msg.what == CAREMA) {
                if (msg.obj instanceof Bitmap) {
                    iv.setImageBitmap((Bitmap) msg.obj);
                }
            } 
            return false;
        }
    });

效果如下:

1.在相簿中選中圖片:

這裡寫圖片描述

2.載入選中的圖片 :

這裡寫圖片描述

android 檢視圖片,進行縮放,拖拉功能

1.首先載入適屏的圖片,通過設定Matrix 來,放置到中間:

    private Runnable loadBitmapRunnable = new Runnable() {
        @Override
        public void run() {
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            try {
                Thread.sleep(500);
                Bitmap bitmap = BitmapUtils.decodeFileCreateBitmap(path 
                       , toucher_iv.getWidth(), toucher_iv.getHeight());
                handler.obtainMessage(0, bitmap).sendToTarget();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    };  
      //記錄當前圖片的Matrix
    private Matrix beforeMatrix = new Matrix();

    private Handler handler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            if (msg.what == 0) {
                bitmap = (Bitmap) msg.obj;

                if (bitmap == null) {
                    return false;
                }
                float y = ((getPhoneMetrics(BaseApplication.getAppContext()).heightPixels -  
                               bitmap.getHeight()) / 2);
                if (y > 0) {
                    beforeMatrix.setTranslate(0, y);
                    toucher_iv.setImageMatrix(beforeMatrix);
                }
                toucher_iv.setImageBitmap(bitmap);
            }
            return false;
        }
    });

2.進行設定縮放,和拖拉的功能程式碼:
先實現 View.OnTouchListener類,複寫onTouch(View v, MotionEvent event)方法。

 //記錄當前圖片的Matrix
    private Matrix beforeMatrix = new Matrix();
    //改變狀態後的Matrix
    private Matrix changeMatrix = new Matrix();
    //記錄當前圖片處於的狀態
    private int currentState = 0;//當前狀態
    private final static int MOVE = 1;//拖動
    private final static int SCALLE = 2;//縮放
    //記錄兩個座標(float型別)
    private PointF before_PointF = new PointF();
    //記錄下一開兩個手指間的距離:
    private float before_Distance;
    //記錄下開始時,兩個手指間的中心點
    private PointF midPointf;


    /**
     * 1.拖動分析:
     * 在原本Matrix基礎上新增移動的x,y距離,實現拖動
     * 做法:先記錄開始的Matrix1,
     * 然後計算移動的x,y,
     * 建立一個以Matrix1為基礎的新的Matrix2,新增移動的x,y
     * 圖片設定新的Matrix2
     * <p/>
     * <p/>
     * 2.縮放分析:
     * 先記錄中心點(縮放,旋轉都需要用到),原本的Matrix,原本手指間距離,通過距離比率來設定
     *
     * @param v
     * @param event
     * @return
     */
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
            //手指壓下螢幕
            case MotionEvent.ACTION_DOWN:
                currentState = MOVE;
                beforeMatrix.set(toucher_iv.getImageMatrix());
                before_PointF.set(event.getX(), event.getY());
                break;
            //手指在滑動
            case MotionEvent.ACTION_MOVE:
                if (currentState == MOVE) {//拖動狀態
                    float distance_x = event.getX() - before_PointF.x;
                    float ditance_y = event.getY() - before_PointF.y;
                    changeMatrix.set(beforeMatrix);
                    changeMatrix.postTranslate(distance_x, ditance_y);
                } else if (currentState == SCALLE) {//縮放狀態
                    float endDistance = distance(event);
                    if (endDistance > 10f) {
                        float scale = endDistance / before_Distance;
                        changeMatrix.set(beforeMatrix);
                        changeMatrix.postScale(scale, scale, midPointf.x, midPointf.y);
                    }
                }
                break;
            //手指離開觸控
            case MotionEvent.ACTION_UP:
                currentState = 0;
                break;
            //多一個手指在觸控
            case MotionEvent.ACTION_POINTER_DOWN:
                currentState = SCALLE;
                //開始時,兩個手指間距離
                before_Distance = distance(event);
                if (before_Distance > 10f) {
                    midPointf = mid(event);
                    beforeMatrix.set(toucher_iv.getImageMatrix());
                }
                break;
            //還有手指在觸控
            case MotionEvent.ACTION_POINTER_UP:
                currentState = 0;
                break;
        }
        toucher_iv.setImageMatrix(changeMatrix);
        return true;
    }

    /**
     * 計算兩個手指間的距離
     */
    private float distance(MotionEvent event) {
        float dx = event.getX(1) - event.getX(0);
        float dy = event.getY(1) - event.getY(0);
        /** 使用勾股定理返回兩點之間的距離 */
        return (float) Math.sqrt(dx * dx + dy * dy);
    }

    /**
     * 計算兩個手指間的中間點
     */
    private PointF mid(MotionEvent event) {
        float midX = (event.getX(1) + event.getX(0)) / 2;
        float midY = (event.getY(1) + event.getY(0)) / 2;
        return new PointF(midX, midY);
    }

效果如下:
這裡沒有製作gif來顯示效果,感興趣的可以下載專案來體驗效果:

這裡寫圖片描述

android 壓縮圖片
現在的手機拍照產生的相片都了幾M,甚至可能是5M多,上傳到伺服器是不會上傳那麼大的圖片的。這邊需要用到壓縮。
壓縮圖片一般通過Bitmap#compress()來實現的。 一般都是通過for迴圈來計算最小壓縮體積:

這裡是將Bitmap按最小體積量來壓縮成byte[]來上傳的。

 public static byte[] bitmapCompressToByteArray(Bitmap bitmap, int bitmapSize, boolean isRecycle) {

        int quality = 100;//範圍0~100
        ByteArrayOutputStream byteArrayOutputStream = null;
        try {
            byteArrayOutputStream = new ByteArrayOutputStream();
            bitmap.compress(Bitmap.CompressFormat.PNG,quality,byteArrayOutputStream);
            while (byteArrayOutputStream.toByteArray().length/1024>bitmapSize){
                   if(quality<=10){
                        break;
                   }
                   byteArrayOutputStream.reset();
                   quality-=10;
                   bitmap.compress(Bitmap.CompressFormat.PNG,quality,byteArrayOutputStream);
            }
            byte[] b = byteArrayOutputStream.toByteArray();
            if (isRecycle) {
                bitmap.recycle();
            }

            return  b;
        } catch (Exception e) {
            e.printStackTrace();
            return  null;
        } finally {
            try {
                if (byteArrayOutputStream != null) {
                    byteArrayOutputStream.close();
                }
            } catch (Exception e1) {
                e1.printStackTrace();
            }
        }
    }

經過測試發現,若是一個幾M的圖片進行這樣的壓縮處理,時間花費是很久的,甚至可能是幾分鐘時間。

這裡的的處理方式是:按上傳的體積量來計算出圖片的解析度,按這個解析度來載入適合的圖片,然後將這個圖片再來編碼成byte[]

    /**
     * 將bitmap編碼成byte[]
     */
    private Runnable bitmapEndecodeByteRunnable = new Runnable() {
        @Override
        public void run() {
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            try {
                //這裡是250*250大小的圖片
                byte[] b = BitmapUtils.bitmapCompressToByteArray( 
                      BitmapUtils.decodeFileCreateBitmap(imagePath1 
                       , 250, 250), 200, true);
                handler.obtainMessage(FILEUPLOAD, b).sendToTarget();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }; 

最後將圖片對應的byte[]通過volley中自定義的MultiPartRequest上傳:

private Handler handler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
          if (msg.what == FILEUPLOAD) {
                byte[] b = (byte[]) msg.obj;
                if (b != null) {
                    sendFileUploadRequest(b);
                }
            }
            return false;
        }
    });

    /**
     * 將bitmap編碼成byte[],然後上傳到伺服器
     *
     * @param bitmap
     */
    public void sendFileUploadRequest(byte[] bitmap) {
        System.out.print("開始上傳");
        MultiPartRequest<JsonBean> request = new MultiPartRequest<>(Request.Method.POST, "http://192.168.1.102:8080/SSMProject/file/fileUpload", JsonBean.class, new Response.Listener<JsonBean>() {
            @Override
            public void onResponse(JsonBean jsonBean) {
                path_tv.setText("bitmap儲存在:"+jsonBean.path);
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError volleyError) {
                Toast.makeText(MultiPartRequestActivity.this, volleyError.getMessage(), Toast.LENGTH_LONG).show();
            }
        });
        request.addFile(bitmap);

        request.setRetryPolicy(new DefaultRetryPolicy(50000,
                DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
                DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
        request.setTag(TAG);
        VolleySingleton.getInstance().addToRequestQueue(request);
    }

相關知識點: