项目简介
模仿微信聊天界面的时候发现微信那个菜单很有意思,手指按哪里菜单就从哪里弹出来。
Demo效果图
这图上传后自带马赛克。。。。。
需求分析
主显示框架可以用PopupWindow + ListView
,然后在消息列表的ListView
上获取触摸位置,最后加点特效就可以随便乱弹了。
关于弹出方向总共有四种:右下(默认弹出方向)、左下(下边空间充足,右边空间不足时)、右上(下边空间不足,右边空间充足)、左上(下边空间不足,右边空间不足),这个在Popwindow
弹出时候应该计算出来,我自定义一个类继承自Popwindow
,在reckonPopWindowShowPos(int posX, int posY)
方法中根据触摸位置计算应该弹出位置以及确定弹出方向。
囿于ListView
中的wrap_content
和match_parent
一样的效果(由于MeasureSpec
特性),所有也自定义一个类继承在ListView
,自己计算尺寸。
具体实现
- 首先自定义一个
PopupMenuWindows
继承自Popwindow
/**
* @author fengshawn on 2017/8/3.
* @version 1.0
*/
public class PopupMenuWindows extends PopupWindow {
private final static int SHOW_ON_LEFT = 0X11;
private final static int SHOW_ON_RIGHT = 0X12;
private final static int SHOW_ON_UP = 0X13;
private final static int SHOW_ON_DOWN = 0X14;
private View contentView;
private Context context;
private int showAtVertical, showAtOrientation;
private OnMenuItemClickListener listener;
private ListAdapter adapter;
private boolean isSetAutoFitStyle = false;
public interface OnMenuItemClickListener {
void onMenuItemClickListener(AdapterView<?> parent, View view, int position, long id);
}
public void setOnMenuItemClickListener(OnMenuItemClickListener listener) {
this.listener = listener;
}
public PopupMenuWindows(Context context, int resId, ListAdapter adapter) {
super(context);
this.context = context;
this.adapter = adapter;
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
contentView = inflater.inflate(resId, null);
setContentView(contentView);
initPopWindow();
}
private void initPopWindow() {
Drawable dw;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setElevation(35);
} else {
dw = ContextCompat.getDrawable(context, R.drawable.bg);
}
setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
setFocusable(true);
setOutsideTouchable(true);
//背景为白色
dw = ContextCompat.getDrawable(context, R.color.colorWhite);
setBackgroundDrawable(dw);
//绑定ListViewSelfAdapt数据
ListViewSelfAdapt listViewSelfAdapt = (ListViewSelfAdapt) contentView.findViewById(R.id.popup_menu_general_layout_lv);
listViewSelfAdapt.setAdapter(adapter);
listViewSelfAdapt.setOnItemClickListener((parent, view, position, id) -> {
listener.onMenuItemClickListener(parent, view, position, id);
});
}
public View getContentView() {
return contentView;
}
/**
* 计算弹出位置,弹出位置空间不足的向反方向弹出
*
* @param posX 触摸的X坐标
* @param posY 触摸的Y坐标
* @return 长度为2的坐标数组, 为重新计算过的弹出位置,0为x坐标,1为y坐标
*/
public int[] reckonPopWindowShowPos(int posX, int posY) {
int screenH = HelpUtils.getScreenHeight(context);
int screenW = HelpUtils.getScreenWidth(context);
//popupWindow显示区域View
contentView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
int windowsHeight = contentView.getMeasuredHeight();
int windowsWidth = contentView.getMeasuredWidth();
int x = posX, y = posY; //窗口弹出坐标
//向上弹出
if (screenH - posY < windowsHeight) {
y = posY - windowsHeight;
showAtVertical = SHOW_ON_UP;
} else { //向下弹出
showAtVertical = SHOW_ON_DOWN;
}
//左弹出
if (screenW - posX < windowsWidth) {
x = posX - windowsWidth;
showAtOrientation = SHOW_ON_LEFT;
} else { //右弹出
showAtOrientation = SHOW_ON_RIGHT;
}
int[] posArr = new int[2];
posArr[0] = x;
posArr[1] = y;
//防止设置自适应动画方法在此方法之前调用
if (isSetAutoFitStyle) {
setAutoFitStyle(true);
}
return posArr;
}
/**
* 自适应触摸位置弹出动画
*
* @param isSet
*/
public void setAutoFitStyle(boolean isSet) {
isSetAutoFitStyle = isSet;
if (isSet) {
if (showAtOrientation == SHOW_ON_RIGHT && showAtVertical == SHOW_ON_UP) {
setAnimationStyle(R.style.PopupWindowAnimationLB);
}
if (showAtOrientation == SHOW_ON_RIGHT && showAtVertical == SHOW_ON_DOWN) {
setAnimationStyle(R.style.PopupWindowAnimationLT);
}
if (showAtOrientation == SHOW_ON_LEFT && showAtVertical == SHOW_ON_UP) {
setAnimationStyle(R.style.PopupWindowAnimationRB);
}
if (showAtOrientation == SHOW_ON_LEFT && showAtVertical == SHOW_ON_DOWN) {
setAnimationStyle(R.style.PopupWindowAnimationRT);
}
}
}
}
自定义类ListViewSelfAdapt
,主要重新计算尺寸
/**
* @author fengshawn on 2017/8/7.
*/
public class ListViewSelfAdapt extends ListView {
public ListViewSelfAdapt(Context context) {
super(context);
}
public ListViewSelfAdapt(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ListViewSelfAdapt(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = getMaxChildWidth() + getPaddingLeft() + getPaddingRight();
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), expandSpec);
}
/**
* 获取最大子View宽度
* @return
*/
private final int getMaxChildWidth() {
int max = 0;
ListAdapter adapter = getAdapter();
int childCount = adapter == null ? 0 : adapter.getCount();
View childView = null;
if (childCount > 0) {
for (int i = 0; i < childCount; i++) {
childView = adapter.getView(i, childView, this);
childView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
max = max > childView.getMeasuredWidth() ? max : childView.getMeasuredWidth();
}
}
ViewGroup.LayoutParams params = getLayoutParams();
params.width = max;
setLayoutParams(params);
return max;
}
}
当然还有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:orientation="vertical">
<com.shawn.fakewechat.component.ListViewSelfAdapt
android:id="@+id/popup_menu_general_layout_lv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:divider="@null"
android:dividerHeight="0dp" />
</LinearLayout>
添加一个Adapter以便填充数据
/**
* Created by fengshawn on 2017/8/3.
*/
public class SimpleMenuAdapter<T> extends ArrayAdapter {
private Context context;
private List<T> list;
private int resource;
public SimpleMenuAdapter(@NonNull Context context, @LayoutRes int resource, @NonNull List list) {
super(context, resource, list);
this.context = context;
this.list = list;
this.resource = resource;
}
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
View v = LayoutInflater.from(context).inflate(resource, parent, false);
ViewHolder vh = new ViewHolder(v);
vh.tv.setText((String) list.get(position));
//v.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
//Log.i("tag", v + " height: " + v.getMeasuredHeight());
return v;
}
class ViewHolder {
TextView tv;
public ViewHolder(View v) {
tv = (TextView) v.findViewById(R.id.item_menu_tv);
}
}
}
好了,轮子造好了,最后就是应用了,主要就是填充数据,业务逻辑判断,指定弹出位置。
/**
* 初始化popup菜单
*/
private void initPopupMenu(View anchorView, int posX, int posY, ContactAdapter adapter, int itemPos, List<ContactShowInfo> data) {
List<String> list = new ArrayList<>();
ContactShowInfo showInfo = data.get(itemPos);
//初始化弹出菜单项
switch (showInfo.getAccountType()) {
case TYPE_SERVICE:
list.clear();
if (showInfo.isRead())
list.add("标为未读");
else
list.add("标为已读");
list.add("删除该聊天");
break;
case TYPE_SUBSCRIBE:
list.clear();
if (showInfo.isRead())
list.add("标为未读");
else
list.add("标为已读");
list.add("置顶公众号");
list.add("取消关注");
list.add("删除该聊天");
break;
case TYPE_USER:
list.clear();
if (showInfo.isRead())
list.add("标为未读");
else
list.add("标为已读");
list.add("置顶聊天");
list.add("删除该聊天");
break;
}
SimpleMenuAdapter<String> menuAdapter = new SimpleMenuAdapter<>(this, R.layout.item_menu, list);
PopupMenuWindows ppm = new PopupMenuWindows(this, R.layout.popup_menu_general_layout, menuAdapter);
int[] posArr = ppm.reckonPopWindowShowPos(posX, posY);
ppm.setAutoFitStyle(true);
ppm.setOnMenuItemClickListener((parent, view, position, id) -> {
switch (list.get(position)) {
case "标为未读":
setIsRead(false, itemPos, adapter, data);
break;
case "标为已读":
setIsRead(true, itemPos, adapter, data);
break;
case "置顶聊天":
case "置顶公众号":
stickyTop(adapter, data, itemPos);
break;
case "取消关注":
case "删除该聊天":
deleteMsg(itemPos, adapter, data);
break;
}
ppm.dismiss();
});
//指定位置弹出
ppm.showAtLocation(anchorView, Gravity.NO_GRAVITY, posArr[0], posArr[1]);
}
项目地址
https://github.com/fx4758/FakeWechat
Demo下载:
https://download.csdn.net/download/fx475870569/10398673