今天做消息推送功能時,業務要求當用戶收到推送消息時 信封消息角標需要顯示數字氣泡提醒 ,其實想想 微信 、QQ收到消息時就是這么實現的 既然有設計樣板 那么我們想想該如何實現。
I. 最容易想到的是采用布局文件實現,比如FrameLayout 采用層疊的方式 如代碼:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:padding="2dip" android:src="@drawable/icon_message_nromal" /> <FrameLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:padding="1dip" > <TextView android:layout_width="16dip" android:layout_height="16dip" android:layout_marginLeft="2dp" android:background="@drawable/text_tip_bg" android:gravity="center" android:text="1" android:textColor="@android:color/white" android:textSize="12dp" android:textStyle="bold" /> </FrameLayout> </FrameLayout>
原理比較簡單: 就是在消息圖片ImageView 上面蓋上一個TextView , 其中textView 背景色是一個紅色的實心圓, 然后字體顏色使用白色 大致一看有點數字氣泡的感覺 。
布局效果:
當然了 有些人會想到用RelativeLayout 布局,無可爭議了,原理都類似,需要注意的是 數字TextView 需要放在最外層。
這里說明一下:這種實現的效果是靜態的 類似微信里面的效果,只做一些簡單的監聽 如 點擊后 氣泡消失等 。無法達到QQ里面的拖拽效果。
II 復雜一點 就自定義View 了,在onDraw方法中繪制各個子 view 。然后通過各個監聽 實現效果。這里借鑒一個師兄(高德地圖)的地圖拖拽氣泡原理。
package com.bubble; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Point; import android.text.TextPaint; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.animation.CycleInterpolator; import android.widget.Toast; public class BubbleView extends View { String text="8"; private int curRadius; private Paint paint; // 繪制圓形圖形 private TextPaint textPaint; // 繪制圓形圖形 private Paint.FontMetrics textFontMetrics; private Point end; private Point base; private Point touch; Path path = new Path(); private int moveRadius=20; private int maxDistance=150,curDistance=0; private boolean isMove=false; public BubbleMove(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public void setRadius(int r) { moveRadius=r; curRadius=r; } public BubbleMove(Context context) { super(context); init(context); } public void setBasePoint(int x,int y) { base=new Point(x, y); } public void init(Context context) { // 設置繪制flag的paint paint = new Paint(); paint.setColor(Color.RED); paint.setAntiAlias(true); // 設置繪制文字的paint textPaint = new TextPaint(); textPaint.setAntiAlias(true); textPaint.setColor(Color.WHITE); textPaint.setTextSize(18); textPaint.setTextAlign(Paint.Align.CENTER); textFontMetrics = textPaint.getFontMetrics(); //初始坐標 end=new Point((int)moveRadius,(int)moveRadius); } public void setText(String s) { this.text=s; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //背景透明 canvas.drawColor(Color.TRANSPARENT); //畫移動圓圈 canvas.drawCircle(end.x+base.x, end.y+base.y, moveRadius, paint); //畫貝塞爾曲線 if(isMove&&curDistance<maxDistance) { canvas.drawCircle(base.x+moveRadius, base.y+moveRadius, curRadius, paint); path.reset(); double sin = -1.0*(end.y-touch.y) / curDistance; double cos = 1.0*(end.x-touch.x) /curDistance ; // table圓上兩點 path.moveTo( (float) (base.x+moveRadius - curRadius * sin), (float) (base.y+moveRadius - curRadius * cos) ); path.lineTo( (float) (base.x+moveRadius + curRadius * sin), (float) (base.y+moveRadius + curRadius * cos) ); // move圓上兩點 path.quadTo( (base.x+moveRadius + base.x+end.x) / 2, (base.y+moveRadius + base.y+end.y) / 2, (float) (base.x+end.x + moveRadius* sin), (float) (base.y+end.y + moveRadius * cos) ); path.lineTo( (float) (base.x+end.x - moveRadius * sin), (float) (base.y+end.y- moveRadius * cos) ); // 閉合 path.quadTo( (base.x+moveRadius + base.x+end.x) / 2, (base.y+moveRadius +base.y+ end.y) / 2, (float) (base.x+moveRadius - curRadius * sin), (float) (base.y+moveRadius - curRadius * cos) ); canvas.drawPath(path, paint); } //移動圓上的文字 float textH = - textFontMetrics.ascent - textFontMetrics.descent; canvas.drawText(text,end.x+base.x, end.y+base.y+ textH / 2, textPaint); } @Override public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: touch=new Point((int)event.getX(),(int)event.getY()); isMove=true; break; case MotionEvent.ACTION_MOVE: end.x = (int)(event.getX()); end.y = (int) (event.getY()); double offx=event.getX()-touch.x; double offy=event.getY()-touch.y; //當前拉伸距離 curDistance=(int)Math.sqrt(offx*offx+offy*offy); //定點圓隨着距離變大而變小 curRadius=(int)(moveRadius*(1.0-1.0*curDistance/maxDistance)); postInvalidate(); break; case MotionEvent.ACTION_UP: isMove=false; curRadius=moveRadius; Point old=new Point(end); end=new Point((int)moveRadius,(int)moveRadius); postInvalidate(); if(curDistance<maxDistance){ shakeAnimation(old); }else Toast.makeText(getContext(), "超過設置距離", Toast.LENGTH_SHORT).show(); break; } return true; } //CycleTimes動畫重復的次數 public void shakeAnimation(Point end) { float x,y; x=0.3f*(end.x-touch.x)*curDistance/maxDistance; y=0.3f*(end.y-touch.y)*curDistance/maxDistance; ObjectAnimator animx = ObjectAnimator .ofFloat(this, "translationX", x); animx.setInterpolator(new CycleInterpolator(1)); ObjectAnimator animy = ObjectAnimator .ofFloat(this, "translationY", y); animy.setInterpolator(new CycleInterpolator(1)); AnimatorSet set=new AnimatorSet(); set.setDuration(200); set.playTogether(animx,animy); set.start(); } public void setMaxDistance(int dis) { maxDistance=dis; } }
如上代碼 關鍵點理解Path 類的幾個方法,再者就是 API中的x.y 都是從左上角(0,0)開始計算的.額,說到坐標,我想起幾個很容易混淆的方法。
getLocationOnScreen 得到該視圖在全局坐標系中的x,y值 這里包括手機頂部的標題欄(通信類型、時間、電量等)
getLocationInWindow 個人理解:這個應該和pc中一樣,是系統給予用戶能夠使用的范圍,window窗體。
getLeft , getTop, getBottom,getRight 這些獲取的是當前視圖在它上一級中的坐標位置。
a. public void moveTo(float x, float y) 移動繪制的起點,從點(x,y)點開始進行繪制
b. public void lineTo(float x, float y) 連接起始點與點(x,y)的直線,如果沒有使用moveTo則起始點默認為(0,0)
c. public void quadTo(float x1, float y1, float x2, float y2) 根據兩個控制點繪制貝塞爾曲線。如果沒有使用moveTo指定起點,起點默認為(0,0),即(0,0)到(x1,y1)之間 繪制貝塞爾曲線,(x1,y1)到(x2,y2)之間繪制貝塞爾曲線。