先上效果图:
之前用手机QQ时,一直很觉得这个窗口提示挺不错的,今天将它大概地实现了一遍。
首先是:提示窗口的三角下标是可以改变位置的,然后窗口中有很多小的item,item被点击时会显示出不同的颜色,
同时三角下标的颜色也随着改变。
然后是:提示窗口的item们会根据传入的坐标实现向上显示或向下显示。
一、对TipView定义一些成员变量
private static final int STATUS_DOWN = 1;
private static final int STATUS_UP = 2;
// 初始窗口的Item显示在上方
private int mStatus = STATUS_UP;
// 分隔Item之间的线
private int mSeparateLineColor ;
// 最边上两个方块的边角半径大小
private int mCorner = dip2px(6);
private Paint mPaint; // 画方块和文字的画笔
private Paint doPaint; // 画三角的画笔
private Path mPath; // 绘制的路径
private int mBorderMargin = dip2px(5); // 提示窗口与屏幕(根布局)的最小距离
private int mItemWidth = dip2px(50); // 窗口Item的宽度
private int mItemHeight = dip2px(48); // 窗口Item的高度
private int mTriangleTop = dip2px(50); // 三角小标的顶点
private int mHalfTriangleWidth = dip2px(6); // 三角小标的半宽
private int mItemBorder; // 三角小标与窗口Item的临界
private int realLeft; // 窗口的left值
private List mItemList = new ArrayList<>(); // 存储每个Item的信息
private List mItemRectList = new ArrayList<>(); // 存储每个方块
private OnItemClickListener onItemClickListener; // Item点击接口
private int choose = -1; // 是否有Item被按下,是为Item的序号,否为-1
private int x; // 外界出入的x坐标
private int y; // 外界传入的y坐标
二、TipView的初始化
TipView的构造方法:
public TipView(Context context, ViewGroup rootView,int x,int y,List mItemList) {
super(context);
this.x = x; // 设置传入过来的x轴坐标
this.y = y; // 设置传入过来的y轴坐标
// x和y决定了三角下标的位置
initPaint(); // 初始化画笔
setTipItemList(mItemList); // 初始化Item集合,并对Item的字符串长度进行处理
//【当字符串过长时,使用后端省略处理:“xxx...”】
addView(rootView); // 将TipView实例添加到传入的rootView中。
// 注意:rootView需要FrameLayout/RelativeLayout或其子类实例
initView(); // 根据传入的点击坐标x、y进行状态判断和最左位置的处理
}
initView()的具体实现:
private void initView() {
// 获取屏幕宽度
int mScreenWidth = getResources().getDisplayMetrics().widthPixels;
// 当点击事件的纵坐标y比较小时(点击的位置比较上面)
if (y/2 mScreenWidth) {
// 跑出屏幕右边的话,则减去溢出的宽度,再减去距离值
realLeft -= realLeft + (mItemWidth*mItemList.size())-mScreenWidth+mBorderMargin;
// 防止三角下标与方块分离
if(x+mCorner>=realLeft+mItemWidth*mItemList.size()) x = realLeft+mItemWidth*mItemList.size()-mCorner*2;
}
// 没有对窗口左右边都超出的情况做处理,大家有没有好的解决方案
}
三、TipView的绘制
首先绘制图层为透明,再根据判断得来的状态绘制窗口。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawBackground(canvas);
switch (mStatus) {
case STATUS_DOWN:
drawItemDown(canvas);
break;
case STATUS_UP:
drawItemUp(canvas);
break;
default:
break;
}
}
具体绘制窗口代码太长不便贴出,可到源码参考。
四、TipView中的Item点击事件的监听
设计一个监听器接口:
public interface OnItemClickListener {
// 设计两个参数
void onItemClick(String name,int position);
void dismiss();
}
覆写TipView的onTouchEvent方法:
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
for (int i = 0; i < mItemRectList.size(); i++) {
if (onItemClickListener != null && isPointInRect(new PointF(event.getX(), event.getY()), mItemRectList.get(i))) {
// 被按下时,choose值为当前方块Item序号
choose = i;
// 更新视图
invalidate(mItemRectList.get(i));
}
}
return true;
case MotionEvent.ACTION_UP:
for (int i = 0; i < mItemRectList.size(); i++) {
if (onItemClickListener != null && isPointInRect(new PointF(event.getX(), event.getY()), mItemRectList.get(i))) {
// 触发方法,传入两个参数
onItemClickListener.onItemClick(mItemList.get(i).getTitle(),i);
choose = -1;
}
}
if (onItemClickListener != null) {
onItemClickListener.dismiss(); // 触发方法
}
removeView(); // 移除TipView
return true;
}
return true;
}
五、让设计模式为Build模式:
public static class Builder {
// TipView中一些重要的成员变量
private OnItemClickListener onItemClickListener;
private Context mContext;
private ViewGroup mRootView;
private List mTipItemList = new ArrayList<>();
private int mSeparateLineColor = Color.WHITE;
private int x ,y;
public Builder(Context context, ViewGroup rootView,int x,int y) {
mContext = context;
mRootView = rootView;
this.x = x;
this.y = y;
}
public Builder addItem(TipItem tipItem) {
mTipItemList.add(tipItem);
return this;
}
public Builder addItems(List list) {
mTipItemList.addAll(list);
return this;
}
public Builder setSeparateLineColor(int color) {
mSeparateLineColor = color;
return this;
}
public Builder setOnItemClickListener(OnItemClickListener onItemClickListener){
this.onItemClickListener = onItemClickListener;
return this;
}
// 创建TipView实例
public TipView create() {
TipView flipShare = new TipView(mContext, mRootView,x,y,mTipItemList);
flipShare.setOnItemClickListener(onItemClickListener);
flipShare.setSeparateLineColor(mSeparateLineColor);
return flipShare;
}
}
六、使用示例:
创建TipView实例时,需要传入控件被点击/长按的位置坐标。
注:传给rootView的参数需要FrameLayout/RelativeLayout或其子类实例
public class MainActivity extends AppCompatActivity {
private Button button0;
private RelativeLayout rl;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button0 = (Button) findViewById(R.id.button0);
rl = (RelativeLayout) findViewById(R.id.activity_main);
button0.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
int x = 0;int y = 0;
if(event.getAction()==MotionEvent.ACTION_UP){
// 获取点击位置
x= (int) event.getX();y = (int) event.getY();
Log.i("zz",x+" "+y);
// 传入视图上的坐标
TipView share = new TipView.Builder(MainActivity.this,rl,x+v.getLeft(),y+v.getTop())
.addItem(new TipItem("复制复制"))
.addItem(new TipItem("粘贴"))
.addItem(new TipItem("删除"))
.addItem(new TipItem("收藏"))
.addItem(new TipItem("转发"))
.addItem(new TipItem("更多"))
.setOnItemClickListener(new TipView.OnItemClickListener() {
@Override
public void onItemClick(String str,int a) {
Toast.makeText(MainActivity.this,str,Toast.LENGTH_SHORT).show();
}
@Override
public void dismiss() {
}
})
.create();
}
return true;
}
});
}
}