如今人人玩抖音、小视频的社会,功能特效也越来越多,越来越具有特色性,玩法多重多样,刷着视频突然对一个主播感兴趣,点击进入一个直播间,哇,不愧是大主播,礼物特效、消息一条一条喷薄而出,作为一名技术开发者,就喜欢多多钻钻,善于发挥脑细胞能量;
场景分析:进入一个直播间,上下滑动可以切换直播间,左右滑动可以清屏直播,向右滑动可以进入博主主页进行查看,滑动消息区域可以查看以前的消息等;看起来一个简单的操作,分析过后发现用到的技术还不少;
技术分析:首先想到的是既然可以上下滑动,Recyclerview主动举起了手,左右滑动、上下滑动那就是监听手势相关,dispatchTouchEvent()–>onInterceptTouchEvent()–>onTouchEvent(),滑动消息列表通知父View消息View自己要消费事件,通过ACTION_DOWN、ACTION_MOVE,监听点击区域和滑动区域来控制事件有谁消费,事件传递不再累赘可自行查阅,主要是在onInterceptTouchEvent里面通过点击区域来控制谁消费;
分析完成就开始干吧,拿笔来。
这里自定义一个FramLayout嵌套一个RecyclerView(直播间)+Fragment(适配器Item)+Recyclerview(消息列表)
/**
* 视频播放上下滑动切换控件
*/
public class LiveRecyclerView extends FrameLayout {
private static final String TAG = LiveRecyclerView.class.getSimpleName();
private static final int COLOR_BG = Color.BLACK; // 背景色
private static final float DRAG_RATE = 2.5f; // 下拉上拉的粘性(数值越大越难下拉)
private static final int TEXT_COLOR = 0xff999999; // 提示文字颜色
private static final float TEXT_SIZE = 12; // 提示文字大小
private static final float TEXT_MARGIN = 150; // 提示文字和 RecyclerView 的间距
private RecyclerView recyclerView;
private TextView tvTip;
private PagerLayoutManager layoutManager;
private float mDownX = -1;
private float mDownY = -1;
private float mLastY = -1;
private boolean isPulling;
private RoomScrollControlListener mRoomScrollListener = null;
public void setRoomScrollListener(RoomScrollControlListener roomScrollListener) {
mRoomScrollListener = roomScrollListener;
}
public LiveRecyclerView(Context context) {
super(context);
init();
}
public LiveRecyclerView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public LiveRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
setBackgroundColor(COLOR_BG);
// 提示TextView
tvTip = new TextView(getContext());
tvTip.setTextSize(TEXT_SIZE);
tvTip.setTextColor(TEXT_COLOR);
tvTip.setGravity(Gravity.CENTER_HORIZONTAL);
addView(tvTip, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
// RecyclerView
recyclerView = new NestRecyclerView(getContext());
addView(recyclerView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
layoutManager = new PagerLayoutManager(getContext());
recyclerView.setLayoutManager(layoutManager);
recyclerView.getParent().requestDisallowInterceptTouchEvent(true);
}
public RecyclerView getRecyclerView() {
return recyclerView;
}
public void setAdapter(LivePlayAdapter adapter) {
recyclerView.setAdapter(adapter);
layoutManager.setOnPageChangeListener(adapter);
}
public void scrollToPosition(int position) {
layoutManager.scrollToPositionWithOffset(position, 0);
}
//是否禁止滑动
public void setCanSwipe(boolean canSwipe) {
this.canSwipe = canSwipe;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mDownX = ev.getRawX();
mDownY = ev.getRawY();
mLastY = ev.getRawY();
Log.e(TAG, "onInterceptTouchEvent ACTION_DOWN X坐标:" + mDownX + " Y坐标:" + mDownY);
break;
case MotionEvent.ACTION_MOVE:
// Log.e(TAG, "onInterceptTouchEvent ACTION_MOVE");
float deltaX = ev.getRawX() - mDownX;
float deltaY = ev.getRawY() - mDownY;
if (Math.abs(deltaY) > Math.abs(deltaX)) {
if (mDownX > 63 && mDownX < 450 && mDownY > 750 && mDownY < 1221) {
Log.e(TAG, "上下滑动大于左右且点击在消息列表区域");
setCanSwipe(false);
recyclerView.setNestedScrollingEnabled(false);
if (mRoomScrollListener!=null){
mRoomScrollListener.closeRefresh();
}
return false;
}
...
}
break;
}
return canSwipe && super.onInterceptTouchEvent(ev);
}
private boolean canSwipe = true;
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (mDownY == -1) {
mDownY = ev.getRawY();
}
if (mLastY == -1) {
mLastY = ev.getRawY();
}
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
float x = ev.getRawX();
float y = ev.getRawY();
Log.e(TAG, "onTouchEvent ACTION_DOWN " + x + " y坐标 " + y);
return false;
case MotionEvent.ACTION_MOVE:
if (mRoomScrollListener!=null){
mRoomScrollListener.showRefresh();
}
Log.e(TAG, "onTouchEvent ACTION_MOVE");
float deltaY = ev.getRawY() - mDownY;
if (deltaY > 0 && !recyclerView.canScrollVertically(-1)) {
tvTip.setText("没有更多直播啦");
tvTip.setY(deltaY / DRAG_RATE - TEXT_MARGIN);
recyclerView.setY(deltaY / DRAG_RATE);
isPulling = true;
} else if (deltaY < 0 && !recyclerView.canScrollVertically(1)) {
tvTip.setText("已经到底啦");
tvTip.setY(getHeight() + deltaY / DRAG_RATE + TEXT_MARGIN);
recyclerView.setY(deltaY / DRAG_RATE);
isPulling = true;
}
mLastY = ev.getRawY();
break;
default:
mDownY = -1;
mLastY = -1;
if (isPulling) {
TranslateAnimation animation;
// TextView 归位
if (recyclerView.getY() > 0) {
animation = new TranslateAnimation(0, 0, tvTip.getY(), -TEXT_MARGIN - tvTip.getHeight());
} else {
animation = new TranslateAnimation(0, 0, tvTip.getY(), getHeight() + TEXT_MARGIN);
}
animation.setDuration(300);
animation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
tvTip.setY(-TEXT_MARGIN - tvTip.getHeight());
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
tvTip.setY(0);
tvTip.startAnimation(animation);
// RecyclerView 归位
TranslateAnimation animation1 = new TranslateAnimation(0, 0, recyclerView.getY(), 0);
animation1.setDuration(300);
animation1.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
recyclerView.setY(0);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
recyclerView.setY(0);
recyclerView.startAnimation(animation1);
isPulling = false;
}
break;
}
return canSwipe && super.onTouchEvent(ev);
}
}
public class NestRecyclerView extends RecyclerView {
private static final String TAG = NestRecyclerView.class.getSimpleName();
private float downX, downY;
public NestRecyclerView(@NonNull Context context) {
super(context);
}
public NestRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public NestRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = ev.getX();
downY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
int width = getWidth();
int height = getHeight();
if (downX > width/11.0769 && downX < width/1.6 && downY > height/1.813 && downY < height/1.1138) {
Log.e(TAG, "onInterceptTouchEvent ACTION_MOVE 在消息列表区域");
//消息区域
return false;
}
}
return super.onInterceptTouchEvent(ev);
}
}