xml
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"> <VideoViewandroid:id="@+id/video_view"android:layout_width="match_parent"android:layout_height="match_parent"android:clickable="false"android:focusable="false"android:focusableInTouchMode="false"/></LinearLayout>java
public class VideoViewActivity extends AppCompatActivity { private VideoView mVideoView; @Override protected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_video_view);if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);// getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);}mVideoView = (VideoView) findViewById(R.id.video_view);mVideoView.setVideoURI(Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.kr36));mVideoView.start();mVideoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) {mVideoView.start(); }}); }}2、自定义的VideoView
布局文件基本同上,除了控件名和id
...<com.example.test.test_fitstatusbar.CustomVideoViewandroid:id="@+id/custom_video_view"...Activity.java也是基本同上。这里是自定义VideoView的java代码,只重写了onMeasure方法。
public class CustomVideoView extends VideoView { public CustomVideoView(Context context) {super(context); } public CustomVideoView(Context context, AttributeSet attrs) {super(context, attrs); } public CustomVideoView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int width = getDefaultSize(0, widthMeasureSpec);int height = getDefaultSize(0, heightMeasureSpec);setMeasuredDimension(width, height); }}二、在对比原生VideoView的onMeasure方法之前,先了解一些事情。
/*** 1、UNSPECIFIED* 根据源码的注释,其大概意思是parent不对child做出限制.它想要什么size就给什么size.看了一些教程,都说用得很少,或者是系统内部才用得上.所以先不管了* 2、EXACTLY* 对应于match_parent和给出具体的数值* 3、AT_MOST* 对应于wrap_content*/ public static class MeasureSpec {private static final int MODE_SHIFT = 30;private static final int MODE_MASK = 0x3 << MODE_SHIFT;public static final int UNSPECIFIED = 0 << MODE_SHIFT;public static final int EXACTLY= 1 << MODE_SHIFT;public static final int AT_MOST= 2 << MODE_SHIFT; ......public static int getMode(int measureSpec) { return (measureSpec & MODE_MASK);}public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK);} ...... }而这里,我所有控件的width和height都是mach_parent,所以以下分析都是基于MeasureSpec.EXACTLY这个mode。
public static int getDefaultSize(int size, int measureSpec) {int result = size;int specMode = MeasureSpec.getMode(measureSpec);int specSize = MeasureSpec.getSize(measureSpec);switch (specMode) {case MeasureSpec.UNSPECIFIED: result = size; break;case MeasureSpec.AT_MOST:case MeasureSpec.EXACTLY: result = specSize; break;}return result; }因为都是MeasureSpec.EXACTLY,所以最终返回的结果是MeasureSpec.getSize(measureSpec),与size,也就是第一个参数无关。
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {boolean optical = isLayoutModeOptical(this);if (optical != isLayoutModeOptical(mParent)) { Insets insets = getOpticalInsets(); int opticalWidth = insets.left + insets.right; int opticalHeight = insets.top + insets.bottom; measuredWidth += optical ? opticalWidth : -opticalWidth; measuredHeight += optical ? opticalHeight : -opticalHeight;}setMeasuredDimensionRaw(measuredWidth, measuredHeight); }中间的判断语句,涉及到ViewGroup的LayoutMode,它有两个值,一个是默认值clipBounds,效果就是保留子view之间的空白,因为有些控件看上去要比实际的小,但它仍然是占了给定的大小,只是系统让它的一部分边缘变成留白,这样的话,不至于子view真的是连接在一起;另一个是opticalBounds,它就是用来消除clipBounds的效果。一般情况下,都不会进入判断语句块里。
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {mMeasuredWidth = measuredWidth;mMeasuredHeight = measuredHeight;mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET; }这个方法就是将最终的测量结果赋值给对应的view的全局变量,意味着measure部分结束。
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//Log.i("@@@@", "onMeasure(" + MeasureSpec.toString(widthMeasureSpec) + ", "//+ MeasureSpec.toString(heightMeasureSpec) + ")");int width = getDefaultSize(mVideoWidth, widthMeasureSpec);int height = getDefaultSize(mVideoHeight, heightMeasureSpec); if (mVideoWidth > 0 && mVideoHeight > 0) { int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) {// the size is fixedwidth = widthSpecSize;height = heightSpecSize;// for compatibility, we adjust size based on aspect ratioif ( mVideoWidth * height < width * mVideoHeight ) { //Log.i("@@@", "image too wide, correcting"); width = height * mVideoWidth / mVideoHeight;} else if ( mVideoWidth * height > width * mVideoHeight ) { //Log.i("@@@", "image too tall, correcting"); height = width * mVideoHeight / mVideoWidth;} } else if (widthSpecMode == MeasureSpec.EXACTLY) { ...... } else if (heightSpecMode == MeasureSpec.EXACTLY) { ...... } else { ...... }} else { // no size yet, just adopt the given spec sizes}setMeasuredDimension(width, height); }为了方便对比,再贴出onMeasure方法。我在这个方法中,打印过width和height的值,它们的值就是屏幕显示部分的分辨率。意思是说,按这里的情况来讲,当状态栏和底部的导航栏都是透明时,width是1080,height是1920,正好是Google Nexus 5的分辨率。
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = getDefaultSize(0, widthMeasureSpec);int height = getDefaultSize(0, heightMeasureSpec);setMeasuredDimension(width, height); }现在对比原生的onMeasure方法来分析。