前言:
現在很多app里面,都會有這么一個需求,就是上傳圖片的按鈕,當然按了這個按鈕之后,就會出現二種選擇: 1. 直接拍照,2. 相冊選擇現有圖片。
因為現在的app這塊功能會有二個大的情況:
- 全部原生的 app 來實現。
- HyBrid 的 app 來實現。
本文先討論HyBrid的app的實現情況,下次再討論原生,不過其實大部分實現都是相似的。
其實這種在WebView配合下實現這類功能的文章很多很多,但是大多數都是上傳一大段代碼,然后讓大家自己看,千篇一律,所以本文主要是寫的完整的思路。
正文:
我們知道用戶會在網頁上點擊了某個按鈕,然后調用起安卓方面的相關操作。然后實現完整的功能。
1. 網頁端:
其實網頁端很簡單,只需要實現一個簡單的<input>標簽即可。
總體思路是一個<input>
標簽和一個<img>
標簽重疊在一起(<input>
在上,<img>
在下,類似可以理解<img>
作為背景),當選完照片后,最后把圖片賦值給<img>
標簽。
但是在給<img>
賦值的時候我遇到過不同的情況:
當在Android這邊拍照或者進入圖庫選完照片后,把圖片信息給了網頁端后,
<input>
標簽的onchange監聽到了圖片選擇好了,網頁端直接把圖片上傳到服務器并傳回來一個地址,顯示時把地址拼接成可以找到路徑的地址放在<img>
標簽中就可以了。配合FileReader,FileReader是作為文件API的重要成員用于讀取文件。可以參考: h5 實現調用系統拍照或者選擇照片并預覽
2. Android端:
2.1 WebChromeClient
因為Android端訪問網頁大部分使用的是WebView
,所以我們這里還是用WebView
來說明。
既然用戶在網頁上點擊了<input>
,我們肯定需要WebView
能監聽到,好比原生的Button
點擊我們要監聽也要寫一個OnclickListener
來實現監聽。我們這里使用的是WebChromeClient
。
public class ImgWebChromeClient extends WebChromeClient {
//.......
//.......
public ImgWebChromeClient(Activity activity) {
this.activity = activity;
}
}
我們實現我們的類,繼承WebChromeClient。然后我們就可以把這個我們自己定義的WebChromeClient設置給我們的WebView。
webView.setWebChromeClient(new ImgWebChromeClient(this));
我們可以看到我們在WebChromeClient在監聽<input>點擊事件的時候,還要根據不同的版本來區分,主要是以Android 5.0版本來進行大的劃分。
-
Android 5.0及以上版本:
-
Android 5.0以下版本:
都是openFileChooser方法,不同版本的里面參數不同。
所以我們可以看到主要是openFileChooser
和onShowFileChooser
方法。
// For Android < 3.0
public void openFileChooser(ValueCallback<Uri> valueCallback) {
***
}
// For Android >= 3.0
public void openFileChooser(ValueCallback valueCallback, String acceptType) {
***
}
//For Android >= 4.1
public void openFileChooser(ValueCallback<Uri> valueCallback,
String acceptType, String capture) {
***
}
// For Android >= 5.0
@Override
public boolean onShowFileChooser(WebView webView,
ValueCallback<Uri[]> filePathCallback,
WebChromeClient.FileChooserParams fileChooserParams) {
***
return true;
}
不管是什么版本,我們看到這幾個方法的參數里面都有ValueCallback
參數。
/**
* A callback interface used to provide values asynchronously.
*/
public interface ValueCallback<T> {
/**
* Invoked when the value is available.
* @param value The value.
*/
public void onReceiveValue(T value);
};
所以我們知道了,我們最后是調用openFileChooser
和onShowFileChooser
方法里面的ValueCallback
參數,調用它的onReceiveValue
方法把我們的選擇的圖片的Uri傳入,網頁端那邊就會收到信息了,就知道用戶到底選擇了什么圖片。
所以我們最終目標就是獲取選擇的圖片的Uri,然后給ValueCallback.onReceiveValue方法,這就是我們整個文章的最主要的流程。
2.2 獲取相關圖片的Uri
上面我們提到了,我們最終目標是獲取用戶選取的圖片Uri
,然后傳給ValueCallback
就可以。
所以我們這里就要講二大塊:
- 用戶怎么跳到自己想要的界面(相機 or 圖庫)
- 用戶在自己想要的界面選擇好了圖片后 (拍好了照片 or 在圖庫選擇好了圖片),如何獲取相關圖片的
Uri
。
根據這二點,我們一步步來分析。
2.2.1 相機 or 圖庫
我們肯定想到是用戶點擊了某個按鈕后,我們需要跳出一個彈框,然后上面有拍照和圖庫按鈕:
比如我使用系統自帶的選擇框(不同手機顯示的彈框不同):
所以我們這里知道了這個又要細分任務:
- 獲取相關權限
- 如何點擊按鈕后可以跳到相應界面(拍照 or 圖庫)。
- 如何創建彈框,把上面的按鈕顯示在上面
2.2.1.1 獲取相關權限
emmm......這塊我覺得應該不需要花更多的時間來說明了吧,主要就是:
- 檢查權限 (checkSelfPermission)
- 請求權限(requestPermissions)
- 回調事件處理(onRequestPermissionsResult)
而我們要申請的權限無非就是 Camera的權限,還有讀寫外部存儲的權限。
2.2.1.2 如何點擊按鈕后可以跳到相應界面(拍照 or 圖庫):
我們先來看拍照:
2.2.1.2.1 設置打開相機Intent的Action
我們知道打開某個界面,就是startActivity(Intent);平常比如跳到撥號界面等。只要對Intent設定相應的Action即可。
具體我們可以看谷歌的Android官方教程網頁即可:
我們可以看到有這些:
我們可以這個目錄中看到了相機,我們具體看相機的介紹:
注:當您使用 ACTION_IMAGE_CAPTURE拍攝照片時,相機可能還會在結果 Intent 中返回縮小尺寸的照片副本(縮略圖),這個副本以 Bitmap 形式保存在名為
data
的extra
字段中。
所以我們這里跳到拍照界面也是一樣,只要建立跳到相機界面的Intent即可:
Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
2.2.1.2.2 設置相機拍攝的照片的存儲位置
因為有些人需要在自己的APP中調用拍照的功能,存在自己指定的目錄下面,所以需要在startActivity啟動相機界面時候同時傳遞過去信息,告訴拍照了之后照片存的位置。
1.我們先指定我們的要存儲的照片的路徑Uri:
其實很簡單,設定我們接下去要拍的照片的完整存儲路徑,然后得到File
對象,再通過Uri.fromFile
方法再通過剛才我們的File
對象來獲得Uri
。
(當然如果這里你只需要打開系統相機,以下第二部分可以忽略)
2.獲取所有相機的Intent集合:
因為我們手機上面可能有很多個相機軟件,所以我們需要先找到能打開各自相機軟件的Intent,我們通過PackageManager.queryIntentActivities
的方式來進行符合拍照Action的Intent的軟件,然后得到它們具體的詳細信息,比如包名及對應的activity名字等,然后把相應的Intent變得更加詳細即可。
3.把uri賦值給Intent:
在上面貼出的Android 官方網頁上面的相機部分其實也提到過了如何設置存儲位置:
所以這里我們只需要找到相應的Intent,然后把我們的Uri位置賦值給Intent即可:
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
最后只需要startActivity去啟動我們這個指定了打開相機的特定Intent即可。
拍照說完了,我們再來看在圖庫界面選擇圖片:
其實總體思路和拍照是一模一樣,無非就是指定Intent是打開了圖庫的Intent。
還是在剛才的Android 官網我們可以看到:
因為我們是查看本地的圖片,所以我們要使用 ACTION_GET_CONTENT
,同時指定MIME
類型是圖片類型,如果要進行圖片多選,就再指定EXTRA_ALLOW_MULTIPLE
為true
。
同時也給出了實例代碼:
這里我要提一下,我們在設置Intent
的Action
的時候不只是可以使用ACTION_GET_CONTENT
,還可以使用ACTION_PICK
。
我們可以看到上面寫著可以用來選擇數據,然后返回被選中的選項。
但是在具體手機操作上有點不同(不知道不同的手機系統會不會結果不同,我只測了模擬器):
ps:最坑的是用ACTION_GET_CONTEN時候多選圖片要長按操作,一直以為沒成功,以為多選圖片功能沒實現,后來在 Android: Intent.EXTRA_ALLOW_MULTIPLE allows only single picking 上面找到別人的答案,說需要長按
2.2.2 出現選擇框讓用戶選擇
我們可以看到可以自定義彈框,比如我們設定固定的按鈕,然后再點擊特定按鈕后啟動我們的上面提過的特定的Intent即可。
這里我們講如果只是給定我們想要啟動的多個Intent的選項,讓系統幫我們彈出彈框及相關按鈕,關鍵字就是Intent.createChooser
方法
直接看圖片即可,寫的很詳細了,或者大家搜相關的關鍵字也是有很多文章的。比如:Android createChooser方法源碼簡析等。
2.2.3 獲取用戶在相機或者圖庫選擇的圖片Uri
因為我們不是單純的跳到了相機界面或者是圖庫界面就可以了,我們還需要獲取用戶在那些應用外的界面到底選了什么圖片,所以單純的
startActivity
肯定不夠,所以大家肯定想到了使用startActivityForResult
來啟動,這樣才能根據用戶不同的操作來進行相應的處理。
我們知道需要復寫onActivityResult
來處理,主要也就三個參數(int requestCode, int resultCode, Intent data)
。具體的內容圖片里面也寫的很清楚。
2.3 Uri 和 ValueCallback
所以我們ValueCallback實例在 WebChromeClient的方法里面拿到了,Uri也通過相機或者圖庫的選擇下獲取到了。最終調用把獲取的Uri 賦值給ValueCallback.onReceiveValue()即可。
PS: 取消這次網頁點擊選取圖片的請求,只需要調用onReceiveValue(null)即可。
結語:
emm.......大家輕噴即可。。。。