數字消息氣泡--實現消息提示功能


    今天做消息推送功能時,業務要求當用戶收到推送消息時 信封消息角標需要顯示數字氣泡提醒  ,其實想想  微信 、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)之間繪制貝塞爾曲線。

 

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM