可拖动、可靠边的 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);
});
相关推荐
阿巴斯甜15 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker15 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952716 小时前
Andorid Google 登录接入文档
android
黄林晴18 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_2 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android