Android使用MediaRecorder類實現(xiàn)視頻和音頻錄制功能

一、前期基礎知識儲備
Android提供了MediaRecorder這一個類來實現(xiàn)視頻和音頻的錄制。
由官方配圖可知,MediaRecorder用于錄制視頻時需要調用一系列的API來設置和錄制相關的配置,而且調用方法的順序是固定的,必須按照這個順序進行API調用才能正確利用手機攝像頭實現(xiàn)錄像功能。
調用MediaRecorder的錄像API順序如下:
1)Open Camera - Use the Camera.open() to get an instance of the camera object.
2)Connect Preview - Prepare a live camera image preview by connecting a SurfaceView to the camera using Camera.setPreviewDisplay().
3)Start Preview - Call Camera.startPreview() to begin displaying the live camera images.
4)Start Recording Video - The following steps must be completed in order to successfully record video:
a.Unlock the Camera - Unlock the camera for use by MediaRecorder by calling Camera.unlock().
b.Configure MediaRecorder - Call in the following MediaRecorder methods in this order:
setCamera() - Set the camera to be used for video capture,綁定Camera進行視頻錄制。
setAudioSource() - Set the audio source,設置音頻源。
setVideoSource() - Set the video source,設置視頻源。
setProfile() - Set the video output format and encoding,錄制效果的配置。
setOutputFile() - Set the output file, 設置錄制好的文件存儲位置。
setPreviewDisplay() - Connect Preview,設置預覽效果。
c.Prepare MediaRecorder- Prepare the MediaRecorder with provided configuration settings by calling MediaRecorder.prepare().
d.Start MediaRecorder - Start recording video by calling MediaRecorder.start().
停止錄像時調用的API順序如下:
1)Stop MediaRecorder - Stop recording video by calling MediaRecorder.stop().
2)Reset MediaRecorder - Optionally, remove the configuration settings from the recorder by calling MediaRecorder.reset().
3)Release MediaRecorder - Release the MediaRecorder by calling MediaRecorder.release().
4)Lock the Camera - Lock the camera so that future MediaRecorder sessions can use it by calling Camera.lock().
5)Stop the Preview - When your activity has finished using the camera, stop the preview using Camera.stopPreview().
6)Release Camera - Release the camera so that other applications can use it by calling Camera.release().
二、上代碼,具體實現(xiàn)錄制視頻和視頻播放功能
這里調用MediaRecorder的API實現(xiàn)視頻錄制功能并借用MediaPlayer多媒體播放類實現(xiàn)錄制好的視頻播放。
(1)布局文件如下,非常簡單兩個按鈕下放置一個SurfaceView;
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/record_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_weight="1"
android:text="record" />
<Button
android:id="@+id/play_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="15dp"
android:layout_weight="1"
android:text="play" />
</LinearLayout>
<SurfaceView
android:id="@+id/surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="20dp" />
</LinearLayout>
(2)相機錄像前的準備代碼;
/*
* 相機預覽前的準備工作代碼 單獨抽出來
* */
private boolean prepareVideoRecorder() throws IOException {
if (mMediaRecorder == null) {
mMediaRecorder = new MediaRecorder();
mMediaRecorder.reset();
}
/*camera相關設置部分*/
mCamera = Camera.open(0);//Camera.CameraInfo.CAMERA_FACING_BACK
if (mCamera != null) {
//設置旋轉角度,順時針方向,因為默認是逆向90度的,這樣圖像就是正常顯示了
mCamera.setDisplayOrientation(90);
mCamera.unlock();
mMediaRecorder.setCamera(mCamera);
}
/*recorder設置部分*/
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
mMediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH));
mMediaRecorder.setOutputFile(getOutputMediaFile());
mMediaRecorder.setPreviewDisplay(mSurfaceView.getHolder().getSurface());
mMediaRecorder.prepare();
return true;
}
(3)創(chuàng)建錄像文件存儲位置代碼;
/*
* 獲取手機外部存儲路徑
* */
private String getOutputFile() {
File mediaFile = null;
boolean OutputExist = Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED);
if (OutputExist) {
mediaFile = Environment.getExternalStorageDirectory();
return mediaFile.toString();
}
return null;
}
/*
* 獲取錄制視頻的日期 作為存儲文件路徑一部分
* */
private String getDate() {
Log.d(TAG, "獲取錄制視頻的日期 ");
Calendar ca = Calendar.getInstance();
int year = ca.get(Calendar.YEAR); // 獲取年份
int month = ca.get(Calendar.MONTH); // 獲取月份
int day = ca.get(Calendar.DATE); // 獲取日
String date = "" + year + "_" + (month + 1) + "_" + day;
return date;
}
/*
*創(chuàng)建視頻存儲文件夾 錄制好的視頻存儲在手機外部存儲中 以錄像時間+mp4格式命名
* */
private String getOutputMediaFile() {
Log.d(TAG, "獲取視頻存儲的位置 ");
String mediaPath = getOutputFile();
if (mediaPath != null) {
File mediaFile = new File(mediaPath + "/recordVideo");
if (!mediaFile.exists()) {
mediaFile.mkdir();
}
return mMediaPath = mediaFile.getAbsolutePath() + File.separator + getDate() + ".mp4";
}
return null;
}
(4)錄制視頻結束時釋放相機資源;
/*
* 錄制視頻結束時釋放相機資源
* */
private void releaseMediaRecorder() {
Log.d(TAG, "錄制結束后釋放資源 ");
if (mMediaRecorder != null) {
mMediaRecorder.reset(); // clear recorder configuration
mMediaRecorder.release(); // release the recorder object
mMediaRecorder = null;
mCamera.lock(); // lock camera for later use
}
}
(5)點擊錄制視頻按鈕mRecordBtn開始錄制和再次點擊停止錄制;
private void initBtnClick() {
StartRecording();
mPlayBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (mMediaPlayer == null) {
mMediaPlayer = new MediaPlayer();
mMediaPlayer.reset();
Uri uri = Uri.parse(mMediaPath);
mMediaPlayer = MediaPlayer.create(MainActivity.this,uri);
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.setDisplay(mSurfaceHolder);
try{
mMediaPlayer.prepare();
}catch (Exception e){
e.printStackTrace();
}
mMediaPlayer.start();
}
}
});
}
private void StartRecording(){
mRecordBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (!mIsRecord) {
try {
Log.d(TAG, "首次點擊開始錄像 ");
if (prepareVideoRecorder()) {
mMediaRecorder.start();
mIsRecord = true;
mRecordBtn.setText("stop");
}
} catch (IOException e) {
e.printStackTrace();
}
} else {
Log.d(TAG, "再次點擊停止錄像");
mMediaRecorder.stop();
releaseMediaRecorder();
mCamera.lock();
mRecordBtn.setText("record");
mIsRecord = false;
if (mCamera != null) {
mCamera.release();
mCamera = null;
}
}
}
});
}
(6)點擊播放視頻按鈕 mPlayBtn開始播放錄制剛剛錄制好的視頻;
mPlayBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (mMediaPlayer == null) {
mMediaPlayer = new MediaPlayer();
mMediaPlayer.reset();
Uri uri = Uri.parse(mMediaPath);
mMediaPlayer = MediaPlayer.create(MainActivity.this,uri);
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.setDisplay(mSurfaceHolder);
try{
mMediaPlayer.prepare();
}catch (Exception e){
e.printStackTrace();
}
mMediaPlayer.start();
}
}
});
(7)針對6.0以上系統(tǒng)進行運行時權限申請
private void requestCameraAndStoragePermission() {
//檢查用戶是否授權
for (int i = 0; i < permissions.length; i++) {
if (ContextCompat.checkSelfPermission(MainActivity.this, permissions[i]) != PackageManager.PERMISSION_GRANTED) {
//沒有授權則請求相應權限
ActivityCompat.requestPermissions(MainActivity.this, new String[]{permissions[i]}, 1);
}
}
//利用權限申請工具類來實現(xiàn)
mPermissionsUtils = PermissionsUtils.getInstance();
mPermissionsUtils.chekPermissions(MainActivity.this,permissions, permissionsResult);
}
//創(chuàng)建監(jiān)聽權限的接口對象
PermissionsUtils.IPermissionsResult permissionsResult = new PermissionsUtils.IPermissionsResult() {
@Override
public void passPermissons() {
//StartRecording(); 注意這里的邏輯 并不是權限通過了就立即開始錄像了 而是權限通過了 就可以打開Camera進行預覽
mCamera = Camera.open(0);//Camera.CameraInfo.CAMERA_FACING_BACK
}
@Override
public void forbitPermissons() {
Toast.makeText(MainActivity.this, "You denyied the permission", Toast.LENGTH_SHORT).show();
}
};
錄制視頻及播放錄制視頻完整代碼如下
public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback{
private static final String TAG = "MainActivity";
private SurfaceView mSurfaceView;
private Button mRecordBtn, mPlayBtn;
private boolean mIsRecord = false; //是否正在錄像
private Camera mCamera;
private MediaRecorder mMediaRecorder;
private String mMediaPath;
private MediaPlayer mMediaPlayer;
private SurfaceHolder mSurfaceHolder;
private PermissionsUtils mPermissionsUtils;
private String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.CAMERA,
Manifest.permission.RECORD_AUDIO};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//6.0及以上系統(tǒng)請求運行時權限 利用權限申請工具類(見下文)
requestCameraAndStoragePermission();
mSurfaceView = (SurfaceView) findViewById(R.id.surface_view);
mSurfaceView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); // 必須-設置Surface不需要維護自己的緩沖區(qū)
mRecordBtn = (Button) findViewById(R.id.record_btn);
mPlayBtn = (Button) findViewById(R.id.play_btn);
initBtnClick();
SurfaceHolder holder = mSurfaceView.getHolder();
holder.addCallback(this);
}
private void requestCameraAndStoragePermission() {
//檢查用戶是否授權
for (int i = 0; i < permissions.length; i++) {
if (ContextCompat.checkSelfPermission(MainActivity.this, permissions[i]) != PackageManager.PERMISSION_GRANTED) {
//沒有授權則請求相應權限
ActivityCompat.requestPermissions(MainActivity.this, new String[]{permissions[i]}, 1);
}
}
//利用權限申請工具類來實現(xiàn)
mPermissionsUtils = PermissionsUtils.getInstance();
mPermissionsUtils.chekPermissions(MainActivity.this,permissions, permissionsResult);
}
//創(chuàng)建監(jiān)聽權限的接口對象
PermissionsUtils.IPermissionsResult permissionsResult = new PermissionsUtils.IPermissionsResult() {
@Override
public void passPermissons() {
// StartRecording(); 注意這里的邏輯 并不是權限通過了就立即開始錄像了 而是權限通過了 就可以打開Camera進行預覽
mCamera = Camera.open(0);//Camera.CameraInfo.CAMERA_FACING_BACK
}
@Override
public void forbitPermissons() {
Toast.makeText(MainActivity.this, "You denyied the permission", Toast.LENGTH_SHORT).show();
}
};
private void StartRecording(){
mRecordBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (!mIsRecord) {
try {
Log.d(TAG, "首次點擊開始錄像 ");
if (prepareVideoRecorder()) {
mMediaRecorder.start();
mIsRecord = true;
mRecordBtn.setText("stop");
}
} catch (IOException e) {
e.printStackTrace();
}
} else {
Log.d(TAG, "再次點擊停止錄像");
mMediaRecorder.stop();
releaseMediaRecorder();
mCamera.lock();
mRecordBtn.setText("record");
mIsRecord = false;
if (mCamera != null) {
mCamera.release();
mCamera = null;
}
}
}
});
}
private void initBtnClick() {
StartRecording();
mPlayBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (mMediaPlayer == null) {
mMediaPlayer = new MediaPlayer();
mMediaPlayer.reset();
Uri uri = Uri.parse(mMediaPath);
mMediaPlayer = MediaPlayer.create(MainActivity.this,uri);
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.setDisplay(mSurfaceHolder);
try{
mMediaPlayer.prepare();
}catch (Exception e){
e.printStackTrace();
}
mMediaPlayer.start();
}
}
});
}
/*
* 相機預覽前的準備工作代碼 單獨抽出來
* */
private boolean prepareVideoRecorder() throws IOException {
if (mMediaRecorder == null) {
mMediaRecorder = new MediaRecorder();
mMediaRecorder.reset();
}
/*camera相關設置部分*/
mCamera = Camera.open(0);//Camera.CameraInfo.CAMERA_FACING_BACK
if (mCamera != null) {
//設置旋轉角度,順時針方向,因為默認是逆向90度的,這樣圖像就是正常顯示了
mCamera.setDisplayOrientation(90);
mCamera.unlock();
mMediaRecorder.setCamera(mCamera);
}
/*recorder設置部分*/
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
mMediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH));
mMediaRecorder.setOutputFile(getOutputMediaFile());
mMediaRecorder.setPreviewDisplay(mSurfaceView.getHolder().getSurface());
mMediaRecorder.prepare();
return true;
}
/*
* 獲取手機外部存儲路徑
* */
private String getOutputFile() {
File mediaFile = null;
boolean OutputExist = Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED);
if (OutputExist) {
mediaFile = Environment.getExternalStorageDirectory();
return mediaFile.toString();
}
return null;
}
/*
* 獲取錄制視頻的日期 作為存儲文件路徑一部分
* */
private String getDate() {
Log.d(TAG, "獲取錄制視頻的日期 ");
Calendar ca = Calendar.getInstance();
int year = ca.get(Calendar.YEAR); // 獲取年份
int month = ca.get(Calendar.MONTH); // 獲取月份
int day = ca.get(Calendar.DATE); // 獲取日
String date = "" + year + "_" + (month + 1) + "_" + day;
return date;
}
/*
*創(chuàng)建視頻存儲文件夾 錄制好的視頻存儲在手機外部存儲中 以錄像時間+mp4格式命名
* */
private String getOutputMediaFile() {
Log.d(TAG, "獲取視頻存儲的位置 ");
String mediaPath = getOutputFile();
if (mediaPath != null) {
File mediaFile = new File(mediaPath + "/recordVideo");
if (!mediaFile.exists()) {
mediaFile.mkdir();
}
return mMediaPath = mediaFile.getAbsolutePath() + File.separator + getDate() + ".mp4";
}
return null;
}
/*
* 錄制視頻結束時釋放相機資源
* */
private void releaseMediaRecorder() {
Log.d(TAG, "錄制結束后釋放資源 ");
if (mMediaRecorder != null) {
mMediaRecorder.reset(); // clear recorder configuration
mMediaRecorder.release(); // release the recorder object
mMediaRecorder = null;
mCamera.lock(); // lock camera for later use
}
}
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
mSurfaceHolder = surfaceHolder;
}
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
mSurfaceHolder = surfaceHolder;
}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
mSurfaceView = null;
mSurfaceHolder = null;
releaseMediaRecorder();
if (mCamera != null) {
mCamera.release();
mCamera = null;
}
if (mMediaPlayer != null){
mMediaPlayer.release();
mMediaPlayer = null;
}
}
}
三、延伸知識,運行時權限申請工具類
調用手機系統(tǒng)內置的攝像頭進行視頻錄制時及錄制視頻后將視頻保存在本地都需要申請系統(tǒng)權限,而且申請的權限(調用攝像頭權限、存儲權限)都屬于26個危險權限,針對6.0以上的手機,需要進行運行時權限的申請,由于申請的權限過多,而且申請的時間不一致,所以這里提供一個權限申請工具類協(xié)助實現(xiàn)權限申請。(來自腳本之家文章:Android動態(tài)請求權限的工具類(可請求多個,并且功能完善))
完整代碼如下
/**
* 運行時權限申請工具類:
* 檢查用戶是否授權——ContextCompat.checkSelfPermission
* 如果沒有授權,那么申請授權——ActivityCompat.requestPermissions
* 申請授權之后的回調——onRequestPermissionsResult
* 精髓:檢查權限 申請權限的代碼寫在工具類內 同時寫入一個接口 兩個抽象方法-獲取權限成功 + 獲取權限失敗 然后在外部使用權限工具類時實現(xiàn)這兩個抽象方法
* Created by Administrator on 2018/7/3.
*/
public class PermissionsUtils {
private final int mRequestCode = 100;//權限請求碼
public static boolean showSystemSetting = true;
private PermissionsUtils() {
}
private static PermissionsUtils permissionsUtils;
private IPermissionsResult mPermissionsResult;
/*
* 單例模式創(chuàng)建PermissionUtils實例 工具類中的靜態(tài)方法可以直接使用類名+方法名調用 非靜態(tài)方法還是需要獲取到工具類的實例 實例對方法進行調用
* */
public static PermissionsUtils getInstance() {
if (permissionsUtils == null) {
synchronized (PermissionsUtils.class) {
if (permissionsUtils == null)
permissionsUtils = new PermissionsUtils();
}
}
return permissionsUtils;
}
/*
* 檢查用戶是否授權 + 如果沒有授權 則申請授權 - 系統(tǒng)標準方法
* */
public void chekPermissions(Activity context, String[] permissions, @NonNull IPermissionsResult permissionsResult) {
mPermissionsResult = permissionsResult;
if (Build.VERSION.SDK_INT < 23) {//6.0系統(tǒng)及以上才會動態(tài)申請權限 以下不用 所以直接return出去
permissionsResult.passPermissons();
return;
}
//創(chuàng)建一個mPermissionList,逐個判斷哪些權限未授予,未授予的權限存儲到mPerrrmissionList中
List<String> mPermissionList = new ArrayList<>();
//逐個判斷你要的權限是否已經(jīng)通過
for (int i = 0; i < permissions.length; i++) {
if (ContextCompat.checkSelfPermission(context, permissions[i]) != PackageManager.PERMISSION_GRANTED) {
mPermissionList.add(permissions[i]);//添加還未授予的權限
}
}
//申請權限
if (mPermissionList.size() > 0) {//有權限沒有通過,需要申請
ActivityCompat.requestPermissions(context, permissions, mRequestCode);
} else {
//說明權限都已經(jīng)通過,利用接口變量調用實現(xiàn)的接口方法 即有權限之后需要調用的方法
permissionsResult.passPermissons();
return;
}
}
//請求權限后回調的方法
//參數(shù): requestCode 是我們自己定義的權限請求碼
//參數(shù): permissions 是我們請求的權限名稱數(shù)組
//參數(shù): grantResults 是我們在彈出頁面后是否允許權限的標識數(shù)組,數(shù)組的長度對應的是權限名稱數(shù)組的長度,數(shù)組的數(shù)據(jù)0表示允許權限,-1表示我們點擊了禁止權限
public void onRequestPermissionsResult(Activity context, int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
boolean hasPermissionDismiss = false;//有權限沒有通過
if (mRequestCode == requestCode) {
for (int i = 0; i < grantResults.length; i++) {
if (grantResults[i] == -1) {
hasPermissionDismiss = true;
}
}
//如果有權限沒有被允許
if (hasPermissionDismiss) {
if (showSystemSetting) {
showSystemPermissionsSettingDialog(context);//跳轉到系統(tǒng)設置權限頁面,或者直接關閉頁面,不讓他繼續(xù)訪問
} else {
mPermissionsResult.forbitPermissons();
}
} else {
//全部權限通過,可以進行下一步操作。。。
mPermissionsResult.passPermissons();
}
}
}
/**
* 不再提示權限時的展示對話框
*/
AlertDialog mPermissionDialog;
private void showSystemPermissionsSettingDialog(final Activity context) {
final String mPackName = context.getPackageName();
if (mPermissionDialog == null) {
mPermissionDialog = new AlertDialog.Builder(context)
.setMessage("已禁用權限,請手動授予")
.setPositiveButton("設置", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
cancelPermissionDialog();
Uri packageURI = Uri.parse("package:" + mPackName);
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, packageURI);
context.startActivity(intent);
context.finish();
}
})
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//關閉頁面或者做其他操作
cancelPermissionDialog();
//mContext.finish();
mPermissionsResult.forbitPermissons();
}
})
.create();
}
mPermissionDialog.show();
}
//關閉對話框
private void cancelPermissionDialog() {
if (mPermissionDialog != null) {
mPermissionDialog.cancel();
mPermissionDialog = null;
}
}
public interface IPermissionsResult {
void passPermissons();
void forbitPermissons();
}
}
總結
以上所述是小編給大家介紹的Android使用MediaRecorder實現(xiàn)錄制視頻功能,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
相關文章
Android開發(fā)Jetpack組件LiveData使用講解
LiveData是Jetpack組件的一部分,更多的時候是搭配ViewModel來使用,相對于Observable,LiveData的最大優(yōu)勢是其具有生命感知的,換句話說,LiveData可以保證只有在組件( Activity、Fragment、Service)處于活動生命周期狀態(tài)的時候才會更新數(shù)據(jù)2022-08-08
android中soap協(xié)議使用(ksoap調用webservice)
kSOAP是如何調用ebservice的呢,首先要使用SoapObject,這是一個高度抽象化的類,完成SOAP調用??梢哉{用它的addProperty方法填寫要調用的webservice方法的參數(shù)2014-02-02
Android 中使用RecyclerView實現(xiàn)底部翻頁
這篇文章主要介紹了Android 中使用RecyclerView實現(xiàn)底部翻頁功能,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2017-11-11
AndroidStudio修改Code Style來格式化自定義標簽的xml文件方式
這篇文章主要介紹了AndroidStudio修改Code Style來格式化自定義標簽的xml文件方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-03-03
Android實現(xiàn)一個絲滑的自動輪播控件實例代碼
輪播圖對大家來說應該再熟悉不過了,下面這篇文章主要給大家介紹了關于Android實現(xiàn)一個絲滑的自動輪播控件的相關資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考借鑒,下面隨著小編來一起學習學習吧2018-08-08
Android UI設計與開發(fā)之ViewPager介紹和簡單實現(xiàn)引導界面
這篇文章主要為大家詳細介紹了Android UI設計與開發(fā)之ViewPager介紹和簡單實現(xiàn)引導界面,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-08-08

