Android自定義TipView仿QQ長按后的提示窗口
自定義view--TipView
TipView其實(shí)就是類似QQ長按消息彈出來的橫放的提示框。
通過看書和參考各位大神的博客(再次對大神表示恭敬),我用了一下午時間寫完了這么一個view。
先來看圖:



1 自定義TipView思路
1 首先我們考慮是繼承View還是ViewGroup
其實(shí)TipView直觀看更像是一個group,里面有子view。但其實(shí)我們并不需要繼承ViewGroup,因?yàn)槲覀儾挥孟馤inearLayout那樣在布局文件里面去添加子view,而且TipView的item我們用文字就好。如果繼承于Group我們還要考慮onLayout的問題,為了簡單我直接繼承自View。
2 重寫方法
TipView要像PopupWindow、Dialog一樣顯示在Activity上而不是添加到父容器中,原因是如果創(chuàng)建后添加到父容器中去托管的話,父容器的布局規(guī)則會影響我們TipView的顯示效果。所以我們要使用WindowManager來把TipView添加到外層布局,并且要充滿屏幕,i原因?yàn)槲覀円c(diǎn)擊tem之外的地方使TipView消失。所以view大小是固定充滿屏幕的,不需要重寫onMeasure。
需要重寫onDraw來繪制view。
3 顯示位置
TipView主要分兩部分,一部分是三角標(biāo),一部分是帶有圓角的主體。
當(dāng)我們點(diǎn)擊后,三角標(biāo)頂點(diǎn)始終在點(diǎn)擊位置上方一定距離(如果頂點(diǎn)定位在點(diǎn)擊位置,會導(dǎo)致手指擋住一部分三角,用戶體驗(yàn)度不佳),并且主體不要與屏幕左右邊界碰撞,當(dāng)要遮擋ToolBar時向下繪制。
2 定義變量
public static final int TOP = 0;//從點(diǎn)擊位置上面繪制 public static final int DOWN = 1;//...下面... private int mItemWidth;//item寬 private int mItemHeight;//item高 private int mTriaHeight;//三角的高度 private int mHalfTriaWidth;//三角的半寬 private int mTriaAcme;//三角的頂點(diǎn) private int mTriaItemBorder;//三角的頂點(diǎn) private int realLeft;//窗口距左邊的值 private int marginSide;//窗口距左右邊的值,防止出現(xiàn)的窗口緊貼邊界 private int mSeparateLineColor = Color.WHITE; private int mTextSize;//選項(xiàng)文字的大小 private int mTextColor;//選項(xiàng)文字的顏色 private int mItemSeparation;//分割線寬度; private int mRadius;//圓角 private List<TextItem> items;//存放item的集合 private List<Rect> mItemRectList = new ArrayList<>(); // 存儲每個方塊 private Paint mPaint;//畫筆 private Paint mSeparationPaint;//分割線畫筆 private Paint mSPaint;//三角的畫筆 private Path mPath;//路徑 private int x, y;//點(diǎn)擊的位置 private ViewGroup viewRoot;//父容器 private int location = TOP;//繪制位置 private int choose = -1;//點(diǎn)擊的item private int mToolbarBottom;//Toolbar下邊距屏幕上距離 private WindowManager windowManager; private WindowManager.LayoutParams layoutParams;//windowManger布局管理器,為了像Dialog一樣在Activity彈出,而不是依附于某個group private onItemCilckLinener itemCilckLinener; private Context context = null;
3 構(gòu)造函數(shù)以及初始化方法
private MyTipView(Context context, int x, int y, ViewGroup viewRoot, List<TextItem> items) {
super(context);
this.viewRoot = viewRoot;
this.context = context;
this.x = x;
this.y = y;
this.items = items;
windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
layoutParams = new WindowManager.LayoutParams();
layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;//窗口的寬
layoutParams.height = WindowManager.LayoutParams.MATCH_PARENT;//窗口的高
//設(shè)置LayoutParams的屬性
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;//該Type描述的是形成的窗口的層級關(guān)系,下面會詳細(xì)列出它的屬性
layoutParams.format = PixelFormat.TRANSLUCENT;//不設(shè)置這個彈出框的透明遮罩顯示為黑色
//layoutParams.token = viewRoot.getWindowToken();//設(shè)置Token
int[] location = new int[2];
viewRoot.getLocationInWindow(location);//獲取在當(dāng)前窗口內(nèi)的絕對坐標(biāo)
viewRoot.getLocationOnScreen(location);//獲取在整個屏幕內(nèi)的絕對坐標(biāo)
mToolbarBottom = location[1];//[0]是x軸坐標(biāo),[1]y軸
windowManager.addView(this, layoutParams);
init();
initView();
}
//初始化畫筆
private void init() {
mPaint = new Paint();
mSPaint = new Paint();
mPath = new Path();
mSeparationPaint = new Paint();
mSeparationPaint.setStyle(Paint.Style.FILL);
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setTextSize(Sp2Px(14));
mPaint.setColor(Color.BLACK);
mSPaint.setAntiAlias(true);
mSPaint.setStyle(Paint.Style.FILL);
mSPaint.setColor(Color.BLACK);
//初始變量
mItemWidth = Dp2Px(50);
mItemHeight = Dp2Px(48);
mTriaHeight = Dp2Px(10);//三角的高度
mHalfTriaWidth = Dp2Px(6);//三角的半寬
mTriaAcme = Dp2Px(6);//三角的頂點(diǎn)
marginSide = Dp2Px(4);//左右邊距
mItemSeparation = Dp2Px(1);//分割線寬度;
mRadius = Dp2Px(6);//圓角
mTextColor = Color.WHITE;
mTextSize = Sp2Px(14);
}
4 計算三角頂點(diǎn)位置
private void initView() {
int count = items.size();
int width = count * mItemWidth + mItemSeparation * (count - 1);
int mScreenWidth = getResources().getDisplayMetrics().widthPixels;
if (y - mToolbarBottom < (mItemHeight + mTriaHeight + mTriaAcme)) {
location = DOWN;//下方顯示
mTriaAcme += y;//設(shè)置三角頂點(diǎn)y軸值;
mTriaItemBorder = mTriaAcme + mTriaHeight;//計算三角方塊交界y
} else {
location = TOP;
mTriaAcme = y - mTriaAcme;//計算頂點(diǎn)位置y軸值
mTriaItemBorder = mTriaAcme - mTriaHeight;//計算三角方塊交界y值
}
if (x < (width / 2 + marginSide)) {
realLeft = marginSide;//計算最左側(cè)距離屏幕左邊距離,左邊撐不下
} else if ((mScreenWidth - x) < (width / 2 + marginSide)) {
realLeft = mScreenWidth - marginSide - width;//計算最左側(cè)距離屏幕左邊距離,右邊撐不下
} else {
realLeft = x - width / 2;//計算最左側(cè)距離屏幕左邊距離,觸碰不到邊界
}
}
5 設(shè)置背景為透明
private void drawBackground(Canvas canvas) {
canvas.drawColor(Color.TRANSPARENT);
}
6 繪制三角
private void drawTop(Canvas canvas) {
//繪制三角
mPath.reset();
mPath.moveTo(x, mTriaAcme);
mPath.lineTo(x - mHalfTriaWidth, mTriaAcme - mTriaHeight);
mPath.lineTo(x + mHalfTriaWidth, mTriaAcme - mTriaHeight);
canvas.drawPath(mPath, mSPaint);
MyDraw(canvas, mTriaItemBorder - mItemHeight);
}
private void drawDown(Canvas canvas) {
//繪制三角
mPath.reset();//清理路徑
mPath.moveTo(x, mTriaAcme);
mPath.lineTo(x - mHalfTriaWidth, mTriaAcme + mTriaHeight);
mPath.lineTo(x + mHalfTriaWidth, mTriaAcme + mTriaHeight);
canvas.drawPath(mPath, mSPaint);
//繪制方塊
MyDraw(canvas, mTriaItemBorder);
}
7 繪制方塊
繪制時因?yàn)榈谝粋€和最后一個方塊帶有圓角,單獨(dú)繪制
private void MyDraw(Canvas canvas, int t) {
//繪制item
int count = items.size();
int width = (count - 1) * mItemSeparation + count * mItemWidth;
int l = realLeft + mItemWidth + mItemSeparation;
mItemRectList.clear();
for (int i = 0; i < items.size(); i++) {
if (choose == i) {//當(dāng)前是否被點(diǎn)擊,改變顏色
mPaint.setColor(Color.DKGRAY);
} else {
mPaint.setColor(Color.BLACK);
}
if (i == 0) {//繪制第一個帶圓角的item
mPath.reset();
mPath.moveTo(realLeft + mItemWidth, t);
mPath.lineTo(realLeft + mRadius, t);
mPath.quadTo(realLeft, t, realLeft, t + mRadius);
mPath.lineTo(realLeft, t + mItemHeight - mRadius);
mPath.quadTo(realLeft, t + mItemHeight, realLeft + mRadius, mItemHeight + t);
mPath.lineTo(realLeft + mItemWidth, t + mItemHeight);
canvas.drawPath(mPath, mPaint);
mSeparationPaint.setColor(mSeparateLineColor);
canvas.drawLine(realLeft + mItemWidth, t, realLeft + mItemWidth,
t + mItemHeight, mSeparationPaint);
} else if (i == (items.size() - 1)) {//繪制最后一個
mPath.reset();
mPath.rMoveTo(realLeft + width - mItemWidth, t);
mPath.lineTo(realLeft + width - mRadius, t);
mPath.quadTo(realLeft + width, t, realLeft + width, t + mRadius);
mPath.lineTo(realLeft + width, t + mItemHeight - mRadius);
mPath.quadTo(realLeft + width, t + mItemHeight, realLeft + width - mRadius, t + mItemHeight);
mPath.lineTo(realLeft + width - mItemWidth, t + mItemHeight);
canvas.drawPath(mPath, mPaint);
} else {//繪制中間方塊和分割線
mPath.reset();
mPath.moveTo(l, t);
mPath.lineTo(l + mItemWidth, t);
mPath.lineTo(l + mItemWidth, t + mItemHeight);
mPath.lineTo(l, t + mItemHeight);
canvas.drawPath(mPath, mPaint);
canvas.drawLine(l + mItemWidth, t, l + mItemWidth, t + mItemHeight,
mSeparationPaint);
l += mItemWidth + mItemSeparation;
}
mItemRectList.add(new Rect(realLeft + i * (mItemSeparation + mItemWidth), t, realLeft + i * (mItemSeparation + mItemWidth) + mItemWidth, t + mItemHeight));
}
}
最后一行代碼
用一個List來存放Rect(矩形),這些矩形對應(yīng)的是每一個item的方塊,但是并沒有繪制出來,只是存放起來,矩形是為了在繪制文字的時候提供文字居中時用到的。
8 繪制文字
private void drawTitle(Canvas canvas) {
for (int i = 0; i < items.size(); i++) {
Rect rect = mItemRectList.get(i);//用于文字居中
//mPaint.setColor(Color.WHITE);
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
p.setAntiAlias(true);
p.setStrokeWidth(3);
int s = Dp2Px(items.get(i).getTextSize());
p.setTextSize(mTextSize);
if (s != 0)//如果在TextItem中設(shè)置了size,就是用設(shè)置的size
p.setTextSize(s);
p.setColor(mTextColor);
Paint.FontMetricsInt fontMetricsInt = p.getFontMetricsInt();
p.setTextAlign(Paint.Align.CENTER);
int baseline = (rect.bottom + rect.top - fontMetricsInt.bottom - fontMetricsInt.top) / 2;//文字居中,基線算法
canvas.drawText(items.get(i).getTitle(), rect.centerX(), baseline, p);
}
}
9 點(diǎn)擊變色,以及點(diǎn)擊事件實(shí)現(xiàn)
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
for (int i = 0; i < items.size(); i++) {
if (itemCilckLinener != null && isPointInRect(new PointF(event.getX(), event.getY()), mItemRectList.get(i))) {
choose = i;//記錄點(diǎn)擊item編號
Rect rect = mItemRectList.get(i);
postInvalidate(rect.left, rect.top, rect.right, rect.bottom);//刷新視圖
return true;
}
}
removeView();//點(diǎn)擊item以外移除
return false;
case MotionEvent.ACTION_UP:
for (int i = 0; i < items.size(); i++) {
if (itemCilckLinener != null && isPointInRect(new PointF(event.getX(), event.getY()), mItemRectList.get(i))) {
if (i == choose) {//與down的item一樣時才觸發(fā)
itemCilckLinener.onItemCilck(items.get(i).getTitle(), i);//觸發(fā)點(diǎn)擊事件
removeView();
return true;
}
} else {//點(diǎn)下后移動出item,初始化視圖
postInvalidate();//刷新視圖
}
}
choose = -1;//重置
return false;
}
return false;
}
/**
* 判斷這個點(diǎn)有沒有在矩形內(nèi)
*
* @param pointF
* @param targetRect
* @return
*/
private boolean isPointInRect(PointF pointF, Rect targetRect) {
if (pointF.x < targetRect.left) {
return false;
}
if (pointF.x > targetRect.right) {
return false;
}
if (pointF.y < targetRect.top) {
return false;
}
if (pointF.y > targetRect.bottom) {
return false;
}
return true;
}
10 Builder模式創(chuàng)建
public static class Builder {
private List<TextItem> items = new ArrayList<>();
private int x = 0, y = 0;
private Context context;
private ViewGroup viewRoot;
private onItemCilckLinener itemCilckLinener;
private int mRadius;
public Builder(Context context, ViewGroup viewRoot) {
this.context = context;
this.viewRoot = viewRoot;
}
public Builder addItem(TextItem item) {
items.add(item);
return this;
}
public Builder setmRadius(int radius) {
mRadius = radius;
return this;
}
public Builder setxAndy(int x, int y) {
this.x = x;
this.y = y;
return this;
}
public Builder setOnItemClickLinener(onItemCilckLinener itemClickLinener) {
this.itemCilckLinener = itemClickLinener;
return this;
}
public MyTipView create() {
if (items.size() == 0) {
try {
throw new Exception("item count is 0");
} catch (Exception e) {
e.printStackTrace();
}
}
MyTipView myTipView = new MyTipView(context, x, y, viewRoot, items);
myTipView.setItemCilckLinener(itemCilckLinener);
if (mRadius != 0)
myTipView.setRadius(mRadius);
return myTipView;
}
}
11 item
//TipView的item
public static class TextItem {
private String title;
private int textSize;
private int textColor = Color.WHITE;
public TextItem(String title) {
this.title = title;
}
public TextItem(String title, int textSize) {
this.title = title;
this.textSize = textSize;
}
public TextItem(String title, int textSize, int textColor) {
this.title = title;
this.textSize = textSize;
this.textColor = textColor;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getTextSize() {
return textSize;
}
public void setTextSize(int textSize) {
this.textSize = textSize;
}
public int getTextColor() {
return textColor;
}
public void setTextColor(int textColor) {
this.textColor = textColor;
}
}
12 使用示例
MyTipView.Builder builder = new MyTipView.Builder(this, linearLayout);
builder.addItem(new MyTipView.TextItem("1"))
.addItem(new MyTipView.TextItem("2"))
.addItem(new MyTipView.TextItem("3"))
.addItem(new MyTipView.TextItem("4"))
.setxAndy((int) x, (int) y)
.setOnItemClickLinener(new MyTipView.onItemCilckLinener() {
@Override
public void onItemCilck(String title, int i) {
Toast.makeText(MainActivity.this, title, Toast.LENGTH_SHORT).show();
}
})
.create();
13 源碼
https://github.com/liujiakuoyx/learn/blob/master/MyTipView.java
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
強(qiáng)制Android應(yīng)用使用某個Locale的方法
這篇文章主要介紹了強(qiáng)制Android應(yīng)用使用某個Locale的方法,涉及Android基于Locale進(jìn)行語言設(shè)置的相關(guān)技巧,需要的朋友可以參考下2015-10-10
Android判斷軟鍵盤彈出并隱藏的簡單完美解決方法(推薦)
下面小編就為大家?guī)硪黄狝ndroid判斷軟鍵盤彈出并隱藏的簡單完美解決方法(推薦)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-10-10
Android滑動優(yōu)化高仿QQ6.0側(cè)滑菜單(滑動優(yōu)化)
之前的實(shí)現(xiàn)只是簡單的可以顯示和隱藏左側(cè)的菜單,但是特別生硬,而且沒有任何平滑的趨勢,那么今天就來優(yōu)化一下吧,加上平滑效果,而且可以根據(jù)手勢滑動的方向來判斷是否是顯示和隱藏2016-02-02
Android編程設(shè)計模式之單例模式實(shí)例詳解
這篇文章主要介紹了Android編程設(shè)計模式之單例模式,結(jié)合實(shí)例形式詳細(xì)分析了Android開發(fā)設(shè)計模式中單例模式的概念、功能、實(shí)現(xiàn)、使用方法及相關(guān)注意事項(xiàng),需要的朋友可以參考下2017-12-12
Android進(jìn)度條控件progressbar使用方法詳解
這篇文章主要為大家詳細(xì)介紹了Android進(jìn)度條控件progressbar的使用方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-08-08
詳解Android中實(shí)現(xiàn)Redux方法
本篇文章給大家通過代碼實(shí)例教學(xué)Android中實(shí)現(xiàn)Redux的方法,有需要的朋友跟著參考下吧。2018-01-01
Notification消息通知 自定義消息通知內(nèi)容布局
這篇文章主要為大家詳細(xì)介紹了Notification消息通知,消息合并且顯示條數(shù),自定義消息通知內(nèi)容布局,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-09-09
Android仿QQ消息提示實(shí)現(xiàn)彈出式對話框
這篇文章主要為大家詳細(xì)介紹了Android仿QQ消息提示實(shí)現(xiàn)彈出式對話框,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-10-10

