在抖音的视频播放体验中,你是否曾疑惑为何首次加载视频要等那么久?这背后隐藏着ViewPager2控件的秘密。这一控件默默在背后工作,影响着我们的观看体验,也存在着很多值得深入探究的点。
ViewPager2的首次加载策略
在用户启动抖音视频页面时,ViewPager2会同时加载当前视频与下一个视频的界面。据了解,这一策略是为了能在用户观看下一个视频时快速响应。在许多测试中显示,这种预加载方式在多数设备上,首次加载等待时间会增加5 - 10秒。不少用户对此不太满意。其实这是一种权衡,增加首次加载时间来换取后续观看的流畅性。比如有时在网络不好的环境下,这种预加载还能保证观看下一个视频时不会卡顿太久。
从技术角度出发,ViewPager2的这种加载机制是综合多方面的考虑。它要满足用户快速切换视频的需求,如果没有预加载,在有些手机性能较差的情况下,切换视频就可能会有明显的卡顿现象。
dependencies {
........
implementation 'com.google.android.material:material:1.3.0-alpha04'
}
滑动过程中的视图管理
dependencies {
........
implementation "androidx.viewpager2:viewpager2:1.0.0"
}
当我们在抖音上滑动切换视频时,ViewPager2的操作十分有学问。向上滑动拉取下一视频界面时,它不会立刻销毁上一个视频界面,而是销毁上上个视频界面,下滑也是如此。这一设计是很聪明的。实际测试数据表明,这种操作极大地保障了滑动的流畅度。几乎90%以上的用户都认为在抖音滑动视频是非常顺滑的一个体验。
从技术原理看,这种视图管理有效地利用了设备的内存和性能。比如有用户的手机内存不足时,因为它合理的视图管理,也很少会出现卡顿在切换视频的时候。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
tools:context=".MainActivity">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/view_pager2"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
ViewPager2取代ViewPager的缘由
ViewPager2是用于替换ViewPager的,这其中有着重要的原因。ViewPager存在很多痛点,例如在支持RTL布局方面较差。而根据开发文档的记录,ViewPager2很好地解决了这一问题。在垂直方向滚动这个需求上,ViewPager也表现不佳,ViewPager2却完美支持。
实际开发中,可修改的Fragment集合等方面的改进,让ViewPager2更适应现代应用的开发需求。很多开发团队在新的项目中都选择使用ViewPager2而放弃ViewPager,就是基于这些因素的考虑。
public class ViewPagerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final static String TAG = "ViewPagerAdapter";
private List<ShortVideoInfo> itemDataList = null;
private Context context = null;
public ViewPagerAdapter(Context context, List<ShortVideoInfo> itemDataList) {
this.itemDataList = itemDataList;
this.context = context;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
int viewType) {
View v = LayoutInflater.from(context).inflate(R.layout.layout_viewpager2_item, parent, false);
final RecyclerView.ViewHolder holder = new RecyclerItemNormalHolder(context, v);
return holder;
}
@Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
RecyclerItemNormalHolder recyclerItemViewHolder = (RecyclerItemNormalHolder) holder;
recyclerItemViewHolder.setRecyclerBaseAdapter(this);
recyclerItemViewHolder.onBind(position, itemDataList.get(position));
}
@Override
public int getItemCount() {
return itemDataList.size();
}
@Override
public int getItemViewType(int position) {
return 1;
}
}
在xml代码中的定义
在抖音的代码中,定义了androidx.viewpager2.widget.ViewPager2视图控件用来加载渲染数据列表集视图。这一步骤在整体的视频播放流程中有着特殊意义。通过代码分析可知,它的作用和RecyclerView控件有类似之处。在代码结构方面,它处在关键层次,关联着很多其他模块,比如数据传输和显示模块都和它有着紧密的联系。
从开发实践的角度来看,正确定义这个视图控件对于后续的开发工作有着重要的引导作用。比如说,一旦这个定义出现错误,可能导致视频无法正常加载或者画面显示存在问题。
public class RecyclerItemNormalHolder extends RecyclerItemBaseHolder {
public final static String TAG = "RecyclerItemNormalHolder";
protected Context context = null;
@BindView(R.id.video_item_player)
SampleCoverVideo gsyVideoPlayer;
@BindView(R.id.tv_content)
TextView tvContent;
ImageView imageView;
GSYVideoOptionBuilder gsyVideoOptionBuilder;
public RecyclerItemNormalHolder(Context context, View v) {
super(v);
this.context = context;
ButterKnife.bind(this, v);
imageView = new ImageView(context);
gsyVideoOptionBuilder = new GSYVideoOptionBuilder();
}
public void onBind(final int position, ShortVideoInfo videoModel) {
String videoUrl = videoModel.getVideoUrl();
String textContent = videoModel.getTextContent();
Glide.with(context).load(videoModel.getVideoCover()).into(imageView);
tvContent.setText(" " + textContent);
Map<String, String> header = new HashMap<>();
header.put("ee", "33");
// 防止错位,离开释放
//gsyVideoPlayer.initUIState();
gsyVideoOptionBuilder
.setIsTouchWiget(false)
.setThumbImageView(imageView)
.setUrl(videoUrl)
.setVideoTitle(textContent)
.setCacheWithPlay(true)
.setRotateViewAuto(true)
.setLockLand(true)
.setPlayTag(TAG)
.setMapHeadData(header)
.setShowFullAnimation(true)
.setNeedLockFull(true)
.setPlayPosition(position)
.setVideoAllCallBack(new GSYSampleCallBack() {
@Override
public void onPrepared(String url, Object... objects) {
super.onPrepared(url, objects);
GSYVideoManager.instance().setNeedMute(false);
}
@Override
public void onQuitFullscreen(String url, Object... objects) {
super.onQuitFullscreen(url, objects);
GSYVideoManager.instance().setNeedMute(false);
}
@Override
public void onEnterFullscreen(String url, Object... objects) {
super.onEnterFullscreen(url, objects);
GSYVideoManager.instance().setNeedMute(false);
gsyVideoPlayer.getCurrentPlayer().getTitleTextView().setText((String) objects[0]);
}
}).build(gsyVideoPlayer);
//增加title
gsyVideoPlayer.getTitleTextView().setVisibility(View.GONE);
//设置返回键
gsyVideoPlayer.getBackButton().setVisibility(View.GONE);
//设置全屏按键功能
gsyVideoPlayer.getFullscreenButton().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
resolveFullBtn(gsyVideoPlayer);
}
});
}
/**
* 全屏幕按键处理
*/
private void resolveFullBtn(final StandardGSYVideoPlayer standardGSYVideoPlayer) {
standardGSYVideoPlayer.startWindowFullscreen(context, true, true);
}
public SampleCoverVideo getPlayer() {
return gsyVideoPlayer;
}
}
ViewPagerAdapter的构造与作用
代码中自定义的ViewPagerAdapter()构造方法非常关键,它用来接收从Activity或者Fragment传递过来的上下文和视频List数据集。其中的Holder作用也不可小觑,它能设置视频播放的控制和item视图上的文字内容。同时这里的数据关联是系统运行的核心部分。
在实际的一系列操作中,在适配器ViewPagerAdapter的onBindViewHolder()方法中绑定该Holder从而进行数据关联是必须的步骤。据开发工程师反馈,如果这一步骤出现问题,视频播放的相关操作,如暂停、播放等就会出现异常。
页面更改的监听与操作
private void getData() {
setData();
viewPagerAdapter = new ViewPagerAdapter(this, mList);
viewPager2.setOrientation(ViewPager2.ORIENTATION_VERTICAL);
viewPager2.setAdapter(viewPagerAdapter);
viewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
// 大于0说明有播放
int playPosition = GSYVideoManager.instance().getPlayPosition();
if (playPosition >= 0) {
// 对应的播放列表TAG
if (GSYVideoManager.instance().getPlayTag().equals(RecyclerItemNormalHolder.TAG)
&& (position != playPosition)) {
playPosition(position);
}
}
}
});
viewPager2.post(new Runnable() {
@Override
public void run() {
playPosition(0);
}
});
}
viewPager2.registerOnPageChangeCallback()作为监听页面更改回调函数相当重要。它的onPageSelected()方法可以在所选页面上进行很多操作。例如设置当前界面的视频播放、暂停以及界面上的按钮点击事件操作等,这是与用户交互最为直接的一部分。
从开发过程来看,这一监听回调函数在保证用户体验方面是一个关键环节。一旦它出现故障,用户期望的视频播放控制就会失灵。
你是否在使用抖音的时候感受到了ViewPager2对视频浏览体验的细微影响?欢迎点赞、分享并评论。