編輯:關於Android編程
項目下載地址:https://github.com/Aiushtha/android-PictureSelector
最早使用android調用系統拍照然後遇到很多空指針等問題以及各種android 不同版本Intent取data有時候會空指針之類的api兼容問題,像使用紅米note在開了很多應用後,再啟動拍照系統,會發生拍照崩潰圖片丟失等問題,用微信控件有時拍照有極小概率拍照無效等等奇怪的問題,其原因是因為Activity被回收了,變量變成null,
還有三星手機可能會遇到變量空針
需要在AndroidManifest.xml的Activity裡加入
android:configChanges="mcc|mnc|keyboard|keyboardHidden|navigation|orientation|screenSize|fontScale"
如何在Fragment上調用拍照,圖片如何壓縮
經過一段時間優化,修復了一些坑。我覺得目前的代碼比較可靠,總結一下封裝後分享出來。
package com.cn.demo.takephoto; import android.content.Intent; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; import java.io.File; import java.text.DecimalFormat; import java.util.List; import me.lxz.photopicker.camera.PhotoPickManger; import me.lxz.photopicker.tools.SimpleImageLoader; public class SimpleDemoActivity extends AppCompatActivity { PhotoPickManger pickManger; private View btn; private ImageView img; private TextView tv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); /**圖片加載器*/ SimpleImageLoader.init(this.getApplicationContext()); btn = findViewById(R.id.btn); img = (ImageView) findViewById(R.id.img); tv=(TextView)findViewById(R.id.tv); pickManger = new PhotoPickManger("pick",this, savedInstanceState,new PhotoPickManger.OnPhotoPickFinsh() { @Override public void onPhotoPick(Listlist) { tv.setText(""); Toast.makeText(getApplicationContext(), "path:" + list.get(0).getPath() + " length:" + list.get(0).length(), Toast.LENGTH_SHORT).show(); tv.append("path:" + list.get(0).getPath()); tv.append("\nlength:" + new DecimalFormat("#.##").format((1.0d*list.get(0).length()/1024/1024))+"MB"); /**是否圖片壓縮*/ processImg(); } }); /**是否在*/ pickManger.setCut(false); pickManger.flushBundle(); btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { pickManger.clearCache(); pickManger.start(PhotoPickManger.Mode.SYSTEM_CAMERA); } }); } /**圖片壓縮*/ private void processImg() { pickManger.doProcessedPhotos(new PhotoPickManger.OnProcessedPhotos() { @Override public void onProcessed(List list) { SimpleImageLoader.displayImage(list.get(0), img); tv.append("\nprogress length:" + new DecimalFormat("#.##").format((1.0d*list.get(0).length()/1024/1024))+"MB"); } }); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); pickManger.onActivityResult(requestCode, resultCode, data); } @Override public void onSaveInstanceState(Bundle savedInstanceState) { super.onSaveInstanceState(savedInstanceState); pickManger.onSaveInstanceState(savedInstanceState); } }
package me.lxz.photopicker.camera; import android.app.Activity; import android.content.Intent; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.provider.MediaStore; import android.util.Log; import android.widget.Toast; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.InputStream; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.Iterator; import java.util.List; import me.iwf.photopicker.PhotoPickerActivity; import me.iwf.photopicker.utils.PhotoPickerIntent; import me.lxz.photopicker.tools.PictureUtil; /** * 圖片選擇器 */ public final class PhotoPickManger { public interface OnProcessedPhotos { void onProcessed(Listlist); } public interface OnPhotoPickFinsh { public void onPhotoPick(List list); } /** * 模式 */ public enum Mode { /** * 系統相機 */SYSTEM_CAMERA, /** * 系統圖庫 */SYSTEM_IMGCAPTRUE, /** * 類似微信圖庫 */AS_WEIXIN_IMGCAPTRUE } /** * 用於區別哪一個圖片選擇器 */ private static String currentPickMangerName; /** * 測試用 */ private boolean isDebugToast = true; /** * 用於區別其他圖片選擇器 */ private String name; public final String SAVE_STATIC_NAME = "save_currentPickMangerName"; /** * 字段保存所已選擇的圖片 */ public final String SAVE_SELECTED_PHOTOS = "save_selected_photos"; /** * 字段保存所拍照已選擇的圖片 */ public final String SAVE_CACHE_CAMERA = "save_cache_camera"; /** * 字段保存所拍照是否選擇裁剪 */ public final String SAVE_CACHE_IS_CUT = "save_cache_is_cut"; /** * 字段保存待處理圖片 */ public final String SAVE_CACHE_CUT_QUEUE = "save_cache_cut_queue"; /** * 是否裁剪 只對系統相機和系統相冊有效 */ private boolean isCut = false; /** * 是否縮略 */ private boolean isOptimize = false; /** * 設置返回最大圖片數 對系統相機和相冊調用無效 * 默認1 */ private int returnFileCount = 1; private OnPhotoPickFinsh onPhotoPickFinsh; private File tempFile; private Handler handler = new Handler(); /** * 已經選擇的拍照圖片 */ public ArrayList selectsPhotos = new ArrayList<>(); private Activity activity; /** * 用於緩存 */ public Bundle bundle; /** * 系統拍照標示code */ private final int PHOTO_REQUEST_TAKEPHOTO = 1;// 拍照 /** * 系統相冊標示code */ private final int PHOTO_REQUEST_GALLERY = 2;// 從相冊中選擇 /** * 系統照片返回標示code */ private final int PHOTO_REQUEST_CUT = 3;// 結果 /** * 仿微信相冊返回code */ public final static int AS_WEIXIN_REQUEST_CODE = 4; /** * 如果多圖待剪切則保存到該隊列裡 */ public ArrayList willCutOfFileQueue = new ArrayList<>(); /** * 圖片緩存地址 */ public String cacheFilePath = "/img/"; /** * 默認裁剪大小 */ public int defaultCutSize = 150; /** * 至少大於多少的圖片進行處理 */ public int needProcessFileLength = (int) 0.5 * 1024 * 1024; /*** * 構造方法 * * @param name 為圖片選擇器默認一個別名 用來區別那一個選擇器被選擇了 * @param activity * @param bundle 當系統內存不足時,重建時取出變量 * @param onPhotoPickFinsh 圖片選擇成功時回調 */ public PhotoPickManger(String name, Activity activity, Bundle bundle, OnPhotoPickFinsh onPhotoPickFinsh) { this.onPhotoPickFinsh = onPhotoPickFinsh; this.name = name; this.activity = activity; this.bundle = bundle; if (bundle != null) { isCut = bundle.getBoolean(SAVE_CACHE_IS_CUT + "_" + name); currentPickMangerName = bundle.getString(SAVE_STATIC_NAME); willCutOfFileQueue = (ArrayList ) bundle.getSerializable(SAVE_CACHE_CUT_QUEUE + "name"); } } /** * 處理掉重建時的緩存 */ public void flushBundle() { if (bundle != null) { if (isDebugToast) { Toast.makeText(activity, "bundle is refresh", Toast.LENGTH_LONG).show(); } selectsPhotos = (ArrayList ) bundle.getSerializable(SAVE_SELECTED_PHOTOS + "_" + name); if (selectsPhotos == null) { selectsPhotos = new ArrayList<>(); } tempFile = (File) bundle.getSerializable(SAVE_CACHE_CAMERA + "_" + name); if (tempFile != null) { if (tempFile.exists()) { if (tempFile.length() > 0) { if (!isCut) { selectsPhotos.add(tempFile); } else { startPhotoZoom(Uri.fromFile(tempFile), defaultCutSize); } tempFile = null; } else { tempFile.delete(); tempFile = null; } } } bundle.remove(SAVE_CACHE_CAMERA + "_" + name); if (!selectsPhotos.isEmpty()) { if (onPhotoPickFinsh != null) onPhotoPickFinsh.onPhotoPick(selectsPhotos); } } } /** * 保存變量 */ public void onSaveInstanceState(Bundle savedInstanceState) { this.bundle = savedInstanceState; if (selectsPhotos != null && !selectsPhotos.isEmpty()) { savedInstanceState.putSerializable(SAVE_SELECTED_PHOTOS + "_" + name, selectsPhotos); } if (tempFile != null) { savedInstanceState.putSerializable(SAVE_CACHE_CAMERA + "_" + name, tempFile); } savedInstanceState.putBoolean(SAVE_CACHE_IS_CUT + "_" + name, isCut); savedInstanceState.putSerializable(SAVE_CACHE_CUT_QUEUE + "_" + name, willCutOfFileQueue); savedInstanceState.putSerializable(SAVE_STATIC_NAME, currentPickMangerName); } /** * 如果是單一拍照,在拍照前應該清理緩存 */ public void clearCache() { getSelectsPhotos().clear(); tempFile = null; if (bundle != null) { bundle.remove(SAVE_SELECTED_PHOTOS + "_" + name); bundle.remove(SAVE_CACHE_CAMERA + "_" + name); } } /** * 生成一個臨時的緩存文件 */ private File getFile() { File dir = new File(Environment.getExternalStorageDirectory().getPath() + cacheFilePath); if (!dir.exists()) { dir.mkdirs(); } File file = new File(Environment.getExternalStorageDirectory() .getPath() + cacheFilePath, getPhotoFileName()); return file; } // 使用系統當前日期加以調整作為照片的名稱 private String getPhotoFileName() { Date date = new Date(System.currentTimeMillis()); SimpleDateFormat dateFormat = new SimpleDateFormat( "'IMG'_yyyyMMdd_HHmmss"); return dateFormat.format(date) + ".jpg"; } /** * // 調用系統的拍照功能 */ private void startCamearPicCut() { tempFile = getFile(); Log.d("test", "start:" + tempFile.exists() + " " + tempFile.length()); // this.isCutOut = b; // 調用系統的拍照功能 Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); intent.putExtra("camerasensortype", 2);// 調用前置攝像頭 intent.putExtra("autofocus", true);// 自動對焦 intent.putExtra("fullScreen", false);// 全屏 intent.putExtra("showActionIcons", false); intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(tempFile)); activity.startActivityForResult(intent, PHOTO_REQUEST_TAKEPHOTO); } /** * 調用系統的相冊 */ private void startImageCaptrue() { tempFile = getFile(); Intent intent = new Intent(Intent.ACTION_PICK, null); intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*"); activity.startActivityForResult(intent, PHOTO_REQUEST_GALLERY); } /** * 調用仿微信圖庫 */ private void startAsWeixinImageCaptrue() { PhotoPickerIntent intent = new PhotoPickerIntent(activity); intent.setPhotoCount(returnFileCount); activity.startActivityForResult(intent, AS_WEIXIN_REQUEST_CODE); } /** * 啟動 */ public void start(Mode mode) { currentPickMangerName = this.name; switch (mode) { case SYSTEM_CAMERA: startCamearPicCut(); break; case SYSTEM_IMGCAPTRUE: startImageCaptrue(); break; case AS_WEIXIN_IMGCAPTRUE: startAsWeixinImageCaptrue(); break; } } /** * 回調onActivityResult事件 */ @SuppressWarnings("unused") public void onActivityResult(final int requestCode, final int resultCode, final Intent data) { if (currentPickMangerName == null) { if (bundle != null) { currentPickMangerName = bundle.getString(SAVE_STATIC_NAME); if (!currentPickMangerName.equals(name)) return; } } else { if (!currentPickMangerName.equals(name)) return; } try { switch (requestCode) { case PHOTO_REQUEST_TAKEPHOTO: if (isCut) { startPhotoZoom(Uri.fromFile(tempFile), defaultCutSize); // 裁剪 } else { if (tempFile.length() == 0) { tempFile.delete(); } else { finish(tempFile); } } return; case PHOTO_REQUEST_GALLERY: if (isCut) { if (data != null) { startPhotoZoom(data.getData(), defaultCutSize); } } else { File file = null; try { String path = getRealPathFromURI(data.getData()); file = new File(path); if (file == null) return; if (file.length() == 0) { file.delete(); return; } finish(file); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } return; case PHOTO_REQUEST_CUT: if (data != null) { setCutPicToView(data); } else { flushCutPhotos(); } return; case AS_WEIXIN_REQUEST_CODE: List photos = null; try { if (requestCode == AS_WEIXIN_REQUEST_CODE) { if (data != null) { photos = data.getStringArrayListExtra(PhotoPickerActivity.KEY_SELECTED_PHOTOS); if (!isCut) { List list = new ArrayList (); if (photos != null && !photos.isEmpty()) { for (String str : photos) { list.add(new File(str)); } } else { return; } finish(list); } else { if (photos != null && !photos.isEmpty()) { /**如果只有一張照片可以直接裁剪 否則*/ if (photos.size() == 1) { for (String str : photos) { tempFile = getFile(); copyFile(str, tempFile.getAbsolutePath()); startPhotoZoom(Uri.fromFile(tempFile), defaultCutSize); // 裁剪 } } else { for (String str : photos) { willCutOfFileQueue.add(new File(str)); } tempFile = getFile(); copyFile(willCutOfFileQueue.get(0).getAbsolutePath(), tempFile.getAbsolutePath()); startPhotoZoom(Uri.fromFile(tempFile), defaultCutSize); // 裁剪 willCutOfFileQueue.remove(0); } } } } } } catch (Exception e) { e.printStackTrace(); } return; } } catch (Exception e) { e.printStackTrace(); } } /** * 如果多圖處理裁剪隊列 */ private void flushCutPhotos() { if (willCutOfFileQueue != null && !willCutOfFileQueue.isEmpty()) { tempFile = getFile(); copyFile(willCutOfFileQueue.get(0).getAbsolutePath(), tempFile.getAbsolutePath()); willCutOfFileQueue.remove(0); startPhotoZoom(Uri.fromFile(tempFile), defaultCutSize); // 裁剪 } } /** * 文件復制 */ private void copyFile(String oldPath, String newPath) { try { int bytesum = 0; int byteread = 0; File oldfile = new File(oldPath); if (oldfile.exists()) { //文件存在時 InputStream inStream = new FileInputStream(oldPath); //讀入原文件 FileOutputStream fs = new FileOutputStream(newPath); byte[] buffer = new byte[1444]; int length; while ((byteread = inStream.read(buffer)) != -1) { bytesum += byteread; //字節數 文件大小 System.out.println(bytesum); fs.write(buffer, 0, byteread); } inStream.close(); } } catch (Exception e) { e.printStackTrace(); } } /** * 對當前已選圖片進行壓縮 */ public void doProcessedPhotos(final OnProcessedPhotos on) { if (getSelectsPhotos() != null && !getSelectsPhotos().isEmpty()) { new Thread(new Runnable() { @Override public void run() { for (Iterator it = getSelectsPhotos().iterator(); it.hasNext(); ) { try { File file = it.next(); if (file.length() > needProcessFileLength) { final Bitmap bm = PictureUtil.getSmallBitmap(file.getAbsolutePath(), 720, 1200); try { FileOutputStream fos = new FileOutputStream(file); bm.compress(Bitmap.CompressFormat.JPEG, 95, fos); } catch (FileNotFoundException e) { e.printStackTrace(); } } } catch (Exception e) { e.printStackTrace(); } } handler.post(new Runnable() { @Override public void run() { on.onProcessed(getSelectsPhotos()); } }); } }).start(); } } /** * 圖片選擇完成並回調 */ private void finish(final File file) { finish(createFiles(file)); } /** * 圖片選擇完成並回調 */ private void finish(final List files) { new Thread(new Runnable() { @Override public void run() { for (Iterator it = files.iterator(); it.hasNext(); ) { File file = it.next(); if (!file.exists() || file.length() == 0) { it.remove(); } else { changFile(file.getPath()); } } handler.post(new Runnable() { @Override public void run() { selectsPhotos.addAll(files); if (!files.isEmpty()) { if (onPhotoPickFinsh != null) onPhotoPickFinsh.onPhotoPick(files); } tempFile = null; flushCutPhotos(); } }); } }).start(); } /** * 三星手機將橫向圖片轉換為豎向 */ public void changFile(String file) { BitmapFactory.Options options = new BitmapFactory.Options(); /** * 最關鍵在此,把options.inJustDecodeBounds = true; * 這裡再decodeFile(),返回的bitmap為空,但此時調用options.outHeight時,已經包含了圖片的高了 */ options.inJustDecodeBounds = true; BitmapFactory.decodeFile(file, options); int width = options.outWidth; int height = options.outHeight; if (width > height) { Bitmap bit = bitmapFromFile(file, width, height); bit = adjustPhotoRotation(bit, 1); try { bit.compress(Bitmap.CompressFormat.PNG,100,new FileOutputStream(file)); } catch (FileNotFoundException e) { e.printStackTrace(); } } } /** * 旋轉圖片 */ public Bitmap adjustPhotoRotation(Bitmap bm, int count) { int orientationDegree = 90; Matrix m = new Matrix(); for (int i = 0; i < count; i++) { m.setRotate(orientationDegree, bm.getWidth(), bm.getHeight()); float targetX, targetY; if (orientationDegree == 90) { targetX = bm.getHeight(); targetY = 0; } else { targetX = bm.getHeight(); targetY = bm.getWidth(); } final float[] values = new float[9]; m.getValues(values); float x1 = values[Matrix.MTRANS_X]; float y1 = values[Matrix.MTRANS_Y]; m.postTranslate(targetX - x1, targetY - y1); } Bitmap bm1 = Bitmap.createBitmap(bm.getHeight(), bm.getWidth(), Bitmap.Config.ARGB_8888); Paint paint = new Paint(); Canvas canvas = new Canvas(bm1); canvas.drawBitmap(bm, m, paint); // ?????bitmap???? bm.recycle(); return bm1; } private List createFiles(File file) { List list = new ArrayList<>(); list.add(file); return list; } /** * 根據Uri獲得File文件路徑 */ public String getRealPathFromURI(Uri contentUri) { String res = null; String[] proj = {MediaStore.Images.Media.DATA}; Cursor cursor = activity.getContentResolver().query(contentUri, proj, null, null, null); if (cursor.moveToFirst()) { ; int column_index = cursor .getColumnIndexOrThrow(MediaStore.Images.Media.DATA); res = cursor.getString(column_index); } cursor.close(); return res; } /** * 啟動裁剪 */ private void startPhotoZoom(Uri uri, int size) { Log.d("test", uri.toString()); Intent intent = new Intent("com.android.camera.action.CROP"); intent.setDataAndType(uri, "image/*"); // crop為true是設置在開啟的intent中設置顯示的view可以剪裁 intent.putExtra("crop", "true"); // aspectX aspectY 是寬高的比例 intent.putExtra("aspectX", 1); intent.putExtra("aspectY", 1); // outputX,outputY 是剪裁圖片的寬高 intent.putExtra("outputX", size); intent.putExtra("outputY", size); intent.putExtra("return-data", true); intent.putExtra("noFaceDetection", true); activity.startActivityForResult(intent, PHOTO_REQUEST_CUT); } // 將進行剪裁後的圖片顯示到UI界面上 private void setCutPicToView(Intent picdata) { Bundle bundle = picdata.getExtras(); if (bundle != null) { Bitmap photo = bundle.getParcelable("data"); if (photo != null) { FileOutputStream fOut = null; try { fOut = new FileOutputStream(tempFile); } catch (FileNotFoundException e) { e.printStackTrace(); } photo.compress(Bitmap.CompressFormat.JPEG, 100, fOut); } finish(tempFile); } } /** * 獲取一個指定大小的bitmap * * @param reqWidth 目標寬度 * @param reqHeight 目標高度 */ public Bitmap bitmapFromFile(String pathName, int reqWidth, int reqHeight) { if (reqHeight == 0 || reqWidth == 0) { return BitmapFactory.decodeFile(pathName); } else { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFile(pathName, options); options = calculateInSampleSize(options, reqWidth, reqHeight); return BitmapFactory.decodeFile(pathName, options); } } /** * 圖片壓縮處理(使用Options的方法) * * * 說明 使用方法: * 首先你要將Options的inJustDecodeBounds屬性設置為true,BitmapFactory.decode一次圖片 。 * 然後將Options連同期望的寬度和高度一起傳遞到到本方法中。 * 之後再使用本方法的返回值做參數調用BitmapFactory.decode創建圖片。 *
* * 說明 BitmapFactory創建bitmap會嘗試為已經構建的bitmap分配內存 * ,這時就會很容易導致OOM出現。為此每一種創建方法都提供了一個可選的Options參數 * ,將這個參數的inJustDecodeBounds屬性設置為true就可以讓解析方法禁止為bitmap分配內存 * ,返回值也不再是一個Bitmap對象, 而是null。雖然Bitmap是null了,但是Options的outWidth、 * outHeight和outMimeType屬性都會被賦值。 * * @param reqWidth 目標寬度,這裡的寬高只是閥值,實際顯示的圖片將小於等於這個值 * @param reqHeight 目標高度,這裡的寬高只是閥值,實際顯示的圖片將小於等於這個值 */ public BitmapFactory.Options calculateInSampleSize( final BitmapFactory.Options options, final int reqWidth, final int reqHeight) { // 源圖片的高度和寬度 final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { // 計算出實際寬高和目標寬高的比率 final int heightRatio = Math.round((float) height / (float) reqHeight); final int widthRatio = Math.round((float) width / (float) reqWidth); // 選擇寬和高中最小的比率作為inSampleSize的值,這樣可以保證最終圖片的寬和高 // 一定都會大於等於目標的寬和高。 inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio; } // 設置壓縮比例 options.inSampleSize = inSampleSize; options.inJustDecodeBounds = false; return options; } public Activity getActivity() { return activity; } public PhotoPickManger setActivity(Activity activity) { this.activity = activity; return this; } public boolean isCut() { return isCut; } public PhotoPickManger setIsCut(boolean isCut) { this.isCut = isCut; return this; } public boolean isOptimize() { return isOptimize; } public PhotoPickManger setIsOptimize(boolean isOptimize) { this.isOptimize = isOptimize; return this; } public int getReturnFileCount() { return returnFileCount; } public PhotoPickManger setReturnFileCount(int returnFileCount) { this.returnFileCount = returnFileCount; return this; } public OnPhotoPickFinsh getOnPhotoPickFinsh() { return onPhotoPickFinsh; } public PhotoPickManger setOnPhotoPickFinsh(OnPhotoPickFinsh onPhotoPickFinsh) { this.onPhotoPickFinsh = onPhotoPickFinsh; return this; } public boolean isDebugToast() { return isDebugToast; } public PhotoPickManger setDebugToast(boolean isDebugToast) { this.isDebugToast = isDebugToast; return this; } public void setCut(boolean isCut) { this.isCut = isCut; } public Bundle getBundle() { return bundle; } public PhotoPickManger setBundle(Bundle bundle) { this.bundle = bundle; return this; } public File getTempFile() { return tempFile; } public ArrayList
getSelectsPhotos() { return selectsPhotos; } public int getNeedProcessFileLength() { return needProcessFileLength; } public PhotoPickManger setNeedProcessFileLength(int needProcessFileLength) { this.needProcessFileLength = needProcessFileLength; return this; } public static String getCurrentPickMangerName() { return currentPickMangerName; } public static void setCurrentPickMangerName(String currentPickMangerName) { PhotoPickManger.currentPickMangerName = currentPickMangerName; } public String getCacheFilePath() { return cacheFilePath; } public PhotoPickManger setCacheFilePath(String cacheFilePath) { this.cacheFilePath = cacheFilePath; return this; } public String getName() { return name; } public PhotoPickManger setName(String name) { this.name = name; return this; } public PhotoPickManger setTempFile(File tempFile) { this.tempFile = tempFile; return this; } }
第二十一章、裝飾模式 裝飾模式也稱為包裝模式,是結構型設計模式之一。裝飾模式是一種用於替代繼承技術的一種方案。1.定義動態的給一個對象添加一些額外的職責。就增加功能來說,
方法1:使用內部APIs該方法和其他所有內部沒有向外正式公布的APIs一樣存在它自己的風險。原理是通過獲得WindowManager的一個實例來訪問injectKeyEv
原文地址:http://android.xsoftlab.net/training/graphics/opengl/draw.html在定義了圖形之後,你接下來需要做的就
一、技術准備今天我們來看一下如何修改Android中編譯時的資源Id的值,在講解這內容之前,我們需要先了解一下Android中的資源編譯之後的結構和編譯過程,這裡就不多說