Android ViewPager中顯示圖片與播放視頻的填坑記錄
ViewPager介紹
ViewPager的功能就是可以使視圖滑動,就像Lanucher左右滑動那樣。
ViewPager用于實(shí)現(xiàn)多頁面的切換效果,該類存在于Google的兼容包android-support-v4.jar里面.
ViewPager:
1)ViewPager類直接繼承了ViewGroup類,所有它是一個(gè)容器類,可以在其中添加其他的view類。
2)ViewPager類需要一個(gè)PagerAdapter適配器類給它提供數(shù)據(jù)。
3)ViewPager經(jīng)常和Fragment一起使用,并且提供了專門的FragmentPagerAdapter和FragmentStatePagerAdapter類供Fragment中 的ViewPager使用。
4)在編寫ViewPager的應(yīng)用的使用,還需要使用兩個(gè)組件類分別是PagerTitleStrip類和PagerTabStrip類,PagerTitleStrip類直接繼承 自ViewGroup類,而PagerTabStrip類繼承PagerTitleStrip類,所以這兩個(gè)類也是容器類。但是有一點(diǎn)需要注意,在定義XML的layout 的時(shí)候,這兩個(gè)類必須是ViewPager標(biāo)簽的子標(biāo)簽,不然會出錯。
本文將詳細(xì)介紹關(guān)于Android ViewPager中顯示圖片與播放視頻填坑的相關(guān)內(nèi)容,分享出來供大家參考學(xué)習(xí),下面話不多說了,來一起看看詳細(xì)的介紹吧。
一.需求來源與實(shí)現(xiàn)思路
1.最近項(xiàng)目需求中有用到需要在ViewPager中播放視頻和顯示圖片的功能,視頻是本地視頻,最開始的實(shí)現(xiàn)思路是ViewPager中根據(jù)當(dāng)前item位置對應(yīng)的是圖片還是視頻去初始化PhotoView和SurfaceView,同時(shí)銷毀時(shí)根據(jù)item的位置去判斷移除PhotoView和SurfaceView。
2.上面那種方式確實(shí)是可以實(shí)現(xiàn)的,但是存在2個(gè)問題,第一,MediaPlayer的生命周期不容易控制并且存在內(nèi)存泄漏問題。第二,連續(xù)三個(gè)item都是視頻時(shí),來回滑動的過程中發(fā)現(xiàn)會出現(xiàn)上個(gè)視頻的最后一幀畫面的bug。
3.未提升用戶體驗(yàn),視頻播放器初始化完成前上面會覆蓋有該視頻的第一幀圖片,但是發(fā)現(xiàn)存在第一幀圖片與視頻第一幀信息不符的情況,后面會通過代碼給出解決方案。
4.圖片和視頻尺寸如何適配以保證不變形。
二.需要填的坑
1.對于MediaPlayer的生命周期不容易控制的本質(zhì)原因是這種實(shí)現(xiàn)思路上我的播放器只有1個(gè),頻繁的初始化和銷毀造成了問題,所以后面我更改了實(shí)現(xiàn)方式,一個(gè)item的視頻對應(yīng)一個(gè)播放器。
2.對于滑動過程中發(fā)現(xiàn)會出現(xiàn)上個(gè)視頻的最后一幀畫面的bug,發(fā)現(xiàn)是surfaceView這個(gè)控件造成的,后面通過將播放的載體更換為TextureView完美解決該問題。
3.SurfaceView與TextureView的本質(zhì)異同
第一:兩者都能在獨(dú)立的線程中繪制和渲染,在專用的GPU線程中大大提高渲染的性能。
第二:SurfaceView專門提供了嵌入視圖層級的繪制界面,開發(fā)者可以控制該界面像Size等的形式,能保證界面在屏幕上的正確位置。但也有局限:
1.由于是獨(dú)立的一層View,更像是獨(dú)立的一個(gè)Window,不能加上動畫、平移、縮放;
2.兩個(gè)SurfaceView不能相互覆蓋。
第三:Texture更像是一般的View,像TextView那樣能被縮放、平移,也能加上動畫。TextureView只能在開啟了硬件加速的Window中使用,并且消費(fèi)的內(nèi)存要比SurfaceView多,并伴隨著1-3幀的延遲。
第四:屏幕鎖屏?xí)rSurfaceView會銷毀重建,TextureView不會!
三.具體實(shí)現(xiàn)核心代碼
1.ViewPager的初始化
mAdapter = ImageBrowseFragmentPagerAdapter(supportFragmentManager, this, imgs) imgs_viewpager.offscreenPageLimit = 1 imgs_viewpager.adapter = mAdapter imgs_viewpager.currentItem = mPosition //為了處理首次點(diǎn)擊時(shí)視頻播放的問題 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))
//刪除視頻時(shí)刷新ui
DELETE_VIDEO -> {
mAdapter?.setImgs(imgs)
}
//解決刪除視頻時(shí)之后跳轉(zhuǎn)到另一個(gè)item,當(dāng)它是視頻時(shí)不繼續(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ù)使得圖片的寬度充滿屏幕計(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) {
//說明是當(dāng)前條目
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進(jìn)行視頻畫面的結(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);//在視頻播放的時(shí)候保持屏幕的高亮
if (mIsSelected){
//異步準(zhǔn)備
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);
//處理鎖屏?xí)r播放器停止播放
if (mIjkVodMediaPlayer != null && mIjkVodMediaPlayer.isPlaying()){
mIjkVodMediaPlayer.pause();
mIjkVodMediaPlayer.stop();
}
super.onPause();
}
//屏幕打開時(shí)重新播放
@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) {
// 這里的時(shí)間是以微秒為單位
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.結(jié)語
筆者使用這種方式實(shí)現(xiàn)了項(xiàng)目需求,但是由于本人接觸音視頻的相關(guān)內(nèi)容比較少,全是在不斷探索和學(xué)習(xí)中前進(jìn),如有不足之處請?jiān)u論指正,謝謝。大家共同學(xué)習(xí)共同進(jìn)步。
好了以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
- android使用surfaceview+MediaPlayer播放視頻
- Android中使用TextureView播放視頻
- Android編程實(shí)現(xiàn)播放視頻時(shí)切換全屏并隱藏狀態(tài)欄的方法
- Android仿搜狐視頻、微視等列表播放視頻功能
- Android編程實(shí)現(xiàn)播放視頻的方法示例
- Android多媒體教程之播放視頻的四種方法
- Android 播放視頻常見問題小結(jié)
- Android DragVideo實(shí)現(xiàn)播放視頻時(shí)任意拖拽的方法
- Android仿新浪微博/QQ空間滑動自動播放視頻功能
- android surfaceView實(shí)現(xiàn)播放視頻功能
相關(guān)文章
Android自定義View實(shí)現(xiàn)簡單炫酷的球體進(jìn)度球?qū)嵗a
這篇文章主要給大家介紹了關(guān)于Android自定義View實(shí)現(xiàn)簡單炫酷的球體進(jìn)度球的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對各位Android開發(fā)者們具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-01-01
一文帶你了解Android?Flutter中Transform的使用
flutter的強(qiáng)大之處在于,可以對所有的widget進(jìn)行Transform,因此可以做出非??犰诺男Ч?。本文就來大家了解一下Transform的具體使用,感興趣的可以了解一下2023-01-01
Android開發(fā)之如何自定義數(shù)字鍵盤詳解
這篇文章主要給大家介紹了關(guān)于Android開發(fā)之如何自定義數(shù)字鍵盤的相關(guān)資料,本文語言是基于kotlin實(shí)現(xiàn)的,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起看看吧。2017-09-09
Android自定義view實(shí)現(xiàn)圓環(huán)效果實(shí)例代碼
本文通過實(shí)例代碼給大家介紹了Android自定義view實(shí)現(xiàn)圓環(huán)效果,代碼簡單易懂,非常不錯,具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-07-07

