欧同学

高仿音悦台播放页面交互效果

新版的音悦台 APP 播放页面交互非常有意思,可以把播放器往下拖动,这个页面透明渐变,然后到底部可以左右拖动关闭播放器,然后点击视频列表有个页面弹出来的效果,十分炫酷,于是我自己动手实现了这个交互炫酷的播放器页面。

1.废话不多说,直接演示实现效果

1.1.点击某个视频,然后手指上下拖动,播放器做尺寸比例的渐变,视频相关信息做透明度渐变
这里写图片描述

1.2.播放器只有在底部的时候,才能左右拖动,此时播放器做透明度渐变,拖动一定范围可以关闭播放器;然后它只有在原始位置的一小段距离内可以往上拖动
这里写图片描述

1.3.点击视频列表的时候,若是上次视频是左右拖动关闭的话,会有个弹起播放页面的效果;若是返回键和返回箭头则无效果
这里写图片描述

2.实现的思路讲解

  • 毫无疑问,需要自定义一个容器,然后处理它的触摸事件,对它的子 View 进行不同的处理。触摸事件的处理使用 ViewDragHelper 是再适合不过了,然后你需要实现容器 onMeasure 和 onLayout,由于使用了 ViewDragHelper,有些坑在代码解析的时候就会讲解。
  • 播放页面是用新的 Activity 还仅仅是当前 Activity 的View的问题,由于播放器缩小到底部的时候,用户是可以滑动视频列表的,所以我个人认为就是在当前 Activity 放置一个自定义容器即可,因此为了效率考虑你可以用 ViewStub 来懒加载处理,这里方便演示我就直接 View 的形式了。

3.代码解析

3.1.需要的变量

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
/**
* Created by Oubowu on 201612/26 13:58.<p>
* 实现了布局交互的容器
*/
public class YytLayout extends ViewGroup {
private static final int MIN_FLING_VELOCITY = 400;
private ViewDragHelper mDragHelper;
// 拖动的宽度
private int mDragWidth;
// 拖动的高度
private int mDragHeight;
// 响应拖动做缩放的View
private View mFlexView;
// 与mFlexView联动做透明度渐变的View
private View mFollowView;
// 响应拖动做缩放的View保存的位置
private ChildLayoutPosition mFlexLayoutPosition;
// 与mFlexView联动的View保存的位置
private ChildLayoutPosition mFollowLayoutPosition;
// 水平拖动与否的标志位
private boolean mHorizontalDragEnable;
public boolean isHorizontalDragEnable() {
return mHorizontalDragEnable;
}
// 垂直拖动与否的标志位
private boolean mVerticalDragEnable = true;
// 是否正在关闭页面的标志位
private boolean mIsClosing;
// 监听布局是否水平拖动关闭了
private OnLayoutStateListener mOnLayoutStateListener;
// 做拖放缩放的子View的宽度
private int mFlexWidth;
// 做拖放缩放的子View的高度
private int mFlexHeight;
// mFlexView缩放的比率
private float mFlexScaleRatio = 1;
// mFlexView缩放的基准点的偏移值
private int mFlexScaleOffset;
// 触摸事件是否发生在mFlexView的区域
private boolean mInFlexViewTouchRange;

3.2.初始化做 ViewDragHelper 的初始化,然后 post 拿到两个子 View,这里强制规定只能有两个子元素

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
public YytLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
final float density = getResources().getDisplayMetrics().density;
final float minVel = MIN_FLING_VELOCITY * density;
ViewGroupCompat.setMotionEventSplittingEnabled(this, false);
FlexCallback flexCallback = new FlexCallback();
mDragHelper = ViewDragHelper.create(this, 1.0f, flexCallback);
// 最小拖动速度
mDragHelper.setMinVelocity(minVel);
post(new Runnable() {
@Override
public void run() {
// 需要添加的两个子View,其中mFlexView作为拖动的响应View,mLinkView作为跟随View
mFlexView = getChildAt(0);
mFollowView = getChildAt(1);
mDragHeight = getMeasuredHeight() - mFlexView.getMeasuredHeight();
mFlexWidth = mFlexView.getMeasuredWidth();
mFlexHeight = mFlexView.getMeasuredHeight();
}
});
}

3.3. ViewDragHelper 的回调需要做的事情比较多,在 mFlexView 拖动的时候需要同时设置 mFlexView 和 mFollowView 的相应变化效果,在 mFlexView 释放的时候需要处理关闭或收起等效果

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
private class FlexCallback extends ViewDragHelper.Callback {
@Override
public boolean tryCaptureView(View child, int pointerId) {
// mFlexView来响应触摸事件
return mFlexView == child;
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return Math.max(Math.min(mDragWidth, left), -mDragWidth);
}
@Override
public int getViewHorizontalDragRange(View child) {
return mDragWidth * 2;
}
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
if (!mVerticalDragEnable) {
// 不允许垂直拖动的时候是mFlexView在底部水平拖动一定距离时设置的,返回mDragHeight就不能再垂直做拖动了
return mDragHeight;
}
return Math.max(Math.min(mDragHeight, top), 0);
}
@Override
public int getViewVerticalDragRange(View child) {
return mDragHeight;
}
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
if (mHorizontalDragEnable) {
// 如果水平拖动有效,首先根据拖动的速度决定关闭页面,方向根据速度正负决定
if (xvel > 1500) {
mDragHelper.settleCapturedViewAt(mDragWidth, mDragHeight);
mIsClosing = true;
} else if (xvel < -1500) {
mDragHelper.settleCapturedViewAt(-mDragWidth, mDragHeight);
mIsClosing = true;
} else {
// 速度没到关闭页面的要求,根据透明度来决定关闭页面,方向根据releasedChild.getLeft()正负决定
float alpha = releasedChild.getAlpha();
if (releasedChild.getLeft() < 0 && alpha <= 0.4f) {
mDragHelper.settleCapturedViewAt(-mDragWidth, mDragHeight);
mIsClosing = true;
} else if (releasedChild.getLeft() > 0 && alpha <= 0.4f) {
mDragHelper.settleCapturedViewAt(mDragWidth, mDragHeight);
mIsClosing = true;
} else {
mDragHelper.settleCapturedViewAt(0, mDragHeight);
}
}
} else {
// 根据垂直方向的速度正负决定布局的展示方式
if (yvel > 1500) {
mDragHelper.settleCapturedViewAt(0, mDragHeight);
} else if (yvel < -1500) {
mDragHelper.settleCapturedViewAt(0, 0);
} else {
// 根据releasedChild.getTop()决定布局的展示方式
if (releasedChild.getTop() <= mDragHeight / 2) {
mDragHelper.settleCapturedViewAt(0, 0);
} else {
mDragHelper.settleCapturedViewAt(0, mDragHeight);
}
}
}
invalidate();
}
@Override
public void onViewPositionChanged(final View changedView, int left, int top, int dx, int dy) {
float fraction = top * 1.0f / mDragHeight;
// mFlexView缩放的比率
mFlexScaleRatio = 1 - 0.5f * fraction;
mFlexScaleOffset = changedView.getWidth() / 20;
// 设置缩放基点
changedView.setPivotX(changedView.getWidth() - mFlexScaleOffset);
changedView.setPivotY(changedView.getHeight() - mFlexScaleOffset);
// 设置比例
changedView.setScaleX(mFlexScaleRatio);
changedView.setScaleY(mFlexScaleRatio);
// mFollowView透明度的比率
float alphaRatio = 1 - fraction;
// 设置透明度
mFollowView.setAlpha(alphaRatio);
// 根据垂直方向的dy设置top,产生跟随mFlexView的效果
mFollowView.setTop(mFollowView.getTop() + dy);
// 到底部的时候,changedView的top刚好等于mDragHeight,以此作为水平拖动的基准
mHorizontalDragEnable = top == mDragHeight;
if (mHorizontalDragEnable) {
// 如果水平拖动允许的话,由于设置缩放不会影响mFlexView的宽高(比如getWidth),所以水平拖动距离为mFlexView宽度一半
mDragWidth = (int) (changedView.getMeasuredWidth() * 0.5f);
// 设置mFlexView的透明度,这里向左右水平拖动透明度都随之变化
changedView.setAlpha(1 - Math.abs(left) * 1.0f / mDragWidth);
// 水平拖动一定距离的话,垂直拖动将被禁止
mVerticalDragEnable = left < 0 && left >= -mDragWidth * 0.05;
} else {
// 不是水平拖动的处理
changedView.setAlpha(1);
mDragWidth = 0;
mVerticalDragEnable = true;
}
if (mFlexLayoutPosition == null) {
// 创建子元素位置缓存
mFlexLayoutPosition = new ChildLayoutPosition();
mFollowLayoutPosition = new ChildLayoutPosition();
}
// 记录子元素的位置
mFlexLayoutPosition.setPosition(mFlexView.getLeft(), mFlexView.getRight(), mFlexView.getTop(), mFlexView.getBottom());
mFollowLayoutPosition.setPosition(mFollowView.getLeft(), mFollowView.getRight(), mFollowView.getTop(), mFollowView.getBottom());
// Log.e("FlexCallback", "225行-onViewPositionChanged(): 【" + mFlexView.getLeft() + ":" + mFlexView.getRight() + ":" + mFlexView.getTop() + ":" + mFlexView
// .getBottom() + "】 【" + mFollowView.getLeft() + ":" + mFollowView.getRight() + ":" + mFollowView.getTop() + ":" + mFollowView.getBottom() + "】");
}
}

3.4.接下来是处理测量和定位,我们实现的排列效果类似 LinearLayout 垂直排列的效果,这里被 measureChildWithMargins 的 heightUse 摆了一道;onLayout 的时候在位置缓存不为空的时候直接定位是因为 ViewDragHelper 在处理触摸事件子元素在做一些平移之类的,若是有元素更新了 UI 会导致重新 Layout,例如我的播放器在更新时间的 TextView 时就会如此,因此在 FlexCallback 的 onViewPositionChanged 方法记录位置,在重新 Layout 时恢复位置即可,这个也坑了好久

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
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int desireHeight = 0;
int desireWidth = 0;
int tmpHeight = 0;
if (getChildCount() != 2) {
throw new IllegalArgumentException("只允许容器添加两个子View!");
}
if (getChildCount() > 0) {
for (int i = 0; i < getChildCount(); i++) {
final View child = getChildAt(i);
// 测量子元素并考虑外边距
// 参数heightUse:父容器竖直已经被占用的空间,比如被父容器的其他子 view 所占用的空间;这里我们需要的是子View垂直排列,所以需要设置这个值
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, tmpHeight);
// 获取子元素的布局参数
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
// 计算子元素宽度,取子控件最大宽度
desireWidth = Math.max(desireWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
// 计算子元素高度
tmpHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
desireHeight += tmpHeight;
}
// 考虑父容器内边距
desireWidth += getPaddingLeft() + getPaddingRight();
desireHeight += getPaddingTop() + getPaddingBottom();
// 尝试比较建议最小值和期望值的大小并取大值
desireWidth = Math.max(desireWidth, getSuggestedMinimumWidth());
desireHeight = Math.max(desireHeight, getSuggestedMinimumHeight());
}
// 设置最终测量值
setMeasuredDimension(resolveSize(desireWidth, widthMeasureSpec), resolveSize(desireHeight, heightMeasureSpec));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mFlexLayoutPosition != null) {
// 因为在用到ViewDragHelper处理布局交互的时候,若是有子View的UI更新导致重新Layout的话,需要我们自己处理ViewDragHelper拖动时子View的位置,否则会导致位置错误
// Log.e("YytLayout1", "292行-onLayout(): " + "自己处理布局位置");
mFlexView.layout(mFlexLayoutPosition.getLeft(), mFlexLayoutPosition.getTop(), mFlexLayoutPosition.getRight(), mFlexLayoutPosition.getBottom());
mFollowView.layout(mFollowLayoutPosition.getLeft(), mFollowLayoutPosition.getTop(), mFollowLayoutPosition.getRight(), mFollowLayoutPosition.getBottom());
return;
}
final int paddingLeft = getPaddingLeft();
final int paddingTop = getPaddingTop();
int multiHeight = 0;
int count = getChildCount();
if (count != 2) {
throw new IllegalArgumentException("此容器的子元素个数必须为2!");
}
for (int i = 0; i < count; i++) {
// 遍历子元素并对其进行定位布局
final View child = getChildAt(i);
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
int left = paddingLeft + lp.leftMargin;
int right = child.getMeasuredWidth() + left;
int top = (i == 0 ? paddingTop : 0) + lp.topMargin + multiHeight;
int bottom = child.getMeasuredHeight() + top;
child.layout(left, top, right, bottom);
multiHeight += (child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
}
}

3.5.触摸事件的处理,由于缩放不会影响 mFlexView 真实宽高,ViewDragHelper 仍然会阻断 mFlexView 的真实宽高的区域,所以这里判断手指是否落在 mFlexView 视觉上的范围内,在才去调 ViewDragHelper 的 shouldInterceptTouchEvent 方法

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
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// Log.e("YytLayout", mFlexView.getLeft() + ";" + mFlexView.getTop() + " --- " + ev.getX() + ":" + ev.getY());
// 由于缩放不会影响mFlexView真实宽高,这里手动计算视觉上的范围
float left = mFlexView.getLeft() + mFlexWidth * (1 - mFlexScaleRatio) - mFlexScaleOffset * (1 - mFlexScaleRatio);
float top = mFlexView.getTop() + mFlexHeight * (1 - mFlexScaleRatio) - mFlexScaleOffset * (1 - mFlexScaleRatio);
// 这里所做的是判断手指是否落在mFlexView视觉上的范围内
mInFlexViewTouchRange = ev.getX() >= left && ev.getY() >= top;
if (mInFlexViewTouchRange) {
return mDragHelper.shouldInterceptTouchEvent(ev);
} else {
return super.onInterceptTouchEvent(ev);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mInFlexViewTouchRange) {
// 这里还要做判断是因为,即使我不阻断事件,但是此Layout的子View不消费的话,事件还是给回此Layout
mDragHelper.processTouchEvent(event);
return true;
} else {
// 不在mFlexView触摸范围内,并且子View没有消费,返回false,把事件传递回去
return false;
}
}

3.6.在 computeScroll 中,若是 mIsClosing 为 true,即关闭的整个平移执行完毕了,通知回调事件

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
@Override
public void computeScroll() {
if (mDragHelper.continueSettling(true)) {
invalidate();
} else if (mIsClosing && mOnLayoutStateListener != null) {
// 正在关闭的情况下,并且拖动结束后,告知将要关闭页面
mOnLayoutStateListener.onClose();
mIsClosing = false;
}
}
/**
* 监听布局是否水平拖动关闭了
*/
public interface OnLayoutStateListener {
void onClose();
}
public void setOnLayoutStateListener(OnLayoutStateListener onLayoutStateListener) {
mOnLayoutStateListener = onLayoutStateListener;
}
/**
* 展开布局
*/
public void expand() {
mDragHelper.smoothSlideViewTo(mFlexView, 0, 0);
invalidate();
}

3.7.容器实现了,接下来我们继承 YytLayout 实现播放器页面的组合控件即可,再封装一些常用的方法,这里使用的是大名鼎鼎的 Ijkplayer 实现的播放器,屏蔽了 IjkVideoView 的触摸事件自己处理了;顺带一提,为了实现播放器 Controller 跟随拖动缩放的效果,放弃了常用的 PopupWindow 实现的思路,IjkController 直接是添加到 IjkVideoView 中的,要不弹窗实现跟随播放器太麻烦了

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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
/**
* Created by Oubowu on 2016/12/27 17:32.<p>
* 仿音悦台播放页面的具体实现,组合控件的形式
*/
public class YytPlayer extends YytLayout {
private IjkController mIjkController;
private IjkVideoView mIjkVideoView;
private ImageView mIvAvatar;
private TextView mTvName;
private TextView mTvTime;
private TextView mTvTitle;
private TextView mTvDesc;
private RecyclerView mYytRecyclerView;
private VideoListAdapter mVideoListAdapter;
public YytPlayer(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
// 继承YytLayout并且通过merge标签减少层级来实现组合控件
LayoutInflater.from(context).inflate(R.layout.yyt_player, this, true);
setOnLayoutStateListener(new OnLayoutStateListener() {
@Override
public void onClose() {
setVisibility(View.INVISIBLE);
mIjkVideoView.release(true);
}
});
mIjkVideoView = (IjkVideoView) findViewById(R.id.ijk_player_view);
final int scaledTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
mIjkVideoView.setOnTouchListener(new OnTouchListener() {
float mDownX = 0;
float mDownY = 0;
boolean mClickCancel;
@Override
public boolean onTouch(View v, MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownX = x;
mDownY = y;
break;
case MotionEvent.ACTION_MOVE:
if (Math.abs(mDownX - x) > scaledTouchSlop || Math.abs(mDownY - y) > scaledTouchSlop) {
mClickCancel = true;
}
break;
case MotionEvent.ACTION_UP:
if (!mClickCancel && Math.abs(mDownX - x) <= scaledTouchSlop && Math.abs(mDownY - y) <= scaledTouchSlop) {
// 点击事件偶尔失效,只好这里自己解决了
if (isHorizontalDragEnable()) {
expand();
} else {
mIjkVideoView.toggleMediaControlsVisibility();
}
}
mClickCancel = false;
break;
case MotionEvent.ACTION_CANCEL:
mClickCancel = false;
break;
}
return true;
}
});
mIvAvatar = (ImageView) findViewById(R.id.iv_avatar);
mTvName = (TextView) findViewById(R.id.tv_name);
mTvTime = (TextView) findViewById(R.id.tv_time);
mTvTitle = (TextView) findViewById(R.id.tv_title);
mTvDesc = (TextView) findViewById(R.id.tv_desc);
mVideoListAdapter = new VideoListAdapter();
mVideoListAdapter.setOnItemClickCallback(new OnItemClickCallback() {
@Override
public void onClick(View view, int position) {
int pos = (Integer) view.getTag();
VideoSummary summary = mVideoListAdapter.getData().get(pos);
playVideo(mVideoListAdapter.getData(), summary);
}
});
mYytRecyclerView = (RecyclerView) findViewById(R.id.yyt_recycler_view);
GridLayoutManager gridLayoutManager = new GridLayoutManager(context, 2, LinearLayoutManager.VERTICAL, false);
mYytRecyclerView.setLayoutManager(gridLayoutManager);
mYytRecyclerView.setNestedScrollingEnabled(false);
mYytRecyclerView.addItemDecoration(new VideoListItemDecoration(context));
mYytRecyclerView.setAdapter(mVideoListAdapter);
}
// 播放视频
private void playVideo(String path, String name) {
try {
if (mIjkController == null) {
IjkMediaPlayer.loadLibrariesOnce(null);
IjkMediaPlayer.native_profileBegin("libijkplayer.so");
mIjkController = new IjkController(mIjkVideoView, name);
mIjkController.setOnViewStateListener(new IjkController.OnViewStateListener() {
@Override
public void onBackPress() {
stop();
}
});
mIjkVideoView.setMediaController(mIjkController);
mIjkVideoView.setOnPreparedListener(new IMediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(IMediaPlayer mp) {
mIjkVideoView.start();
}
});
mIjkVideoView.setOnErrorListener(new IMediaPlayer.OnErrorListener() {
@Override
public boolean onError(IMediaPlayer mp, int what, int extra) {
Toast.makeText(getContext(), "视频播放出错了╮(╯Д╰)╭", Toast.LENGTH_SHORT).show();
return true;
}
});
} else {
// 重新设置视频名字
mIjkController.setVideoName(name);
}
// 设置这个TextureView播放器缩放就正常了
mIjkVideoView.setRender(IjkVideoView.RENDER_TEXTURE_VIEW);
// 因为每次setRender都会移除view再添加,为了缩放效果这里控制器是添加到IjkVideoView中的,所以这里也要重新添加才能在IjkVideoView的最上面
mIjkController.updateControlView();
// 显示加载条
mIjkController.showProgress();
// 播放视频
mIjkVideoView.setVideoURI(Uri.parse(path));
} catch (UnsatisfiedLinkError e) {
e.printStackTrace();
Toast.makeText(getContext(), "你的CPU是" + Build.CPU_ABI + ",当前播放器使用的编译版本" + BuildConfig.FLAVOR + "不匹配!", Toast.LENGTH_LONG).show();
}
}
/**
* 显示布局,并且播放视频
*
* @param data 视频列表,用于播放页面下面的列表布局
* @param summary 播放的视频信息
*/
public void playVideo(List<VideoSummary> data, VideoSummary summary) {
// 拿到数据,设置到播放的布局的相关信息
Glide.with(getContext()).load(summary.mTopicImg).transform(new GlideCircleTransform(getContext())).into(mIvAvatar);
mTvName.setText(summary.mTopicName);
mTvTime.setText(summary.mPtime);
mTvTitle.setText(Html.fromHtml(summary.mTitle));
if (summary.mDescription.isEmpty()) {
mTvDesc.setText(summary.mTopicDesc);
} else {
mTvDesc.setText(Html.fromHtml(summary.mDescription));
}
// 设置YytLayout可见,并且展开
setVisibility(View.VISIBLE);
expand();
mVideoListAdapter.setData(data);
mVideoListAdapter.setItemWidth(mYytRecyclerView.getWidth() / 2);
mVideoListAdapter.notifyDataSetChanged();
// 播放视频
playVideo(summary.mMp4HdUrl == null ? summary.mMp4Url : summary.mMp4HdUrl, summary.mTitle);
}
// 开始播放
public void start() {
if (mIjkVideoView != null && !mIjkVideoView.isPlaying()) {
mIjkVideoView.start();
}
}
// 暂停播放
public void pause() {
if (mIjkVideoView != null && mIjkVideoView.isPlaying()) {
mIjkVideoView.pause();
}
}
// 停止播放
public void stop() {
setVisibility(View.INVISIBLE);
if (mIjkVideoView != null) {
mIjkVideoView.release(true);
}
}
public boolean isShowing() {
return getVisibility() == VISIBLE;
}
}

4.总结

说难也不难,就是各种抠细节需要脑洞,各位不妨看到好玩的交互自己打开脑洞一下,接下来可能要实现下 UC 浏览器播放器的效果,感觉也是非常有意思。

#扔个Demo地址吧:Github:YinyuetaiPlayer,觉得还不错的话给个Star哦!