Android 頭像設(shè)置 圖片上傳 基本解決方案

頭像設(shè)置是Android 開發(fā)中比較常見的需求戳粒,基本內(nèi)容有:

  • 通過相機(jī)拍取一張圖片,或者從圖庫(kù)、文件中選擇一張圖片
  • 對(duì)圖片進(jìn)行裁剪
  • 上傳圖片祖很,監(jiān)聽上傳進(jìn)度

通過相機(jī)拍取圖片

調(diào)用相機(jī)拍照需要兩個(gè)步驟:

  1. 應(yīng)用內(nèi)生成一個(gè)空文件愿险,使用FileProvider獲取文件的Uri颇蜡,照片最后會(huì)保存在這個(gè)文件里价说。一定要確保這個(gè)文件所在目錄在FileProvider的共享目錄下
  • 使用Intent啟動(dòng)相機(jī),并將文件Uri傳入(相當(dāng)于告訴相機(jī)將照片保存到這個(gè)文件里)

創(chuàng)建FileProvider
AndroidManifest.xml中添加provider风秤,注意android:authorities屬性:

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.xxx.xxx.fileProvider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths"/>
</provider>

/res/xml/目錄下新建file_paths.xml文件鳖目,定義FileProvider共享文件的目錄:

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path
        name="download"
        path="Download"/>
    <external-files-path
        name="my_images"
        path="Pictures/"/>
</paths>

具體使用方式參見API FileProvider

創(chuàng)建文件,注意文件一定要在FileProvider共享文件的目錄中缤弦,否則會(huì)報(bào)錯(cuò):

public static File createImageFile(Context context) throws IOException {
    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
    String imageFileName = "IMG_" + timeStamp;
    File dir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
    return File.createTempFile(imageFileName, ".jpg", dir);
}

生成Uri领迈,AUTHORITYAndroidManifest.xmlandroid:authorities屬性:

File imageFile = FileUtil.createImageFile(this);
Uri imageUri = FileProvider.getUriForFile(getContext(), AUTHORITY, imageFile);

使用Intent啟動(dòng)相機(jī)拍照并傳入生成的Uri

Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
    startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
}

這樣當(dāng)拍照成功后照片就存儲(chǔ)在我們創(chuàng)建的文件中了


從圖庫(kù)或文件選擇圖片

從圖庫(kù)選擇文件:

Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        startActivityForResult(intent, REQUEST_IMAGE_PICK);

從圖庫(kù)或文件選擇圖片:

Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
startActivityForResult(intent, REQUEST_IMAGE_PICK);

獲取結(jié)果:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    switch (requestCode) {
        case REQUEST_IMAGE_PICK:
        if (resultCode == RESULT_OK) {
            Uri uri = data.getData();
            if (uri != null) {
                // 獲得了圖片
            } else {
                // TODO 處理異常
            }
        }
        break;
    }
}

對(duì)圖片進(jìn)行裁剪

由于系統(tǒng)提供了圖片裁剪的功能,這樣就減小了開發(fā)者自己實(shí)現(xiàn)圖片裁剪的難度碍沐,這里使用系統(tǒng)提供的圖片裁剪功能狸捅。和拍照類似,為了獲得裁剪后的圖片累提,我們也需要?jiǎng)?chuàng)建一個(gè)空文件尘喝,并將其傳入Intent

void startCropImage(Uri uri) {
    Intent intent = new Intent("com.android.camera.action.CROP");
    intent.setDataAndType(uri, "image/*");
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
    // 使圖片處于可裁剪狀態(tài)
    intent.putExtra("crop", "true");
    // 裁剪框的比例(根據(jù)需要顯示的圖片比例進(jìn)行設(shè)置)
    intent.putExtra("aspectX", 1);
    intent.putExtra("aspectY", 1);
    // 讓裁剪框支持縮放
    intent.putExtra("scale", true);
    // 裁剪后圖片的大小(注意和上面的裁剪比例保持一致)
    intent.putExtra("outputX", 300);
    intent.putExtra("outputY", 300);
    // 傳遞原圖路徑
    try {
        cropFile = FileUtil.getCacheImageFile(this);
    } catch (IOException e) {
        e.printStackTrace();
        // 處理錯(cuò)誤
        showDialog("打開文件失敗");
        return;
    }
    cropImageUri = FileProvider.getUriForFile(getContext(), AUTHORITY, cropFile);
    intent.putExtra(MediaStore.EXTRA_OUTPUT, cropImageUri);
    // 設(shè)置裁剪區(qū)域的形狀斋陪,默認(rèn)為矩形瞧省,也可設(shè)置為原形
    intent.putExtra("circleCrop", false);
    // 設(shè)置圖片的輸出格式
    intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
    // return-data=true傳遞的為縮略圖,小米手機(jī)默認(rèn)傳遞大圖鳍贾,所以會(huì)導(dǎo)致onActivityResult調(diào)用失敗
    intent.putExtra("return-data", false);
    // 是否需要人臉識(shí)別
    intent.putExtra("noFaceDetection", true);
    List<ResolveInfo> resInfoList = getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
    for (ResolveInfo resolveInfo : resInfoList) {
        String packageName = resolveInfo.activityInfo.packageName;
        grantUriPermission(packageName, cropImageUri,
                Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
    }
    if (resInfoList.size() > 0) {
        startActivityForResult(intent, REQUEST_IMAGE_CROP);
    }
}

好了鞍匾,現(xiàn)在來梳理下拍照、選圖片骑科、裁剪的流程:

  1. 通過Initent啟動(dòng)系統(tǒng)功能并傳入處理后圖片的保存位置(如果需要)
  • 通過onActivityResult獲取調(diào)用是否成功橡淑,并做進(jìn)一步處理

注意:

  1. 因?yàn)橄到y(tǒng)權(quán)限的限制,可能需要對(duì)目標(biāo)程序加入對(duì)應(yīng)的讀寫權(quán)限
  • 選擇圖片返回的Uri直接做為圖片裁剪的源文件可能導(dǎo)致出錯(cuò)咆爽,這個(gè)時(shí)候需要將原來的Uri復(fù)制到我們自己生成的文件中梁棠,再啟動(dòng)圖片裁剪

圖片上傳并顯示進(jìn)度

圖片上傳本質(zhì)是文件上傳,這里使用Retrofit

添加進(jìn)度回調(diào)接口:

public interface ProgressRequestListener {
    void onRequestProgress(long bytesSend, long contentLength, boolean done);
}

編寫ProgressRequestBody類實(shí)現(xiàn)插入進(jìn)度回調(diào):

public class ProgressRequestBody extends RequestBody {

    private final RequestBody requestBody;
    private final List<ProgressRequestListener> listeners;
    private BufferedSink bufferedSink;

    public ProgressRequestBody(RequestBody requestBody, List<ProgressRequestListener> listeners) {
        this.requestBody = requestBody;
        this.listeners = listeners;
    }

    @Override
    public MediaType contentType() {
        return requestBody.contentType();
    }

    @Override
    public long contentLength() throws IOException {
        return requestBody.contentLength();
    }

    @Override
    public void writeTo(BufferedSink sink) throws IOException {
        //包裝
        bufferedSink = Okio.buffer(sink(sink));
        //寫入
        requestBody.writeTo(bufferedSink);
        //必須調(diào)用flush斗埂,否則最后一部分?jǐn)?shù)據(jù)可能不會(huì)被寫入
        bufferedSink.flush();
    }

    private Sink sink(Sink sink) {
        return new ForwardingSink(sink) {
            //當(dāng)前寫入字節(jié)數(shù)
            long bytesWritten = 0L;
            //總字節(jié)長(zhǎng)度符糊,避免多次調(diào)用contentLength()方法
            long contentLength = 0L;

            @Override
            public void write(Buffer source, long byteCount) throws IOException {
                super.write(source, byteCount);
                if (contentLength == 0) {
                    //獲得contentLength的值,后續(xù)不再調(diào)用
                    contentLength = contentLength();
                }
                //增加當(dāng)前寫入的字節(jié)數(shù)
                bytesWritten += byteCount;
                //回調(diào)
                for (ProgressRequestListener listener : listeners) {
                    listener.onRequestProgress(bytesWritten, contentLength, bytesWritten == contentLength);
                }
            }
        };
    }
}

編寫服務(wù)器接口:

@Multipart
@POST("xxx")
Observable<FileBean> uploadAvatar(@Part MultipartBody.Part part);

服務(wù)器接口調(diào)用:

public Observable<FileBean> uploadAvatar(File imageFile) {
    RequestBody requestBody = RequestBody.create(MediaType.parse("multipart/form-data"), imageFile);
    ProgressRequestBody progressRequestBody = new ProgressRequestBody(requestBody, requestListeners);
    MultipartBody.Part body =
            MultipartBody.Part.createFormData("name", "filename", progressRequestBody);  // 根據(jù)需要填寫名字
    return serverApi.uploadAvatar(body);
}

好了呛凶,我們只需要把裁剪后的圖片上傳男娄,并添加ProgressRequestListener,頭像設(shè)置就搞定了~


參考文章

官方培訓(xùn)
Android 啟動(dòng)系統(tǒng)相機(jī)漾稀,相冊(cè)模闲,裁剪圖片及6.0權(quán)限管理
你需要知道的Android拍照適配方案
Android之圖片選擇與裁剪
Retrofit 2.0 超能實(shí)踐(三),輕松實(shí)現(xiàn)多文件/圖片上傳/Json字符串/表單
OkHttp與Retrofit上傳文件詳解
Retrofit Multipart多文件上傳

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末崭捍,一起剝皮案震驚了整個(gè)濱河市尸折,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌殷蛇,老刑警劉巖实夹,帶你破解...
    沈念sama閱讀 221,576評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件橄浓,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡亮航,警方通過查閱死者的電腦和手機(jī)荸实,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來塞赂,“玉大人泪勒,你說我怎么就攤上這事昼蛀⊙缁” “怎么了?”我有些...
    開封第一講書人閱讀 168,017評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵叼旋,是天一觀的道長(zhǎng)仇哆。 經(jīng)常有香客問我,道長(zhǎng)夫植,這世上最難降的妖魔是什么讹剔? 我笑而不...
    開封第一講書人閱讀 59,626評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮详民,結(jié)果婚禮上延欠,老公的妹妹穿的比我還像新娘。我一直安慰自己沈跨,他們只是感情好由捎,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著饿凛,像睡著了一般狞玛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上涧窒,一...
    開封第一講書人閱讀 52,255評(píng)論 1 308
  • 那天心肪,我揣著相機(jī)與錄音,去河邊找鬼纠吴。 笑死硬鞍,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的戴已。 我是一名探鬼主播膳凝,決...
    沈念sama閱讀 40,825評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼恭陡!你這毒婦竟也來了蹬音?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,729評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤休玩,失蹤者是張志新(化名)和其女友劉穎著淆,沒想到半個(gè)月后劫狠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,271評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡永部,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評(píng)論 3 340
  • 正文 我和宋清朗相戀三年独泞,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片苔埋。...
    茶點(diǎn)故事閱讀 40,498評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡懦砂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出组橄,到底是詐尸還是另有隱情荞膘,我是刑警寧澤,帶...
    沈念sama閱讀 36,183評(píng)論 5 350
  • 正文 年R本政府宣布玉工,位于F島的核電站羽资,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏遵班。R本人自食惡果不足惜屠升,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望狭郑。 院中可真熱鬧腹暖,春花似錦、人聲如沸翰萨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)缨历。三九已至以蕴,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間辛孵,已是汗流浹背丛肮。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留魄缚,地道東北人宝与。 一個(gè)月前我還...
    沈念sama閱讀 48,906評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像冶匹,于是被迫代替她去往敵國(guó)和親习劫。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容