Android圖片識別應(yīng)用詳解
最近由于參加一個小小的創(chuàng)意比賽,用安卓做了一個小小的圖片識別應(yīng)用,主要是通過拍照識別圖片中的菜品,還有對象位置查找的東西。之前沒有做過安卓,都是拼拼湊湊多篇博客完成的,我也把這個項目的一些過程分享一下。先把功能貼一下,其實就是點擊拍照,將照片保存在本地,然后識別出圖中的菜品,然后用紅色方框圈出來,并顯示菜品種類。采用最新的Camera2的API,的確是比Camera好用。
1、界面
我采用了一個SurfaceView用來顯示攝像頭的預覽畫面,重寫了一個SurfaceView來進行紅色方框還有菜品名字的繪制。圖片是一個ImageVIew,相當于拍照按鈕的功能。
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.hd.hd.MainActivity"> <SurfaceView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/surfaceView" android:layout_centerHorizontal="true" android:layout_centerVertical="true"/> <com.hd.hd.SVDraw android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/mySurfaceView" android:layout_centerHorizontal="true" android:layout_centerVertical="true"/> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_alignParentBottom="true" > <ImageView android:id="@+id/btngal" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center_horizontal" android:layout_alignParentBottom="true" android:src="@drawable/s_8" android:layout_alignParentLeft="true" /> <TextView android:id="@+id/textview" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:textColor="@color/white" android:textSize="20dp" android:layout_toRightOf="@id/btngal" android:layout_alignParentTop="true" /> </LinearLayout> </RelativeLayout>
SVDraw,,繼承SurfaceView,用于繪制紅色方框
package com.hd.hd; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.PorterDuff; import android.util.AttributeSet; import android.view.SurfaceHolder; import android.view.SurfaceView; import java.util.List; /*定義一個畫矩形框的類*/ public class SVDraw extends SurfaceView implements SurfaceHolder.Callback{ protected SurfaceHolder sh; private int mWidth; private int mHeight; public SVDraw(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub sh = getHolder(); sh.addCallback(this); sh.setFormat(PixelFormat.TRANSPARENT); setZOrderOnTop(true); } public void surfaceChanged(SurfaceHolder arg0, int arg1, int w, int h) { // TODO Auto-generated method stub } public void surfaceCreated(SurfaceHolder sh) { // TODO Auto-generated method stub mWidth = this.getWidth(); mHeight = this.getHeight(); } public void surfaceDestroyed(SurfaceHolder arg0) { // TODO Auto-generated method stub } void clearDraw() { Canvas canvas = sh.lockCanvas(); canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); sh.unlockCanvasAndPost(canvas); } public void drawLine(List<String> keys, List<String> values) { Canvas canvas = sh.lockCanvas(); canvas.drawColor(Color.TRANSPARENT); Paint p = new Paint(); p.setAntiAlias(true); p.setColor(Color.RED); p.setStrokeWidth(6); p.setStyle(Paint.Style.STROKE);//設(shè)置空心 p.setTextSize(160); Paint p1 = new Paint(); p1.setColor(Color.WHITE); p1.setTextSize(80); for(int i = 0;i < keys.size();i++){ String v = values.get(i); v = v.replace("[",""); v = v.replace("]",""); String[] value = v.split(","); canvas.drawRect(mWidth - Integer.parseInt(value[3]), Integer.parseInt(value[0]), mHeight - Integer.parseInt(value[1]), Integer.parseInt(value[2]), p);// 正方形 canvas.drawText(keys.get(i), mWidth - Integer.parseInt(value[3]), Integer.parseInt(value[0])-5, p1); } sh.unlockCanvasAndPost(canvas); } }
2、上傳圖片到服務(wù)器,我沒有采用JSon的格式,而是直接將圖片文件轉(zhuǎn)化為字節(jié)數(shù)組,發(fā)送給服務(wù)器。使用一個異步任務(wù),完成后,直接在onPostExcute()方法里繪制。
package com.hd.hd; import android.os.AsyncTask; import android.util.Log; import android.widget.TextView; import org.json.JSONException; import org.json.JSONObject; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.UUID; /** * Created by asus on 2017/8/13. */ public class MyTask extends AsyncTask<String, Integer, String> { private static String TAG = "MainActivity"; private File file; //需要發(fā)送的圖片 private String result_content; //服務(wù)器返回的結(jié)果 private SVDraw surfaceView; //需要繪制的surfaceview private TextView tv; //顯示文字 private static final int TIME_OUT = 10 * 1000; // 超時時間 private static final String CHARSET = "utf-8"; // 設(shè)置編碼 public MyTask(File f,SVDraw s,TextView tv){ this.file = f; this.surfaceView = s; this.tv = tv; } @Override protected void onPreExecute() { } //doInBackground方法內(nèi)部執(zhí)行后臺任務(wù),不可在此方法內(nèi)修改UI @Override protected String doInBackground(String... params) { //調(diào)用文件上傳方法 result_content = uploadFile(file,"http://13.76.211.62/"); return null; } //onProgressUpdate方法用于更新進度信息 @Override protected void onProgressUpdate(Integer... progresses) { } //onPostExecute方法用于在執(zhí)行完后臺任務(wù)后更新UI,顯示結(jié)果 @Override protected void onPostExecute(String result) { //由于返回的是一個python的字典形式的字符串,用json來解析 JSONObject obj = null; List<String> keys = new ArrayList<String>(); List<String> values = new ArrayList<String>(); try { obj = new JSONObject(result_content); //json對象的Key的迭代器,用來遍歷json Iterator it = obj.keys(); while (it.hasNext()) { String key = (String) it.next(); String value = obj.getString(key); keys.add(key); values.add(value); } } catch (JSONException e) { e.printStackTrace(); } //繪制圖形 surfaceView.clearDraw(); surfaceView.drawLine(keys,values); tv.setText("搭配很贊哦"); } //onCancelled方法用于在取消執(zhí)行中的任務(wù)時更改UI @Override protected void onCancelled() { } /** * 上傳圖片文件到服務(wù)器 * @param file * @param RequestURL * @return */ public static String uploadFile(File file, String RequestURL) { String result = null; String BOUNDARY = UUID.randomUUID().toString(); // 邊界標識 隨機生成 String PREFIX = "--", LINE_END = "\r\n"; String CONTENT_TYPE = "multipart/form-data"; // 內(nèi)容類型 try { //創(chuàng)建URL連接,指明連接地址 URL url = new URL(RequestURL); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); //設(shè)置http請求的屬性為POST conn.setReadTimeout(TIME_OUT); conn.setConnectTimeout(TIME_OUT); conn.setDoInput(true); // 允許輸入流 conn.setDoOutput(true); // 允許輸出流 conn.setUseCaches(false); // 不允許使用緩存 conn.setRequestMethod("POST"); // 請求方式 conn.setRequestProperty("Charset", CHARSET); // 設(shè)置編碼 conn.setRequestProperty("connection", "keep-alive"); conn.setRequestProperty("Content-Type", CONTENT_TYPE + ";boundary=" + BOUNDARY); if (file != null) { /** * 當文件不為空,把文件包裝并且上傳 */ Log.i(TAG,"upload"); DataOutputStream dos = new DataOutputStream(conn.getOutputStream()); Log.e(TAG,"not null"); /** * 這里重點注意: name里面的值為服務(wù)端需要key 只有這個key 才可以得到對應(yīng)的文件 * filename是文件的名字,包含后綴名的 比如:abc.png */ InputStream is = new FileInputStream(file); byte[] bytes = new byte[1024]; int len; while ((len = is.read(bytes)) != -1) { dos.write(bytes, 0, len); } is.close(); dos.flush(); Log.e(TAG,"sent"); /** * 獲取響應(yīng)碼 200=成功 當響應(yīng)成功,獲取響應(yīng)的流 */ int res = conn.getResponseCode(); Log.e(TAG, "response code:" + res); Log.e(TAG, "request success"); InputStream input = conn.getInputStream(); StringBuffer sb1 = new StringBuffer(); int ss; while ((ss = input.read()) != -1) { sb1.append((char) ss); } result = sb1.toString(); Log.e(TAG, "result : " + result); } } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return result; } }
3、初始化界面、照相機,使得照相機能夠?qū)崟r預覽,并實現(xiàn)拍照功能
package com.hd.hd; import android.Manifest; import android.content.Context; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.ImageFormat; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraManager; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureResult; import android.hardware.camera2.TotalCaptureResult; import android.media.Image; import android.media.ImageReader; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; import android.support.annotation.NonNull; import android.support.annotation.RequiresApi; import android.support.v4.app.ActivityCompat; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.util.SparseIntArray; import android.view.Surface; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Arrays; public class MainActivity extends AppCompatActivity{ private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); private String TAG = "MainActivity"; ///為了使照片豎直顯示 static { ORIENTATIONS.append(Surface.ROTATION_0, 90); ORIENTATIONS.append(Surface.ROTATION_90, 0); ORIENTATIONS.append(Surface.ROTATION_180, 270); ORIENTATIONS.append(Surface.ROTATION_270, 180); } private SurfaceView mSurfaceView; private SurfaceHolder mSurfaceHolder; private CameraManager mCameraManager;//攝像頭管理器 private Handler childHandler, mainHandler; private String mCameraID;//攝像頭Id 0 為后 1 為前 private ImageReader mImageReader; private CameraCaptureSession mCameraCaptureSession; private CameraDevice mCameraDevice; private SVDraw hSurfaceView; private MyTask myTask; private CaptureRequest.Builder captureRequestBuilder; private TextView tv; private final int DRAW_ORDER = 10; private Handler myHandler; private ImageView imageView; private String dir = Environment.getExternalStorageDirectory().getAbsolutePath() + "/Healthy_d/"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //此步驟非常重要,安卓不用自動幫你創(chuàng)建文件夾來保存拍照的照片 File dirFirstFolder = new File(dir);//方法二:通過變量文件來獲取需要創(chuàng)建的文件夾名字 if(!dirFirstFolder.exists()) { //如果該文件夾不存在,則進行創(chuàng)建 dirFirstFolder.mkdirs();//創(chuàng)建文件夾 } //Android 6后有些敏感的權(quán)限不能隨意分配,必須向用戶發(fā)送請求賦予 //這里請求用戶賦予拍照,讀寫內(nèi)存卡,連接網(wǎng)絡(luò)的權(quán)限,其實只有拍照權(quán)限需要向用戶請求,但是有備無患吧 if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { Log.e(TAG,ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)+""); ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 43); } if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 44); } if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.INTERNET) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.INTERNET}, 45); } initVIew(); } /** * 初始化視圖 */ private void initVIew() { HandlerThread handlerThread = new HandlerThread("Camera2"); handlerThread.start(); childHandler = new Handler(handlerThread.getLooper()); //mSurfaceView mSurfaceView = (SurfaceView) findViewById(R.id.surfaceView); hSurfaceView = (SVDraw) findViewById(R.id.mySurfaceView); imageView = (ImageView) findViewById(R.id.btngal); tv = (TextView)findViewById(R.id.textview); //設(shè)置ImageView監(jiān)聽器,點擊圖片,拍照 imageView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(MainActivity.this, "正在識別,請稍等", Toast.LENGTH_LONG).show(); if (mCameraDevice == null) return; // 創(chuàng)建拍照需要的CaptureRequest.Builder try { captureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); // 將imageReader的surface作為CaptureRequest.Builder的目標 captureRequestBuilder.addTarget(mImageReader.getSurface()); // 自動對焦 captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); // 自動曝光 captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); // 獲取手機方向 int rotation = getWindowManager().getDefaultDisplay().getRotation(); // 根據(jù)設(shè)備方向計算設(shè)置照片的方向 captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation)); //拍照 CaptureRequest mCaptureRequest = captureRequestBuilder.build(); mCameraCaptureSession.capture(mCaptureRequest, mSessionCaptureCallback, childHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } }); mSurfaceHolder = mSurfaceView.getHolder(); mSurfaceHolder.setKeepScreenOn(true); // mSurfaceView添加回調(diào) mSurfaceHolder.addCallback(new SurfaceHolder.Callback() { @Override public void surfaceCreated(SurfaceHolder holder) { //SurfaceView創(chuàng)建 // 初始化Camera initCamera2(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { //SurfaceView銷毀 // 釋放Camera資源 if (null != mCameraDevice) { mCameraDevice.close(); mCameraDevice = null; } } }); } //拍照時,可以對照片進行操作,這里可以不寫,因為我沒對其進行操作 private CameraCaptureSession.CaptureCallback mSessionCaptureCallback = new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {} @Override public void onCaptureProgressed(CameraCaptureSession session, CaptureRequest request, CaptureResult partialResult){}}; /** * 初始化Camera2 */ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) private void initCamera2() { HandlerThread handlerThread = new HandlerThread("Camera2"); handlerThread.start(); childHandler = new Handler(handlerThread.getLooper()); mainHandler = new Handler(getMainLooper()); mCameraID = "" + CameraCharacteristics.LENS_FACING_FRONT;//后攝像頭 mImageReader = ImageReader.newInstance(mSurfaceView.getWidth(), mSurfaceView.getHeight(), ImageFormat.JPEG,1); mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() { //可以在這里處理拍照得到的臨時照片 例如,寫入本地 @Override public void onImageAvailable(ImageReader reader) { Image image = reader.acquireNextImage(); ByteBuffer buffer = image.getPlanes()[0].getBuffer(); byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes);//由緩沖區(qū)存入字節(jié)數(shù)組 Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length); String fileName = "test"; File file = new File(dir + fileName + ".jpg"); String state = Environment.getExternalStorageState(); //如果狀態(tài)不是mounted,無法讀寫 if (!state.equals(Environment.MEDIA_MOUNTED)) { return; } FileOutputStream out = null; try { out = new FileOutputStream(file); bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);//轉(zhuǎn)化為jpeg圖片 out.flush(); out.close(); image.close();//一定要記得關(guān),否則會出現(xiàn)程序崩潰 } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } new MyTask(file,hSurfaceView,tv).execute(); } }, mainHandler); //獲取攝像頭管理 mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); try { if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, 42); } //打開攝像頭 mCameraManager.openCamera(mCameraID, stateCallback, mainHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } /** * 當發(fā)送權(quán)限請求用戶響應(yīng)時,回調(diào)該函數(shù) * @param requestCode * @param permissions * @param grantResults */ @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == 42) { Toast.makeText(this, "CAMERA PERMISSION GRANTED", Toast.LENGTH_SHORT).show(); if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { //申請成功,可以拍照 Log.i(TAG,"apply camera success"); CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); try { if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { Toast.makeText(this, "CAMERA PERMISSION DENIED", Toast.LENGTH_SHORT).show(); } mCameraManager.openCamera(mCameraID, stateCallback, mainHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } else { Toast.makeText(this, "CAMERA PERMISSION DENIED", Toast.LENGTH_SHORT).show(); } return; } super.onRequestPermissionsResult(requestCode, permissions, grantResults); } /** * 攝像頭創(chuàng)建監(jiān)聽 */ private CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() { @Override public void onOpened(CameraDevice camera) {//打開攝像頭 mCameraDevice = camera; //開啟預覽 takePreview(); } @Override public void onDisconnected(CameraDevice camera) {//關(guān)閉攝像頭 if (null != mCameraDevice) { mCameraDevice.close(); mCameraDevice = null; } } @Override public void onError(CameraDevice camera, int error) {//發(fā)生錯誤 Toast.makeText(MainActivity.this, "攝像頭開啟失敗", Toast.LENGTH_SHORT).show(); } }; /** * 開始預覽 */ private void takePreview() { try { // 創(chuàng)建預覽需要的CaptureRequest.Builder final CaptureRequest.Builder previewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); // 將SurfaceView的surface作為CaptureRequest.Builder的目標 previewRequestBuilder.addTarget(mSurfaceHolder.getSurface()); // previewRequestBuilder.addTarget(mImageReader.getSurface()); // 創(chuàng)建CameraCaptureSession,該對象負責管理處理預覽請求和拍照請求 mCameraDevice.createCaptureSession(Arrays.asList(mSurfaceHolder.getSurface(), mImageReader.getSurface()), new CameraCaptureSession.StateCallback() // ③ { @Override public void onConfigured(CameraCaptureSession cameraCaptureSession) { if (null == mCameraDevice) return; // 當攝像頭已經(jīng)準備好時,開始顯示預覽 mCameraCaptureSession = cameraCaptureSession; try { // 自動對焦 previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); // 打開閃光燈 previewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); // 顯示預覽 CaptureRequest previewRequest = previewRequestBuilder.build(); mCameraCaptureSession.setRepeatingRequest(previewRequest, null, childHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) { Toast.makeText(MainActivity.this, "配置失敗", Toast.LENGTH_SHORT).show(); } }, childHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } }
4、AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.hd.hd"> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.CAMERA"/> <uses-feature android:name="android.hardware.camera2.full" /> <uses-permission android:name="android.permission.INTERNET" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
今天代碼先分享到那么多,明天給大家分享一下Camera2的架構(gòu)。有不懂的可以評論,一起討論。
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android 將本地資源圖片轉(zhuǎn)換成Drawable,進行設(shè)置大小操作
這篇文章主要介紹了Android 將本地資源圖片轉(zhuǎn)換成Drawable,進行設(shè)置大小操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-08-08Android開發(fā)實現(xiàn)跟隨手指的小球效果示例
這篇文章主要介紹了Android開發(fā)實現(xiàn)跟隨手指的小球效果,涉及Android圖形繪制、事件響應(yīng)、界面布局等相關(guān)操作技巧,需要的朋友可以參考下2019-04-04Android中查看USB連接的外接設(shè)備信息的代碼實例
這篇文章主要介紹了Android中查看USB連接的外接設(shè)備信息的代碼實例,需要的朋友可以參考下2014-04-04Kotlin中常見內(nèi)聯(lián)擴展函數(shù)的使用方法教程
在Kotlin中,使用inline修飾符標記內(nèi)聯(lián)函數(shù),既會影響到函數(shù)本身, 也影響到傳遞給它的Lambda表達式,這兩者都會被內(nèi)聯(lián)到調(diào)用處。下面這篇文章主要給大家介紹了關(guān)于Kotlin中常見內(nèi)聯(lián)擴展函數(shù)的使用方法,需要的朋友可以參考下。2017-12-12Android實現(xiàn)實時滑動ViewPager的2種方式
這篇文章主要為大家詳細介紹了Android實現(xiàn)實時滑動ViewPager的2種方式,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-10-10Android Studio 3.6中新的視圖綁定工具ViewBinding 用法詳解
這篇文章主要介紹了Android Studio 3.6中新的視圖綁定工具ViewBinding 用法,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-03-03Android通過反射實現(xiàn)強制停止應(yīng)用程序的方法
這篇文章主要介紹了Android通過反射實現(xiàn)強制停止應(yīng)用程序的方法,涉及Android的反射機制與進程操作的相關(guān)技巧,需要的朋友可以參考下2016-02-02