高仿微信跟随手指自适应弹出菜单

项目简介

模仿微信聊天界面的时候发现微信那个菜单很有意思,手指按哪里菜单就从哪里弹出来。

Demo效果图

这图上传后自带马赛克。。。。。
这里写图片描述

需求分析

主显示框架可以用PopupWindow + ListView,然后在消息列表的ListView上获取触摸位置,最后加点特效就可以随便乱弹了。

关于弹出方向总共有四种:右下(默认弹出方向)、左下(下边空间充足,右边空间不足时)、右上(下边空间不足,右边空间充足)、左上(下边空间不足,右边空间不足),这个在Popwindow弹出时候应该计算出来,我自定义一个类继承自Popwindow,在reckonPopWindowShowPos(int posX, int posY) 方法中根据触摸位置计算应该弹出位置以及确定弹出方向。

囿于ListView中的wrap_contentmatch_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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值