Android 呼吸燈效果

android呼吸燈效果設(shè)計思路

  • 使用顏色漸變禾怠,使其邊緣燈光區(qū)莫湘,與背景色無縫連接
  • 使用屬性動畫倾鲫,控制呼吸燈的發(fā)光

代碼文件

package com.tian.criminalintent;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.RadialGradient;
import android.graphics.Shader;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

/**
 * Created by XuZhen on 2019/5/13 18:02
 */
public class BreatheView extends View {
    private int mInsideViewWidth = dp2px(getContext(), 70);
    private int mInsideViewHeight = dp2px(getContext(), 40);
    private int mLightDiffusionWidth = dp2px(getContext(), 20);  //擴散區(qū)寬度
    private int mTextColor = Color.parseColor("#ffffff"); //默認字體顏色為白色
    private int mTextSize = sp2px(getContext(), 22);
    private int mInsideColor = Color.parseColor("#9FB6FF");//內(nèi)部圖形顏色粗合,為藍色
    private boolean isLight = false; //控制是否開啟動畫

    private int[] colors = {Color.parseColor("#9FB6FF"), Color.parseColor("#A7BAFE"), Color.parseColor("#F26882"), Color.parseColor("#F8DF57")};//變動顏色

    private int mOutsideColor;
    private Paint mTextPaint;
    private Paint mInsideViewPaint;
    private Paint mOutsideRectPaint;
    private float mOutsideRectHeight;
    private Paint mOutsideCirclePaint;

    private int mRealWidth;
    private int mRealHeight;
    private LinearGradient mLinearGradient;
    private RadialGradient mRadialGradient;
    private String mBreatheViewText;
    private ValueAnimator mGoLightAnim;
    private ValueAnimator mBackLightAnim;
    private ValueAnimator mColorAnim;
    private int mColor;

    private int mEndGraidentColor = Color.parseColor("#2e2e3D");

    public BreatheView(Context context) {
        this(context, null);
    }

    public BreatheView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BreatheView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        obtainAttributes(attrs);
        initPaint();
    }

    /**
     * 步驟一:根據(jù)xml屬性萍嬉,設(shè)置控件的屬性
     *
     * @param attrs
     */
    private void obtainAttributes(AttributeSet attrs) {
        TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.BreatheView);
        mInsideViewWidth = typedArray.getDimensionPixelSize(R.styleable.BreatheView_insideViewWidth, mInsideViewWidth);
        mInsideViewHeight = typedArray.getDimensionPixelSize(R.styleable.BreatheView_insideViewHeight, mInsideViewHeight);
        mLightDiffusionWidth = (int) typedArray.getDimension(R.styleable.BreatheView_lightDiffusionWidth, mLightDiffusionWidth);

        mTextSize = typedArray.getDimensionPixelSize(R.styleable.BreatheView_BreatheViewTextSize, mTextSize);
        mTextColor = typedArray.getColor(R.styleable.BreatheView_BreatheViewTextColor, mTextColor);
        //控件文字
        mBreatheViewText = typedArray.getString(R.styleable.BreatheView_BreatheViewText);
        //內(nèi)部顏色
        mInsideColor = typedArray.getColor(R.styleable.BreatheView_BreatheViewInsideColor, mInsideColor);
        //是否開啟燈光擴散,在xml中設(shè)置
        isLight = typedArray.getBoolean(R.styleable.BreatheView_BreatheViewIsLight, isLight);
        typedArray.recycle();//回收資源
    }

    /**
     * 步驟二:初始化繪制控件的筆觸
     */
    private void initPaint() {
        //文本文字
        mTextPaint = new Paint();
        mTextPaint.setStyle(Paint.Style.FILL);
        mTextPaint.setAntiAlias(true);
        mTextPaint.setColor(mTextColor);
        mTextPaint.setTextSize(mTextSize);
        //內(nèi)部的圖形
        mInsideViewPaint = new Paint();
        mInsideViewPaint.setStyle(Paint.Style.FILL);
        mInsideViewPaint.setAntiAlias(true);
        mInsideViewPaint.setColor(mInsideColor);


        //外部方區(qū)域圖形
        mOutsideColor = mInsideColor;
        mOutsideRectPaint = new Paint();
        mOutsideRectHeight = mInsideViewHeight;
        mLinearGradient = new LinearGradient(0, 0, 0, mOutsideRectHeight,
                new int[]{Color.WHITE, mOutsideColor, Color.WHITE}, new float[]{0, 0.5f, 1}, Shader.TileMode.CLAMP);
        mOutsideRectPaint.setShader(mLinearGradient);
        mOutsideRectPaint.setStyle(Paint.Style.FILL);
        mOutsideRectPaint.setAntiAlias(true);
        mOutsideRectPaint.setColor(mOutsideColor);


        //外部的圓形區(qū)域
        mOutsideCirclePaint = new Paint();
        mOutsideCirclePaint.setStyle(Paint.Style.FILL);
        mOutsideCirclePaint.setAntiAlias(true);
        mOutsideCirclePaint.setColor(mOutsideColor);
        mRadialGradient = new RadialGradient(mOutsideRectHeight / 2, mOutsideRectHeight / 2,
                mOutsideRectHeight / 2, mOutsideColor, Color.WHITE, Shader.TileMode.CLAMP);
        mOutsideCirclePaint.setShader(mRadialGradient);

    }

    /**
     * 第三步隙疚,測試控件大小
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int height = getPaddingBottom() + getPaddingTop() + Math.abs(mLightDiffusionWidth * 2) + mInsideViewHeight;
        int width = getPaddingLeft() + getPaddingRight() + mInsideViewWidth + mInsideViewHeight / 2 + mLightDiffusionWidth;
        mRealWidth = resolveSize(width, widthMeasureSpec);
        mRealHeight = resolveSize(height, heightMeasureSpec);
        setMeasuredDimension(mRealWidth, mRealHeight);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
    }

    /**
     * 第四步壤追,繪制控件
     *
     * @param canvas
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //變色
        mInsideViewPaint.setColor(mInsideColor);
        mInsideViewPaint.setAlpha(150);

        canvas.translate(mInsideViewHeight / 2 + mLightDiffusionWidth + getPaddingLeft(), mRealHeight >> 1);
        canvas.save();//保存圖層

        if (isLight) {
            mOutsideCirclePaint.setColor(mOutsideColor);
            mOutsideRectPaint.setColor(mOutsideColor);
            mOutsideCirclePaint.setAlpha(150);
            mOutsideRectPaint.setAlpha(150);
            //繪制外部的長方形
            canvas.translate(0, -mOutsideRectHeight / 2);
            canvas.drawRect(0, 0, mInsideViewWidth, mOutsideRectHeight, mOutsideRectPaint);
            //繪制外部的半圓
            canvas.translate(-mOutsideRectHeight / 2, 0);
            canvas.drawArc(0, 0, (int) mOutsideRectHeight, (int) mOutsideRectHeight, 90, 180, true, mOutsideCirclePaint);
            canvas.restore();
            canvas.save();
        }

        //繪制內(nèi)部的長方形與半圓
        canvas.translate(0, -(mInsideViewHeight >> 1));
        canvas.drawRect(0, 0, mInsideViewWidth, mInsideViewHeight, mInsideViewPaint);
        canvas.translate(-(mInsideViewHeight / 2), 0);
        canvas.drawArc(0, 0, mInsideViewHeight, mInsideViewHeight, 90, 180, true, mInsideViewPaint);
        canvas.restore();
        canvas.save();
        canvas.translate(0, -(mInsideViewHeight >> 1));

        //文字的x軸坐標(biāo)
        float stringWidth = mTextPaint.measureText(mBreatheViewText);
        float x = (mInsideViewWidth - stringWidth) / 2;
        //文字的y軸坐標(biāo)
        Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
        float y = (float) mInsideViewHeight / 2 + (Math.abs(fontMetrics.ascent) - fontMetrics.descent) / 2;
        canvas.drawText(mBreatheViewText, x, y, mTextPaint);
        canvas.restore();
    }

    /**
     * 這邊是設(shè)置動畫,動畫分成兩個部分供屉,第一部分行冰,是擴散出去,第二部分是擴散回來伶丐,同時顏色漸變
     */
    public void startLightAnim() {
        mGoLightAnim = ValueAnimator.ofFloat(0f, mLightDiffusionWidth);
        mGoLightAnim.setDuration(1500);
        //動態(tài)開始設(shè)置擴散出去動畫
        mGoLightAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //// TODO: 動畫里面里面不應(yīng)該不停的new對象悼做,對內(nèi)存會造成影響,,需要優(yōu)化
                mOutsideRectHeight = (int) (mInsideViewHeight + (float) animation.getAnimatedValue() * 2);
                setGradient();
                invalidate();
            }
        });
        mGoLightAnim.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                if (!isLight) {
                    return;
                }
                mGoLightAnim.setStartDelay(1500);
                mGoLightAnim.start();
          
            }
        });

        //擴散回來的顏色動畫
        mBackLightAnim = ValueAnimator.ofFloat(mLightDiffusionWidth, 0f);
        mBackLightAnim.setDuration(1500);
        mBackLightAnim.setStartDelay(1500);
        mBackLightAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mOutsideRectHeight = (int) (mInsideViewHeight + (float) animation.getAnimatedValue() * 2);
                setGradient();
                invalidate();
            }
        });
        mBackLightAnim.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                if (!isLight) {
                    return;
                }
                mBackLightAnim.setDuration(1500);
                mBackLightAnim.start();

       
            }
        });

        //第二部分顏色漸變動畫
        int i = (int) (Math.random() * 3);
        mColor = colors[i];
        mColorAnim = ValueAnimator.ofObject(new ArgbEvaluator(), mInsideColor, mColor);
        mColorAnim.setDuration(1500);
        mColorAnim.setStartDelay(1500);
        mColorAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mInsideColor = ((Integer) animation.getAnimatedValue());
                mOutsideColor = mInsideColor;
                invalidate();
            }
        });
        mColorAnim.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                if (!isLight) {
                    return;
                }
          
                int i = (int) (Math.random() * 3);
                mColor = colors[i];
                Log.e("TAG", i + "");
                mColorAnim.setObjectValues(mInsideColor, mColor);
                mColorAnim.setStartDelay(1500);
                mColorAnim.start();
            }
        });

        mGoLightAnim.start();
        mBackLightAnim.start();
        mColorAnim.start();
        isLight = true;
    }

    /**
     * 重新設(shè)置Gradient
     */
    private void setGradient() {
        mLinearGradient = new LinearGradient(0, 0, 0, mOutsideRectHeight,
                new int[]{mEndGraidentColor, mOutsideColor,mEndGraidentColor}, new float[]{0, 0.5f, 1}, Shader.TileMode.CLAMP);
        mOutsideRectPaint.setShader(mLinearGradient);
        mRadialGradient = new RadialGradient(mOutsideRectHeight / 2, mOutsideRectHeight / 2,
                mOutsideRectHeight / 2, mOutsideColor, mEndGraidentColor, Shader.TileMode.CLAMP);
        mOutsideCirclePaint.setShader(mRadialGradient);
    }

    /**
     * 設(shè)置漸變底色
     * @param endGraidentColor
     */
    public void setEndGraidentColor(int endGraidentColor) {
        mEndGraidentColor = endGraidentColor;
    }

    /**
     * 關(guān)閉動畫
     */
    public void cancelLightAnim() {
        isLight = false;//關(guān)閉動畫
        if (mGoLightAnim != null) {
            mGoLightAnim.cancel();
        }
        if (mBackLightAnim != null) {
            mBackLightAnim.cancel();
        }
        if (mColorAnim != null) {
            mColorAnim.cancel();
        }
        mOutsideRectHeight = mInsideViewHeight;
        mInsideColor = mColor;
        mOutsideColor = mColor;
        invalidate();
    }

    /**
     * dp轉(zhuǎn)換成px哗魂,scale為像素密度肛走,density越高,分辨率越高
     *
     * @param context 獲取屏幕
     * @param dpVal
     * @return
     */
    public static int dp2px(Context context, int dpVal) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpVal * scale + 0.5f);
    }

    /**
     * sp轉(zhuǎn)換成px录别,fontSCale為文字像素密度朽色,fontScale越高,分辨率越高
     *
     * @param context
     * @param spVal
     * @return
     */
    public static int sp2px(Context context, int spVal) {
        final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
        return (int) (spVal * fontScale + 0.5f);
    }
}

attrs:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="BreatheView">
        <attr name="insideViewWidth" format="dimension" /> <!--內(nèi)部TextView的寬度-->
        <attr name="insideViewHeight" format="dimension" /> <!--內(nèi)部TextView的高度-->

        <attr name="lightDiffusionWidth" format="dimension" /> <!--光擴散的間距-->

        <attr name="BreatheViewTextSize" format="dimension" />  <!--設(shè)置文字的大小-->
        <attr name="BreatheViewTextColor" format="color" />  <!--設(shè)置文字的顏色-->
        <attr name="BreatheViewText" format="string" /> <!--控件的文字-->

        <attr name="BreatheViewInsideColor" format="color" />  <!--設(shè)置內(nèi)部View的顏色-->

        <attr name="BreatheViewIsLight" format="boolean" /> <!--設(shè)置控件是否擴散-->

    </declare-styleable>
</resources>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末庶灿,一起剝皮案震驚了整個濱河市纵搁,隨后出現(xiàn)的幾起案子吃衅,更是在濱河造成了極大的恐慌往踢,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件徘层,死亡現(xiàn)場離奇詭異峻呕,居然都是意外死亡,警方通過查閱死者的電腦和手機趣效,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門瘦癌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人跷敬,你說我怎么就攤上這事讯私。” “怎么了西傀?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵斤寇,是天一觀的道長。 經(jīng)常有香客問我拥褂,道長娘锁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任饺鹃,我火速辦了婚禮莫秆,結(jié)果婚禮上间雀,老公的妹妹穿的比我還像新娘。我一直安慰自己镊屎,他們只是感情好惹挟,可當(dāng)我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著缝驳,像睡著了一般匪煌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上党巾,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天萎庭,我揣著相機與錄音,去河邊找鬼齿拂。 笑死驳规,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的署海。 我是一名探鬼主播吗购,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼砸狞!你這毒婦竟也來了捻勉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤刀森,失蹤者是張志新(化名)和其女友劉穎踱启,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體研底,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡埠偿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了榜晦。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片冠蒋。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖乾胶,靈堂內(nèi)的尸體忽然破棺而出抖剿,到底是詐尸還是另有隱情,我是刑警寧澤识窿,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布斩郎,位于F島的核電站,受9級特大地震影響腕扶,放射性物質(zhì)發(fā)生泄漏孽拷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一半抱、第九天 我趴在偏房一處隱蔽的房頂上張望脓恕。 院中可真熱鬧膜宋,春花似錦、人聲如沸炼幔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽乃秀。三九已至肛著,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間跺讯,已是汗流浹背枢贿。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留刀脏,地道東北人局荚。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像愈污,于是被迫代替她去往敵國和親耀态。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 在小鎮(zhèn)看著藍天 聽一首緩慢動人的歌曲 一不小心就入迷了
    伍月的晴空閱讀 399評論 3 6
  • 今天仙逻,朋友圈“山竹”刷屏了。 Part 1 我是一個不看各種新聞的原始人揍魂,遇到各種江湖上的江湖事桨醋,基本都會自動屏蔽...
    轉(zhuǎn)轉(zhuǎn)小仙女閱讀 367評論 3 2
  • 洞房 一個可以瘋狂的熾熱開張 生命在此刻熱血升華 有一些人善始卻不能善終 心事 這是一處隱秘的洞穴 阿里巴巴也不知...
    情也閱讀 1,036評論 19 53
  • 天干:甲(jiǎ)棚瘟、乙( yǐ)现斋、 丙(bǐng)、顿苏骸(dīng)庄蹋、戊(wù)、 己(jǐ)迷雪、庚(gēng)限书、辛(...
    金石明鏡閱讀 10,434評論 0 2
  • 2018.03.30.星期五,天氣晴 昨晚大寶和我們一起睡的章咧,弄的我一晚上沒睡好倦西,怕擠著小寶又擔(dān)心大寶蹬被子,...
    任昱丞媽媽閱讀 115評論 0 0