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);
}
}