您好,登錄后才能下訂單哦!
小編給大家分享一下Android ViewPager中如何顯示圖片與播放視頻,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
ViewPager介紹
ViewPager的功能就是可以使視圖滑動,就像Lanucher左右滑動那樣。
ViewPager用于實現多頁面的切換效果,該類存在于Google的兼容包android-support-v4.jar里面.
ViewPager:
1)ViewPager類直接繼承了ViewGroup類,所有它是一個容器類,可以在其中添加其他的view類。
2)ViewPager類需要一個PagerAdapter適配器類給它提供數據。
3)ViewPager經常和Fragment一起使用,并且提供了專門的FragmentPagerAdapter和FragmentStatePagerAdapter類供Fragment中 的ViewPager使用。
4)在編寫ViewPager的應用的使用,還需要使用兩個組件類分別是PagerTitleStrip類和PagerTabStrip類,PagerTitleStrip類直接繼承 自ViewGroup類,而PagerTabStrip類繼承PagerTitleStrip類,所以這兩個類也是容器類。但是有一點需要注意,在定義XML的layout 的時候,這兩個類必須是ViewPager標簽的子標簽,不然會出錯。
本文將詳細介紹關于Android ViewPager中顯示圖片與播放視頻填坑的相關內容,分享出來供大家參考學習,下面話不多說了,來一起看看詳細的介紹吧。
一.需求來源與實現思路
1.最近項目需求中有用到需要在ViewPager中播放視頻和顯示圖片的功能,視頻是本地視頻,最開始的實現思路是ViewPager中根據當前item位置對應的是圖片還是視頻去初始化PhotoView和SurfaceView,同時銷毀時根據item的位置去判斷移除PhotoView和SurfaceView。
2.上面那種方式確實是可以實現的,但是存在2個問題,第一,MediaPlayer的生命周期不容易控制并且存在內存泄漏問題。第二,連續三個item都是視頻時,來回滑動的過程中發現會出現上個視頻的最后一幀畫面的bug。
3.未提升用戶體驗,視頻播放器初始化完成前上面會覆蓋有該視頻的第一幀圖片,但是發現存在第一幀圖片與視頻第一幀信息不符的情況,后面會通過代碼給出解決方案。
4.圖片和視頻尺寸如何適配以保證不變形。
二.需要填的坑
1.對于MediaPlayer的生命周期不容易控制的本質原因是這種實現思路上我的播放器只有1個,頻繁的初始化和銷毀造成了問題,所以后面我更改了實現方式,一個item的視頻對應一個播放器。
2.對于滑動過程中發現會出現上個視頻的最后一幀畫面的bug,發現是surfaceView這個控件造成的,后面通過將播放的載體更換為TextureView完美解決該問題。
3.SurfaceView與TextureView的本質異同
第一:兩者都能在獨立的線程中繪制和渲染,在專用的GPU線程中大大提高渲染的性能。
第二:SurfaceView專門提供了嵌入視圖層級的繪制界面,開發者可以控制該界面像Size等的形式,能保證界面在屏幕上的正確位置。但也有局限:
1.由于是獨立的一層View,更像是獨立的一個Window,不能加上動畫、平移、縮放;
2.兩個SurfaceView不能相互覆蓋。
第三:Texture更像是一般的View,像TextView那樣能被縮放、平移,也能加上動畫。TextureView只能在開啟了硬件加速的Window中使用,并且消費的內存要比SurfaceView多,并伴隨著1-3幀的延遲。
第四:屏幕鎖屏時SurfaceView會銷毀重建,TextureView不會!
三.具體實現核心代碼
1.ViewPager的初始化
mAdapter = ImageBrowseFragmentPagerAdapter(supportFragmentManager, this, imgs) imgs_viewpager.offscreenPageLimit = 1 imgs_viewpager.adapter = mAdapter imgs_viewpager.currentItem = mPosition //為了處理首次點擊時視頻播放的問題 val message = Message.obtain() message.what = START_PLAY_VIDEO mHandler.sendMessageDelayed(message, 200)
2.Handler處理消息
private val START_PLAY_VIDEO = 0 private var DELETE_VIDEO = 1 private var DELETE_VIDEO_START_PLAY = 2 private var mHandler = Handler(Handler.Callback { msg -> when (msg.what) { //開始播放視頻 START_PLAY_VIDEO -> NotifyDispatch.dispatch(PreviewPlayVideoEvent(mPosition)) //刪除視頻時刷新ui DELETE_VIDEO -> { mAdapter?.setImgs(imgs) } //解決刪除視頻時之后跳轉到另一個item,當它是視頻時不繼續播放的問題 DELETE_VIDEO_START_PLAY -> NotifyDispatch.dispatch(PreviewPlayVideoEvent(mDeletePosition)) } true })
3.刪除視頻或圖片的處理邏輯
private fun deletePhotos(position: Int) { if (imgs!!.isEmpty()) { return } ThreadDispatch.right_now.execute({ var file: File? file = File(imgs.get(position)) if (file != null && file?.exists()!!) { val intent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE) val uri = Uri.fromFile(file) intent.data = uri sendBroadcast(intent) file?.delete() imgs.removeAt(position) } if (position == imgs.size) { mDeletePosition = position - 1 } else { mDeletePosition = position } val message = Message.obtain() message.what = DELETE_VIDEO mHandler.sendMessage(message) NotifyDispatch.dispatch(DeletePreviewPhotoEvent(imgs)) val message1 = Message.obtain() message1.what = DELETE_VIDEO_START_PLAY mHandler.sendMessageDelayed(message1, 200) if (imgs.isEmpty()) { finish() } }) // } }
4.ViewPager對應的Adapter
package com.immomo.camerax.gui.view.adapter; import android.content.Context; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentStatePagerAdapter; import android.support.v4.app.FragmentTransaction; import android.view.View; import android.view.ViewGroup; import com.immomo.camerax.gui.fragment.PreviewImgFragment; import com.immomo.camerax.gui.fragment.PreviewVideoFragment; import java.util.ArrayList; import java.util.List; /** * Created by liuxu on 2018/3/26. */ public class ImageBrowseFragmentPagerAdapter extends FragmentStatePagerAdapter { private Context mContext; private List<String> datas; private int mCurrentSelectedPosition = -1; private FragmentManager mFragmentManager; private FragmentTransaction mFragmentTransaction; private ArrayList<Fragment> mFragments = new ArrayList<>(); public ImageBrowseFragmentPagerAdapter(FragmentManager fm, Context context, List<String> datas) { super(fm); mFragmentManager = fm; mContext = context; this.datas = datas; } public void removeContext(){ mContext = null; } @Override public void setPrimaryItem(ViewGroup container, int position, Object object) { mCurrentSelectedPosition = position; } @Override public void startUpdate(ViewGroup container) { super.startUpdate(container); } public void setImgs(List<String> imgs) { this.datas = imgs; notifyDataSetChanged(); } //處理更新無效----刪除條目 @Override public int getItemPosition(Object object) { return POSITION_NONE; } public int getPrimaryItemPosition() { return mCurrentSelectedPosition; } public ImageBrowseFragmentPagerAdapter(FragmentManager fm) { super(fm); } @Override public boolean isViewFromObject(View view, Object object) { return view == ((Fragment) object).getView(); } @Override public Fragment getItem(int position) { Bundle bundle = new Bundle(); bundle.putString("url", datas.get(position)); bundle.putInt("position", position); if (datas.get(position).endsWith(".jpg")) { PreviewImgFragment previewImgFragment = new PreviewImgFragment(); previewImgFragment.setArguments(bundle); return previewImgFragment; } else { PreviewVideoFragment previewVideoFragment = new PreviewVideoFragment(); previewVideoFragment.setArguments(bundle); return previewVideoFragment; } } @Override public int getCount() { return datas == null ? 0 : datas.size(); } @Override public Object instantiateItem(ViewGroup container, int position) { return super.instantiateItem(container,position); } @Override public void destroyItem(ViewGroup container, int position, Object object) { super.destroyItem(container,position,object); } }
5顯示圖片對應的Fragment
package com.immomo.camerax.gui.fragment; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.bumptech.glide.Glide; import com.immomo.camerax.R; import com.immomo.camerax.foundation.util.StatusBarUtils; import com.immomo.camerax.gui.view.ResizablePhotoView; /** * Created by liuxu on 2018/3/27. */ public class PreviewImgFragment extends Fragment { @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_preview_photo, null); ResizablePhotoView resizablePhotoView = view.findViewById(R.id.customPhotoView); String url = getArguments().getString("url"); Glide.with(getContext()).load(url).into(resizablePhotoView); resizablePhotoView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { getActivity().finish(); } }); return view; } @Override public void onPause() { super.onPause(); } @Override public void onResume() { super.onResume(); } @Override public void onDetach() { super.onDetach(); } @Override public void onDestroyView() { super.onDestroyView(); } @Override public void onStart() { super.onStart(); } @Override public void onDestroy() { super.onDestroy(); } @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); } }
6.圖片根據寬度適配高度的自定義View
package com.immomo.camerax.gui.view; import android.content.Context; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import com.github.chrisbanes.photoview.PhotoView; /** * Created by liuxu on 2018/4/7. */ public class ResizablePhotoView extends PhotoView { public ResizablePhotoView(Context context) { super(context); } public ResizablePhotoView(Context context, AttributeSet attr) { super(context, attr); } public ResizablePhotoView(Context context, AttributeSet attr, int defStyle) { super(context, attr, defStyle); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { Drawable d = getDrawable(); if (d != null){ int width = MeasureSpec.getSize(widthMeasureSpec); //高度根據使得圖片的寬度充滿屏幕計算而得 int height = (int) Math.ceil((float) width * (float) d.getIntrinsicHeight() / (float) d.getIntrinsicWidth()); setMeasuredDimension(width, height); }else { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } } }
7.播放視頻對應的Fragment
/** * Created by liuxu on 2018/3/27. */ public class PreviewVideoFragment extends Fragment { private ImageView mPhotoView; private TextureView mTextureView; private String mUrl; private int mPosition; private AndroidMediaPlayer mIjkVodMediaPlayer; private boolean mIsSelected; private boolean mIsFirstPrepared; private PreviewPlayVideoSubscriber mPreviewPlayVideoSubscriber = new PreviewPlayVideoSubscriber() { @Override public void onEventMainThread(PreviewPlayVideoEvent event) { super.onEventMainThread(event); MDLog.e("liuxu",event.getPosition()+""); if (event != null && event.getPosition() == mPosition) { //說明是當前條目 if (mIjkVodMediaPlayer != null && !mIjkVodMediaPlayer.isPlaying()) { if (mTextureView != null) { mIjkVodMediaPlayer.setSurface(mSurface); mIjkVodMediaPlayer.prepareAsync(); mPhotoView.setVisibility(View.VISIBLE); } } mIsSelected = true; } else { if (mIjkVodMediaPlayer != null && mIjkVodMediaPlayer.isPlaying()) { mIjkVodMediaPlayer.pause(); mIjkVodMediaPlayer.stop(); } if (mPhotoView != null) { mPhotoView.setVisibility(View.VISIBLE); } mIsSelected = false; } } }; private String mWidth; private String mHeight; @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { mPreviewPlayVideoSubscriber.register(); View view = inflater.inflate(R.layout.fragment_preview_video, null); mPhotoView = view.findViewById(R.id.photoView); mTextureView = view.findViewById(R.id.surfaceView); mUrl = getArguments().getString("url"); mPosition = getArguments().getInt("position"); layoutPlayer(); loadVideoScreenshot(getContext(), mUrl, mPhotoView, 1); view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { release(); getActivity().finish(); } }); initTextureMedia(); return view; } private void initTextureMedia() { mTextureView.setSurfaceTextureListener(mSurfaceTextureListener); } private void play(String url) { try { mIjkVodMediaPlayer = new AndroidMediaPlayer(); mIjkVodMediaPlayer.reset(); mIjkVodMediaPlayer.setDataSource(url); //讓MediaPlayer和TextureView進行視頻畫面的結合 mIjkVodMediaPlayer.setSurface(mSurface); //設置監聽 mIjkVodMediaPlayer.setOnBufferingUpdateListener((mp, percent) -> { }); mIjkVodMediaPlayer.setOnCompletionListener(mp -> { mp.seekTo(0); mp.start(); }); mIjkVodMediaPlayer.setOnInfoListener((mp1, what, extra) -> { if (what == IMediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) { mPhotoView.setVisibility(View.GONE); mIsFirstPrepared = true; } return false; }); mIjkVodMediaPlayer.setOnErrorListener((mp, what, extra) -> false); mIjkVodMediaPlayer.setOnPreparedListener(mp -> { mp.start(); if (!mIsFirstPrepared){ }else { mPhotoView.setVisibility(View.GONE); } }); mIjkVodMediaPlayer.setScreenOnWhilePlaying(true);//在視頻播放的時候保持屏幕的高亮 if (mIsSelected){ //異步準備 mIjkVodMediaPlayer.prepareAsync(); } } catch (Exception e) { e.printStackTrace(); } } private Surface mSurface; private TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() { @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { mSurface = new Surface(surface); play(mUrl); } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { if (mSurface != null){ mSurface.release(); mSurface = null; } if (mTextureView != null){ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { mTextureView.releasePointerCapture(); } } release(); return false; } @Override public void onSurfaceTextureUpdated(SurfaceTexture surface) { } }; @Override public void onStart() { super.onStart(); } @Override public void onAttach(Context context) { super.onAttach(context); } @Override public void onDetach() { super.onDetach(); } @Override public void onPause() { MDLog.e("liuxu", "onPause" + mPosition); //處理鎖屏時播放器停止播放 if (mIjkVodMediaPlayer != null && mIjkVodMediaPlayer.isPlaying()){ mIjkVodMediaPlayer.pause(); mIjkVodMediaPlayer.stop(); } super.onPause(); } //屏幕打開時重新播放 @Override public void onResume() { MDLog.e("liuxu", "onResume" + mPosition); super.onResume(); if (mIsSelected && mIjkVodMediaPlayer != null && !mIjkVodMediaPlayer.isPlaying()) { mIjkVodMediaPlayer.prepareAsync(); } } @Override public void onDestroy() { MDLog.e("liuxu", "onDestroy"); release(); if (mPreviewPlayVideoSubscriber.isRegister()) { mPreviewPlayVideoSubscriber.unregister(); } super.onDestroy(); } private void release() { if (mIjkVodMediaPlayer == null) { return; } if (mIjkVodMediaPlayer.isPlaying()) { mIjkVodMediaPlayer.stop(); } mIjkVodMediaPlayer.release(); mIjkVodMediaPlayer = null; } @Override public boolean getUserVisibleHint() { return super.getUserVisibleHint(); } /** * 動態設置視頻寬高信息 */ private void layoutPlayer() { //獲取視頻寬高比 getPlayInfo(mUrl); float ratio = Float.parseFloat(mHeight) / Float.parseFloat(mWidth); MDLog.e("type", mPosition + "ratio" + ratio); int type = 0; //添加容錯值 if (ratio < MediaConstants.INSTANCE.getASPECT_RATIO_1_1().toFloat() + MediaConstants.INSTANCE.getSCREEN_DEFAULT_VALUE() && ratio > MediaConstants.INSTANCE.getASPECT_RATIO_1_1().toFloat() - MediaConstants.INSTANCE.getSCREEN_DEFAULT_VALUE()) { type = ScreenAdapterUtils.INSTANCE.getSCALE_TYPE_11(); } else if (ratio < MediaConstants.INSTANCE.getASPECT_RATIO_4_3().toFloat() + MediaConstants.INSTANCE.getSCREEN_DEFAULT_VALUE() && ratio > MediaConstants.INSTANCE.getASPECT_RATIO_4_3().toFloat() - MediaConstants.INSTANCE.getSCREEN_DEFAULT_VALUE()) { type = ScreenAdapterUtils.INSTANCE.getSCALE_TYPE_43(); MDLog.e("type", "43"); } else if (ratio < MediaConstants.INSTANCE.getASPECT_RATIO_16_9().toFloat() + MediaConstants.INSTANCE.getSCREEN_DEFAULT_VALUE() && ratio > MediaConstants.INSTANCE.getASPECT_RATIO_16_9().toFloat() - MediaConstants.INSTANCE.getSCREEN_DEFAULT_VALUE()) { type = ScreenAdapterUtils.INSTANCE.getSCALE_TYPE_169(); MDLog.e("type", "169"); } FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) mTextureView.getLayoutParams(); layoutParams.height = ScreenAdapterUtils.INSTANCE.getSurfaceHeight(type); mTextureView.setLayoutParams(layoutParams); FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) mPhotoView.getLayoutParams(); params.height = ScreenAdapterUtils.INSTANCE.getSurfaceHeight(type); mPhotoView.setLayoutParams(params); MDLog.e("params.height", params.height + ""); } private void getPlayInfo(String mUri) { android.media.MediaMetadataRetriever mmr = new android.media.MediaMetadataRetriever(); try { if (mUri != null) { mmr.setDataSource(mUri); } else { //mmr.setDataSource(mFD, mOffset, mLength); } //寬 mWidth = mmr.extractMetadata(android.media.MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH); //高 mHeight = mmr.extractMetadata(android.media.MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT); // mBitmap = mmr.getFrameAtTime(1 ); } catch (Exception ex) { } finally { mmr.release(); } } public static void loadVideoScreenshot(final Context context, String uri, ImageView imageView, long frameTimeMicros) { // 這里的時間是以微秒為單位 RequestOptions requestOptions = RequestOptions.frameOf(frameTimeMicros); requestOptions.set(FRAME_OPTION, MediaMetadataRetriever.OPTION_CLOSEST); requestOptions.transform(new BitmapTransformation() { @Override protected Bitmap transform(@NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) { return toTransform; } @Override public void updateDiskCacheKey(MessageDigest messageDigest) { try { messageDigest.update((context.getPackageName() + "RotateTransform").getBytes("utf-8")); } catch (Exception e) { e.printStackTrace(); } } }); Glide.with(context).load(uri).apply(requestOptions).into(imageView); } }
以上是“Android ViewPager中如何顯示圖片與播放視頻”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。