使用android.graphics.Path类自绘制PopupWindow背景

PopupWindow简介

PopupWindow是悬浮在当前activity上的一个容器,用它可以展示任意的内容。

PopupWindow跟位置有关的API有下面几个:

  • showAsDropDown(View anchor, int xoff, int yoff, int gravity)

    显示在anchor的左下角,通过xoff,yoff调整距离,gravity是popup相对于anchor的对齐方式。如果popup超出屏幕,并且展示内容的根容器是滑动控件,将以滑动方式展示。如果展示内容根容器不是滑动控件,超出屏幕内容将不可见。

  • showAsDropDown (View anchor, int xoff, int yoff)

    同上

  • showAsDropDown (View anchor)

    同上

  • showAtLocation (View parent, int gravity, int x, int y)

    展示在屏幕的特定位置,如果内容超出屏幕将被裁剪。

    gravity 为NO_GRAVITY等同于 Gravity.LEFT | Gravity.TOP

showAsDropDown 还是showAtLocation?

如果有anchor,可以使用showAsDropDown 方法,如果没有anchor可以使用showAtLocation 方法,注意使用showAtLocation 方法popup内容超出屏幕即使内容放到ScrollView里也不会滚动。

使用Path类自绘制PopupWindow背景

这里选择showAtLocation方法,使用Path类自绘制PopupWindow背景。

绘制规则如下:

给定Popup锚点的x坐标,anchorX;y坐标,anchorYDown,anchorYUp,自定义view会自动计算三角绘制位置,以及显示在anchor下方还是上方。默认显示在下方,下方显示不下再显示在上方。不足是内容太长无法滚动显示

实现

package com.xxx;

import com.xxx.utils.log.LogUtils;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.PathShape;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout.LayoutParams;
import android.widget.PopupWindow;
import android.widget.TextView;

/**
 * TextView with popup style background (has triangle on top or bottom). The
 * anchor triangle will show accurately below or above the anchor position.
 *
 * @author wangwenping
 * @date 2015-6-27
 */
@SuppressLint("DrawAllocation")
public class PopupTextView extends TextView
{
    private static final String TAG = "PopupTextView";
    private static final boolean IS_DEBUG = false;
    /**
     * x of anchor triangle in the popup
     */
    private float mTriangleX;
    /**
     * border color
     */
    private int mBorderColor = 0xff1fc38f;
    /**
     * border width
     */
    private int mBorderWidth = 2;
    /**
     * background color
     */
    private int mBgColor = 0xffffffff;
    /**
     * background color in dark mode
     */
    private int mBgColorDark = 0xff999999;
    /**
     * anchor height
     */
    private float mAnchorHeight = 20;
    /**
     * anchor width
     */
    private float mAnchorWidth = 30;
    /**
     * If content under anchor
     */
    private boolean mShowDown = true;
    /**
     * Below items for draw
     */
    private ShapeDrawable mBorderDrawable;
    private Path mBorderPath;
    private ShapeDrawable mBgDrawable;
    private Path mBgPath;
    private int mWidth;
    private int mHeight;
    /**
     * Keep a record of original padding.
     */
    private int mPadding;
    /**
     * Is night mode.
     */
    private boolean mIsNightMode;
    /**
     * anchor x, y in screen
     */
    private int mAnchorYUp;
    private int mAnchorYDown;
    private int mAnchorX;

    /**
     * screen height & width
     */
    private int mScreenHeight;
    private int mScreenWidth;
    private float mDensity;
    private PopupWindow mPopupWindow;
    private Context mCtx;
    /**
     * Touch listener
     */
    private OnTouchListener mOnTouchListener;
    private boolean mDismissAfterTouch = true;
    /**
     * The minimum margin to left or right.
     */
    private int TRIANGLE_MINIMUM_MARGIN = 10;

    public PopupTextView(Context context)
    {
        super(context);
        setFocusable(true);
        init(context);
    }

    public PopupTextView(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
        init(context);
    }

    public PopupTextView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        init(context);
    }

    private void init(Context c)
    {
        mCtx = c;
        mPadding = getPaddingBottom();
        DisplayMetrics dm = c.getResources().getDisplayMetrics();
        mScreenHeight = dm.heightPixels;
        mScreenWidth = dm.widthPixels;
        mDensity = dm.scaledDensity;
    }

    /**
     * Show as pop up window
     */
    public void show()
    {
        if (mPopupWindow != null)
        {
            mPopupWindow.dismiss();
        }

        if (IS_DEBUG)
        {
            LogUtils.d(TAG, "mAnchorX=" + mAnchorX + " mWidth=" + mWidth + " mHeight=" + mHeight);
        }

        mPopupWindow = new PopupWindow(this, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
        if (mOnTouchListener != null)
        {
            mPopupWindow.setTouchInterceptor(new OnTouchListener()
            {
                @Override
                public boolean onTouch(View arg0, MotionEvent arg1)
                {
                    mOnTouchListener.onTouch(arg0, arg1);
                    if (mDismissAfterTouch && arg1.getAction() == MotionEvent.ACTION_UP)
                    {
                        mPopupWindow.dismiss();
                    }
                    return false;
                }
            });
        }
        mPopupWindow.setFocusable(true);
        mPopupWindow.setTouchable(true);
        mPopupWindow.setBackgroundDrawable(new BitmapDrawable());

        int popX = 0, popY = 0;
        if (mWidth <= 0 || mHeight <= 0)
        {
            // The first time we showthe pop up window out of the screen to get
            // the size of itself.
            popX = mScreenWidth;
            popY = mScreenHeight;
        }
        else
        {
            // The second time we calculate the pop up window‘s right position.
            Point pos = getLayoutValue();
            popX = pos.x;
            popY = pos.y;
            mTriangleX = mAnchorX - pos.x;
            mTriangleX = Math.max(mTriangleX, TRIANGLE_MINIMUM_MARGIN);
            mTriangleX = Math.min(mTriangleX, mWidth - TRIANGLE_MINIMUM_MARGIN - mAnchorWidth);
        }
        mPopupWindow.showAtLocation(this, Gravity.LEFT | Gravity.TOP, popX, popY);
    }

    /**
     * Calculate the pop up window‘s right position.
     *
     * @return
     */
    private Point getLayoutValue()
    {
        int x = mAnchorX - mWidth / 2;
        if (x < 10 * mDensity)
        {
            x = (int) (10 * mDensity);
        }
        else if (x + mWidth > mScreenWidth - 10 * mDensity)
        {
            x = (int) (mScreenWidth - mWidth - 10 * mDensity);
        }
        boolean showDown = mAnchorYDown + mHeight < mScreenHeight || mAnchorYDown <= mScreenHeight / 2;
        setShowDown(showDown);
        int y = showDown ? mAnchorYDown : mAnchorYUp - mHeight;
        return new Point(x, y);
    }

    /**
     * Init drawble path.
     *
     * @param width
     * @param height
     */
    private void initPath(int width, int height)
    {
        mBorderPath = new Path();
        mBgPath = new Path();

        if (mShowDown)
        {
            /**
             * ....|<----------------width-------->|<br>
             * ....|<--archorX------>|<br>
             * ....................2<br>
             * ..................../\ (anchor)<br>
             * ....0/7-------------1 3-----------4...........----<br>
             * ....|...............................|.............|<br>
             * ....|...............................|.............height<br>
             * ....|...............................|.............|<br>
             * ....6-------------------------------5............---<br>
             */
            PointF[] borderPoints = new PointF[] { new PointF(0, mAnchorHeight),
                    new PointF(mTriangleX - mAnchorWidth / 2, mAnchorHeight), new PointF(mTriangleX, 0),
                    new PointF(mTriangleX + mAnchorWidth / 2, mAnchorHeight), new PointF(width, mAnchorHeight),
                    new PointF(width, height), new PointF(0, height), new PointF(0, mAnchorHeight), };
            mBorderPath = createLIneToPath(borderPoints);

            PointF[] bgPoints = new PointF[] {
                    new PointF(borderPoints[0].x + mBorderWidth, borderPoints[0].y + mBorderWidth),
                    new PointF(borderPoints[1].x + mBorderWidth, borderPoints[1].y + mBorderWidth),
                    new PointF(borderPoints[2].x, borderPoints[2].y + mBorderWidth),
                    new PointF(borderPoints[3].x - mBorderWidth, borderPoints[3].y + mBorderWidth),
                    new PointF(borderPoints[4].x - mBorderWidth, borderPoints[4].y + mBorderWidth),
                    new PointF(borderPoints[5].x - mBorderWidth, borderPoints[5].y - mBorderWidth),
                    new PointF(borderPoints[6].x + mBorderWidth, borderPoints[6].y - mBorderWidth),
                    new PointF(borderPoints[7].x + mBorderWidth, borderPoints[7].y + mBorderWidth), };
            mBgPath = createLIneToPath(bgPoints);
        }
        else
        {
            /**
             * 0/7-----------------------------1<br>
             * |...............................|<br>
             * |...............................|<br>
             * 6------------------5..3---------2<br>
             * ....................\/<br>
             * ....................4<br>
             */
            PointF[] borderPoints = new PointF[] { new PointF(0, 0), new PointF(width, 0),
                    new PointF(width, height - mAnchorHeight),
                    new PointF(mTriangleX + mAnchorWidth / 2, height - mAnchorHeight), new PointF(mTriangleX, height),
                    new PointF(mTriangleX - mAnchorWidth / 2, height - mAnchorHeight),
                    new PointF(0, height - mAnchorHeight), new PointF(0, 0), };
            mBorderPath = createLIneToPath(borderPoints);

            PointF[] bgPoints = new PointF[] {
                    new PointF(borderPoints[0].x + mBorderWidth, borderPoints[0].y + mBorderWidth),
                    new PointF(borderPoints[1].x - mBorderWidth, borderPoints[1].y + mBorderWidth),
                    new PointF(borderPoints[2].x - mBorderWidth, borderPoints[2].y - mBorderWidth),
                    new PointF(borderPoints[3].x - mBorderWidth, borderPoints[3].y - mBorderWidth),
                    new PointF(borderPoints[4].x, borderPoints[4].y - mBorderWidth),
                    new PointF(borderPoints[5].x + mBorderWidth, borderPoints[5].y - mBorderWidth),
                    new PointF(borderPoints[6].x + mBorderWidth, borderPoints[6].y - mBorderWidth),
                    new PointF(borderPoints[7].x + mBorderWidth, borderPoints[7].y + mBorderWidth), };
            mBgPath = createLIneToPath(bgPoints);
        }
    }

    private Path createLIneToPath(PointF[] points)
    {
        Path path = new Path();
        if (points != null && points.length > 1)
        {
            path.moveTo(points[0].x, points[0].y);
            for (int i = 1; i < points.length; i++)
            {
                path.lineTo(points[i].x, points[i].y);
            }
        }
        path.close();
        return path;
    }

    public int getAnchorYUp()
    {
        return mAnchorYUp;
    }

    public void setAnchorYUp(int mAnchorYUp)
    {
        this.mAnchorYUp = mAnchorYUp;
    }

    public int getAnchorYDown()
    {
        return mAnchorYDown;
    }

    public void setAnchorYDown(int mAnchorYDown)
    {
        this.mAnchorYDown = mAnchorYDown;
    }

    public int getAnchorX()
    {
        return mAnchorX;
    }

    public void setAnchorX(int anchorX)
    {
        this.mAnchorX = anchorX;
    }

    public void setOnTouchListener(OnTouchListener l)
    {
        mOnTouchListener = l;
    }

    public void setDismissAfterTouch(boolean dismissAfterTouch)
    {
        mDismissAfterTouch = dismissAfterTouch;
    }

    public boolean getDismissAfterTouch()
    {
        return mDismissAfterTouch;
    }

    public void setShowDown(boolean showDown)
    {
        mShowDown = showDown;
        if (mShowDown)
        {
            setPadding(getPaddingLeft(), (int) mAnchorHeight + mPadding, getPaddingRight(), mPadding);
        }
        else
        {
            setPadding(getPaddingLeft(), mPadding, getPaddingRight(), (int) mAnchorHeight + mPadding);
        }
    }

    public void setNightMode(boolean isNightMode)
    {
        mIsNightMode = isNightMode;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh)
    {
        super.onSizeChanged(w, h, oldw, oldh);
        if (IS_DEBUG)
        {
            LogUtils.d(TAG, "w=" + w + " h=" + h + " oldw=" + oldw + " oldh=" + oldh);
        }
        mWidth = w;
        mHeight = h;
        show();
    }

    @Override
    protected void onDraw(Canvas canvas)
    {
        initPath(mWidth, mHeight);
        mBorderDrawable = new ShapeDrawable(new PathShape(mBorderPath, mWidth, mHeight));
        mBorderDrawable.getPaint().setColor(mBorderColor);
        mBgDrawable = new ShapeDrawable(new PathShape(mBgPath, mWidth, mHeight));
        int bgColor = mBgColor;
        if (mIsNightMode)
        {
            bgColor = mBgColorDark;
        }
        mBgDrawable.getPaint().setColor(bgColor);

        int x = 0;
        int y = 0;
        mBorderDrawable.setBounds(x, y, x + mWidth, y + mHeight);
        mBorderDrawable.draw(canvas);
        mBgDrawable.setBounds(x, y, x + mWidth, y + mHeight);
        mBgDrawable.draw(canvas);
        super.onDraw(canvas);
    }
}

4 下载

Github

csdn下载

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-07-29 01:33:52

使用android.graphics.Path类自绘制PopupWindow背景的相关文章

Android中Path类的lineTo方法和quadTo方法画线的区别

当我们需要在屏幕上形成画线时,Path类的应用是必不可少的,而Path类的lineTo和quadTo方法实现的绘制线路形式也是不一样的,下面就以代码的实现来直观的探究这两个方法的功能实现区别: 1. Path--->quadTo(float x1, float y1, float x2, float y2): 该方法的实现是当我们不仅仅是画一条线甚至是画弧线时会形成平滑的曲线,该曲线又称为"贝塞尔曲线"(Bezier curve),其中,x1,y1为控制点的坐标值,x2,y2为终

Android通过Path实现复杂效果(搜索按钮+时钟的实现 )

Path : 在Android中复杂的图形的绘制绝大多数是通过path来实现,比如绘制一条曲线,然后让一个物体随着这个曲线运动,比如搜索按钮,比如一个简单时钟的实现: 那么什么是path呢! 定义:path  就是路径,就是图形的路径的集合,它里边包含了路径里边的坐标点,等等的属性.我们可以获取到任意点的坐标,正切值. 那么要获取Path上边所有点的坐标还需要用到一个类,PathMeasure; PathMesure: PathMeasure是一个用来测量Path的类,主要有以下方法: 构造方法

Android Graphics之PathEffect

#本文基于android sdk 22 在android graphics模块中有一类特效类叫做"path effect",他们有一个共同的基类"PathEffect".这些path effect的唯一目的就是给path增加特效,换句话话说只有当paint的style为"STROKE"或者"FILL_AND_STROKE"时,path effect才会生效.添加path effect的方式很简单,只需要调用Paint.setP

Android中android.graphics下面的绘制图形类Canvas,Paint,Bitmap,Drawable

1.概念区别: 很多网友刚刚开始学习Android平台,对于Drawable.Bitmap.Canvas和Paint它们之间的概念不是很清楚, 其实它们除了Drawable外早在Sun的J2ME中就已经出现了,但是在Android平台中,Bitmap.Canvas相关的都有所变化. 首先让我们理解下Android平台中的显示类是View,但是还提供了底层图形类android.graphics,今天所说的这些均为graphics底层图形接口. Bitmap - 称作位图,一般位图的文件格式后缀为b

android Path类

1.Why 因为需要自己写一个自定义日历控件,所以需要了解一些android图形的基础类. 这篇文章里主要关于Path类的API. 2.moveTo moveTo 不会进行绘制,只用于移动移动画笔. 结合以下方法进行使用. 3.lineTo lineTo 用于进行直线绘制. mPath.lineTo(300, 300); canvas.drawPath(mPath, mPaint); 默认从坐标(0,0)开始绘制.如图: 刚才我们不说了moveTo是用来移动画笔的吗? mPath.moveTo(

Android -- 自定义View小Demo,关于Path类的使用(一)

1,在我们知道自定义view中onDraw()方法是用于绘制图形的,而Path类则是其中的一个重要的类,如下图效果: 代码也没有什么难度,直接贴出来吧 @Override protected void onDraw(Canvas canvas) { canvas.drawColor(Color.WHITE); Paint paint = new Paint(); paint.setAntiAlias(true); paint.setColor(0xFFFF6600); paint.setText

android.graphics包简介

Tip android.graphics 包 Abstract android.graphics 包android提供的2D开发包,它提供了一些初级图形工具,诸如画布.颜色过滤器.画 笔等 Usage surfaceview结合graphics包可以创建自定义控件 MyTick 绘图时可以想想自己在一个绘图软件中绘画,在一块画布(canvas)上,使用自己调好的笔(Paint)画线(Path)画形状(drawRect drawOval),插入想要的图片(drawBitmap),插入想要的特定格式

Android自定义控件-Path之贝赛尔曲线和手势轨迹、水波纹效果

从这篇开始,我将延续androidGraphics系列文章把图片相关的知识给大家讲完,这一篇先稍微进阶一下,给大家把<android Graphics(二):路径及文字>略去的quadTo(二阶贝塞尔)函数,给大家补充一下. 本篇最终将以两个例子给大家演示贝塞尔曲线的强大用途: 1.手势轨迹 利用贝塞尔曲线,我们能实现平滑的手势轨迹效果 2.水波纹效果 电池充电时,有些手机会显示水波纹效果,就是这样做出来的. 废话不多说,开整吧 一.概述 在<android Graphics(二):路径

android游戏开发中图形绘制:Canvas和Paint的使用

android游戏开发中,使用android.graphics中的类来绘制2D向量图和文字. 一 画布Canvas 在Android中的绘图应该继承View组件,并重写它的onDraw(Canvas canvas)方法. Canvas代表指定View上的画布,常用方法如图: 二 画刷Paint Paint代表Canvas上的画刷,主要用于绘制风格,包括画刷颜色.画刷笔触粗细.填充风格等. 大体上可以分为两类,一类与图形绘制相关,一类与文本绘制相关. 常用方法如图: 三 路径Path Path表示