可拖动、可靠边的 popupWindow 实现

0 背景

  开发要实现一个可以拖动的圆角小窗,要求松手时,哪边近些靠哪边。并且还规定了拖动范围。样式如下:
在这里插入图片描述

1 实现

首先把 PopupWindow 的布局文件 pop.xml 实现

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="88dp"
    android:layout_height="132dp"
    android:background="@drawable/radius_12"
    android:id="@+id/mini_popup"
    android:visibility="visible">

    <com.google.android.material.imageview.ShapeableImageView
        android:id="@+id/iv_live_cover"
        android:layout_width="88dp"
        android:scaleType="fitXY"
        android:layout_height="132dp"
        android:background="@color/purple_200"
        app:shapeAppearanceOverlay="@style/MiniDialogRoundedImageStyle" />

    <ImageView
        android:id="@+id/iv_close"
        android:layout_width="16dp"
        android:layout_height="16dp"
        android:layout_alignParentRight="true"
        android:layout_marginTop="4dp"
        android:layout_marginRight="4dp"
        android:src="@color/teal_200" />
</RelativeLayout>

布局中圆角和 PopupWindow 的动画 style.xml

    <!-- 圆角图片 -->
    <style name="MiniDialogRoundedImageStyle">
        <item name="cornerFamily">rounded</item>
        <item name="cornerSize">12dp</item>
    </style>

    <!-- PopupWindow 的动画效果 -->
    <style name="PopupWindowAnimation">
        <item name="android:windowEnterAnimation">@anim/live_popup_window_in_anim</item>
    </style>

radius_12.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <corners android:radius="12dp"/>
    <solid android:color="@color/white"/>
</shape>

MyPopupWindow.java

package com.example.myapplication.popupwindow;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.PopupWindow;


import com.bumptech.glide.Glide;
import com.example.myapplication.R;

public class MyPopupWindow extends PopupWindow {
    private Context mContext;
    private View mRootView;

    // 背景
    private ImageView mBackground;

    // 关闭弹窗
    private ImageView mIvClose;


    // 弹窗的移动范围
    private int mMinX;
    private int mMinY;
    private int mMaxX;
    private int mMaxY;

    // 屏幕宽高
    private int mScreenWidth;

    public MyPopupWindow(Context context) {
        super(context);
        mContext = context;
        mRootView = View.inflate(mContext, R.layout.pop, null);

        mScreenWidth = getScreenWidth(mContext);
        mMinX = dp2px(12);
        mMaxX = mScreenWidth - dp2px(12) - dp2px(88);
        mMinY = dp2px(12);
        mMaxY = dp2px(500);

        // 为了保证整体是圆角形状
        mRootView.findViewById(R.id.mini_popup).setClipToOutline(true);
        initView();
    }

    private void initView() {
        setContentView(mRootView);
        mBackground = mRootView.findViewById(R.id.iv_live_cover);
        mIvClose = mRootView.findViewById(R.id.iv_close);

        mIvClose.setOnClickListener(view -> this.dismiss());
        // 小窗的宽高
        setHeight(dp2px(132));
        setWidth(dp2px(88));
        this.setTouchInterceptor(new View.OnTouchListener() {
            int orgX, orgY;
            int offsetX, offsetY;

            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                switch (motionEvent.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        orgX = (int) motionEvent.getX();
                        orgY = (int) motionEvent.getY();
                        break;
                    case MotionEvent.ACTION_MOVE:
                        offsetX = (int) motionEvent.getRawX() - orgX;
                        offsetY = (int) motionEvent.getRawY() - orgY;
                        // 限制 x 坐标
                        offsetX = Math.max(offsetX, mMinX);
                        offsetX = Math.min(offsetX, mMaxX);
                        // 限制 y 坐标
                        offsetY = Math.max(offsetY, mMinY);
                        offsetY = Math.min(offsetY, mMaxY);
                        update(offsetX, offsetY, -1, -1, true);
                        break;
                    case MotionEvent.ACTION_UP:
                        // 小窗靠边
                        if (offsetX < mScreenWidth / 2) {
                            offsetX = mMinX;
                        } else {
                            offsetX = mMaxX;
                        }
                        update(offsetX, offsetY, -1, -1, true);
                        break;
                }
                // 避免 view 中的其他点击事件被吞掉
                return false;
            }
        });
        // 设置小窗背景
        this.setBackgroundDrawable(mContext.getResources().getDrawable(R.drawable.abc_vector_test));
        // 出现的动画
        this.setAnimationStyle(R.style.PopupWindowAnimation);
    }

    public void show(View anchor) {
        this.showAtLocation(anchor, Gravity.NO_GRAVITY, mMaxX, mMaxY);
    }

    @SuppressLint("CheckResult")
    public void setBackground(String url) {
        if (url != null && !TextUtils.isEmpty(url))
            Glide.with(mContext).load(url).into(mBackground);
    }

    public int dp2px(float dpValue) {
        return (int) (0.5f + dpValue * Resources.getSystem().getDisplayMetrics().density);
    }
    public int getScreenWidth(Context context) {
        DisplayMetrics localDisplayMetrics = new DisplayMetrics();
        ((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(localDisplayMetrics);
        return localDisplayMetrics.widthPixels;
    }
}

最后在 MainActivity 中使用

mTextView = findViewById(R.id.myView);
if (mMyPopupWindow == null) {
    mMyPopupWindow = new MyPopupWindow(MainActivity.this);
}
mTextView.post(() -> {
    mMyPopupWindow.show(mTextView);
});

2 添加点击事件

  由于在触碰 view 时添加了移动事件,现在又要添加点击事件。此时我们进行这样认定,如果 ACTION_DOWN 到 ACTION_UP 时间不超过 250 ms,且 view 未移动,则认定此次是点击事件而非移动事件。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值