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ī)則會(huì)影響我們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)擊位置,會(huì)導(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<>(); // 存儲(chǔ)每個(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)系,下面會(huì)詳細(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)下后移動(dòng)出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)文章
強(qiáng)制Android應(yīng)用使用某個(gè)Locale的方法
這篇文章主要介紹了強(qiáng)制Android應(yīng)用使用某個(gè)Locale的方法,涉及Android基于Locale進(jìn)行語言設(shè)置的相關(guān)技巧,需要的朋友可以參考下2015-10-10Android判斷軟鍵盤彈出并隱藏的簡單完美解決方法(推薦)
下面小編就為大家?guī)硪黄狝ndroid判斷軟鍵盤彈出并隱藏的簡單完美解決方法(推薦)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-10-10Android滑動(dòng)優(yōu)化高仿QQ6.0側(cè)滑菜單(滑動(dòng)優(yōu)化)
之前的實(shí)現(xiàn)只是簡單的可以顯示和隱藏左側(cè)的菜單,但是特別生硬,而且沒有任何平滑的趨勢,那么今天就來優(yōu)化一下吧,加上平滑效果,而且可以根據(jù)手勢滑動(dòng)的方向來判斷是否是顯示和隱藏2016-02-02Android編程設(shè)計(jì)模式之單例模式實(shí)例詳解
這篇文章主要介紹了Android編程設(shè)計(jì)模式之單例模式,結(jié)合實(shí)例形式詳細(xì)分析了Android開發(fā)設(shè)計(jì)模式中單例模式的概念、功能、實(shí)現(xiàn)、使用方法及相關(guān)注意事項(xiàng),需要的朋友可以參考下2017-12-12Android進(jìn)度條控件progressbar使用方法詳解
這篇文章主要為大家詳細(xì)介紹了Android進(jìn)度條控件progressbar的使用方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08詳解Android中實(shí)現(xiàn)Redux方法
本篇文章給大家通過代碼實(shí)例教學(xué)Android中實(shí)現(xiàn)Redux的方法,有需要的朋友跟著參考下吧。2018-01-01Notification消息通知 自定義消息通知內(nèi)容布局
這篇文章主要為大家詳細(xì)介紹了Notification消息通知,消息合并且顯示條數(shù),自定義消息通知內(nèi)容布局,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-09-09Android仿QQ消息提示實(shí)現(xiàn)彈出式對話框
這篇文章主要為大家詳細(xì)介紹了Android仿QQ消息提示實(shí)現(xiàn)彈出式對話框,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10Android滾動(dòng)菜單ListView實(shí)例詳解
這篇文章主要為大家詳細(xì)介紹了Android滾動(dòng)菜單ListView實(shí)例,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10