學習筆記之Android調用系統相冊選擇圖片

前言

在相冊裏選擇圖片上傳也是很常見的功能了例如微信朋友圈等等。但是他們是自定義的選擇器,可以選擇多張圖片並修改。這裏我們講一個最簡單的:調用系統的相冊選擇一張圖片並展示。另外有的讀者還想到要通過相機拍照來選擇圖片的功能,也可以參考一下我的另一篇文章學習筆記之Android使用系統相機進行拍照

使用步驟

這裏我是通過一個簡單的demo來講解怎麼去實現這個功能。首先看佈局:

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp"
        android:layout_marginEnd="52dp"
        android:layout_marginRight="52dp"
        android:text="choose"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="29dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/button"
        app:srcCompat="@mipmap/ic_launcher_round" />

很簡單,就是一個按鈕和一個imageView。然後接下來讓我們想想這個功能怎麼去實現:

首先打開相冊,那麼肯定要通過隱式啓動相冊activity;然後相冊返回一個路徑,我們就拿這個路徑把路徑上對應的照片展示出來。思路挺簡單的,讓我們寫寫看:
首先看代碼:

	private Uri imageUri;
    private ImageView imageView;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        imageView = findViewById(R.id.imageView);
        Button button1 = findViewById(R.id.button2);
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
            //動態申請權限
                if (ContextCompat.checkSelfPermission(MainActivity.this,Manifest.permission
                        .WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED){
                    ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1);
                }else{
                //執行啓動相冊的方法
                    openAlbum();
                }
            }
        });
     }
//獲取權限的結果
@Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == 1){
            if (grantResults.length>0&&grantResults[0] == PackageManager.PERMISSION_GRANTED) openAlbum();
            else Toast.makeText(MainActivity.this,"你拒絕了",Toast.LENGTH_SHORT).show();
        }
    }

//啓動相冊的方法
private void openAlbum(){
        Intent intent = new Intent("android.intent.action.GET_CONTENT");
        intent.setType("image/*");
        startActivityForResult(intent,2);
    }

這裏先初始化控件,然後動態申請權限,因爲我們要讀取照片肯定是要讀取內存的權限,記得在AndroidManifest中要寫明權限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
獲取權限後就打開相冊選擇。相冊對應的action是android.intent.action.GET_CONTENT,setType(“image/*”)這個方法表示把所有照片顯示出來,然後開啓活動。啓動活動選擇完照片後就會返回一個intent到onActivityResult方法中,所以接下來的主要工作就是如果獲取到返回的路徑。

我們知道在安卓4.4以後是不能把文件的真實路徑直接給別的應用的,所以返回的uri是經過封裝的,所以我們要進行解析取出裏面的路徑。所以這裏我們要進行判斷安卓版本來進行不同的邏輯,先看代碼:

@Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    if (requestCode == 2){
    //判斷安卓版本
 			if (resultCode == RESULT_OK&&data!=null){
                if (Build.VERSION.SDK_INT>=19)
                handImage(data);
                else handImageLow(data);
            }
        }
    }

//安卓版本大於4.4的處理方法
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
    private void handImage(Intent data){
        String path =null;
        Uri uri = data.getData();
        //根據不同的uri進行不同的解析
        if (DocumentsContract.isDocumentUri(this,uri)){
            String docId = DocumentsContract.getDocumentId(uri);
            if ("com.android.providers.media.documents".equals(uri.getAuthority())){
                String id = docId.split(":")[1];
                String selection = MediaStore.Images.Media._ID+"="+id;
                path = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,selection);
            }else if("com.android.providers.downloads.documents".equals(uri.getAuthority())){
                Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"),Long.valueOf(docId));
                path = getImagePath(contentUri,null);
            }
        }else if ("content".equalsIgnoreCase(uri.getScheme())){
            path = getImagePath(uri,null);
        }else if ("file".equalsIgnoreCase(uri.getScheme())){
            path = uri.getPath();
        }
        //展示圖片
        displayImage(path);
    }


//安卓小於4.4的處理方法
private void handImageLow(Intent data){
        Uri uri = data.getData();
        String path = getImagePath(uri,null);
        displayImage(path);
    }

//content類型的uri獲取圖片路徑的方法
private String getImagePath(Uri uri,String selection) {
        String path = null;
        Cursor cursor = getContentResolver().query(uri,null,selection,null,null);
        if (cursor!=null){
            if (cursor.moveToFirst()){
                path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
            }
            cursor.close();
        }
        return path;
    }

//根據路徑展示圖片的方法
private void displayImage(String imagePath){
        if (imagePath != null){
            Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
            imageView.setImageBitmap(bitmap);
        }else{
            Toast.makeText(this,"fail to set image",Toast.LENGTH_SHORT).show();
        }
    }

上面的代碼很多但是不要慌,咱們一個一個來,不難理解的。首先我們知道不同的版本有兩個不同的方法來展示圖片,就是:handImage和handImageLow。content類型的uri通過getImagePath這個方法來獲取真實路徑,真實路徑通過displayImage這個方法就可以展示出來了。所以主要的工作就是怎麼拿到真實路徑。現在思路清晰了,讓我們一個個來看:

首先來看一下兩個工具方法:getImagePath和displayImage。

  • getImagePath學過內容提供器會知道這個就是通過內容提供器來獲取數據。通過這個uri以及selection獲取到一個Cursor對象。Cursor是什麼呢?不瞭解的讀者可以查看這篇博客Android中的Cursor。然後通過這個Cursor對象的MediaStore.Images.Media.DATA這個參數就可以獲取到真實路徑了。
  • displayImage這個方法收一個真實路徑字符串,直接通過BitmapFactory.decodeFile這個方法獲取到Bitmap再顯示出來就行了

瞭解了工具方法後,我們的目的就很明確啦:content類型的uri或者真實路徑的String。
首先是版本低於4.4的,因爲返回的是真實的uri,也就是content開頭的那個,所以直接通過getImagePath獲取真實路徑再通過displayImage展示即可。

接下來這個可能看起來有點頭疼,因爲要解析不同類型的Uri。我們一個個來看:

  • 第一種是document類型的uri。至於什麼是document類型的uri這裏就不深入了,只要知道有這種類型的uri,要怎麼處理就好了。首先我們要獲取一個DocumentId,然後再分兩種情況處理:
    第一種的是media格式的,然後我們要取出後半截字符串我們才能獲取到真正的id,這裏就真正的id指的是對應數據庫表中的id,用於selection的。MediaStore.Images.Media.EXTERNAL_CONTENT_URI就是這個照片的content類型uri,再把selection放進去即可。
    第二種通過ContentUris.withAppendedId這個方法即可獲取到content類型的uri,這個方法負責把id和contentUri連接成一個新的Uri。這個方法在這裏也不詳細講解。

  • 第二種的是content類型的,那不用說直接用就行了

  • 第三種的是file類型的,這個就是真實路徑了,直接getPath就可以獲取到了。

好了,到此我們的所有疑問也就解決了。

小結

看完之後是不是發現思路很簡單但是實現起來很多的知識盲區呢?確實是這樣。但是當我們把這些細節都解決了之後我們就會學到很多的東西,相當於以點帶面。文中還有好多沒有詳解的:
ContentUris,BitmapFactory,Cursor,DocumentsContract等等。因爲這是另外一塊比較大的內容,如果要講的話將會涉及到很多內容就很容易偏離我們的主題了,所以只要知道大概是什麼就可以了。
·
·
·

參考資料

《第一行代碼》郭霖

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章