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



1 自定義TipView思路
1 首先我們考慮是繼承View還是ViewGroup
其實(shí)TipView直觀看更像是一個(gè)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時(shí)向下繪制。
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<>(); // 存儲每個(gè)方塊 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彈出,而不是依附于某個(gè)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è)置這個(gè)彈出框的透明遮罩顯示為黑色
//layoutParams.token = viewRoot.getWindowToken();//設(shè)置Token
int[] location = new int[2];
viewRoot.getLocationInWindow(location);//獲取在當(dāng)前窗口內(nèi)的絕對坐標(biāo)
viewRoot.getLocationOnScreen(location);//獲取在整個(gè)屏幕內(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 計(jì)算三角頂點(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;//計(jì)算三角方塊交界y
} else {
location = TOP;
mTriaAcme = y - mTriaAcme;//計(jì)算頂點(diǎn)位置y軸值
mTriaItemBorder = mTriaAcme - mTriaHeight;//計(jì)算三角方塊交界y值
}
if (x < (width / 2 + marginSide)) {
realLeft = marginSide;//計(jì)算最左側(cè)距離屏幕左邊距離,左邊撐不下
} else if ((mScreenWidth - x) < (width / 2 + marginSide)) {
realLeft = mScreenWidth - marginSide - width;//計(jì)算最左側(cè)距離屏幕左邊距離,右邊撐不下
} else {
realLeft = x - width / 2;//計(jì)算最左側(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 繪制方塊
繪制時(shí)因?yàn)榈谝粋€(gè)和最后一個(gè)方塊帶有圓角,單獨(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) {//繪制第一個(gè)帶圓角的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)) {//繪制最后一個(gè)
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));
}
}
最后一行代碼
用一個(gè)List來存放Rect(矩形),這些矩形對應(yīng)的是每一個(gè)item的方塊,但是并沒有繪制出來,只是存放起來,矩形是為了在繪制文字的時(shí)候提供文字居中時(shí)用到的。
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一樣時(shí)才觸發(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;
}
/**
* 判斷這個(gè)點(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)文章
Android獲取應(yīng)用程序名稱(ApplicationName)示例
本文以實(shí)例方式為大家介紹下獲取應(yīng)用程序名稱(ApplicationName)的具體實(shí)現(xiàn),感興趣的各位可以參考下哈2013-06-06
Android?源碼淺析RecyclerView?Adapter
這篇文章主要介紹了Android?源碼淺析之RecyclerView?Adapter示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12
Android中控件GridView實(shí)現(xiàn)設(shè)置行列分割線的方法示例
這篇文章主要介紹了利用Android中控件GridView實(shí)現(xiàn)設(shè)置行列分割線的方法,文中給出了詳細(xì)的介紹與示例代碼,相信對大家具有一定的參考價(jià)值,有需要的朋友們下面來一起看看吧。2017-01-01
Android定制RadioButton樣式三種實(shí)現(xiàn)方法
三種方法實(shí)現(xiàn)Android定制RadioButton樣式:使用XML文件進(jìn)行定義/在JAVA代碼中定義等等,感興趣的朋友可以參考下,希望可以幫助到你2013-02-02
Android AlertDialog(對話框)實(shí)例詳解
Android在開發(fā)中經(jīng)常會遇到有彈框的需求,經(jīng)常使用的有Dialog彈框,Window彈框,他們之間最本質(zhì)的區(qū)別是dialog是非阻塞式對話框,popupwindow是阻塞式對話框,這篇文章主要給大家介紹了關(guān)于Android AlertDialog(對話框)的相關(guān)資料,需要的朋友可以參考下2021-11-11
Android 異步任務(wù)和消息機(jī)制面試題分析
這篇文章主要為大家介紹了Android 異步任務(wù)和消息機(jī)制面試題分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07
Android仿網(wǎng)易嚴(yán)選底部彈出菜單效果
這篇文章主要為大家詳細(xì)介紹了Android仿網(wǎng)易嚴(yán)選底部彈出菜單效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07
Android控件FlowLikeView實(shí)現(xiàn)點(diǎn)贊動畫
這篇文章主要為大家詳細(xì)介紹了一個(gè)點(diǎn)贊動畫的優(yōu)雅控件FlowLikeView,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-06-06
android仿華為手機(jī)懸浮窗設(shè)計(jì)
這篇文章主要為大家詳細(xì)介紹了android仿華為手機(jī)懸浮窗設(shè)計(jì),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08

