数字消息气泡--实现消息提示功能

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

 

  

 

转载于:https://www.cnblogs.com/tubao1991/p/5827033.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值