可拖动、可靠边的 popupWindow 实现

0 背景

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

1 实现

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

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

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 复制代码
<?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

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 中使用

java 复制代码
mTextView = findViewById(R.id.myView);
if (mMyPopupWindow == null) {
    mMyPopupWindow = new MyPopupWindow(MainActivity.this);
}
mTextView.post(() -> {
    mMyPopupWindow.show(mTextView);
});
相关推荐
帅得不敢出门5 小时前
安卓设备adb执行AT指令控制电话卡
android·adb·sim卡·at指令·电话卡
我又来搬代码了7 小时前
【Android】使用productFlavors构建多个变体
android
德育处主任9 小时前
Mac和安卓手机互传文件(ADB)
android·macos
芦半山9 小时前
Android“引用们”的底层原理
android·java
迃-幵9 小时前
力扣:225 用队列实现栈
android·javascript·leetcode
大风起兮云飞扬丶10 小时前
Android——从相机/相册获取图片
android
Rverdoser10 小时前
Android Studio 多工程公用module引用
android·ide·android studio
aaajj10 小时前
[Android]从FLAG_SECURE禁止截屏看surface
android
@OuYang10 小时前
android10 蓝牙(二)配对源码解析
android
Liknana10 小时前
Android 网易游戏面经
android·面试