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