在我们初次使用App或升级后首次使用App会有一个引导页,通常用来介绍新版本特性。大多数引导页以图文为主,而那些配以动画或视频的引导页总是让人眼前一亮。而作为开发者的我们,在弄明白原理后,总是迫切的想要去实现。本文将从实践出发,讲解具体的实现过程,最终实现一个完整的包含视频的引导页。

技术选型

在Android中常用VideoView实现简单的视频播放功能,另一种方法是使用MediaPlayer + SurfaceView来实现,本质上VideoView对它俩进行了封装,使开发者不用关心其细节,降低开发难度。

写到这里,相信大家心里已经有了实现思路,想要实现带视频播放功能的引导页无非就是在ViewPager中每个页面添加VideoView,滑动到相应页面播放则对应的视频。

那么教程到此就可以结束了!!!

但是这么做会有一个问题,每次打开引导页播放在视频前会先黑屏一下,并且ViewPager滑动时两个页面之间会有明显的黑缝隙。作为一个有追求的开发者这显然是不能接受的。

TextureView

TextureView又是个什么东东的呢?我们知道SurfaceView为了提高工作效率,它的工作方式是创建一个置于应用窗口之后的新窗口,SurfaceView的内容不在应用窗口上,所以不能使用变换(平移、缩放、旋转等)。也难以放在ListView或者ScrollView中,不能使用UI控件的一些特性比如View.setAlpha()。
作为SurfaceView的兄弟,TextureView终于在Android 4.0来救场了。
TextureView并没有创建新的窗口,可以像使用普通View一样执行变换操作,使用TextureView务必开启硬件加速

实践

科学证明使用MediaPlayer与TextureView能确保达到最佳效果。

视频文件
视频文件来源于闲鱼App

1
2
3
4
5
6
//将视频文件放至在assets/guide目录中
├── guide
│   ├── guide_four.mp4
│   ├── guide_one.mp4
│   ├── guide_three.mp4
│   └── guide_two.mp4

GuideActivity
MediaPlayer的setOnPreparedListener()方法可以设置一个监听器,在视频预处理完成之后产生回调,这里我们可以将视频默认设置到第一帧,从而避免黑屏。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
public class GuideActivity extends AppCompatActivity {
private ViewPager viewPager = null;
private List<ImageView> indicators = new ArrayList<>(4);
private List<View> views = new ArrayList<>(4);
private List<MediaPlayer> mediaPlayers = new ArrayList<>(4);

private TextureView textureViewOne;
private TextureView textureViewTwo;
private TextureView textureViewThree;
private TextureView textureViewFour;

private MediaPlayer mediaPlayerOne;
private MediaPlayer mediaPlayerTwo;
private MediaPlayer mediaPlayerThree;
private MediaPlayer mediaPlayerFour;


@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.guide);
int flag = WindowManager.LayoutParams.FLAG_FULLSCREEN;
Window w = getWindow();
w.setFlags(flag, flag);

initVideos();
initViews();
}

private void initVideos() {
try {
mediaPlayerOne = createMediaPlayer("guide/guide_one.mp4");
mediaPlayerTwo = createMediaPlayer("guide/guide_two.mp4");
mediaPlayerThree = createMediaPlayer("guide/guide_three.mp4");
mediaPlayerFour = createMediaPlayer("guide/guide_four.mp4");

mediaPlayers.add(mediaPlayerOne);
mediaPlayers.add(mediaPlayerTwo);
mediaPlayers.add(mediaPlayerThree);
mediaPlayers.add(mediaPlayerFour);
} catch (IOException e) {
finish();
e.printStackTrace();
}
}

private MediaPlayer createMediaPlayer(String filePath) throws IOException {
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
AssetFileDescriptor fileDescriptor = getAssets().openFd(filePath);
mediaPlayer.setDataSource(fileDescriptor.getFileDescriptor(),
fileDescriptor.getStartOffset(), fileDescriptor.getLength());
return mediaPlayer;
}

final ViewPager.OnPageChangeListener changeListener = new ViewPager.OnPageChangeListener() {
@Override
public void onPageSelected(int index) {
for (int i = 0; i < mediaPlayers.size(); i++) {
MediaPlayer mediaPlayer = mediaPlayers.get(i);
ImageView indicate = indicators.get(i);

if (i == index) {
mediaPlayer.start();
indicate.setBackgroundResource(R.drawable.page_now);
continue;
}

if (mediaPlayer.isPlaying()) {
mediaPlayer.pause();
mediaPlayer.seekTo(0);
}

indicate.setBackgroundResource(R.drawable.page);
}
}

@Override
public void onPageScrolled(int pos, float positionOffset, int pix) {
}

@Override
public void onPageScrollStateChanged(int arg0) {

}
};

private PagerAdapter adapter = new PagerAdapter() {
@Override
public int getCount() {
return views.size();
}

@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView(views.get(position));
}

@Override
public Object instantiateItem(ViewGroup container, int position) {
View view = views.get(position);
container.addView(view);
return view;
}
};

private void initViews() {
LayoutInflater inflater = LayoutInflater.from(this);
//指示器
ImageView p1 = (ImageView) this.findViewById(R.id.p1);
ImageView p2 = (ImageView) this.findViewById(R.id.p2);
ImageView p3 = (ImageView) this.findViewById(R.id.p3);
ImageView p4 = (ImageView) this.findViewById(R.id.p4);
indicators.add(p1);
indicators.add(p2);
indicators.add(p3);
indicators.add(p4);

View viewOne = inflater.inflate(R.layout.guide_one, null);
View viewTwo = inflater.inflate(R.layout.guide_two, null);
View viewThree = inflater.inflate(R.layout.guide_three, null);
View viewFour = inflater.inflate(R.layout.guide_four, null);
views.add(viewOne);
views.add(viewTwo);
views.add(viewThree);
views.add(viewFour);

textureViewOne = (TextureView) viewOne.findViewById(R.id.texture_view);
textureViewTwo = (TextureView) viewTwo.findViewById(R.id.texture_view);
textureViewThree = (TextureView) viewThree.findViewById(R.id.texture_view);
textureViewFour = (TextureView) viewFour.findViewById(R.id.texture_view);

textureViewOne.setSurfaceTextureListener(new TextureListener(mediaPlayerOne, 1));
textureViewTwo.setSurfaceTextureListener(new TextureListener(mediaPlayerTwo, 2));
textureViewThree.setSurfaceTextureListener(new TextureListener(mediaPlayerThree, 3));
textureViewFour.setSurfaceTextureListener(new TextureListener(mediaPlayerFour, 4));

viewPager = (ViewPager) this.findViewById(R.id.view_pager);
viewPager.setAdapter(adapter);
viewPager.setOffscreenPageLimit(views.size());
viewPager.addOnPageChangeListener(changeListener);
}

private class TextureListener implements TextureView.SurfaceTextureListener {
private MediaPlayer mediaPlayer;
private int index;

public TextureListener(MediaPlayer mediaPlayer, int index) {
this.mediaPlayer = mediaPlayer;
this.index = index;
}

@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width,
int height) {
Surface surface = new Surface(surfaceTexture);
mediaPlayer.setSurface(surface);
mediaPlayer.prepareAsync();
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
//第一个页面视频加载完后自动播放
if (index == 1) {
mp.start();
} else {
//默认显示第一帧,避免黑屏
mp.seekTo(0);
}
}
});
}

@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
}

@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
if (mediaPlayer != null) {
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
}
return true;
}

@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {

}
}
}

guide.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
>

<android.support.v4.view.ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
>

</android.support.v4.view.ViewPager>


<LinearLayout
android:id="@+id/indicate_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="@dimen/guide_indicate_mar_bottom"
android:layout_marginTop="@dimen/guide_indicate_mar_top"
android:gravity="center"
android:orientation="horizontal"
>

<ImageView
android:id="@+id/p1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/page_now"
android:contentDescription="@string/app_name"
/>

<ImageView
android:id="@+id/p2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:background="@drawable/page"
android:contentDescription="@string/app_name"
/>

<ImageView
android:id="@+id/p3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:background="@drawable/page"
android:contentDescription="@string/app_name"
/>

<ImageView
android:id="@+id/p4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:background="@drawable/page"
android:contentDescription="@string/app_name"
/>
</LinearLayout>

</RelativeLayout>

guide_one.xml、guide_two.xml、guide_three.xml、guide_four.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
>

<TextureView
android:id="@+id/texture_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>

</RelativeLayout>

page.xml

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<size
android:height="8dp"
android:width="8dp"/>
<stroke
android:color="#c9c9c9"
android:width="2px"/>
</shape>

page_now.xml

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<size
android:height="8dp"
android:width="8dp"/>
<solid android:color="#ff6600"/>
</shape>

dimens.xml

1
2
3
4
5
<dimen name="guide_big_font_size">25.7dp</dimen>
<dimen name="guide_normal_font_size">17.9dp</dimen>
<dimen name="guide_indicate_mar_top">22dp</dimen>
<dimen name="guide_indicate_mar_bottom">50dp</dimen>
<dimen name="guide_indicate_text_bottom">180dp</dimen>

成果

本教程效果
闲鱼
闲鱼App效果