今天做消息推送功能时,业务要求当用户收到推送消息时 信封消息角标需要显示数字气泡提醒 ,其实想想 微信 、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)之间绘制贝塞尔曲线。