Pit filling records for displaying pictures and playing videos in Android ViewPager
- 2021-09-05 00:58:20
- OfStack
Introduction to ViewPager
The function of ViewPager is to slide the view, just like Lanucher slides left and right.
ViewPager is used to realize the switching effect of multiple pages. This class exists in the compatibility package android-support-v4.jar of Google.
ViewPager:
1) The ViewPager class inherits directly from the ViewGroup class, so it is a container class in which other view classes can be added.
2) The ViewPager class requires an PagerAdapter adapter class to provide it with data.
3) ViewPager is commonly used with Fragment1, and special FragmentPagerAdapter and FragmentStatePagerAdapter classes are provided for use with ViewPager in Fragment.
4) When writing the application of ViewPager, we also need to use two component classes, namely PagerTitleStrip class and PagerTabStrip class. PagerTitleStrip class directly inherits from ViewGroup class, while PagerTabStrip class inherits PagerTitleStrip class, so these two classes are also container classes. One thing to note, however, is that when defining layout of XML, these two classes must be child tags of ViewPager tag, otherwise an error will occur.
This article will introduce in detail the related contents of displaying pictures and playing videos to fill holes in Android ViewPager, and share them for your reference and study. I won't say much below, let's take a look at the detailed introduction.
1. Requirement sources and implementation ideas
1. In recent project requirements, there are functions that need to play video and display pictures in ViewPager. Video is local video. The initial implementation idea is to initialize PhotoView and SurfaceView according to whether the current position of item corresponds to pictures or videos in ViewPager, and to judge the removal of PhotoView and SurfaceView according to the position of item during destruction.
2. The above method can be realized, but there are two problems. First, the life cycle of MediaPlayer is not easy to control and there is a memory leak problem. Second, when three consecutive item are videos, the bug of the last frame of the previous video will appear in the process of sliding back and forth.
3. The user experience is not improved. Before the initialization of the video player is completed, the first frame picture of the video will be covered. However, it is found that the first frame picture is inconsistent with the information of the first frame of the video, and the solution will be given through the code later.
4. How to fit the sizes of pictures and videos to ensure that they are not deformed.
2. Pits to be filled
1. The essential reason why the life cycle of MediaPlayer is not easy to control is that there is only one player in this implementation idea, and frequent initialization and destruction cause problems, so I changed the implementation mode later, and one video of item corresponds to one player.
2. For bug, which was found to appear in the last frame of the previous video during sliding, it was found that it was caused by surfaceView, and then the problem was perfectly solved by replacing the playing carrier with TextureView.
3. Essential similarities and differences between SurfaceView and TextureView
1: Both can be drawn and rendered in separate threads, greatly improving rendering performance in dedicated GPU threads.
2: SurfaceView provides an embedded view level drawing interface. Developers can control the form of the interface such as Size, which can ensure the correct position of the interface on the screen. But there are limitations:
1. Because it is an independent 1-layer View, it is more like an independent Window, and animation, translation and scaling cannot be added;
2. Two SurfaceView cannot overwrite each other.
3: Texture is more like View, which can be scaled, translated and animated like TextView. TextureView can only be used in Window with hardware acceleration turned on, and consumes more memory than SurfaceView with a delay of 1-3 frames.
4: When the screen is locked, SurfaceView will be destroyed and rebuilt, but TextureView will not!
3. Implement the core code concretely
1. Initialization of ViewPager
mAdapter = ImageBrowseFragmentPagerAdapter(supportFragmentManager, this, imgs)
imgs_viewpager.offscreenPageLimit = 1
imgs_viewpager.adapter = mAdapter
imgs_viewpager.currentItem = mPosition
// In order to deal with the problem of video playback when clicking for the first time,
val message = Message.obtain()
message.what = START_PLAY_VIDEO
mHandler.sendMessageDelayed(message, 200)
2. Handler processing messages
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 playing video
START_PLAY_VIDEO -> NotifyDispatch.dispatch(PreviewPlayVideoEvent(mPosition))
// Refresh when video is deleted ui
DELETE_VIDEO -> {
mAdapter?.setImgs(imgs)
}
// Resolve when deleting a video and then jump to another 1 A item, The problem of not continuing to play when it is video
DELETE_VIDEO_START_PLAY -> NotifyDispatch.dispatch(PreviewPlayVideoEvent(mDeletePosition))
}
true
})
3. Delete the processing logic of video or picture
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. Adapter corresponding to ViewPager
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();
}
// Invalid processing update ---- Delete an entry
@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 Display the corresponding Fragment of the picture
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. Custom View for pictures to fit height according to width
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);
// The height is calculated by filling the screen with the width of the picture
int height = (int) Math.ceil((float) width * (float) d.getIntrinsicHeight() / (float) d.getIntrinsicWidth());
setMeasuredDimension(width, height);
}else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
}
7. Play the Fragment corresponding to the video
/**
* 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) {
// Description is the current entry
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);
// Jean MediaPlayer And TextureView Combine video pictures
mIjkVodMediaPlayer.setSurface(mSurface);
// Set up listening
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);// Keep the screen highlighted while the video is playing
if (mIsSelected){
// Asynchronous preparation
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);
// Player stops playing while handling screen lock
if (mIjkVodMediaPlayer != null && mIjkVodMediaPlayer.isPlaying()){
mIjkVodMediaPlayer.pause();
mIjkVodMediaPlayer.stop();
}
super.onPause();
}
// Replay when the screen is open
@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();
}
/**
* Dynamic setting of video width and height information
*/
private void layoutPlayer() {
// Get video aspect ratio
getPlayInfo(mUrl);
float ratio = Float.parseFloat(mHeight) / Float.parseFloat(mWidth);
MDLog.e("type", mPosition + "ratio" + ratio);
int type = 0;
// Add fault tolerant value
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);
}
// Width
mWidth = mmr.extractMetadata(android.media.MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
// Gao
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) {
// The time here is in microseconds
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);
}
}
4. Conclusion
The author uses this way to achieve the project requirements, but because I contact with audio and video related content is less, all are constantly exploring and learning forward, if there are deficiencies, please comment and correct, thank you. Let's learn and make progress together.