您好,登錄后才能下訂單哦!
小編給大家分享一下Android ViewPager中如何顯示圖片與播放視頻,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
ViewPager介紹
ViewPager的功能就是可以使視圖滑動,就像Lanucher左右滑動那樣。
ViewPager用于實現(xiàn)多頁面的切換效果,該類存在于Google的兼容包android-support-v4.jar里面.
ViewPager:
1)ViewPager類直接繼承了ViewGroup類,所有它是一個容器類,可以在其中添加其他的view類。
2)ViewPager類需要一個PagerAdapter適配器類給它提供數(shù)據(jù)。
3)ViewPager經(jīng)常和Fragment一起使用,并且提供了專門的FragmentPagerAdapter和FragmentStatePagerAdapter類供Fragment中 的ViewPager使用。
4)在編寫ViewPager的應(yīng)用的使用,還需要使用兩個組件類分別是PagerTitleStrip類和PagerTabStrip類,PagerTitleStrip類直接繼承 自ViewGroup類,而PagerTabStrip類繼承PagerTitleStrip類,所以這兩個類也是容器類。但是有一點需要注意,在定義XML的layout 的時候,這兩個類必須是ViewPager標簽的子標簽,不然會出錯。
本文將詳細介紹關(guān)于Android ViewPager中顯示圖片與播放視頻填坑的相關(guān)內(nèi)容,分享出來供大家參考學習,下面話不多說了,來一起看看詳細的介紹吧。
一.需求來源與實現(xiàn)思路
1.最近項目需求中有用到需要在ViewPager中播放視頻和顯示圖片的功能,視頻是本地視頻,最開始的實現(xiàn)思路是ViewPager中根據(jù)當前item位置對應(yīng)的是圖片還是視頻去初始化PhotoView和SurfaceView,同時銷毀時根據(jù)item的位置去判斷移除PhotoView和SurfaceView。
2.上面那種方式確實是可以實現(xiàn)的,但是存在2個問題,第一,MediaPlayer的生命周期不容易控制并且存在內(nèi)存泄漏問題。第二,連續(xù)三個item都是視頻時,來回滑動的過程中發(fā)現(xiàn)會出現(xiàn)上個視頻的最后一幀畫面的bug。
3.未提升用戶體驗,視頻播放器初始化完成前上面會覆蓋有該視頻的第一幀圖片,但是發(fā)現(xiàn)存在第一幀圖片與視頻第一幀信息不符的情況,后面會通過代碼給出解決方案。
4.圖片和視頻尺寸如何適配以保證不變形。
二.需要填的坑
1.對于MediaPlayer的生命周期不容易控制的本質(zhì)原因是這種實現(xiàn)思路上我的播放器只有1個,頻繁的初始化和銷毀造成了問題,所以后面我更改了實現(xiàn)方式,一個item的視頻對應(yīng)一個播放器。
2.對于滑動過程中發(fā)現(xiàn)會出現(xiàn)上個視頻的最后一幀畫面的bug,發(fā)現(xiàn)是surfaceView這個控件造成的,后面通過將播放的載體更換為TextureView完美解決該問題。
3.SurfaceView與TextureView的本質(zhì)異同
第一:兩者都能在獨立的線程中繪制和渲染,在專用的GPU線程中大大提高渲染的性能。
第二:SurfaceView專門提供了嵌入視圖層級的繪制界面,開發(fā)者可以控制該界面像Size等的形式,能保證界面在屏幕上的正確位置。但也有局限:
1.由于是獨立的一層View,更像是獨立的一個Window,不能加上動畫、平移、縮放;
2.兩個SurfaceView不能相互覆蓋。
第三:Texture更像是一般的View,像TextView那樣能被縮放、平移,也能加上動畫。TextureView只能在開啟了硬件加速的Window中使用,并且消費的內(nèi)存要比SurfaceView多,并伴隨著1-3幀的延遲。
第四:屏幕鎖屏時SurfaceView會銷毀重建,TextureView不會!
三.具體實現(xiàn)核心代碼
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) } //解決刪除視頻時之后跳轉(zhuǎn)到另一個item,當它是視頻時不繼續(xù)播放的問題 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對應(yīng)的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顯示圖片對應(yīng)的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.圖片根據(jù)寬度適配高度的自定義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); //高度根據(jù)使得圖片的寬度充滿屏幕計算而得 int height = (int) Math.ceil((float) width * (float) d.getIntrinsicHeight() / (float) d.getIntrinsicWidth()); setMeasuredDimension(width, height); }else { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } } }
7.播放視頻對應(yīng)的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進行視頻畫面的結(jié)合 mIjkVodMediaPlayer.setSurface(mSurface); //設(shè)置監(jiān)聽 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(); } /** * 動態(tài)設(shè)置視頻寬高信息 */ 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中如何顯示圖片與播放視頻”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學習更多知識,歡迎關(guān)注億速云行業(yè)資訊頻道!
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。