如何制作横向的上下文菜单?

How to make a horizontal ContextMenu?

我制作了一个名为 AccountsActivity,我想添加一个水平 ContextMenu。这可能看起来像剪切、复制和粘贴选项。有没有办法在列表项上添加这个水平自定义菜单onLongClick

这是我目前所知道的。

@Override      
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
    super.onCreateContextMenu(menu, v, menuInfo);
    LayoutInflater inflater = getLayoutInflater().from(this);
    View view = inflater.inflate(R.layout.custom_listview, null, false);
    menu.setHeaderView(view);
    menu.add("Delete");
    menu.add("Edit");
}

@Override
public boolean onContextItemSelected(MenuItem item) {
    AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
    int position = info.position;
    View v = listView.getChildAt(position);
    TextView typeTv = v.findViewById(R.id.custom_listview_type);
    TextView userTv = v.findViewById(R.id.custom_listview_user);
    TextView passTv = v.findViewById(R.id.custom_listview_password);

    if (item.getTitle().equals("Delete")) {
        db.execSQL("delete from user_added_accounts where accountType = '" + typeTv.getText().toString() + "' and username = '" + userTv.getText().toString() + "';");
        recreate();
    }

    if (item.getTitle().equals("Edit")) {
        update(typeTv.getText().toString(), userTv.getText().toString(), passTv.getText().toString());
    }

    return true;
}

而现在的UI是这样的。

这是我想要的,

我想你需要的是PopupWindow。它更容易实现并具有自定义布局设置选项。 PopupWindow 可以根据需要设置在自定义位置,并且可以通过 PopupWindow 的实施来实现您正在考虑的实施示例 copy/paste UI 的想法以及。

我发现 this answer 如果您想使用 PopupWindow 来实现您的情况而不是使用上下文菜单来实现它,我发现它非常有用。

在上面我提到并提供了一个赞的答案中,有一个 PopupWindow 只有一个 TextView。您可以实现任何 custom/complex UI 而不是像此处显示的那样使用简单的 TextView

希望对您有所帮助。

更新

如评论中所述,获取 PopupWindow 位置的位置也可以动态设置。我指的是另一个 link,这样您也可以从那里检查实现。

Here's the implementation 在列表中使用 PopupWindow

所以,几年前我写了下面的代码。您需要制作两个 class,第一个是 PopUp,第二个是 TringleView

PopUp :- Make dialog box and open into near your view(where you want to open dialog). You can change popup bg color.

TringleView :- Make tringle view or you can say pointed arrow.You can change pointed arrow bg color.

View contentView = ((FragmentActivity)v.getContext()).getLayoutInflater().inflate(R.layout.edit_delete_layout,getAttachedRecyclerView(),false);

// this view denote where you click or you want to open dialog near 
PopUp.showPopupOnView(((FragmentActivity) v.getContext()).getSupportFragmentManager(),contentView,view,false);

PopUp.class

public class PopUp  extends DialogFragment {

    protected int targetX;
    protected int targetY;
    protected int targetWidth;
    protected int targetHeight;
    protected Bitmap targetViewImage;
    protected View contentView;
    private SmartWorksPopUpViewHolder fragmentViewHolder;
    private static int bgDrawable = R.drawable.round_corner_white_bg;
    protected static int ONE_DIP;
    private static int arrowBgColor = R.color.border_color;
    private static int arrowWidthMultiple = 25;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (ONE_DIP == 0) {
            ONE_DIP = (int) TypedValue.applyDimension(
                    TypedValue.COMPLEX_UNIT_DIP, 1, getResources()
                                                            .getDisplayMetrics());
        }
        setStyle(DialogFragment.STYLE_NO_TITLE, android.R.style.Theme_Translucent);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        AbsoluteLayout parent = new AbsoluteLayout(getActivity());
        parent.setId(R.id.parentLayout);
        return parent;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        this.fragmentViewHolder = createViewHolder(view);
        bindView(fragmentViewHolder);
    }

    protected SmartWorksPopUpViewHolder createViewHolder(
                                                                View fragmentView) {
        return new SmartWorksPopUpViewHolder(fragmentView, contentView);
    }

    private void bindView(SmartWorksPopUpViewHolder vh) {
        if (fragmentViewHolder != null) {
            setupTargetDummyView(vh);
            boolean showOnTop = shouldShowOnTop();
            setupArrow(vh, showOnTop);
            setupContent(vh, showOnTop);
        }
    }

    protected void setupContent(SmartWorksPopUpViewHolder vh, boolean showOnTop) {
        final int y;
        AbsoluteLayout.LayoutParams arrowParams = (android.widget.AbsoluteLayout.LayoutParams) vh.arrow
                                                                                                       .getLayoutParams();
        int measureHeight = View.MeasureSpec.makeMeasureSpec(
                ViewGroup.LayoutParams.WRAP_CONTENT, View.MeasureSpec.UNSPECIFIED);
        int measureWidth = View.MeasureSpec.makeMeasureSpec(
                getActivity().getWindow().getDecorView().getWidth(), View.MeasureSpec.EXACTLY);
        vh.popupView.measure(measureWidth, measureHeight);
        if (showOnTop) {
            y = this.targetY - vh.popupView.getMeasuredHeight() + ONE_DIP;
        } else {
            y = arrowParams.y + arrowParams.height - ONE_DIP * 2;
        }

        updateAbsoluteLayoutParams(
                getActivity().getResources().getDimensionPixelOffset(R.dimen.sixty_dp),
                y,
                getActivity().getWindow().getDecorView().getWidth() -
                        getActivity().getResources().getDimensionPixelOffset(R.dimen.seventy_dp),
                ViewGroup.LayoutParams.WRAP_CONTENT, vh.popupView);

        vh.parent.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                exit();
            }
        });

    }

    private void setupArrow(SmartWorksPopUpViewHolder vh, boolean showOnTop) {
        final int arrowHeight = 15 * ONE_DIP;
        final int arrowWidth = arrowWidthMultiple * ONE_DIP;
        vh.arrow.setDirectionAndColor(showOnTop ? "down" : "top", vh.popupView.getContext().getResources().getColor(arrowBgColor));
        final int x = (int) (targetX + targetWidth / 3 - arrowWidth / 2);
        final int y = targetY + (showOnTop ? -arrowHeight : targetHeight);
        updateAbsoluteLayoutParams(x, y, arrowWidth, arrowHeight, vh.arrow);
    }

    private void setupTargetDummyView(SmartWorksPopUpViewHolder vh) {
        vh.targetViewDummy.setImageBitmap(targetViewImage);
        updateAbsoluteLayoutParams(targetX, targetY, targetWidth, targetHeight, vh.targetViewDummy);
    }

    protected void updateAbsoluteLayoutParams(int x, int y, int width, int height, View view) {
        AbsoluteLayout.LayoutParams layoutParams =
                (android.widget.AbsoluteLayout.LayoutParams) view.getLayoutParams();
        layoutParams.x = x;
        layoutParams.y = y;
        layoutParams.height = height;
        layoutParams.width = width;
        view.setLayoutParams(layoutParams);
    }

    private boolean shouldShowOnTop() {
        int windowHeight = getActivity().getWindow().getDecorView().getHeight();
        int windowMid = windowHeight / 4;
        return targetY > windowMid;
    }

    @Override
    public void onDestroyView() {
        this.fragmentViewHolder = null;
        super.onDestroyView();
    }

    protected static class SmartWorksPopUpViewHolder {
        protected AbsoluteLayout parent;
        protected View popupView;
        protected TringleView arrow;
        protected AppCompatImageView targetViewDummy;
        protected SmartWorksPopUpViewHolder(View fragmentView, View content) {
            this.parent = (AbsoluteLayout) fragmentView;
            final Context mContext = fragmentView.getContext();
            this.popupView = content;
            this.arrow = new TringleView(mContext);
            this.targetViewDummy = new SmartWorksAppCompactImageView(mContext);

            this.parent.addView(popupView);
            this.parent.addView(arrow);
            this.parent.addView(targetViewDummy);
            this.parent.setBackgroundColor(0x00000000);
            content.setBackgroundResource(bgDrawable);
        }
    }

    public static PopUp showPopupOnView(FragmentManager fm, View contentView, View targetView, boolean showTargetView) {
        int[] location = new int[2];
        targetView.getLocationInWindow(location);
        PopUp fragment = new PopUp();
        fragment.targetX = location[0];
        fragment.targetY = (int) (location[1] - TypedValue
                                                        .applyDimension(TypedValue.COMPLEX_UNIT_DIP, 25,
                                                                targetView.getResources().getDisplayMetrics()));
        fragment.targetWidth = targetView.getMeasuredWidth();
        fragment.targetHeight = targetView.getMeasuredHeight();
        fragment.contentView = contentView;
        fragment.show(fm, "offer");
        return fragment;
    }

    public void exit() {
        dismiss();
    }

    public static void setArrowBackgroundColor(int color) {
        arrowBgColor = color;
    }

    public static void setArrowWidthMultiple(int arrowWidth) {
        arrowWidthMultiple = arrowWidth;
    }
}

TringleView.class

public class TringleView extends View {

    private String direction;
    private int color;

    public TringleView(Context context) {
        super(context);
        setDirectionAndColor("right", Color.RED);
    }

    public TringleView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        setDirectionAndColor(attrs.getAttributeValue(null, "direction"), Color.RED);
    }

    public TringleView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setDirectionAndColor(attrs.getAttributeValue(null, "direction"), Color.RED);
    }

    public void setDirectionAndColor(String direction, int color) {
        if (direction != null && !direction.equals(this.direction) || this.color != color) {
            createTriangleDrawable(direction, color);
        }
    }

    private void createTriangleDrawable(String string, int color) {
        int width = MeasureSpec.makeMeasureSpec(30,  MeasureSpec.UNSPECIFIED);
        int height = MeasureSpec.makeMeasureSpec(20, MeasureSpec.UNSPECIFIED);
        Path path = new Path();
        if (string == null) {
            string = "right";
        }
        if (string.equals("top")) {
            path.moveTo(0, height);
            path.lineTo(width / 2, 0);
            path.lineTo(width, height);
        } else if (string.equals("left")) {
            path.moveTo(width, 0);
            path.lineTo(0, height / 2);
            path.lineTo(width, height);
        } else if (string.equals("right")) {
            path.moveTo(0, 0);
            path.lineTo(width, height / 2);
            path.lineTo(0, height);
        } else if (string.equals("down")) {
            path.moveTo(0, 0);
            path.lineTo(width / 2, height);
            path.lineTo(width, 0);
        }

        path.close();
        ShapeDrawable shapeDrawable = new ShapeDrawable(new PathShape(path, width, height));
        shapeDrawable.getPaint().setColor(color);
        setBackground(shapeDrawable);
        this.color = color;
        this.direction = string;
    }

}

edit_delete_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
    app:cardCornerRadius="@dimen/five_dp"
    android:layout_margin="@dimen/ten_dp"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    app:layout_behavior="android.support.design.widget.BottomSheetBehavior"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" >
    <LinearLayout
        app:layout_behavior="android.support.design.widget.BottomSheetBehavior"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:weightSum="5"
        android:gravity="center"
        android:orientation="horizontal"
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto">
        <sis.com.smartworks.widget.SmartWorksTextView
            android:id="@+id/share"
            android:textSize="@dimen/smallest_text_size"
            android:textColor="@color/black"
            android:visibility="visible"
            android:gravity="center"
            android:paddingRight="@dimen/four_dp"
            android:paddingLeft="@dimen/four_dp"
            android:paddingTop="@dimen/ten_dp"
            android:paddingBottom="@dimen/ten_dp"
            android:text="Share"
            android:layout_weight="1"
            android:textStyle="bold"
            android:layout_width="0dp"
            android:layout_height="wrap_content" />
        <View
            android:layout_width="1dp"
            android:background="@color/grey_unselect"
            android:layout_height="match_parent" />
        <sis.com.smartworks.widget.SmartWorksTextView
            android:id="@+id/reportSpam"
            android:textSize="@dimen/smallest_text_size"
            android:textColor="@color/black"
            android:visibility="visible"
            android:gravity="center"
            android:paddingRight="@dimen/four_dp"
            android:paddingLeft="@dimen/four_dp"
            android:paddingTop="@dimen/ten_dp"
            android:paddingBottom="@dimen/ten_dp"
            android:text="Spam"
            android:layout_weight="1"
            android:textStyle="bold"
            android:layout_width="0dp"
            android:layout_height="wrap_content" />
        <View
            android:layout_width="1dp"
            android:background="@color/grey_unselect"
            android:layout_height="match_parent" />
        <!--<View-->
            <!--android:layout_width="match_parent"-->
            <!--android:layout_marginLeft="@dimen/three_dp"-->
            <!--android:layout_marginRight="@dimen/three_dp"-->
            <!--android:background="@color/white"-->
            <!--android:layout_height="@dimen/one_dp" />-->


        <sis.com.smartworks.widget.SmartWorksTextView
            android:id="@+id/edit"
            android:textSize="@dimen/smallest_text_size"
            android:textColor="@color/black"
            android:gravity="center"
            android:paddingRight="@dimen/four_dp"
            android:paddingLeft="@dimen/four_dp"
            android:paddingTop="@dimen/ten_dp"
            android:paddingBottom="@dimen/ten_dp"
            app:swFontName="robotoNormal"
            android:layout_weight="1"
            android:text="@string/edit"
            android:textStyle="bold"
            android:layout_width="0dp"
            android:layout_height="wrap_content" />
        <View
            android:layout_width="1dp"
            android:background="@color/grey_unselect"
            android:layout_height="match_parent" />
        <!--<View-->
            <!--android:layout_width="match_parent"-->
            <!--android:layout_marginLeft="@dimen/three_dp"-->
            <!--android:layout_marginRight="@dimen/three_dp"-->
            <!--android:background="@color/white"-->
            <!--android:layout_height="@dimen/one_dp" />-->

        <sis.com.smartworks.widget.SmartWorksTextView
            android:id="@+id/delete"
            android:textSize="@dimen/smallest_text_size"
            android:textColor="@color/black"
            android:gravity="center"

            android:paddingRight="@dimen/four_dp"
            android:paddingLeft="@dimen/four_dp"
            android:paddingTop="@dimen/ten_dp"
            android:paddingBottom="@dimen/ten_dp"            android:layout_weight="1"
            android:text="@string/delete"
            android:textStyle="bold"
            android:layout_width="0dp"
            android:layout_height="wrap_content" />
        <View
            android:layout_width="1dp"
            android:background="@color/grey_unselect"
            android:layout_height="match_parent" />
        <sis.com.smartworks.widget.SmartWorksTextView
            android:id="@+id/cancel"
            android:textSize="@dimen/smallest_text_size"
            android:textColor="@color/black"
            android:gravity="center"
            android:layout_weight="1"
            android:visibility="visible"
            android:paddingRight="@dimen/four_dp"
            android:paddingLeft="@dimen/four_dp"
            android:paddingTop="@dimen/ten_dp"
            android:paddingBottom="@dimen/ten_dp"
            android:textStyle="bold"
            android:text="@string/select_cancel"
            android:layout_width="0dp"
            android:layout_height="wrap_content" />
    </LinearLayout>

</android.support.v7.widget.CardView>

结果

所以如果你想把视图设置为水平的,那么你需要根据你的要求进行水平布局。所以可以做这个任务来改变你的edit_delete_layout.xml,你把它放入contentView然后传递给Popup class method

注意:- 您可以根据您的要求自定义弹出窗口 class,我知道此代码有很多已弃用的视图,因此您可以自行更新。

要显示紧凑的上下文菜单,您需要为 Menu 创建 ActionMode,让我告诉您如何操作:

Suppose your action menu XML have delete, copy and forward actions:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/action_copy"
        android:icon="@drawable/ic_vector_menu_copy"
        android:title="Copy"
        app:showAsAction="always" />
    <item
        android:id="@+id/action_delete"
        android:icon="@drawable/ic_vector_menu_delete"
        android:title="Delete"
        app:showAsAction="always" />
    <item
        android:id="@+id/action_forward"
        android:icon="@drawable/ic_vector_menu_forward"
        android:title="Forward"
        app:showAsAction="always" />
</menu>

Create your action menu in your Activity

//Global variable in Activity/Fragment to manage close the menu
private ActionMode mActionMode;

//Action mode callbacks
//Contextual Action bar -  for showing delete/copy/... on action bar
private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {

    // Called when the action mode is created; startActionMode() was called
    @Override
    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
        // Inflate a menu resource providing context menu items
        MenuInflater inflater = mode.getMenuInflater();
        inflater.inflate(R.menu.menu_contextual_action, menu);
        return true;
    }

    // Called each time the action mode is shown.
    // Always called after onCreateActionMode, but
    // may be called multiple times if the mode is invalidated.
    @Override
    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
        return false; // Return false if nothing is done
    }

    // Called when the user selects a contextual menu item
    @Override
    public boolean onActionItemClicked(final ActionMode mode, MenuItem item) {
        switch (item.getItemId()) {
            case R.id.action_delete:
                //Do the delete action 
                //mAdapter.resetSelection();
                mode.finish(); // Action picked, so close the TAB
                //showToast "Deleted successfully"
                return true;
            case R.id.action_copy:
                //mAdapter.resetSelection();
                MyClipboardManager.copyToClipboard(ChatDetailActivity.this, mAdapter.getSelectedMessageText());
                mode.finish(); // Action picked, so close the TAB
                //showToast "Text copied to clipboard"
                return true;
            default:
                return false;
        }
    }

    // Called when the user exits the action mode
    @Override
    public void onDestroyActionMode(ActionMode mode) {
        mActionMode = null;
        //mAdapter.resetSelection();
    }
};

@Override
public void onBackPressed() {
    //Closing menu first if it's visible rather than doing the back press action  
    if (mActionMode != null && mActionMode.getMenu().hasVisibleItems()) {
        mActionMode.finish();
        return;
    }
    super.onBackPressed();
}

@Override
public void onDestroy() {
    //Closing menu
    if (mActionMode != null) {
        mActionMode.finish();
    }
    super.onDestroy();
}

*设置回调到全局动作模式变量

mActionMode = startSupportActionMode(mActionModeCallback);

*为菜单设置标题

mActionMode.setTitle("Menu title");

*设置值后菜单失效

mActionMode.invalidate();

Style to manage compact contextual menu

<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <!-- Customize your theme here. -->
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
    <item name="android:windowDisablePreview">true</item>

    <!--CONTEXTUAL action MODE-->
    <item name="android:windowContentOverlay">@null</item>
    <!--ActionMode background color-->
    <!-- <item name="android:actionModeBackground">@color/colorPrimary</item>-->
    <!--To Overlay existing toolbar, NOTE We are not using android: to let it work everywhere-->
    <item name="windowActionModeOverlay">true</item>
    <item name="actionModeStyle">@style/AppActionModeStyle</item>
    <item name="android:actionModeCloseDrawable">@drawable/ic_arrow_back_24dp</item>
</style>

<style name="AppActionModeStyle" parent="@style/Widget.AppCompat.ActionMode">
    <!--ActionMode background color-->
    <item name="background">@color/colorPrimary</item>
   <!--ActionMode text title color-->
    <item name="titleTextStyle">@style/ActionModeTitleTextStyle</item>
</style>

<style name="ActionModeTitleTextStyle" parent="@style/TextAppearance.AppCompat.Widget.ActionMode.Title">
    <item name="android:textColor">@android:color/white</item>
</style>

只需通过QuickAction库即可实现。

https://github.com/piruin/quickaction
https://github.com/lorensiuswlt/NewQuickAction

希望对您有所帮助!!