Android開發丶從相冊中選擇照片,調用攝像頭拍照以及錄製小視頻

在日常開發過程中,這三種功能是再常見不過了,網上相關的文檔也一大堆,不過未免讓人眼花撩亂,因此記錄做下整理!

(PS:目前相冊選擇照片和調用攝像頭拍照已經實現了壓縮,錄製小視頻暫未實現,哪位大佬有好的建議,請指教!)

實現步驟:

1.新建一個AndroidStudio項目。繪製MainActivity的xml文件,這裏爲了直觀,我們放上幾個按鈕和圖片就行。

main_activity.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/main_chooseAlbumBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:text="相冊選擇" />

    <Button
        android:id="@+id/main_takePhotoBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:text="拍照" />

    <Button
        android:id="@+id/main_takeVideoBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:text="拍攝小視頻" />

    <ImageView
        android:id="@+id/main_photoTv"
        android:layout_width="300dp"
        android:layout_height="200dp"
        android:layout_margin="10dp"
        android:scaleType="fitXY" />

    <ImageView
        android:id="@+id/main_videoIv"
        android:layout_width="300dp"
        android:layout_height="200dp"
        android:layout_margin="10dp"
        android:scaleType="fitXY" />
</LinearLayout>

2.打開清單文件,配置以下拍照,讀取存儲內存等權限。

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

3.回到MainActivity,設置控件的點擊事件。

/**
 * 配置組件
 */
private void initViews() {
    chooseAlbumBtn = findViewById(R.id.main_chooseAlbumBtn);
    takePhotoBtn = findViewById(R.id.main_takePhotoBtn);
    takeVideoBtn = findViewById(R.id.main_takeVideoBtn);
    photoTv = findViewById(R.id.main_photoTv);
    videoIv = findViewById(R.id.main_videoIv);

    chooseAlbumBtn.setOnClickListener(this);
    takePhotoBtn.setOnClickListener(this);
    takeVideoBtn.setOnClickListener(this);
}

4.下面開始設置從相冊選擇照片的邏輯。

/**
 * 打開相冊
 */
private void chooseAlbum() {
    Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
    startActivityForResult(intent, ALBUM_REQUEST_CODE);
}

這裏我們設置startActivityForResult用來獲取回調,requestCode設爲ALBUM_REQUEST_CODE。

此時就可以正常打開相冊選擇照片了。值得一提,因爲調用的是系統原生的,因此不同機型的界面也都不一樣。

選擇完照片就可以返回MainActivity了,這時我們在onActivityResult中獲取回調的照片。

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (resultCode == RESULT_OK) {
        switch (requestCode) {
            case ALBUM_REQUEST_CODE: //相冊選擇的回調
                Uri uri = data.getData(); //獲取系統返回的照片uri
                String[] strings = {MediaStore.Images.Media.DATA};
                Cursor cursor = getContentResolver().query(uri, strings, null, null, null);
                cursor.moveToFirst();
                int index = cursor.getColumnIndex(strings[0]);
                String path = cursor.getString(index); //獲取圖片路徑
                cursor.close();
                Log.d("fantasychong_path", path);
                break;
}

這下我們就可以獲取返回的圖片路徑path,運行起來看下結果:

此時我們需要把path轉換成bitmap,從而可以讓其顯示在界面上(或根據項目需求做相應處理)

Bitmap bitmap = BitmapFactory.decodeFile(path);
photoTv.setImageBitmap(bitmap);

運行起來。

哦豁,報錯了,看下日誌

顧名思義,是權限被拒絕了,Android在6.0起已經引入了動態權限獲取,一些諸如拍照讀取內存等敏感操作都要求先獲取動態權限才能進行下一步操作,知道了原因,處理起來就方便多了。

我們在操作從相冊選擇照片時,加入讀寫內存的動態權限處理。

case R.id.main_chooseAlbumBtn: //相冊選擇
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ||
            ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(this,
                new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
                        Manifest.permission.READ_EXTERNAL_STORAGE}, MY_PERMISSION_ALBUM);
    } else {
        chooseAlbum(); //打開相冊
    }
    break;

運行~

沒問題!

由於是從相冊直接選取的照片,對於部分照片可能出現90度旋轉的問題,我們除了做一下壓縮,還得做下旋轉的處理。

/**
 * 處理旋轉後的圖片
 *
 * @param originpath 原圖路徑
 * @param context    上下文
 * @return 返回修復完畢後的圖片路徑
 */
public static String amendRotatePhoto(String originpath, Context context) {
    // 取得圖片旋轉角度
    int angle = readPictureDegree(originpath);
    // 把原圖壓縮後得到Bitmap對象
    Bitmap bmp = getCompressPhoto(originpath);
    // 修復圖片被旋轉的角度
    Bitmap bitmap = rotaingImageView(angle, bmp);
    // 保存修復後的圖片並返回保存後的圖片路徑
    return savePhotoToSD(bitmap, context);
}
/**
 * 把原圖按1/10的比例壓縮
 *
 * @param path 原圖的路徑
 * @return 壓縮後的圖片
 */
public static Bitmap getCompressPhoto(String path) {
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = false;
    options.inSampleSize = 4;  // 圖片的大小設置爲原來的十分之一
    Bitmap bmp = BitmapFactory.decodeFile(path, options);
    options = null;
    return bmp;
}

在onActivityResult中處理圖片。

Bitmap bitmap = BitmapFactory.decodeFile(PhotoBitmapUtils.amendRotatePhoto(path, MainActivity.this));
photoTv.setImageBitmap(bitmap);

5.從相冊選取沒問題了,接下來設置調用攝像頭拍照。

先獲取相機、讀寫內存的動態權限,調用方法

case R.id.main_takePhotoBtn: //攝像頭拍照
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ||
            ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ||
            ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(this,
                new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
                        Manifest.permission.READ_EXTERNAL_STORAGE,
                        Manifest.permission.CAMERA}, MY_PERMISSION_CAMERA);
    } else {
        takeCamera(); //打開相機拍照
    }
    break;

打開相機拍照代碼如下:

/**
 * 打開相機拍照
 */
private void takeCamera() {
    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    File file = new File(Environment.getExternalStorageDirectory(), "PhotoVideoTest");
    if (!file.exists()) {
        file.mkdir();
    }
    File photoFile = new File(file, PhotoBitmapUtils.getImageFileName());
    photoPath = photoFile.getAbsolutePath();
    Uri uri = FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", photoFile);
    intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
    startActivityForResult(intent, CAMERA_REQUEST_CODE);
}

在onActivityResult中獲取回調,依舊做壓縮旋轉處理。

case CAMERA_REQUEST_CODE: //攝像頭拍照的回調
    Bitmap bitmap1 = BitmapFactory.decodeFile(PhotoBitmapUtils.amendRotatePhoto(photoPath, MainActivity.this));
    photoTv.setImageBitmap(bitmap1);
    break;

跑起來~

打開攝像頭時報錯了,看日誌

意爲我們需要在清單文件中聲明一個provider

<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="com.fantasychong.photovideotest0107.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">

    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/provider_paths" />
</provider>

在res目錄下新建一個xml文件夾,新建一個provider_paths文件。

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path
        name="external_storage_directory"
        path="." />
    <external-path
        name="cache"
        path="Android/data/com.fantasychong.photovideotest1227/cache" />
</paths>

跑起來,沒問題了!(虛擬機有點卡。。。)

完美!

6.最後,我們來實現拍攝小視頻

case R.id.main_takeVideoBtn: //攝像頭錄像
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ||
            ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ||
            ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(this,
                new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
                        Manifest.permission.READ_EXTERNAL_STORAGE,
                        Manifest.permission.CAMERA}, MY_PERMISSION_CAMERA);
    } else {
        takeVideo(); //打開相機錄像
    }
    break;
/**
 * 打開相機錄像
 */
private void takeVideo() {
    Intent intent= new Intent();
    //啓動相機
    intent.setAction(MediaStore.ACTION_VIDEO_CAPTURE);
    intent.addCategory(Intent.CATEGORY_DEFAULT);
    //創建文件
    createVideoFile();
    //添加權限
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.M){
        videoUri= FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", file);
    }else {
        videoUri= Uri.fromFile(file);
    }
    intent.putExtra(MediaStore.EXTRA_OUTPUT, videoUri);
    startActivityForResult(intent, VIDEO_RESULT_CODE);
}
/**
 * 創建文件
 */
private void createVideoFile() {
    //設置圖片文件名,以當前時間的毫秒值爲名稱
    String videoName= Calendar.getInstance().getTimeInMillis()+ ".mp4";
    //創建圖片文件
    file = new File(Environment.getExternalStorageDirectory()
            + "/" + getPackageName() + "/", videoName);
    //將圖片的絕對路徑設置給mImagePath,之後會用到
    videoPath= file.getAbsolutePath();
    //按設置好的目錄層級創建
    file.getParentFile().mkdir();
    file.setWritable(true);
}

最後在onActivityResult處理回調即可。

case VIDEO_RESULT_CODE: //攝像頭錄像的回調
    Log.d("fantasychong_video", videoPath);
    break;

跑起來,打印日誌,成功!

 

至此全部完成,demo附上!

資源下載

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