自定义view 写一个popup view

目标:

实现一个popup view 自动显示在点击的view的附近,且箭头一直指向该view的水平中心位置

效果图:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

思路

自定义view包含PopupWindow的实例 对该实例进行操控

步骤1 创建popupview的布局popview_container.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:gravity="center_horizontal"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/arrow_icon_up"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginBottom="-13dp"
        android:src="@drawable/popup_arrow_up" />


    <LinearLayout
        android:id="@+id/content_container"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingStart="7dp"
        android:paddingEnd="7dp"
        android:paddingTop="7dp"
        android:paddingBottom="7dp"
        android:background="#ccc"
        android:orientation="vertical">
    </LinearLayout>

    <ImageView
        android:id="@+id/arrow_icon_down"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginTop="-13dp"
        android:src="@drawable/popup_arrow_down"
        android:visibility="gone" />

</LinearLayout>



使用到的两个箭头
popup_arrow_down.xml

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="40dp"
    android:height="20dp"
    android:viewportWidth="40"
    android:viewportHeight="20">
  <path
      android:pathData="M0,0h40L20,20 0,0z"
      android:fillColor="#ccc"/>
</vector>

popup_arrow_up.xml

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="40dp"
    android:height="20dp"
    android:viewportWidth="40"
    android:viewportHeight="20">
  <path
      android:pathData="M0,20h40L20,0 0,20z"
      android:fillColor="#ccc"/>
</vector>

步骤2 popupview item的布局popview_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:minWidth="480dp"
    android:layout_height="wrap_content"
    android:background="@drawable/popupview_item_bg"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="120dp"
        android:layout_gravity="center_vertical"
        android:orientation="horizontal">

        <ImageView
            android:id="@+id/popup_view_item_icon"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:layout_gravity="center"
            android:layout_marginStart="35dp"
            android:src="@drawable/ic_delete" />

        <TextView
            android:id="@+id/popup_view_item_des"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical|start"
            android:layout_marginStart="35dp"
            android:layout_marginEnd="20dp"
            android:ellipsize="marquee"
            android:singleLine="true"/>
    </LinearLayout>

    <ImageView
        android:id="@+id/popup_view_item_divider"
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="#0f0" />
</LinearLayout>



其中会使用到几个图标和背景
ic_delete.xml

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="50dp"
    android:height="50dp"
    android:viewportWidth="50"
    android:viewportHeight="50">
    <path
        android:fillColor="#0ff"
        android:pathData="M40.206,10.682L9.75,10.682c-0.962,0 -1.75,0.788 -1.75,1.75 0,0.963 0.788,1.75 1.75,1.75h1.444L11.194,38.95c0,3.15 2.538,5.688 5.689,5.688h16.234c3.15,0 5.689,-2.538 5.689,-5.688L38.806,14.183h1.444c0.962,0 1.75,-0.788 1.75,-1.75 -0.044,-0.963 -0.831,-1.75 -1.794,-1.75zM19.815,36.15c0,0.7 -0.57,1.27 -1.313,1.27 -0.744,0 -1.313,-0.57 -1.313,-1.313L17.189,21.228c0,-0.7 0.569,-1.27 1.313,-1.27s1.313,0.57 1.313,1.27v14.921zM26.291,36.15c0,0.7 -0.569,1.27 -1.313,1.27s-1.313,-0.57 -1.313,-1.313L23.665,21.228c0,-0.7 0.57,-1.27 1.313,-1.27 0.744,0 1.313,0.57 1.313,1.27v14.921zM32.767,36.15c0,0.7 -0.569,1.27 -1.313,1.27s-1.312,-0.57 -1.312,-1.313L30.142,21.228c0,-0.7 0.568,-1.27 1.312,-1.27 0.744,0 1.313,0.57 1.313,1.27v14.921zM31.542,7.75c0,-0.962 -0.788,-1.75 -1.75,-1.75h-9.627c-0.963,0 -1.75,0.788 -1.75,1.75L18.415,9.5h13.127L31.542,7.75z" />
</vector>

popupview_item_bg.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true"  android:drawable="@color/colorPrimary" />
    <!-- Non pressed, Non selected, Non focused and Non enabled -->
    <item android:state_pressed="false"  android:drawable="@color/transparent" />
</selector>

步骤3 创建PopupView java文件

步骤3.1在PopupView内部新建内部类PopupViewItem 用于创建PopupView的Item

package com.example.testfragment.ui.main;

import android.content.Context;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.TextView;

import com.example.testfragment.R;
import com.example.testfragment.util.ViewUtil;

import java.util.List;

public class PopupView {
    private final Context mContext;
    private PopupWindow mPopup;
    private View mArrow;

    //构造函数
    public PopupView(Context context) {
        mContext = context;
    }

    public void dismissView() {
        if (mPopup == null) {
            return;
        }
        if (mPopup.isShowing()) {
            mPopup.dismiss();
        }
    }

    private void clear() {
        if (mPopup != null) {
            mPopup.dismiss();
            mPopup = null;
        }
    }

    private void initView(View view) {
        clear();
        mPopup = new PopupWindow(view, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT);
        mPopup.setOutsideTouchable(true);
        mPopup.setTouchable(true);
        mPopup.update();
        mPopup.setFocusable(true);
    }

    //默认以点击view的坐下侧为起点 显示popup
    public void showView(View anchor, List<PopupViewItem> itemList) {
        dismissView();
        if (mContext == null) {
            return;
        }
        if (mContext.getResources() == null) {
            return;
        }
        if (anchor == null) {
            return;
        }

        //start to layout the pop-up view and set its event
        View currentPopupLayoutView = View.inflate(mContext, R.layout.popview_container, null);
        LinearLayout container = currentPopupLayoutView.findViewById(R.id.content_container);
        if (container != null) {
            int i = 0;
            for (PopupViewItem item : itemList) {
                //the last item should not show the divider
                i++;
                addClickActionItem(container, item, i != itemList.size());
            }
        }

        int popupViewHeightWithAnchor = getPopupViewMeasuredHeightWithAnchor(anchor, currentPopupLayoutView);
        int popupViewHeight = getPopupViewMeasuredHeight(currentPopupLayoutView);
        int anchorViewHeight = getAnchorViewMeasuredHeight(anchor);
        if (anchorViewHeight <= 0 || popupViewHeight <= 0 || popupViewHeightWithAnchor <= 0) {
            return;
        }

        //如果Y方向 空间不够显示 需要在Y方向上反转显示方向
        boolean revert = needReverse(anchor, popupViewHeightWithAnchor);
        if (revert) {
            revertPopupView(currentPopupLayoutView);
        }else{
            mArrow = currentPopupLayoutView.findViewById(R.id.arrow_icon_up);
        }

        int anchorViewY = getAnchorViewY(anchor);
        int y = revert ? (anchorViewY - popupViewHeight) : (anchorViewY + anchorViewHeight);

        //默认 popup view的正中与Anchor的水平正中对齐
        //1.计算Anchor x的中心
        int anchorXCenter = getAnchorViewX(anchor) + anchor.getWidth() / 2;
        //2.获取popup view的宽度
        int popupViewWidth = getPopupViewMeasuredWidth(currentPopupLayoutView);
        int popupViewStartX = anchorXCenter - popupViewWidth / 2;

        //如果水平空间不够显示popup view(被强行挤到屏幕内) 需要挪动箭头图标 计算挪动图标的水平值x
        if (popupViewStartX < 0) {
            //向左移动箭头(参数<0)
            mArrow.setTranslationX(popupViewStartX);
        } else if ((popupViewStartX + popupViewWidth) > ViewUtil.getAppScreenWidth(mContext)) {
            //向右移动箭头(参数>0)
            mArrow.setTranslationX(popupViewStartX + popupViewWidth - ViewUtil.getAppScreenWidth(mContext));
        }

        initView(currentPopupLayoutView);
        showView(anchor, popupViewStartX, y);
    }


    private void showView(View anchor, int x, int y) {
        if (mPopup != null) {
            mPopup.showAtLocation(anchor, Gravity.TOP | Gravity.START, x, y);
        }
    }

    //在某个view显示popup view,其中某个view就是anchor
    //计算anchor+popup view的高度
    private int getPopupViewMeasuredHeightWithAnchor(View anchor, View currentPopupLayoutView) {
        if (anchor == null || currentPopupLayoutView == null) {
            return -1;
        }
        //need to call measure() to generate the width and height
        currentPopupLayoutView.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        return currentPopupLayoutView.getMeasuredHeight() + anchor.getMeasuredHeight();
    }

    /**
     * @param container     popup view
     * @param item          popup的item项
     * @param isShowDivider 是否显示divider
     */
    private void addClickActionItem(LinearLayout container, final PopupViewItem item, boolean isShowDivider) {
        if (item == null || container == null) {
            return;
        }
        final View inflateView = ViewUtil.addView(R.layout.popview_item, container);
        if (inflateView != null) {
            if (item.getMinWidth() > -1) {
                inflateView.setMinimumWidth(item.getMinWidth());
            }
            ImageView icon = inflateView.findViewById(R.id.popup_view_item_icon);
            icon.setImageDrawable(mContext.getResources().getDrawable(item.getIconResID(), mContext.getTheme()));
            TextView tvDes = inflateView.findViewById(R.id.popup_view_item_des);
            tvDes.setText(item.getDescriptionStr());
            inflateView.setOnClickListener(item.getOnClickListener());

            if (!isShowDivider) {
                View dividerView = inflateView.findViewById(R.id.popup_view_item_divider);
                dividerView.setVisibility(View.GONE);
            }
        }
    }

    //如果剩余空间不够显示popup,向上反转
    private boolean needReverse(View anchor, int popupViewHeightWithAnchor) {
        return mContext != null && popupViewHeightWithAnchor + getAnchorViewY(anchor) > ViewUtil.getAppScreenHeight(mContext);
    }

    private void revertPopupView(View popupView) {
        if (popupView != null) {
            View arrowUp = popupView.findViewById(R.id.arrow_icon_up);
            View arrowDown = popupView.findViewById(R.id.arrow_icon_down);
            if (arrowUp != null && arrowDown != null) {
                arrowUp.setVisibility(View.GONE);
                arrowDown.setVisibility(View.VISIBLE);
                mArrow = arrowDown;
            }
        }
    }

    //计算anchorView的X绝对坐标
    private int getAnchorViewX(View anchor) {
        int[] locationOnScreen = new int[2];
        anchor.getLocationOnScreen(locationOnScreen);
        return locationOnScreen[0];
    }

    //计算anchorView的Y绝对坐标
    private int getAnchorViewY(View anchor) {
        int[] locationOnScreen = new int[2];
        anchor.getLocationOnScreen(locationOnScreen);
        return locationOnScreen[1];
    }

    //计算anchorView的高度
    private int getAnchorViewMeasuredHeight(View anchor) {
        if (anchor == null) {
            return -1;
        }
        return anchor.getMeasuredHeight();
    }

    //计算PopupView的高度
    private int getPopupViewMeasuredHeight(View currentPopupLayoutView) {
        if (currentPopupLayoutView == null) {
            return -1;
        }
        //need to call measure() to generate the getMeasuredWidth
        int wrapContentSpec = View.MeasureSpec.makeMeasureSpec(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
        currentPopupLayoutView.measure(wrapContentSpec, wrapContentSpec);
        return currentPopupLayoutView.getMeasuredHeight();
    }

    //计算PopupView的宽度
    private int getPopupViewMeasuredWidth(View currentPopupLayoutView) {
        if (currentPopupLayoutView == null) {
            return -1;
        }
        currentPopupLayoutView.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        return currentPopupLayoutView.getMeasuredWidth();
    }

    //内部类 用于创建item
    public static class PopupViewItem {
        private int iconResID;
        private String descriptionStr;
        private View.OnClickListener onClickListener;
        /**
         * if we have not set up this value, it will keep the default with set in R.layout.popview_item
         */
        private int minWidth = -1;

        public PopupViewItem(int iconResID, String descriptionStr, View.OnClickListener onClickListener) {
            this.iconResID = iconResID;
            this.descriptionStr = descriptionStr;
            this.onClickListener = onClickListener;
        }

        public PopupViewItem(int iconResID, String descriptionStr, View.OnClickListener onClickListener, int minWidth) {
            this.iconResID = iconResID;
            this.descriptionStr = descriptionStr;
            this.onClickListener = onClickListener;
            this.minWidth = minWidth;
        }

        int getIconResID() {
            return iconResID;
        }

        View.OnClickListener getOnClickListener() {
            return onClickListener;
        }

        String getDescriptionStr() {
            return descriptionStr;
        }

        int getMinWidth() {
            return minWidth;
        }
    }
}

步骤4 编写activity的布局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <ImageView
        android:layout_width="200dp"
        android:layout_height="100dp"
        android:layout_alignParentEnd="false"
        android:background="#0f0"
        android:onClick="showPopupView1"
        android:src="@drawable/ic_launcher" />

    <ImageView
        android:layout_width="150dp"
        android:layout_height="100dp"
        android:layout_alignParentEnd="true"
        android:background="#0f0"
        android:onClick="showPopupView2"
        android:src="@drawable/ic_launcher" />

    <ImageView
        android:layout_width="170dp"
        android:layout_height="200dp"
        android:layout_alignParentEnd="false"
        android:layout_alignParentBottom="true"
        android:background="#0f0"
        android:onClick="showPopupView3"
        android:scaleType="centerInside"
        android:src="@drawable/ic_launcher" />

    <ImageView
        android:layout_width="250dp"
        android:layout_height="150dp"
        android:layout_alignParentEnd="true"
        android:layout_alignParentBottom="true"
        android:background="#0f0"
        android:onClick="showPopupView4"
        android:src="@drawable/ic_launcher" />

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:background="#0f0"
        android:onClick="showPopupView5"
        android:src="@drawable/ic_launcher" />
</RelativeLayout>

步骤5 编写测试代码

package com.example.testfragment;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

import com.example.testfragment.ui.main.PopupView;

import java.util.ArrayList;
import java.util.List;


public class MainActivity extends AppCompatActivity {
    Context context;

    private PopupView popup1;
    private PopupView popup2;
    private PopupView popup3;
    private PopupView popup4;
    private PopupView popup5;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main_activity);
        context = this;
    }

    public void showPopupView1(View viewClicked) {
        popup1 = new PopupView(this);
        List<PopupView.PopupViewItem> itemList = new ArrayList<>();
        itemList.add(new PopupView.PopupViewItem(R.drawable.ic_launcher, "popup1_1", new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(context, "item1 click", Toast.LENGTH_SHORT).show();
                    }
                });
                if (popup1 != null) {
                    popup1.dismissView();
                }
            }
        }));
        popup1.showView(viewClicked, itemList);
    }

    public void showPopupView2(View view) {
        popup2 = new PopupView(context);
        List<PopupView.PopupViewItem> itemList = new ArrayList<>();
        itemList.add(new PopupView.PopupViewItem(R.drawable.ic_delete, "popup2_1", new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(context, "item1 click", Toast.LENGTH_SHORT).show();
                    }
                });
                if (popup2 != null) {
                    popup2.dismissView();
                }
            }
        }));
        itemList.add(new PopupView.PopupViewItem(R.drawable.ic_delete, "popup2_2", new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(context, "item2 click", Toast.LENGTH_SHORT).show();
                    }
                });
                if (popup2 != null) {
                    popup2.dismissView();
                }
            }
        }));
        itemList.add(new PopupView.PopupViewItem(R.drawable.ic_delete, "popup2_3", new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(context, "item3 click", Toast.LENGTH_SHORT).show();
                    }
                });
                if (popup2 != null) {
                    popup2.dismissView();
                }
            }
        }));
        popup2.showView(view, itemList);
    }

    public void showPopupView3(View view) {
        popup3 = new PopupView(context);
        List<PopupView.PopupViewItem> itemList = new ArrayList<>();
        itemList.add(new PopupView.PopupViewItem(R.drawable.ic_delete, "popup3_1", new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(context, "item1 click", Toast.LENGTH_SHORT).show();
                    }
                });
                if (popup3 != null) {
                    popup3.dismissView();
                }
            }
        }));
        itemList.add(new PopupView.PopupViewItem(R.drawable.ic_delete, "popup3_2", new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(context, "item2 click", Toast.LENGTH_SHORT).show();
                    }
                });
                if (popup3 != null) {
                    popup3.dismissView();
                }
            }
        }, 500));
        popup3.showView(view, itemList);
    }

    public void showPopupView4(View view) {
        popup4 = new PopupView(context);
        List<PopupView.PopupViewItem> itemList = new ArrayList<>();
        itemList.add(new PopupView.PopupViewItem(R.drawable.ic_delete, "popup4_1", new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(context, "item1 click", Toast.LENGTH_SHORT).show();
                    }
                });
                if (popup4 != null) {
                    popup4.dismissView();
                }
            }
        }));
        itemList.add(new PopupView.PopupViewItem(R.drawable.ic_launcher, "popup4_2", new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(context, "item2 click", Toast.LENGTH_SHORT).show();
                    }
                });
                if (popup4 != null) {
                    popup4.dismissView();
                }
            }
        }));
        popup4.showView(view, itemList);
    }

    public void showPopupView5(View view) {
        popup5 = new PopupView(context);
        List<PopupView.PopupViewItem> itemList = new ArrayList<>();
        itemList.add(new PopupView.PopupViewItem(R.drawable.ic_launcher, "popup5_1", new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(context, "item1 click", Toast.LENGTH_SHORT).show();
                    }
                });
                if (popup5 != null) {
                    popup5.dismissView();
                }
            }
        }));
        itemList.add(new PopupView.PopupViewItem(R.drawable.ic_delete, "popup5_2", new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(context, "item2 click", Toast.LENGTH_SHORT).show();
                    }
                });
                if (popup5 != null) {
                    popup5.dismissView();
                }
            }
        }));
        popup5.showView(view, itemList);
    }
}

    /**
     * Get app screen width in pixels
     * @return app screen width in pixels
     */
    public static int getAppScreenWidth(@NonNull Context context) {
        WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Point point = new Point();
        if (windowManager != null){
            windowManager.getDefaultDisplay().getSize(point);
        }
        return point.x;
    }

    public static <T extends View> T addView(int resLayout, ViewGroup parentGroup) {
        View inflateView = LayoutInflater.from(parentGroup.getContext())
                .inflate(resLayout, parentGroup, false);
        parentGroup.addView(inflateView);
        return (T) inflateView;
    }
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值