這篇文章就整理下移動端長按識別二維碼的實現吧!實現方式可以分爲三種
一、長按原生控件,直接獲取控件中的圖片數據(src或background)
第一二種好像沒多少可以說的,但還是按照順序來吧!首先先說下使用的庫,ios使用原生二維碼識別庫(好像是ios7之後纔有的),然後說是WKWebView比UIWebView優化了很多 東西,也解決了內存泄漏的問題那麼js交互的部分我們就用WKWebView吧(說到這裏必須吐槽下android的webView內存泄漏問題,一個字坑)。android沒原生的,我瞭解的比較大衆的就zxing和zbar,經過測試發現在二維碼佔圖片的比例較小識別時zbar的識別比zxing好一些,而且zxing使用截圖的方案實現時當二維碼放在屏幕的底部時識別不出來,所以這裏就直接只貼zbar的代碼吧!
然後呢因爲是寫的demo,代碼是沒優化過的,怎麼方便怎麼來,實際使用還是得根據自己的需要優化一下,個人覺得重要的是實現方案和思路。
一、長按原生控件,直接獲取控件中的圖片數據(src或background)
這裏基本上等於在介紹,二維碼識別的使用了。
(1)android
獲取圖片android就比較簡單了。長按事件就不說了,圖片通常會用ImageView,直接獲取src就行了,特殊點的放background,那麼就獲取background就好了。直接貼代碼吧!
//src
Bitmap mBitmap=((BitmapDrawable) imageView.getDrawable()).getBitmap();
//background
mBitmap=((BitmapDrawable) imageView.getBackground()).getBitmap();
下面就是關鍵zbar 識別圖片中二維碼的代碼了
-----------------------------------------------
public String parseRQ(Bitmap bitmap) {
String text = null;
ImageScanner scanner=new ImageScanner();
scanner.setConfig(0, Config.X_DENSITY,3);
scanner.setConfig(0, Config.Y_DENSITY, 3);
//設置掃描的圖片
Image barcode = new Image(bitmap.getWidth(), bitmap.getHeight(), "Y800");
//設置掃描的圖片的區域,因爲我們不知道二維碼在哪,所以直接設置整張圖片
barcode.setCrop(0, 0, bitmap.getWidth(), bitmap.getHeight());
int[] data = new int[bitmap.getWidth() * bitmap.getHeight()];
byte[] bitmapPixels =
new byte[bitmap.getWidth() * bitmap.getHeight()];
bitmap.getPixels(data, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
for (int i = 0; i < data.length; i++) {
bitmapPixels[i] = (byte) data[i];
}
barcode.setData(bitmapPixels);
//識別圖片中的二維碼,result是二維碼的個數(這點比zxing好,zxing只獲取從左上開始找到的第一個,不過也有可能是我調用的api不對也不一定)
int result = scanner.scanImage(barcode);
if (result != 0)
{
SymbolSet syms = scanner.getResults();
for (Symbol sym : syms)
{
text=sym.getData().trim();
//我們只獲取第一個非空二維碼,習慣性判空,沒測過幾個空字符串可不可以生成二維碼
if(!text.isEmpty())
{
break;
}
}
}
return text;
}
--------------------------------------------------
拿到解碼後的數據,就可以根據需求取實現功能了。
(2)ios
ios獲取UIImageView的圖片更容易直接就是imageView.image就可以了,原生識別二維碼的操作也簡單,個人覺得設置長按事件比這兩個加起來都麻煩點。所以這裏主要就是設置長按事件的代碼了。貼碼。
//創建長按,imageLongClick即爲長按響應的函數
UILongPressGestureRecognizer *longClick=[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(imageLongClick:)];
//觸摸點數,即多少手指點擊
longClick.numberOfTouchesRequired=1;
//開啓觸發事件處理
imageView.userInteractionEnabled=YES;
//imageView添加長按事件
[imageView addGestureRecognizer:longClick];
ios原生識別二維碼,比zxing和zbar都簡單多了,當然這沒做優化策略的,android zbar那那個代碼也一樣
----------------------------------------------
-(void)imageLongClick:(UILongPressGestureRecognizer *)sender{
//按下時
if ([sender state]==UIGestureRecognizerStateBegan) {
NSLog(@"image long click....");
//創建識別器
CIDetector *detector=[CIDetector detectorOfType:CIDetectorTypeQRCode context:nil options:nil];
//image轉爲CGImage進行識別,結果爲所有二維碼結果的對象數組
NSArray *results=[detector featuresInImage:[CIImage imageWithCGImage:[self snapshotView].CGImage]];
if (results.count>0) {
//這裏只拿第一個
CIQRCodeFeature *feature=[results firstObject];
//feature.messageString即爲解碼後的字符串,這裏直接打開瀏覽器
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:feature.messageString]];
}else{
NSLog(@"找不到二維碼");
}
}
}
--------------------------------------------
在此直接獲取控件的圖片直接識別的就這樣結束了,如果要獲取相冊的也一樣,只需將從相冊獲取到的圖片轉爲對應的Bitmap(ios UIImage),其他的都不變就可。
這裏需注意的是背景色如果是透明色是無法識別出來的,所以如果二維碼的來源是自己app的這種方式就很好了,如果是用戶上傳的建議用第二種方式,不可保證不會有哪個坑上傳個透明背景的圖片或上傳個長圖。
二、長按原生控件,截圖識別
長按事件和識別二維碼的代碼是一樣的,就不重複,即獲取到截屏的圖片後調用識別的方法就可以了,所以這裏就只剩截屏功能的代碼了,一樣直接上代碼
(1)android
-----------------------------------------------------
//其實這裏直接傳個View進來也是可以的,比如第三種的長按網頁的就可以將WebView傳就來就可以了
public Bitmap snapshotView(Window window) {
if (window != null) {
//找到當前頁面的根佈局
View view = window.getDecorView().getRootView();
//獲取當前屏幕的大小
int width = view.getWidth();
int height = view.getHeight();
//設置緩存
view.setDrawingCacheEnabled(true);
view.buildDrawingCache();
/*1、從緩存中獲取當前屏幕的圖片,創建一個DrawingCache的拷貝,因爲DrawingCache得到的位圖在禁用後會被回收
*2、這裏的88是去掉無用的部分即你確定是不會有二維碼的部分(當然不做任何操作也是可以的),這裏直接寫死是狀態欄的高度,
*實際真正使用不會這麼寫,而是是去獲取狀態欄的高度(這裏懶就不寫了),我記得如果直接是控件調用buildDrawingCache
*是該控件當前顯示在屏幕上的部分就不用減去狀態欄的高度了
*/
Bitmap temBitmap = Bitmap.createBitmap(view.getDrawingCache(), 0, 88, width, height - 88);
//禁用DrawingCahce否則會影響性能 ,而且不禁止會導致每次截圖到保存的是緩存的位圖
view.destroyDrawingCache();
view.setDrawingCacheEnabled(false);
return temBitmap;
}
return null;
}
---------------------------------------------------
(2)ios
---------------------------------------------
//跟android一樣這裏的UIWindow也可以改成UIView,函數接受UIView的參數,外部就可以直接調用截取指定控件顯示在屏幕的部分截圖了
- (UIImage *)snapshotView {
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
//這裏只獲取大小,所以bounds還是frame都是一樣的
CGRect rect = [keyWindow bounds];
if(UIGraphicsBeginImageContextWithOptions != NULL)
{
//iphone4之後採用Retina屏幕調用這個(不知道有沒有記反,也有其他的截圖方式,只是我就記得這種)
UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0.0);
} else {
UIGraphicsBeginImageContext(rect.size);
}
CGContextRef context = UIGraphicsGetCurrentContext();
[keyWindow.layer renderInContext:context];
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img;
}
-----------------------------------------------------------------------------------------------
三、長按web中的圖片,app識別其中的二維碼
這裏也是截圖實現,爲什麼不是拿原始圖片識別,1是上面說的有可能是長圖或背景透明,2是截圖免下載,在速度體驗上好點。我記得微信也是這樣實現的,在哪提過我忘了。
既然涉及js,那我們就必須先來段js呀,js長按圖片功能代碼(本來是想用jquery的,但想想網頁不一定是自己的,有可能是別人的靜態網頁,根本沒導入jquery庫,而直接 注入<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>的形式是會有跨域的問題的,所以不用jquery,直接寫好了,代碼量其實也差不多):
-----------------------------------------------------
//閉包,這裏個人當成跟java的匿名對象差不多記憶
(function () {
//獲取所有圖片標籤
var allImage = document.getElementsByTagName("img");
var img;
for (var i = 0; i < allImage.length; i++) {
img = allImage[i];
//添加觸摸事件
img.addEventListener('touchstart', function(event) {
touch = event.touches[0];
startevent = event;
//保存觸摸點的x,y軸
startX = Number(touch.pageX);
startY = Number(touch.pageY);
//設置定時器,js沒長按事件,就是使用定時器實現的,800毫秒後觸發,img.src爲圖片地址,這裏可以拿到後做保存圖片發大圖等功能
timeout = setTimeout('longClick('+img.src+');', 800);
});
//移動事件
img.addEventListener('touchmove', function(event) {
touch = event.touches[0];
scx = Math.abs(Number(touch.pageX) - startX);
scy = Math.abs(Number(touch.pageY) - startY);
//過濾掉移動事件,不這樣做,當你手指放在這個圖片往上或往下劃的時候,800毫秒後也會觸發長按事件,精確度可以自己調
if (scx > 10 || scy > 10) {
//取消定時器
clearTimeout(timeout);
} else {
//相當android的攔截分發
event.preventDefault();
}
});
//手指放開時取消定時器
img.addEventListener('touchend', function(event) {
clearTimeout(timeout);
});
}
})();//立即執行
-------------------------------------------------------
app要做的就是兩件事,1、將截圖識別二維碼對象注入js中。2、網頁加載結束後,加載執行上面那個js函數代碼即可。
(1)android
android相對簡單點,創建注入對象
---------------------------------------------------
public class DemoJSBridge{
@JavascriptInterface
public void longClickImage(String imgSrc){
//調用截圖識別二維碼代碼
}
}
//注入
wb.addJavascriptInterface(new DemoJSBridge(), "demoJSBridge");
---------------------------------------------------
這樣就可以注入代碼了,其他功能直接在類中加方法即可,而前端js調用則是
//對象是注入到window對象中的,所以是window.對象.方法
window.demoJSBridge.longClickImage(img.src);
所以上面的js函數中的'longClick('+img.src+');'改掉,然後頁面加載完成後注入執行即可。即
------------------------------------------------------
wb.setWebViewClient(new WebViewClient() {
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
//加載js,而我們那個js函數是立即執行的,所以一加載就會自動執行,getQRJs()即爲上面js函數的String格式
wb.loadUrl("javascript:" + getQRJs());
}
});
------------------------------------------------------
(2)ios個人覺得麻煩點,但安全點
注入js對象
-------------------------------------------------------------------------
//構建script對象,配置頁面加載完成後加載上面js函數的並執行,getJSString即爲上面js函數的字符串,
//WKUserScriptInjectionTimeAtDocumentEnd頁面加載結束後注入
WKUserScript *script=[[WKUserScript alloc] initWithSource:[self getJSString] injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:NO];
//WKWebView配置對象
WKWebViewConfiguration *config=[[WKWebViewConfiguration alloc] init];
config.preferences=[WKPreferences new];
//允許執行javaScript
config.preferences.javaScriptEnabled=YES;
//WKWebView自帶長按事件,會攔截掉我們添加的事件,所以屏蔽掉
NSMutableString *javascript = [NSMutableString string];
//禁止webkitTouchCallout
[javascript appendString:@"document.documentElement.style.webkitTouchCallout='none';"];
[javascript appendString:@"document.documentElement.style.webkitUserSelect='none';"];//禁止選擇
WKUserScript *noneSelectScript = [[WKUserScript alloc] initWithSource:javascript injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
[config.userContentController addUserScript:noneSelectScript];
[config.userContentController addUserScript: script];
//注入對象addScriptMessageHandler響應處理者self
[config.userContentController addScriptMessageHandler:self name:@"demoJSBridge"];
WKWebView *webView=[[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];
---------------------------------------------------------------------------------
然後注意
1、script即我們上面代碼中加載js函數的形式也可以用第二種方式,跟android的差不多,即
-(void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{
[self.webView evaluateJavaScript:[self getJSString] completionHandler:nil];
}
2、實現協議WKNavigationDelegate,WKUIDelegate,WKScriptMessageHandler,第一個頁面加載進度等事件的回調。第二個js對話框的回調,WKWebView把js的對話框就是alert()這些給屏蔽了,我們需實現WKUIDelegate協議自己去彈窗。第三個js調用原生的方法。
3、WKWebView注入的對象跟android和UIWebView都不一樣了,他放在了window.webkit.messageHandlers裏,所以前端js調用時爲
window.webkit.messageHandlers.對象名.postMessage(參數);
所以所以上面的js函數中的'longClick('+img.src+');'改掉,參數直接傳js對象,如{functionName:"longClickImage",data:img.src},實現,如果爲減少與android的差異性,android也可以改爲只有postMessage(String msg)方法,然後根據functionName去執行對應的功能。js對象傳到app後,ios會自動轉爲字典,android爲json字符串,自己轉成json就可以了。
個人理解是WKWebView取消了直接注入對象了,即沒有將self注入到js中,而是於注入一個假對象,裏面只有postMessage函數,當js調用這個函數時他對應的再去調用原生的回調。
4、響應js調用事件,即實現WKScriptMessageHandler協議
------------------------------------------------------------
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
//message中的name即爲注入的對象名,body爲傳過來的數據
if ([message.name isEqualToString:@"demoJSBridge"]) {
//js對象傳過來後會自動轉爲字典
NSDictionary *d=message.body;
if ([@"longClickImage" isEqualToString:[d objectForKey:@"functionName"]]) {
//調用截屏,識別二維碼方法
}
}
}
-------------------------------------------------------------------
理論上js與原生的互調就這樣可以了,但爲了保險一點的話,原生接到js的調用以後再回調一下js比較好一點,因爲有可能有些功能js需要app回傳數據做下一步操作。相當於待人接物而言你叫我做一件事,做好了還是做不了,我得告知你一下,做個有交代有責任的人。
這篇好像有點長,寫得不好的地方還多請見諒,也可在評論指導一下。