android仿QQ长按气泡弹窗效果,仿IOS控件,支持跟随手指显示

Android实现点击按钮弹出气泡 (附完整源码) Android实现点击按钮弹出气泡 (附完整源码) 阅读详情

参考:
BubblePopupWindow:https://github.com/smuyyh/BubblePopupWindow
PopupMenuView:https://github.com/kareluo/PopupMenuView
Android仿QQ长按删除弹出框:https://blog.youkuaiyun.com/shangmingchao/article/details/50244161#comments

项目中要实现类似QQ长按气泡弹窗的效果,GitHub上找了些项目太杂了,还有些在Android P上适配不好,于是站在巨人们的肩上自己动手撸了一个(只有一个类),有需要的自取,废话不多说先看效果。

1.仿IOS效果
在这里插入图片描述
2.跟随手指显示
在这里插入图片描述

气泡组成:由2部分组成:1个横向布局实现item,1个指示器(气泡下方三角)

如何使用:我是结合RecycleView使用的,也可以用在其他地方

            //监听Touch事件,获取手指落下点
            findViewById(R.id.itemLayout).setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    mRawX = event.getRawX();
                    mRawY = event.getRawY();
                    return false;
                }
            });

			//设置长按事件
           findViewById(R.id.itemLayout).setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                	//实例化BubblePopupView
                    BubblePopupView bubblePopup = new BubblePopupView(v.getContext());
                    //是否跟随手指显示,默认false,设置true后翻转高度无效,永远在上方显示
                    bubblePopup.setShowTouchLocation(false);
                    //要实现顶部气泡向下,需要添加翻转高度,默认为0不翻转(举例:导航栏高度40dp,列表item高度40dp,需设置40+40=80)
				    bubblePopup.setmReversalHeight(80f);
                    //参数分别是:View(我这里就是List的item),position(用于回调返回),手指落下X坐标,手指落下Y坐标,气泡按钮集合,回调监听
                    bubblePopup.showPopupListWindow(v, viewHolder.position, mRawX, mRawY, popupItemList, new BubblePopupView.PopupListListener() {
                        @Override
                        public boolean showPopupList(View adapterView, View contextView, int contextPosition) {
                            return true;
                        }


                        @Override
                        public void onPopupListClick(View contextView, int contextPosition, int position) {
                            Toast.makeText(contextView.getContext(), contextPosition + "," + position, Toast.LENGTH_SHORT).show();
                        }
                    });
                    return false;
                }
            });

BubblePopupView 完整类:里面注释我写的算很清楚了,我就不重复解释了


import android.app.Activity;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.StateListDrawable;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.TextView;


import java.util.List;

/**
 * 仿QQ长按气泡弹窗
 * 作者:pixiaozhi
 * 时间:19/05/31.
 */
public class BubblePopupView {

    private static final boolean DEFAULT_SHOW_BOTTOM = false;
    private static final boolean DEFAULT_SHOW_TOUCH_LOCATION = false;
    private static final int DEFAULT_NORMAL_TEXT_COLOR = Color.WHITE;
    private static final int DEFAULT_PRESSED_TEXT_COLOR = Color.WHITE;
    private static final float DEFAULT_TEXT_SIZE_DP = 14;
    private static final float DEFAULT_TEXT_PADDING_LEFT_DP = 10.0f;
    private static final float DEFAULT_TEXT_PADDING_TOP_DP = 5.0f;
    private static final float DEFAULT_TEXT_PADDING_RIGHT_DP = 10.0f;
    private static final float DEFAULT_TEXT_PADDING_BOTTOM_DP = 5.0f;
    private static final int DEFAULT_NORMAL_BACKGROUND_COLOR = 0xCC000000;
    private static final int DEFAULT_PRESSED_BACKGROUND_COLOR = 0xE7777777;
    private static final int DEFAULT_BACKGROUND_RADIUS_DP = 8;
    private static final int DEFAULT_DIVIDER_COLOR = 0x9AFFFFFF;
    private static final float DEFAULT_DIVIDER_WIDTH_DP = 0.5f;
    private static final float DEFAULT_DIVIDER_HEIGHT_DP = 16.0f;
    private static final float DEFAULT_NAVIGATION_BAR_HEIGHT = 0f;

    private Context mContext;
    private PopupWindow mPopupWindow;
    private View mAnchorView;
    private View mContextView;
    private View mIndicatorView;
    private List<String> mPopupItemList;
    private PopupListListener mPopupListListener;
    private int mContextPosition;
    private StateListDrawable mLeftItemBackground;
    private StateListDrawable mRightItemBackground;
    private StateListDrawable mCornerItemBackground;
    private ColorStateList mTextColorStateList;
    private GradientDrawable mCornerBackground;
    //指示器属性
    private int mIndicatorWidth;
    private int mIndicatorHeight;
    //PopupWindow属性
    private int mPopupWindowWidth;
    private int mPopupWindowHeight;
    //文本属性
    private int mNormalTextColor;
    private int mPressedTextColor;
    private float mTextSize;
    private int mTextPaddingLeft;
    private int mTextPaddingTop;
    private int mTextPaddingRight;
    private int mTextPaddingBottom;
    private int mNormalBackgroundColor;
    private int mPressedBackgroundColor;
    private int mBackgroundCornerRadius;
    //分割线属性
    private int mDividerColor;
    private int mDividerWidth;
    private int mDividerHeight;
    //是否显示在下方
    private boolean mIsShowBottom;
    //是否跟随手指显示
    private boolean mIsShowTouchLocation;
    //倒转高度,当落下位置比这个值小时,气泡显示在下方
    private float mReversalHeight;

    public BubblePopupView(Context context) {
        this.mContext = context;
        this.mIsShowBottom = DEFAULT_SHOW_BOTTOM;
        this.mIsShowTouchLocation = DEFAULT_SHOW_TOUCH_LOCATION;
        this.mReversalHeight = dp2px(DEFAULT_NAVIGATION_BAR_HEIGHT);
        this.mNormalTextColor = DEFAULT_NORMAL_TEXT_COLOR;
        this.mPressedTextColor = DEFAULT_PRESSED_TEXT_COLOR;
        this.mTextSize = dp2px(DEFAULT_TEXT_SIZE_DP);
        this.mTextPaddingLeft = dp2px(DEFAULT_TEXT_PADDING_LEFT_DP);
        this.mTextPaddingTop = dp2px(DEFAULT_TEXT_PADDING_TOP_DP);
        this.mTextPaddingRight = dp2px(DEFAULT_TEXT_PADDING_RIGHT_DP);
        this.mTextPaddingBottom = dp2px(DEFAULT_TEXT_PADDING_BOTTOM_DP);
        this.mNormalBackgroundColor = DEFAULT_NORMAL_BACKGROUND_COLOR;
        this.mPressedBackgroundColor = DEFAULT_PRESSED_BACKGROUND_COLOR;
        this.mBackgroundCornerRadius = dp2px(DEFAULT_BACKGROUND_RADIUS_DP);
        this.mDividerColor = DEFAULT_DIVIDER_COLOR;
        this.mDividerWidth = dp2px(DEFAULT_DIVIDER_WIDTH_DP);
        this.mDividerHeight = dp2px(DEFAULT_DIVIDER_HEIGHT_DP);
        this.mIndicatorView = getDefaultIndicatorView(mContext);
        refreshBackgroundOrRadiusStateList();
        refreshTextColorStateList(mPressedTextColor, mNormalTextColor);
    }

    /**
     * 以气泡样式显示弹出窗口
     *
     * @param anchorView        要固定弹出窗口的视图
     * @param contextPosition   上下文位置,当是列表时用于记录Position
     * @param rawX              原始X坐标
     * @param rawY              原始Y坐标
     * @param popupItemList     弹出菜单列表
     * @param popupListListener 监听器
     */
    public void showPopupListWindow(View anchorView, int contextPosition, float rawX, float rawY,
                                    List<String> popupItemList, PopupListListener popupListListener) {
        mAnchorView = anchorView;
        mContextPosition = contextPosition;
        mPopupItemList = popupItemList;
        mPopupListListener = popupListListener;
        mPopupWindow = null;
        mContextView = anchorView;
        if (mPopupListListener != null
                && !mPopupListListener.showPopupList(mContextView, mContextView, contextPosition)) {
            return;
        }
        int[] location = new int[2];
        mAnchorView.getLocationOnScreen(location);
//        LogUtil.e("rawX:" + rawX + ",rawY:" + rawY + ",location[0]:" + location[0] + ",location[1]" + location[1]);
        if (mIsShowTouchLocation) {
            showPopupListWindow(rawX - location[0], rawY - location[1]);
        } else {
            LogUtil.e("navigationBarHeight:" + mReversalHeight + rawY + ",rawY:" + rawY);

            if (mReversalHeight > rawY) {
                mIsShowBottom = true;
                showPopupListWindow(mAnchorView.getWidth() / 2f, mAnchorView.getHeight());
            } else {
                mIsShowBottom = false;
                showPopupListWindow(mAnchorView.getWidth() / 2f, 0);
            }
        }
    }

    /**
     * 创建布局和显示
     */
    private void showPopupListWindow(float offsetX, float offsetY) {
        if (mContext instanceof Activity && ((Activity) mContext).isFinishing()) {
            return;
        }
        if (mPopupWindow == null) {
            //创建根布局
            LinearLayout contentView = new LinearLayout(mContext);
            contentView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
            contentView.setOrientation(LinearLayout.VERTICAL);
            //创建list布局
            LinearLayout popupListContainer = new LinearLayout(mContext);
            popupListContainer.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
            popupListContainer.setOrientation(LinearLayout.HORIZONTAL);
            popupListContainer.setBackgroundDrawable(mCornerBackground);

            //创建指示器
            if (mIndicatorView != null) {
                LinearLayout.LayoutParams layoutParams;
                if (mIndicatorView.getLayoutParams() == null) {
                    layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
                } else {
                    layoutParams = (LinearLayout.LayoutParams) mIndicatorView.getLayoutParams();
                }
                layoutParams.gravity = Gravity.CENTER;
                mIndicatorView.setLayoutParams(layoutParams);
                ViewParent viewParent = mIndicatorView.getParent();
                if (viewParent instanceof ViewGroup) {
                    ((ViewGroup) viewParent).removeView(mIndicatorView);
                }

                if (!mIsShowBottom) {
                    contentView.addView(popupListContainer);
                    contentView.addView(mIndicatorView);
                } else {
                    contentView.addView(mIndicatorView);
                    contentView.addView(popupListContainer);
                }
            }

            //添加list的item
            for (int i = 0; i < mPopupItemList.size(); i++) {
                TextView textView = new TextView(mContext);
                textView.setTextColor(mTextColorStateList);
                textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
                textView.setPadding(mTextPaddingLeft, mTextPaddingTop, mTextPaddingRight, mTextPaddingBottom);
                textView.setClickable(true);
                final int finalI = i;
                //设置点击回调
                textView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        if (mPopupListListener != null) {
                            mPopupListListener.onPopupListClick(mContextView, mContextPosition, finalI);
                            hidePopupListWindow();
                        }
                    }
                });

                textView.setText(mPopupItemList.get(i));

                //设置item的背景
                if (mPopupItemList.size() > 1 && i == 0) {
                    textView.setBackgroundDrawable(mLeftItemBackground);
                } else if (mPopupItemList.size() > 1 && i == mPopupItemList.size() - 1) {
                    textView.setBackgroundDrawable(mRightItemBackground);
                } else if (mPopupItemList.size() == 1) {
                    textView.setBackgroundDrawable(mCornerItemBackground);
                } else {
                    textView.setBackgroundDrawable(getCenterItemBackground());
                }
                popupListContainer.addView(textView);
                //设置2个item中的分割线
                if (mPopupItemList.size() > 1 && i != mPopupItemList.size() - 1) {
                    View divider = new View(mContext);
                    LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(mDividerWidth, mDividerHeight);
                    layoutParams.gravity = Gravity.CENTER;
                    divider.setLayoutParams(layoutParams);
                    divider.setBackgroundColor(mDividerColor);
                    popupListContainer.addView(divider);
                }
            }
            if (mPopupWindowWidth == 0) {
                mPopupWindowWidth = getViewWidth(popupListContainer);
            }
            //获取指示器宽高
            if (mIndicatorView != null && mIndicatorWidth == 0) {
                if (mIndicatorView.getLayoutParams().width > 0) {
                    mIndicatorWidth = mIndicatorView.getLayoutParams().width;
                } else {
                    mIndicatorWidth = getViewWidth(mIndicatorView);
                }
            }
            if (mIndicatorView != null && mIndicatorHeight == 0) {
                if (mIndicatorView.getLayoutParams().height > 0) {
                    mIndicatorHeight = mIndicatorView.getLayoutParams().height;
                } else {
                    mIndicatorHeight = getViewHeight(mIndicatorView);
                }
            }
            if (mPopupWindowHeight == 0) {
                mPopupWindowHeight = getViewHeight(popupListContainer) + mIndicatorHeight;
            }
            mPopupWindow = new PopupWindow(contentView, mPopupWindowWidth, mPopupWindowHeight, true);
            mPopupWindow.setTouchable(true);
            mPopupWindow.setBackgroundDrawable(new BitmapDrawable());
        }
        int[] location = new int[2];
        mAnchorView.getLocationOnScreen(location);
        if (mIndicatorView != null) {
            float leftTranslationLimit = mIndicatorWidth / 2f + mBackgroundCornerRadius - mPopupWindowWidth / 2f;
            float rightTranslationLimit = mPopupWindowWidth / 2f - mIndicatorWidth / 2f - mBackgroundCornerRadius;
            //获取最大绝对宽度,单位是px
            float maxWidth = mContext.getResources().getDisplayMetrics().widthPixels;
            //通过setTranslationX改变view的位置,是不改变view的LayoutParams的,也即不改变getLeft等view的信息
            if (location[0] + offsetX < mPopupWindowWidth / 2f) {
                mIndicatorView.setTranslationX(Math.max(location[0] + offsetX - mPopupWindowWidth / 2f, leftTranslationLimit));
            } else if (location[0] + offsetX + mPopupWindowWidth / 2f > maxWidth) {
                mIndicatorView.setTranslationX(Math.min(location[0] + offsetX + mPopupWindowWidth / 2f - maxWidth, rightTranslationLimit));
            } else {
                mIndicatorView.setTranslationX(0);
            }
        }
        if (!mPopupWindow.isShowing()) {
            int x = (int) (location[0] + offsetX - mPopupWindowWidth / 2f + 0.5f);
            int y = mIsShowBottom ? (int) (location[1] + offsetY + 0.5f) : (int) (location[1] + offsetY - mPopupWindowHeight + 0.5f);
            mPopupWindow.showAtLocation(mAnchorView, Gravity.NO_GRAVITY, x, y);
        }
    }

    /**
     * 刷新背景或附加状态列表
     */
    private void refreshBackgroundOrRadiusStateList() {
        // left
        GradientDrawable leftItemPressedDrawable = new GradientDrawable();
        leftItemPressedDrawable.setColor(mPressedBackgroundColor);
        leftItemPressedDrawable.setCornerRadii(new float[]{
                mBackgroundCornerRadius, mBackgroundCornerRadius,
                0, 0,
                0, 0,
                mBackgroundCornerRadius, mBackgroundCornerRadius});
        GradientDrawable leftItemNormalDrawable = new GradientDrawable();
        leftItemNormalDrawable.setColor(Color.TRANSPARENT);
        leftItemNormalDrawable.setCornerRadii(new float[]{
                mBackgroundCornerRadius, mBackgroundCornerRadius,
                0, 0,
                0, 0,
                mBackgroundCornerRadius, mBackgroundCornerRadius});
        mLeftItemBackground = new StateListDrawable();
        mLeftItemBackground.addState(new int[]{android.R.attr.state_pressed}, leftItemPressedDrawable);
        mLeftItemBackground.addState(new int[]{}, leftItemNormalDrawable);
        // right
        GradientDrawable rightItemPressedDrawable = new GradientDrawable();
        rightItemPressedDrawable.setColor(mPressedBackgroundColor);
        rightItemPressedDrawable.setCornerRadii(new float[]{
                0, 0,
                mBackgroundCornerRadius, mBackgroundCornerRadius,
                mBackgroundCornerRadius, mBackgroundCornerRadius,
                0, 0});
        GradientDrawable rightItemNormalDrawable = new GradientDrawable();
        rightItemNormalDrawable.setColor(Color.TRANSPARENT);
        rightItemNormalDrawable.setCornerRadii(new float[]{
                0, 0,
                mBackgroundCornerRadius, mBackgroundCornerRadius,
                mBackgroundCornerRadius, mBackgroundCornerRadius,
                0, 0});
        mRightItemBackground = new StateListDrawable();
        mRightItemBackground.addState(new int[]{android.R.attr.state_pressed}, rightItemPressedDrawable);
        mRightItemBackground.addState(new int[]{}, rightItemNormalDrawable);
        // corner
        GradientDrawable cornerItemPressedDrawable = new GradientDrawable();
        cornerItemPressedDrawable.setColor(mPressedBackgroundColor);
        cornerItemPressedDrawable.setCornerRadius(mBackgroundCornerRadius);
        GradientDrawable cornerItemNormalDrawable = new GradientDrawable();
        cornerItemNormalDrawable.setColor(Color.TRANSPARENT);
        cornerItemNormalDrawable.setCornerRadius(mBackgroundCornerRadius);
        mCornerItemBackground = new StateListDrawable();
        mCornerItemBackground.addState(new int[]{android.R.attr.state_pressed}, cornerItemPressedDrawable);
        mCornerItemBackground.addState(new int[]{}, cornerItemNormalDrawable);
        mCornerBackground = new GradientDrawable();
        mCornerBackground.setColor(mNormalBackgroundColor);
        mCornerBackground.setCornerRadius(mBackgroundCornerRadius);
    }

    /**
     * 获取中心item背景
     */
    private StateListDrawable getCenterItemBackground() {
        StateListDrawable centerItemBackground = new StateListDrawable();
        GradientDrawable centerItemPressedDrawable = new GradientDrawable();
        centerItemPressedDrawable.setColor(mPressedBackgroundColor);
        GradientDrawable centerItemNormalDrawable = new GradientDrawable();
        centerItemNormalDrawable.setColor(Color.TRANSPARENT);
        centerItemBackground.addState(new int[]{android.R.attr.state_pressed}, centerItemPressedDrawable);
        centerItemBackground.addState(new int[]{}, centerItemNormalDrawable);
        return centerItemBackground;
    }

    /**
     * 刷新文本颜色状态列表
     *
     * @param pressedTextColor 按下文本颜色
     * @param normalTextColor  正常状态下文本颜色
     */
    private void refreshTextColorStateList(int pressedTextColor, int normalTextColor) {
        int[][] states = new int[2][];
        states[0] = new int[]{android.R.attr.state_pressed};
        states[1] = new int[]{};
        int[] colors = new int[]{pressedTextColor, normalTextColor};
        mTextColorStateList = new ColorStateList(states, colors);
    }

    public void hidePopupListWindow() {
        if (mContext instanceof Activity && ((Activity) mContext).isFinishing()) {
            return;
        }
        if (mPopupWindow != null && mPopupWindow.isShowing()) {
            mPopupWindow.dismiss();
        }
    }

    public View getIndicatorView() {
        return mIndicatorView;
    }

    public View getDefaultIndicatorView(Context context) {
        return getTriangleIndicatorView(context, dp2px(16), dp2px(8), DEFAULT_NORMAL_BACKGROUND_COLOR);
    }

    public View getTriangleIndicatorView(Context context, final float widthPixel, final float heightPixel,
                                         final int color) {
        ImageView indicator = new ImageView(context);
        Drawable drawable = new Drawable() {
            @Override
            public void draw(Canvas canvas) {
                Path path = new Path();
                Paint paint = new Paint();
                paint.setColor(color);
                paint.setStyle(Paint.Style.FILL);
                if (!mIsShowBottom) {
                    //这里画的倒三角
                    path.moveTo(0f, 0f);
                    path.lineTo(widthPixel, 0f);
                    path.lineTo(widthPixel / 2, heightPixel);
                    //将图像封闭,这里path.close()等同于 path.moveTo(widthPixel / 2, heightPixel);path.lineTo(widthPixel, 0);
                    path.close();
                } else {
                    //正三角
                    path.moveTo(0f, heightPixel);
                    path.lineTo(widthPixel, heightPixel);
                    path.lineTo(widthPixel / 2, 0);
                    path.close();
                }
                canvas.drawPath(path, paint);
            }

            @Override
            public void setAlpha(int alpha) {

            }

            @Override
            public void setColorFilter(ColorFilter colorFilter) {

            }

            @Override
            public int getOpacity() {
                return PixelFormat.TRANSLUCENT;
            }

            @Override
            public int getIntrinsicWidth() {
                return (int) widthPixel;
            }

            @Override
            public int getIntrinsicHeight() {
                return (int) heightPixel;
            }
        };
        indicator.setImageDrawable(drawable);
        return indicator;
    }

    public void setIndicatorView(View indicatorView) {
        this.mIndicatorView = indicatorView;
    }

    public void setIndicatorSize(int widthPixel, int heightPixel) {
        this.mIndicatorWidth = widthPixel;
        this.mIndicatorHeight = heightPixel;
        LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(mIndicatorWidth, mIndicatorHeight);
        layoutParams.gravity = Gravity.CENTER;
        if (mIndicatorView != null) {
            mIndicatorView.setLayoutParams(layoutParams);
        }
    }

    public int getNormalTextColor() {
        return mNormalTextColor;
    }

    public void setNormalTextColor(int normalTextColor) {
        this.mNormalTextColor = normalTextColor;
        refreshTextColorStateList(mPressedTextColor, mNormalTextColor);
    }

    public int getPressedTextColor() {
        return mPressedTextColor;
    }

    public void setPressedTextColor(int pressedTextColor) {
        this.mPressedTextColor = pressedTextColor;
        refreshTextColorStateList(mPressedTextColor, mNormalTextColor);
    }

    public float getTextSize() {
        return mTextSize;
    }

    public void setTextSize(float textSizePixel) {
        this.mTextSize = textSizePixel;
    }

    public int getTextPaddingLeft() {
        return mTextPaddingLeft;
    }

    public void setTextPaddingLeft(int textPaddingLeft) {
        this.mTextPaddingLeft = textPaddingLeft;
    }

    public int getTextPaddingTop() {
        return mTextPaddingTop;
    }

    public void setTextPaddingTop(int textPaddingTop) {
        this.mTextPaddingTop = textPaddingTop;
    }

    public int getTextPaddingRight() {
        return mTextPaddingRight;
    }

    public void setTextPaddingRight(int textPaddingRight) {
        this.mTextPaddingRight = textPaddingRight;
    }

    public int getTextPaddingBottom() {
        return mTextPaddingBottom;
    }

    public void setTextPaddingBottom(int textPaddingBottom) {
        this.mTextPaddingBottom = textPaddingBottom;
    }

    public void setTextPadding(int left, int top, int right, int bottom) {
        this.mTextPaddingLeft = left;
        this.mTextPaddingTop = top;
        this.mTextPaddingRight = right;
        this.mTextPaddingBottom = bottom;
    }

    public int getNormalBackgroundColor() {
        return mNormalBackgroundColor;
    }

    public void setNormalBackgroundColor(int normalBackgroundColor) {
        this.mNormalBackgroundColor = normalBackgroundColor;
        refreshBackgroundOrRadiusStateList();
    }

    public int getPressedBackgroundColor() {
        return mPressedBackgroundColor;
    }

    public void setPressedBackgroundColor(int pressedBackgroundColor) {
        this.mPressedBackgroundColor = pressedBackgroundColor;
        refreshBackgroundOrRadiusStateList();
    }

    public void setShowBottom(boolean isShowBottom) {
        this.mIsShowBottom = isShowBottom;
    }

    public void setShowTouchLocation(boolean showTouchLocation) {
        mIsShowTouchLocation = showTouchLocation;
    }

    public int getBackgroundCornerRadius() {
        return mBackgroundCornerRadius;
    }

    public void setBackgroundCornerRadius(int backgroundCornerRadiusPixel) {
        this.mBackgroundCornerRadius = backgroundCornerRadiusPixel;
        refreshBackgroundOrRadiusStateList();
    }

    public int getDividerColor() {
        return mDividerColor;
    }

    public void setDividerColor(int dividerColor) {
        this.mDividerColor = dividerColor;
    }

    public int getDividerWidth() {
        return mDividerWidth;
    }

    public void setDividerWidth(int dividerWidthPixel) {
        this.mDividerWidth = dividerWidthPixel;
    }

    public int getDividerHeight() {
        return mDividerHeight;
    }

    public void setDividerHeight(int dividerHeightPixel) {
        this.mDividerHeight = dividerHeightPixel;
    }

   public void setmReversalHeight(float mReversalHeight) {
        this.mReversalHeight = mReversalHeight;
    }

    public Resources getResources() {
        if (mContext == null) {
            return Resources.getSystem();
        } else {
            return mContext.getResources();
        }
    }

    private int getViewWidth(View view) {
        // 1、UNSPECIFIED,不限定。意思就是,子View想要多大,我就可以给你多大,你放心大胆的measure吧,不用管其他的。也不用管我传递给你的尺寸值。(其实Android高版本中推荐,只要是这个模式,尺寸设置为0)
        //
        // 2、EXACTLY,精确的。意思就是,根据我当前的状况,结合你指定的尺寸参数来考虑,你就应该是这个尺寸,具体大小在MeasureSpec的尺寸属性中,自己去查看吧,你也不要管你的content有多大了,就用这个尺寸吧。
        //
        // 3、AT_MOST,最多的。意思就是,根据我当前的情况,结合你指定的尺寸参数来考虑,在不超过我给你限定的尺寸的前提下,你测量一个恰好能包裹你内容的尺寸就可以了。
        view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
        return view.getMeasuredWidth();
    }

    private int getViewHeight(View view) {
        view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
        return view.getMeasuredHeight();
    }

    public int dp2px(float value) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                value, getResources().getDisplayMetrics());
    }

    public int sp2px(float value) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
                value, getResources().getDisplayMetrics());
    }

    /**
     * 回调监听器
     */
    public interface PopupListListener {
        boolean showPopupList(View adapterView, View contextView, int contextPosition);

        void onPopupListClick(View contextView, int contextPosition, int position);
    }

}



为了向别人、向世界证明自己而努力拼搏,而一旦你真的取得了成绩,才会明白:人无须向别人证明什么,只要你能超越自己。

Android实现气泡布局/弹窗,可控制气泡尖角方向及偏移量 (附完整源码) 阅读详情

相关推荐

Android PopupWindow泡泡效果(有listview)

Android PopupWindow泡泡效果(有listview)

Android Stuido中实现点击按钮弹出气泡

今天和大家分享一下实现Android Studio实现点击按钮弹出气泡的实现过程。一开始制作的时候我也不知道具体怎么实现,但是网上看了一些代码之后自己尝试了之后终于实现了这个功能,使用PopWindow就可以实现气泡的谈弹出。 实现效果图 实现代码如下 MainActivity package com.example.qipao; import android.app.Activity; i...

weixin_44223883的博客 2917

Android代码-BubblePopupWindow

BubblePopupWindow Android 实现各个方向的气泡弹窗,可控制气泡尖角偏移量。 截图 依赖 dependencies { compile 'com.yuyh.bubble:library:1.0.0' } 使用 默认弹窗BubblePopupWindow leftTopWindow = new BubblePopupWindow(MainActivity.this); View bubbleView = inflater.inflate(R.layout.layout_popup_view, null); TextView tvContent = (TextView) bubbleView.findViewById(R.id.tvContent); tvContent.setText("HelloWorld"); leftTopWindow.setBubbleView(bubbleView); // 设置气泡内容 leftTopWindow.show(view, Gravity.BOTTOM, 0); // 显示弹窗 /** 显示弹窗 @pa

安卓气泡弹窗

先画好自己的气泡布局 pop_window_view.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:backgroun

lgz0921的博客 2976

Android studio实现点击按钮弹出气泡

该资源使用Android Studio进行开发,实现在手机中点击按钮弹出气泡,并且可以对气泡内的控件进行触控以及触发相应的触控事件

Android 实现长按弹出PopupMenu 菜单栏

主要介绍了Android 实现长按弹出PopupMenu 菜单栏,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下

2024年25个Android酷炫开源UI框架(1),flutter弹窗页面缩小

可以显示水位上升下降的TextView滑动Layout,支持单个View,ListView,GridView可以帮你轻松实现自定义字体的库Android的图案密码解锁APP示例:Android开机的图案密码解锁,支付宝的密码解锁状态切换的 Button,类似,用 View 实现绘制图表的库,支持LineChartView、BarChartView和StackBarChartView三中图表类型,并且支持 Android 2.2及以上的系统。用户简历界面,出品。

2401_84563220的博客 1040

超声信号处理——硬件电路中的混频应用

本文介绍了超声多普勒系统的信号处理原理。系统通过波形振荡器产生高频信号,经换能器转换为超声波发射,接收反射信号后与原始信号混频,利用三角函数的积化和差特性产生包含多普勒频移的低频差频信号和高频和频信号。通过低通滤波器滤除高频成分后,即可获得纯净的多普勒信号。这种处理方法将MHz级高频信号转换为可听低频信号,降低了系统复杂度与成本。数学推导表明,差频信号频率正好

fcxgfdjy的博客 1123

Android-25种开源炫酷动画框架

转载自:http://blog.youkuaiyun.com/u013101864/article/details/51500136 看到这些动画效果甚是激动,最近也在做Dialog弹窗,接下来会整理一下,分享出来~ 1.Side-Menu.Android 分类侧滑菜单,Yalantis 出品。 项目地址:https://github.com/Yalantis/Side-Menu.Androi

xiaomengzi的博客 373

MEME-CHIP实战:5步搞定ChIP-seq数据中的motif发现与可视化

本文提供了使用MEME-CHIP工具进行ChIP-seq数据motif发现与可视化的完整实战指南。通过5个清晰步骤,从数据准备、环境搭建、命令行执行到结果解读与高级可视化,详细讲解了如何利用MEME-CHIP工作流高效完成从原始peak序列到发表级motif图的分析,并分享了关键的参数调优与避坑经验。

alice7model的博客 508

标准高斯过程回归(Gaussian Processes Regression, GPR)从零开始,公式推导

标准高斯过程回归从零开始公式推导

qq_43587949的博客 1万+

Android 使用PopupWindow自定义气泡弹窗

作为一个菜鸟突然想了解一下气泡弹窗,于是查找资料就写了一个简单的弹窗PopupWindow, 个人感觉符合菜鸟的水平; 一.首先自定义好气泡框 public class MyLinear extends LinearLayout { private Paint paint; //view的宽高 int widthSize; int heightSiz...

TGWhuli的博客 4281

Google账号找回通用方法(尤其是知道账号密码仍无法登录和找回的)

Google账号找回通用方法【尤其是知道账号密码仍无法登录和找回的】普通找回注意:适用于常用谷歌账户的设备和网络情况下无法使用手机或辅助邮箱验证码进行密码找回特殊找回方法注意;适用于1.新的设备以及网络环境(包括由于非正常原因导致设备系统以及应用信息变更)2.没有开启辅助验证(手机/邮箱)3.知道账号密码但无法登录或找回。通过申诉进行找回 普通找回 注意:适用于 常用谷歌账户的设备和网络情况下 无法使用手机或辅助邮箱验证码进行密码找回 通过恢复页面进行恢复 https://accounts.google

极客代码-极致生活 23万+

短视频app怎么开发Android Q之气泡弹窗

在短视频APP怎么开发Android Q中,用户可以借助气泡,轻松地在设备上任何位置进行多任务处理。气泡内置于“通知”系统中,它会浮动在其他应用的上层,并会跟随用户的移动而移动到屏幕的任何位置,用于取代SYSTEM_ALERT_WINDOW。气泡可以展开显示应用功能和信息,并在不使用时折叠起来。当设备处于已锁定状态或始终保持活动状态,气泡会像通知那样显示。 一、气泡配置信息 气泡是一种可以选择停用的功能,在应用显示第一个气泡时,系统会弹出权限对话框,提供两种选项: 屏蔽来自您的应用的所有气泡 - 通知不会被

云豹网络科技 2585

山石网科Hillstone防火墙基础上网配置_CLI命令行(最新版)

山石网科Hillstone防火墙安全网关基础上网基础网络配置_CLI命令行(最新版)

路与肥的博客 1万+

LVGL多线程编程避坑指南:从音乐播放器项目看UI与后台任务如何安全协作

本文深入探讨LVGL在多线程环境下的安全协作问题,以音乐播放器项目为例,解析UI与后台任务(如mplayer音频播放器)的典型冲突场景。提供全局互斥锁规范、任务队列异步处理等核心策略,并分享mplayer进程集成方案与进度条优化技巧,帮助开发者规避界面卡顿、内存损坏等常见问题。

weixin_42531494的博客 131

Android气泡弹窗库 - BubblePopupWindow

Android气泡弹窗库 - BubblePopupWindow 项目基础介绍 BubblePopupWindow 是一款专为Android平台设计的开源弹窗库,它允许开发者轻松创建可在任意方向显示的气泡形弹窗,并且支持对气泡尖角的偏移量进行精细控制。此项目由Java语言编成,适配于多种Android开发环境。 核心功能 多向弹窗:支持弹窗从目标视图的顶部、底部、左侧或右侧以气泡形式弹出。 尖角...

gitblog_09203的博客 604

ROS机器人未知环境自主探索功能包explore_lite最全源码详细解析(五)

本系列文章主要针对ROS机器人常使用的未知环境自主探索功能包explore_lite展开全源码的详细解析,并进行概括总结。 本系列文章共包含六篇文章,前五篇文章主要介绍explore_lite功能包中 explore.cpp、costmap_tools.h、frontier_search.cpp、costmap_client.cpp等源码实现文件中全部函数的详细介绍,第六篇文章进行概括总结,包含自主探索功能包explore_lite的简介,实现未知环境自主探索功能的原理及流程总结,效果演示,使用功能包exp

慕羽★的博客 1510

推荐项目:BubblePopupWindow - Android 气泡弹窗库

推荐项目:BubblePopupWindow - Android 气泡弹窗库 项目介绍 在移动应用开发中,创造出引人注目且用户体验友好的UI组件始终是开发者们关注的重点。今天,我们向您推荐一个开源项目 —— BubblePopupWindow,这是一款为Android平台设计的高效能、高度自定义的气泡弹窗库。它允许开发者以各种方向呈现弹窗,并精细控制气泡尖角的位置偏移量,使得弹窗与界面元素的交互更...

gitblog_00056的博客 917

Python爬虫实战:使用最新技术抓取百度贴吧帖子内容

本文详细介绍了使用Python最新技术抓取百度贴吧帖子的完整流程。通过库,我们实现了高效的异步爬虫,具备JavaScript渲染、智能反爬应对、数据持久化等高级功能。这个爬虫框架具有良好的扩展性,可以根据具体需求进行功能增强。

2201_76125261的博客 777

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

BubblePopupWindow

Android 实现各个方向的气泡弹窗,可控制气泡尖角偏移量。 https://github.com/smuyyh/BubblePopupWindow 截图   使用: BubblePopupWindow leftTopWindow = new BubblePopupWindow(MainActivity.this); View bubbleView = inflater.inflate(R....

weixin_34218890的博客 432

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

Android实现点击按钮弹出气泡(附带源码)

Android实现点击按钮弹出气泡(附带源码)

欢迎来到我的博客!这里是一个专注于计算机技术的分享平台,涵盖编程开发、算法研究、系统架构、软件工程等多个领域。无论你是初学者还是资深开发者,都能在这里找到有价值的内容。 1638

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

Android实现气泡布局/弹窗,可控制气泡尖角方向及偏移量(附带源码)

Android实现气泡布局/弹窗,可控制气泡尖角方向及偏移量(附带源码)

欢迎来到我的博客!这里是一个专注于计算机技术的分享平台,涵盖编程开发、算法研究、系统架构、软件工程等多个领域。无论你是初学者还是资深开发者,都能在这里找到有价值的内容。 2720

Android R 通知新特性—人与对话(气泡窗)

Android R 通知新特性—人与对话(气泡窗) google在之前的版本就提出了一个“people and conversations”的概念,目的是在手机界面中增加交流类UI的比重,因为毕竟用户使用手机,有很大一部分行为都是基于社交去进行,社交是人与社会沟通的基础.

weixin_40637496的博客 6962

android 封装的popwindow,Android UI开发 popupwindow介绍以及代码实例

PopupWindow在android.widget包下,弹出窗口的形式展示。官方文档对该控件的描述是:“一个弹出窗口控件,可以用来显示任意视图(View),而且会浮动在当前 活动(activity)的顶部”。PopupWindow可以让我们实现多种自定义控件,例如:menu、alertdialog等弹窗似的View。一、PopupWindow的简单用法在这个例子中,popupwindow在onC...

weixin_35987446的博客 776

阿里云Oss上传(Android版)

写了个简单的阿里云Oss上传,封装成一个类,可以直接使用 首先在您的gradle中配置sdk: dependencies { compile 'com.aliyun.dpa:oss-android-sdk:+' } 然后确保您的 AndroidManifest.xml 文件中已经配置了这些权限 &amp;lt;uses-permission android:name=&quot;android.pe...

皮小智的博客 1万+
上一篇: 第三方直播SDK对比(腾讯云,阿里云,网易云信,七牛云,金山云,声网,即构科技)
下一篇: (踩坑)实现切换动画过程中遇到的一些坑
皮小智
博客等级 码龄10年 56粉丝 34原创
评论 5
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值