前言:
这两年直播带货非常火,前段时间我们项目需求也提了这个功能,于是查看抖音、快手、淘宝的实现方式,结合咱们自己的项目,直播带货有2种场景:
1.一种是在单一页面进行商品购买.
2.一种是全局悬浮窗,在所有页面都可以去购物.
3.单一页面一个拖拽按钮就可以实现,全局悬浮窗比较麻烦,由于涉及到多个页面的跳转传递数据和回收问题。
4.单个页面的悬浮按钮,可以用一个自定义的可拖拽按钮来实现.
5.实现思路:
5.1 通过重写控件的onTouchEvent方法监听触摸效果.
5.2 通过view的setX和setY方法来实现移动.
5.3 使用属性动画来实现边缘吸附效果.
5.4 左右吸附功能:
ScreenUtils.dip2px(getContext(), 20)是控制左右边距的大小,通过dp转化为px,适配不同分辨率的机型。如果需要靠边吸附,更改为0即可,如果不需要自动吸附功能,也直接注释掉就可以了,使用十分简单方便。
5.5 可拖动的悬浮按钮
/** * @作者: njb * @时间: 2019/12/10 14:38 * @描述: 可拖动的悬浮按钮 */ @SuppressLint("AppCompatCustomView") public class DragFloatActionButton extends ImageView { private int parentHeight; private int parentWidth; private int lastX; private int lastY; private boolean isDrag; public DragFloatActionButton(Context context) { super(context); } public DragFloatActionButton(Context context, AttributeSet attrs) { super(context, attrs); } public DragFloatActionButton(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @SuppressLint("ClickableViewAccessibility") @Override public boolean onTouchEvent(MotionEvent event) { int rawX = (int) event.getRawX(); int rawY = (int) event.getRawY(); switch (event.getAction() & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: isDrag = false; setPressed(true); getParent().requestDisallowInterceptTouchEvent(true); lastX = rawX; lastY = rawY; ViewGroup parent; if (getParent() != null) { parent = (ViewGroup) getParent(); parentHeight = parent.getHeight(); parentWidth = parent.getWidth(); } break; case MotionEvent.ACTION_MOVE: if (parentHeight <= 0 || parentWidth == 0) { isDrag = false; break; } else { isDrag = true; } int dx = rawX - lastX; int dy = rawY - lastY; //这里修复一些华为手机无法触发点击事件 int distance = (int) Math.sqrt(dx * dx + dy * dy); if (distance == 0) { isDrag = false; break; } float x = getX() + dx; float y = getY() + dy; //检测是否到达边缘 左上右下 x = x < 0 ? 0 : x > parentWidth - getWidth() ? parentWidth - getWidth() : x; y = getY() < 0 ? 0 : getY() + getHeight() > parentHeight ? parentHeight - getHeight() : y; setX(x); setY(y); lastX = rawX; lastY = rawY; Rlog.d("aa", "isDrag=" + isDrag + "getX=" + getX() + ";getY=" + getY() + ";parentWidth=" + parentWidth); break; case MotionEvent.ACTION_UP: if (isDrag) { //恢复按压效果 setPressed(false); moveHide(rawX); } break; default: break; } //如果是拖拽则消耗事件,否则正常传递即可。 return isDrag || super.onTouchEvent(event); } /** * 吸附动画 * @param rawX */ private void moveHide(int rawX) { if (rawX >= parentWidth / 2) { //靠右吸附 animate().setInterpolator(new DecelerateInterpolator()) .setDuration(500) .xBy(parentWidth - getWidth() - getX() - ScreenUtil.dip2px( 20)) .start(); } else { //靠左吸附 ObjectAnimator oa = ObjectAnimator.ofFloat(this, "x", getX(), ScreenUtil.dip2px(20)); oa.setInterpolator(new DecelerateInterpolator()); oa.setDuration(500); oa.start(); } } }
6.单页面实现的效果截图如下:
点击按钮就可以进入直播购物界面,这里就不写了,很简单的一个跳转事件.后面会给出全局悬浮窗实现方案。
7.全局悬浮窗:
这里全局悬浮窗用的是EasyFloat这个第三方库,由于项目时间太赶,所以用的现成的方案,没有专门用系统的windowmanager, EasyFloat的github地址为: GitHub - princekin-f/EasyFloat: 🔥 EasyFloat:浮窗从未如此简单(Android可拖拽悬浮窗口,支持页面过滤、自定义动画,可设置单页面浮窗、前台浮窗、全局浮窗,浮窗权限按需自动申请...),这里有详细的使用说明和介绍.
7.1 导入方式:implementation 'com.github.princekin-f:EasyFloat:1.2.1'
7.2 如果你要在整个项目全局使用的话就可以直接在application中初始化,
** * @author: njb * @date: 2020/2/28 0028 12:35 * @desc: */ public class App extends Application { //全局Context private static Context sContext; @Override public void onCreate() { super.onCreate(); sContext = getApplicationContext(); //初始化悬浮窗 initEasyFloat(); } private void initEasyFloat() { EasyFloat.init(this); } public static Context getContext() { return sContext; } }
7.3 请求系统浮窗权限
loadPermissions();
/** * 请求悬浮窗权限 */ private void loadPermissions() { if (PermissionUtils.checkPermission(this)) { String liveId = "100086"; //初始化悬浮窗 initFlowWindow(liveId); }else { final AlertDialog.Builder alert = new AlertDialog.Builder(this); alert.setMessage("使用浮窗功能,需要您授权悬浮窗权限。"); alert.setNegativeButton("取消", ((dialog, which) -> dialog.dismiss())); alert.setPositiveButton("去开启", (dialog, which) -> { String liveId = "100086"; initFlowWindow(liveId); dialog.dismiss(); }); alert.show(); } }
7.4 初始化悬浮窗布局,由于我们的需求是从直接跳转到购物页面,所以点击按钮回到直播间,这里只是给出悬浮窗的使用,具体的业务逻辑要根据需求自己去实现,当然回直播间后这个悬浮按钮也随之隐藏,如果需求不需要隐藏也可以设置不隐藏.
/** * 初始化悬浮窗布局 * @param liveId */ private void initFlowWindow(String liveId) { if (!TextUtils.isEmpty(liveId)) { EasyFloat.with(this) // 设置浮窗xml布局文件 .setLayout(R.layout.layout_float_window, view -> { // view就是我们传入的浮窗xml布局 view.findViewById(R.id.go_to_image).setOnClickListener(view1 -> { //回直播 startActivity(new Intent(Mainivity.this,LiveActivity.class)) //隐藏悬浮框 EasyFloat.hideAppFloat("live"); EasyFloat.dismissAppFloat("live"); }); }) // 设置浮窗显示类型,默认只在当前Activity显示,可选一直显示、仅前台显示 .setShowPattern(ShowPattern.FOREGROUND) // 设置吸附方式,共15种模式,详情参考SidePattern .setSidePattern(SidePattern.RESULT_HORIZONTAL) // 设置浮窗的标签,用于区分多个浮窗 .setTag("live") // 设置浮窗是否可拖拽 .setDragEnable(true) // 设置浮窗的对齐方式和坐标偏移量 .setGravity(Gravity.END | Gravity.CENTER_VERTICAL, 0, 200) // 设置宽高是否充满父布局,直接在xml设置match_parent属性无效 .setMatchParent(false, false) // 设置Activity浮窗的出入动画,可自定义,实现相应接口即可(策略模式),无需动画直接设置为null .setAnimator(new DefaultAnimator()) // 设置系统浮窗的出入动画,使用同上 .setAppFloatAnimator(new AppFloatDefaultAnimator()).show(); EasyFloat.showAppFloat("live"); } else { EasyFloat.hide(); EasyFloat.hideAppFloat("live"); } }