• 可拖动、可靠边的 popupWindow 实现


    0 背景

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

    1 实现

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

    
    <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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

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

        
        <style name="MiniDialogRoundedImageStyle">
            "cornerFamily">rounded
            "cornerSize">12dp
        style>
    
        
        <style name="PopupWindowAnimation">
            "android:windowEnterAnimation">@anim/live_popup_window_in_anim
        style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    radius_12.xml

    
    <shape xmlns:android="http://schemas.android.com/apk/res/android">
        <corners android:radius="12dp"/>
        <solid android:color="@color/white"/>
    shape>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124

    最后在 MainActivity 中使用

    mTextView = findViewById(R.id.myView);
    if (mMyPopupWindow == null) {
        mMyPopupWindow = new MyPopupWindow(MainActivity.this);
    }
    mTextView.post(() -> {
        mMyPopupWindow.show(mTextView);
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
  • 相关阅读:
    Seata 1.5.2 源码学习(Client端)
    HDLBits: 在线学习 SystemVerilog(四)-Problem 15-18
    优秀代码赏读之第一篇
    [CISCN2019 华北赛区 Day2 Web1]Hack World 布尔注入
    Docker镜像、容器、仓库及数据管理
    监控技术栈中的知识点
    【Github】sync fork后,意外关闭之前提交分支的pr申请 + 找回被关闭的pr请求分支中的文件
    OSPF状态机+SPF算法
    Scrum敏捷模式的优势点、实践经验及适用企业
    TinyXml库的用法之xml文件节点遍历与节点删除
  • 原文地址:https://blog.csdn.net/weixin_41618135/article/details/134493330