目标:

实现一个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"><ImageViewandroid: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" /><LinearLayoutandroid: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><ImageViewandroid: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"><pathandroid: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"><pathandroid: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"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="120dp"android:layout_gravity="center_vertical"android:orientation="horizontal"><ImageViewandroid: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" /><TextViewandroid: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><ImageViewandroid: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"><pathandroid: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的坐下侧为起点 显示popuppublic 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 eventView 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 divideri++;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(被强行挤到屏幕内) 需要挪动箭头图标 计算挪动图标的水平值xif (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 heightcurrentPopupLayoutView.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 getMeasuredWidthint 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();}//内部类 用于创建itempublic 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"><ImageViewandroid:layout_width="200dp"android:layout_height="100dp"android:layout_alignParentEnd="false"android:background="#0f0"android:onClick="showPopupView1"android:src="@drawable/ic_launcher" /><ImageViewandroid:layout_width="150dp"android:layout_height="100dp"android:layout_alignParentEnd="true"android:background="#0f0"android:onClick="showPopupView2"android:src="@drawable/ic_launcher" /><ImageViewandroid: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" /><ImageViewandroid: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" /><ImageViewandroid: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;@Overrideprotected 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() {@Overridepublic void onClick(View v) {runOnUiThread(new Runnable() {@Overridepublic 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() {@Overridepublic void onClick(View v) {runOnUiThread(new Runnable() {@Overridepublic 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() {@Overridepublic void onClick(View v) {runOnUiThread(new Runnable() {@Overridepublic 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() {@Overridepublic void onClick(View v) {runOnUiThread(new Runnable() {@Overridepublic 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() {@Overridepublic void onClick(View v) {runOnUiThread(new Runnable() {@Overridepublic 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() {@Overridepublic void onClick(View v) {runOnUiThread(new Runnable() {@Overridepublic 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() {@Overridepublic void onClick(View v) {runOnUiThread(new Runnable() {@Overridepublic 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() {@Overridepublic void onClick(View v) {runOnUiThread(new Runnable() {@Overridepublic 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() {@Overridepublic void onClick(View v) {runOnUiThread(new Runnable() {@Overridepublic 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() {@Overridepublic void onClick(View v) {runOnUiThread(new Runnable() {@Overridepublic 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;}

自定义view 写一个popup view相关推荐

  1. 【Android View】写一个蛛网评分控件

    上周项目中要用到一个蛛网评分控件,于是就先上Github搜,搜了半天没搜着(也可能是我搜的关键词不对),那只好自己写一个了,就叫SpiderWebScoreView 先放一张最终效果图: 先整理一下需 ...

  2. android 圆环温度控件,Android自定义View分享——一个圆形温度显示器

    写在前面 笔者近来在学习Android自定义View,收集了一些不算复杂但又"长得"还可以的自定义View效果实现,之前分享过一个水平的进度条,如果你有兴趣的话可以看看: Andr ...

  3. android canvas绘制圆角_Android自定义View撸一个渐变的温度指示器(TmepView)

    秦子帅明确目标,每天进步一点点..... 作者 |  andy 地址 |  blog.csdn.net/Andy_l1/article/details/82910061 1.概述 自定义View对需要 ...

  4. 手把手教你写一个手势密码解锁View(GesturePasswordView)

    相信大家在很多的app肯定看到过手势密码解锁View,但是大家有没有想过怎么实现这样一个View,哈,接下来,小编手把手教大家教写一个GesturePasswordView. 先看一张效果图 要实现这 ...

  5. imos style android,自定义的第一个view

    可以实现 动态 添加 view 并且 能获取 添加的view 所绑定的数据 package app.imos.imosapp.views; import android.content.Context ...

  6. 如果写一个点击view带动画的下滑展开显示隐藏内容的控件

    原理是在onMeasure中得到隐藏内容的高度,点击这个view的时候对隐藏的view startAnimation,让它的高度从0增长到onMeasure得到的这个View的measureHeigh ...

  7. 创建一个Table View

    在本课程中,您将创建应用程序FoodTracker的主屏幕.您将创建第二个,表视图为主场景,列出了用户的菜谱.你会设计定制表格单元格显示每一个菜谱,它是这样的: 学习目标 在课程结束时,你将能够: 创 ...

  8. android自定义view流程,Android 自定义View--从源码理解View的绘制流程

    前言 在Android的世界里,View扮演着很重要的角色,它是Android世界在视觉上的具体呈现.Android系统本身也提供了很多种原生控件供我们使用,然而在日常的开发中我们很多时候需要去实现一 ...

  9. Android自定义半圆形圆盘滚动选择器View

    本文为原创作品,转载请注明出处:https://blog.csdn.net/wjj1996825/article/details/80646526 前段时间公司项目要求做一个特效的滑动选择器,效果如下 ...

最新文章

  1. [Android]ListView性能优化之视图缓存
  2. 深度学习最新方法:随机加权平均,击败了当前最先进的Snapshot Ensembling
  3. 018-继承-OC笔记
  4. jquery 设置style:display 其实很方便的
  5. 【Mybatis 之应用篇】 5_Mybatis总结(附20道练习题以及答案)
  6. [.NET领域驱动设计实战系列]专题二:结合领域驱动设计的面向服务架构来搭建网上书店...
  7. SSL 1613——最短路径问题(最短路)
  8. 第十二章 trie路由--基于Linux3.10
  9. 大数据应用的现实案例
  10. SpringCloud、RabbitMQ、Websocket集群搭建以及集群通信
  11. 两个字说清楚编程语言实质-Python基础前传(3)
  12. 重言式判定------参考了别人的代码。。
  13. 如何使用谷歌浏览器进行Debug断点调试
  14. bzoj 2298 problem a
  15. 两个字符串匹配度算法
  16. 修复运营商网站劫持,Win8.1怎么修改DNS
  17. 电力-故障分析理论及对称分量法
  18. 联想LJ2400打印机 免换齿轮清零方法
  19. OCV 、AOCV、POCV还在傻傻分不清吗
  20. sql sever 索引和视图

热门文章

  1. python 常用包_七月在线—Python和数据分析Lesson 1
  2. 在raspbian上配置apache2/subversion/xdebug及mysql远程访问
  3. Android 之 Window、WindowManager 与窗口管理
  4. Excel与DataGridView的操作示例
  5. aspnet管理员用户登录_WINDOWS/LINUX系统修改管理员密码方法
  6. java 标识符命名规则_java语言基础之标识符和命名规则详解
  7. python socket自动重连_python之tcp自动重连
  8. bdf比特数字基金_第四届世界数字经济大会,比特元BTY作为协办方参与
  9. 资源放送丨《MySQL的查询与优化》PPT视频
  10. openEuler Developer Day 启动大会招募环节,报名通道同步开启!