Android拍照,上傳,預覽綜合【修改】
最近需要做手機拍照【兩種方式:呼叫系統相機(部落格最後);自己寫照相機佈局】,預覽,上傳功能。特地研究了下android的手機拍照。
參考地址:
http://blog.csdn.net/cfwdl/article/details/5746708
http://mjbb.iteye.com/blog/1018006
http://blog.csdn.net/hellogv/article/details/5962494
1、上傳檔案功能網上很多講的,只要細心點,按照格式來寫傳送的資料,都是沒有問題的。
2、預覽使用Gallery和ImageSwitcher就行,我做的很簡單(參考程式碼)。
----------------------------------------------------------------------------------------------------------------------
修改內容:
1、照相功能使用系統自帶照相機(自己寫的照相機屬性設定根據不同照相機會有問題,所以捨棄)
2、預覽功能不再使用Gallery+ImageSwitcher;實用性不強,並且顯示慢且卡。改用非同步載入
3、上傳圖片時,對圖片進行壓縮,增加上傳速度。
4、長按gridView進入編輯模式,批量刪除圖片。參考
5、今天又做修改,之前寫的壓縮圖片方法的竟然會變形(沒有測試大的圖片),修改後不會變形了。
以此,希望能做到最好的使用者體驗。
附上流程圖:
拍照功能:【預覽尺寸有知道的朋友留言告知。】
* 拍照 * @author Administrator */ public class TakePhotoAct extends Activity implements SurfaceHolder.Callback{ private static String imgPath = Environment.getExternalStorageDirectory().getPath() + "/"+Const.imageDir; private SurfaceView surfaceView; //相機畫布 private SurfaceHolder surfaceHolder; private Button takePicView,exitView; private Camera mCamera; //照相機 @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); //這裡我在AndroidManifest.xml的activity中添加了android:theme="@android:style/Theme.NoTitleBar.Fullscreen" /** * 隱藏狀態列和標題欄 this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); requestWindowFeature(Window.FEATURE_NO_TITLE); */ //setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); //橫屏 /** * 獲取Button並且設定事件監聽 */ takePicView = (Button)this.findViewById(R.id.takepic); takePicView.setOnClickListener(TakePicListener); exitView = (Button)this.findViewById(R.id.exit); exitView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { finish(); } }); surfaceView = (SurfaceView)this.findViewById(R.id.surface_camera); surfaceHolder = surfaceView.getHolder(); surfaceHolder.addCallback(this); surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); checkSoftStage(); //首先檢測SD卡是否存在 } /** * 檢測手機是否存在SD卡,網路連線是否開啟 */ private void checkSoftStage(){ if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){ //判斷是否存在SD卡 // String rootPath = Environment.getExternalStorageDirectory().getPath(); //獲取SD卡的根目錄 File file = new File(imgPath); if(!file.exists()){ file.mkdir(); } }else{ new AlertDialog.Builder(this).setMessage("檢測到手機沒有儲存卡!請插入手機儲存卡再開啟本應用。") .setPositiveButton("確定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { finish(); } }).show(); } } /** * 點選拍照按鈕,啟動拍照 */ private final OnClickListener TakePicListener = new OnClickListener(){ @Override public void onClick(View v) { mCamera.autoFocus(new AutoFoucus()); //自動對焦 } }; /** * 自動對焦後拍照 * @author aokunsang * @Date 2011-12-5 */ private final class AutoFoucus implements AutoFocusCallback{ @Override public void onAutoFocus(boolean success, Camera camera) { if(success && mCamera!=null){ mCamera.takePicture(mShutterCallback, null, mPictureCallback); } } } /** * 重點物件、 此處例項化了一個本介面的PictureCallback * 當用戶拍完一張照片的時候觸發,這時候對圖片處理並儲存操作。 * */ private final PictureCallback mPictureCallback = new PictureCallback() { @Override public void onPictureTaken(byte[] data, Camera camera) { try { String fileName = System.currentTimeMillis()+".jpg"; File file = new File(imgPath,fileName); Bitmap bm = BitmapFactory.decodeByteArray(data, 0, data.length); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file)); bm.compress(Bitmap.CompressFormat.JPEG, 60, bos); bos.flush(); bos.close(); Intent intent = new Intent(TakePhotoAct.this,PictureViewAct.class); intent.putExtra("imagePath", file.getPath()); startActivity(intent); } catch (Exception e) { e.printStackTrace(); } } }; /** * 在相機快門關閉時候的回撥介面,通過這個介面來通知使用者快門關閉的事件, * 普通相機在快門關閉的時候都會發出響聲,根據需要可以在該回調介面中定義各種動作, 例如:使裝置震動 */ private final ShutterCallback mShutterCallback = new ShutterCallback() { public void onShutter() { Log.d("ShutterCallback", "...onShutter..."); } }; @Override /** * 初始化相機引數,比如相機的引數: 畫素, 大小,格式 */ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { Camera.Parameters param = mCamera.getParameters(); /** * 設定拍照圖片格式 */ param.setPictureFormat(PixelFormat.JPEG); /** * 設定預覽尺寸【這裡需要注意:預覽尺寸有些數字正確,有些會報錯,不清楚為啥】 */ //param.setPreviewSize(320, 240); /** * 設定圖片大小 */ param.setPictureSize(Const.width, Const.height); mCamera.setParameters(param); /** * 開始預覽 */ mCamera.startPreview(); } @Override /** * 開啟相機,設定預覽 */ public void surfaceCreated(SurfaceHolder holder) { try { mCamera = Camera.open(); //開啟攝像頭 mCamera.setPreviewDisplay(holder); } catch (IOException e) { mCamera.release(); mCamera = null; } } @Override /** * 預覽介面被關閉時,或者停止相機拍攝;釋放相機資源 */ public void surfaceDestroyed(SurfaceHolder holder) { mCamera.stopPreview(); if(mCamera!=null) mCamera.release(); mCamera = null; } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if(keyCode == KeyEvent.KEYCODE_CAMERA){ //按下相機實體按鍵,啟動本程式照相功能 mCamera.autoFocus(new AutoFoucus()); //自動對焦 return true; }else{ return false; } } }
xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<SurfaceView
android:id="@+id/surface_camera"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<Button
android:text="拍照"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/takepic"
/>
<Button
android:text="退出"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/exit"
/>
</LinearLayout>
</LinearLayout>
預覽功能:
/**
* 圖片預覽
* @author: aokunsang
* @date: 2012-8-1
*/
public class PictureScanAct extends Activity {
private GridView gridView;
private ImageAdapter imgAdapter;
private List<String> fileNameList = new ArrayList<String>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.picturescan);
gridView = (GridView)findViewById(R.id.picture_grid);
imgAdapter = new ImageAdapter(this);
gridView.setAdapter(imgAdapter);
gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
String fileName = fileNameList.get(position);
startActivity(new Intent(PictureScanAct.this, PictureViewAct.class).putExtra("flag","upload").putExtra("imagePath",fileName));
}
});
//setProgressBarIndeterminateVisibility(true);
Toast.makeText(this, "載入圖片中....", Toast.LENGTH_LONG).show();
new AsyncLoadedImage().execute();
}
/**
* 向介面卡新增圖片
* @param bitmap
*/
private void addImage(Bitmap... loadImages){
for(Bitmap loadImage:loadImages){
imgAdapter.addPhoto(loadImage);
}
}
/**
* 釋放記憶體
*/
protected void onDestroy() {
super.onDestroy();
final GridView grid = gridView;
final int count = grid.getChildCount();
ImageView v = null;
for (int i = 0; i < count; i++) {
v = (ImageView) grid.getChildAt(i);
((BitmapDrawable) v.getDrawable()).setCallback(null);
}
}
/**
* 非同步載入圖片展示
* @author: aokunsang
* @date: 2012-8-1
*/
class AsyncLoadedImage extends AsyncTask<Object, Bitmap, Boolean> {
@Override
protected Boolean doInBackground(Object... params) {
File fileDir = new File(Const.imgPath);
File[] files = fileDir.listFiles();
boolean result = false;
if(files!=null){
for(File file:files){
String fileName = file.getName();
if (fileName.lastIndexOf(".") > 0
&& fileName.substring(fileName.lastIndexOf(".") + 1,
fileName.length()).equals("jpg")){
Bitmap bitmap;
Bitmap newBitmap;
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 10;
bitmap = BitmapFactory.decodeFile(file.getPath(), options);
newBitmap = ThumbnailUtils.extractThumbnail(bitmap, 100, 100);
bitmap.recycle();
if (newBitmap != null) {
fileNameList.add(file.getPath());
publishProgress(newBitmap);
result = true;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
return result;
}
@Override
public void onProgressUpdate(Bitmap... value) {
addImage(value);
}
@Override
protected void onPostExecute(Boolean result) {
if(!result){
showDialog(1);
}
}
}
@Override
protected Dialog onCreateDialog(int id) {
AlertDialog dialog = new AlertDialog.Builder(PictureScanAct.this).setTitle("溫馨提示").setMessage("暫時還沒有照片,請先採集照片!")
.setPositiveButton("確定", new DialogInterface.OnClickListener(){
@Override
public void onClick(DialogInterface dialog, int which) {
startActivity(new Intent(PictureScanAct.this,TakePhotoAct.class));
}
}).setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
}).show();
return dialog;
}
}
GridView介面卡:
public class ImageAdapter extends BaseAdapter {
private List<Bitmap> picList = new ArrayList<Bitmap>();
private Context mContext;
public ImageAdapter(Context mContext){
this.mContext = mContext;
}
@Override
public int getCount() {
return picList.size();
}
/* (non-Javadoc)
* @see android.widget.Adapter#getItem(int)
*/
@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return picList.get(position);
}
/**
* 新增圖片
* @param bitmap
*/
public void addPhoto(Bitmap loadImage){
picList.add(loadImage);
notifyDataSetChanged();
}
/* (non-Javadoc)
* @see android.widget.Adapter#getItemId(int)
*/
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView = null;
if(convertView == null){
imageView = new ImageView(mContext);
imageView.setLayoutParams(new GridView.LayoutParams(110, 110));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setPadding(5,5,5,5);
}else{
imageView = (ImageView)convertView;
}
imageView.setImageBitmap(picList.get(position));
return imageView;
}
}
圖片預覽介面:
<?xml version="1.0" encoding="utf-8"?>
<GridView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/picture_grid"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:numColumns="4"
android:verticalSpacing="5dip"
android:horizontalSpacing="5dip"
android:stretchMode="columnWidth"
android:columnWidth="120dip"
android:gravity="center"
>
</GridView>
預覽圖片:
上傳工具類:
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;
import java.util.Map.Entry;
import android.util.Log;
import com.peacemap.photo.po.FileInfo;
/**
* POST上傳檔案
* @author aokunsang
* @Date 2011-12-6
*/
public class PostFile {
private static PostFile postFile = new PostFile();
private final static String LINEND = "\r\n";
private final static String BOUNDARY = "---------------------------7da2137580612"; //資料分隔線
private final static String PREFIX = "--";
private final static String MUTIPART_FORMDATA = "multipart/form-data";
private final static String CHARSET = "utf-8";
private final static String CONTENTTYPE = "application/octet-stream";
private PostFile(){}
public static PostFile getInstance(){
return postFile;
}
/**
* HTTP上傳檔案
* @param actionUrl 請求伺服器的路徑
* @param params 傳遞的表單內容
* @param files 多個檔案資訊
* @return
*/
public String post(String actionUrl,Map<String,String> params,FileInfo[] files){
try {
URL url = new URL(actionUrl);
HttpURLConnection urlConn = (HttpURLConnection)url.openConnection();
urlConn.setDoOutput(true); //允許輸出
urlConn.setDoInput(true); //允許輸入
urlConn.setUseCaches(false);
urlConn.setRequestMethod("POST");
urlConn.setRequestProperty("connection", "Keep-Alive");
urlConn.setRequestProperty("Charset", CHARSET);
urlConn.setRequestProperty("Content-Type", MUTIPART_FORMDATA+";boundary="+BOUNDARY);
DataOutputStream dos = new DataOutputStream(urlConn.getOutputStream());
//構建表單資料
String entryText = bulidFormText(params);
Log.i("-------描述資訊---------------", entryText);
dos.write(entryText.getBytes());
StringBuffer sb = new StringBuffer("");
for(FileInfo file : files){
sb.append(PREFIX).append(BOUNDARY).append(LINEND);
sb.append("Content-Disposition: form-data; name=\""+file.getFileTextName()+"\"; filename=\""+file.getFile().getAbsolutePath()+"\""+LINEND);
sb.append("Content-Type:"+CONTENTTYPE+";charset="+CHARSET+LINEND);
sb.append(LINEND);
dos.write(sb.toString().getBytes());
InputStream is = new FileInputStream(file.getFile());
byte[] buffer = new byte[1024];
int len = 0;
while ((len = is.read(buffer)) != -1) {
dos.write(buffer, 0, len);
}
is.close();
dos.write(LINEND.getBytes());
}
//請求的結束標誌
byte[] end_data = (PREFIX + BOUNDARY + PREFIX + LINEND).getBytes();
dos.write(end_data);
dos.flush();
//----------------------------------- 傳送請求資料結束 ----------------------------
//---------------------------------- 接收返回資訊 ------------------------
int code = urlConn.getResponseCode();
if(code!=200){
urlConn.disconnect();
return "";
}else{
BufferedReader br = new BufferedReader(new InputStreamReader(urlConn.getInputStream()));
String result = "";
String line = null;
while((line = br.readLine())!=null){
result += line;
}
br.close();
urlConn.disconnect();
return result;
}
} catch (Exception e) {
Log.e("--------上傳圖片錯誤--------", e.getMessage());
return null;
}
}
/**
* HTTP上傳單個檔案
* @param actionUrl 請求伺服器的路徑
* @param params 傳遞的表單內容
* @param files 單個檔案資訊
* @return
*/
public String post(String actionUrl,Map<String,String> params,FileInfo fileInfo){
return post(actionUrl, params, new FileInfo[]{fileInfo});
}
/**
* 封裝表單文字資料
* @param paramText
* @return
*/
private String bulidFormText(Map<String,String> paramText){
if(paramText==null || paramText.isEmpty()) return "";
StringBuffer sb = new StringBuffer("");
for(Entry<String,String> entry : paramText.entrySet()){
sb.append(PREFIX).append(BOUNDARY).append(LINEND);
sb.append("Content-Disposition:form-data;name=\""
+ entry.getKey() + "\"" + LINEND);
// sb.append("Content-Type:text/plain;charset=" + CHARSET + LINEND);
sb.append(LINEND);
sb.append(entry.getValue());
sb.append(LINEND);
}
return sb.toString();
}
/**
* 封裝檔案文字資料
* @param files
* @return
*/
private String buildFromFile(FileInfo[] files){
StringBuffer sb = new StringBuffer();
for(FileInfo file : files){
sb.append(PREFIX).append(BOUNDARY).append(LINEND);
sb.append("Content-Disposition: form-data; name=\""+file.getFileTextName()+"\"; filename=\""+file.getFile().getAbsolutePath()+"\""+LINEND);
sb.append("Content-Type:"+CONTENTTYPE+";charset="+CHARSET+LINEND);
sb.append(LINEND);
}
return sb.toString();
}
}
上傳圖片時對圖片進行壓縮處理(壓縮處理程式):
/**
* 壓縮圖片上傳
* @param picPath
* @return
*/
private synchronized File compressPicture(String picPath){
int maxWidth = 640,maxHeight=480; //設定新圖片的大小
String fileName = picPath.substring(picPath.lastIndexOf("/"));
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
Bitmap image = BitmapFactory.decodeFile(picPath, options);
double ratio = 1D;
if (maxWidth > 0 && maxHeight <= 0) {
// 限定寬度,高度不做限制
ratio = Math.ceil(options.outWidth / maxWidth);
} else if (maxHeight > 0 && maxWidth <= 0) {
// 限定高度,不限制寬度
ratio = Math.ceil(options.outHeight / maxHeight);
} else if (maxWidth > 0 && maxHeight > 0) {
// 高度和寬度都做了限制,這時候我們計算在這個限制內能容納的最大的圖片尺寸,不會使圖片變形
double _widthRatio = Math.ceil(options.outWidth / maxWidth);
double _heightRatio = (double) Math.ceil(options.outHeight / maxHeight);
ratio = _widthRatio > _heightRatio ? _widthRatio : _heightRatio;
}
if (ratio > 1)
options.inSampleSize = (int) ratio;
options.inJustDecodeBounds = false;
options.inPreferredConfig = Bitmap.Config.RGB_565;
image = BitmapFactory.decodeFile(picPath, options);
//儲存入sdCard
File file = new File(Const.thumbnailPath+fileName);
try {
FileOutputStream out = new FileOutputStream(file);
if(image.compress(Bitmap.CompressFormat.JPEG, 100, out)){
out.flush();
out.close();
}
} catch (Exception e) {
e.printStackTrace();
return new File(picPath);
}finally{
if(image!=null && !image.isRecycled()){
image.recycle();
}
}
return file;
}
-------------------------------------我是個華麗的分割線,哇哈哈-----------------------------------------------
做完這個拍照後,感覺功能太簡單,比如:設定圖片大小,白天夜晚照相等等一些系統照相機帶的功能都沒有,因此用在專案中感覺不炫。 然後就用了簡單點的,直接呼叫系統照相機了。本來想著簡單呢,後來也遇到點問題。
(1)根據Camera Activity返回的時候,會帶一個名為data的Bitmap物件,照片的縮圖(這個地方可以做各種修改,我沒用到不說了),上程式碼:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
checkSoftStage();
try {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(intent, TAKE_PICTURE);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 檢測手機是否存在SD卡,網路連線是否開啟
*/
private void checkSoftStage(){
if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){ //判斷是否存在SD卡
File file = new File(imgPath);
if(!file.exists()){
file.mkdir();
}
}else{
new AlertDialog.Builder(this).setMessage("檢測到手機沒有儲存卡!請插入手機儲存卡再開啟本應用。")
.setPositiveButton("確定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
}).show();
}
}
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == TAKE_PICTURE) {
// 拍照Activity儲存影象資料的key是data,返回的資料型別是Bitmap物件
Bitmap cameraBitmap = (Bitmap) data.getExtras().get("/sdcard/rtest.jpg");
// 在ImageView元件中顯示拍攝的照片
image.setImageBitmap(cameraBitmap);
// 做自己的業務操作。。。。
}
super.onActivityResult(requestCode, resultCode, data);
}
(2)以上程式碼在我的小米手機上測試時,出現問題了。 返回的name為data的Bitmap物件是個Null,我發現小米照完相片之後,他會先跳到一個預覽的介面(系統自帶的頁面),所以得不到Bitmap物件了。
因此我就先儲存照片以及其路徑,然後在onActivityResult中獲取圖片,做業務操作,程式碼如下:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
checkSoftStage();
try {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(intent, TAKE_PICTURE);
} catch (Exception e) {
e.printStackTrace();
}
try {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
String fileName = System.currentTimeMillis()+".jpg";
newImgPath = imgPath + "/" + fileName;
Uri uri = Uri.fromFile(new File(imgPath,fileName));
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
startActivityForResult(intent, TAKE_PICTURE);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void onActivityResult(int requestCode,
int resultCode, Intent data) {
Log.i("--------圖片路徑---------", "------"+newImgPath+"---------");
//.....做一些業務操作
} catch (Exception e) {
e.printStackTrace();
}
super.onActivityResult(requestCode, resultCode, data);
}