字母雨的實(shí)現(xiàn)

有段時(shí)間沒(méi)寫(xiě)博文了,前段時(shí)間比較忙梯捕,這幾天閑下來(lái)匙瘪,想著寫(xiě)點(diǎn)東西铆铆,腦袋一下就閃過(guò)以前學(xué)習(xí)Android的時(shí)候見(jiàn)到的別人實(shí)現(xiàn)的黑客帝國(guó)的字母雨效果蝶缀,當(dāng)時(shí)對(duì)于小菜鳥(niǎo)的自己,那叫一個(gè)膜拜啊薄货,時(shí)隔幾年翁都,自己實(shí)現(xiàn)一下,算是對(duì)以前的自己一個(gè)交代吧菲驴。

【csdn:http://blog.csdn.net/zhangke3016/article/details/51994167]

先看效果:


字母雨

一荐吵、實(shí)現(xiàn)原理

在實(shí)現(xiàn)過(guò)程中骑冗,主要考慮整個(gè)界面由若干個(gè)字母組成的子母線條組成赊瞬,這樣的話把固定數(shù)量的字母封裝成一個(gè)字母線條,而每個(gè)字母又封裝成一個(gè)對(duì)象贼涩,這樣的話巧涧,就形成了如下組成效果:

字母對(duì)象--》字母線條對(duì)象--》界面效果

每個(gè)字母都應(yīng)該知道自己的位置坐標(biāo),自己上面的字母遥倦、以及自己的透明度:

class HackCode{
         Point p = new Point();//每一個(gè)字母的坐標(biāo)
         int alpha = 255;//透明度值  默認(rèn)255
         String code = "A";//字母的值
    }

而每個(gè)子母線條對(duì)象都有自己這條線條的初始底部起點(diǎn)谤绳,內(nèi)部的多個(gè)字母都是根據(jù)線條的初始底部起點(diǎn)依次排列,包含多個(gè)字母對(duì)象集合袒哥,以及這條線條的唯一標(biāo)示:

class HackLine{
    public int NUM = 0;//用于記錄這列的標(biāo)示
    private Point p = new Point();//線的初始位置
    List<HackCode> hcs = new ArrayList<HackView.HackCode>();//黑客字母的一條線
    }

在初始化的時(shí)候創(chuàng)建所有子母線條對(duì)象以及字母對(duì)象存入集合中:

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        
        mWidth = getMeasuredWidth();//獲取控件寬高
        mHeight = getMeasuredHeight();
        mHackLines.clear();//清空集合
        initPlayData();//初始化播放數(shù)據(jù)
    }

    /**
     * 初始化播放數(shù)據(jù)
     */
    public void initPlayData(){
        initHackLine(mWidth/9, mHeight/12);
        initHackLine(mWidth/9, mHeight/7);
        HackLine hl;
        for (int i = 3; i < 9; i++) {
            hl= new HackLine();
            hl.p.x = mWidth/9*(i+1);
            hl.p.y = mHeight/7*(9-i);
            for (int j = 0; j < 7; j++) {
                HackCode hc = new HackCode();
                hc.alpha -= 30*j;
                hc.code = CODES[new Random().nextInt(CODES.length)];
                hc.p.x = hl.p.x;
                hc.p.y = hl.p.y-dip2px(getContext(), 25)*j;
                hl.hcs.add(hc);
            }
            mHackLines.add(hl);
            hl.NUM = mHackLines.size();
        }
    }

然后在onDraw方法中繪制:

@Override
protected void onDraw(Canvas canvas) {
    for (int i = 0; i < mHackLines.size(); i++) {
        drawText(i, canvas);
    }
    mHandler.sendEmptyMessageDelayed(WHAT, 100);//用于開(kāi)啟循環(huán)  線條滾動(dòng)
    }

public void drawText(int nindex,Canvas canvas){
        HackLine hackLine = mHackLines.get(nindex);
        for (int i = 0; i < hackLine.hcs.size(); i++) {
            HackCode hackCode = hackLine.hcs.get(i);
            mPaint.setAlpha(hackCode.alpha);
            canvas.drawText(hackCode.code, hackCode.p.x, hackCode.p.y, mPaint);
        }
    }

接下來(lái)要滾動(dòng)顯示由Handler發(fā)送一個(gè)延時(shí)100毫秒的消息開(kāi)始:

class WeakHandler extends Handler{
        WeakReference<Activity> mActivity; 
        public WeakHandler(Activity activity){
            mActivity = new WeakReference<Activity>(activity);
        }
        @Override
        public void handleMessage(Message msg) {
            if(mActivity.get() != null){
                switch (msg.what) {
                case WHAT:
                    nextPlay(dip2px(getContext(), 20));
                    for (int i = 0; i < mHackLines.size(); i++) {
                        if(mHackLines.get(i).p.y >= mHeight/2*3){
                            addHackLine(mHackLines.get(i));
                        }
                    }
                    invalidate();
                    break;
                }
            }
        }
    }

讓整個(gè)線條往下走其實(shí)也就只用將線條的底部初始值Y坐標(biāo)不斷增加缩筛,內(nèi)部字母隨之更新位置就可以了:

/**
     *  下一幀播放
     * @param Nnum 每次下移多遠(yuǎn) 距離
     */
    public void nextPlay(int Nnum){
        for (int i = 0; i < mHackLines.size(); i++) {
            List<HackCode> hcs = mHackLines.get(i).hcs;
            hcs.clear();
            mHackLines.get(i).p.y+=Nnum;
            for (int j = 0; j < 7; j++) {
                HackCode hc = new HackCode();
                hc.alpha -= 30*j;
                hc.code = CODES[new Random().nextInt(CODES.length)];
                hc.p.x = mHackLines.get(i).p.x;
                hc.p.y = mHackLines.get(i).p.y-dip2px(getContext(), 25)*j;
                hcs.add(hc);
            }
        }
    }

之后我們要考慮在合適的時(shí)間移除掉不需要的字母線條并增加新的子母線條,這里我是判斷如果線條底部超過(guò)屏幕高度的一半時(shí)就移除當(dāng)前線條并根據(jù)唯一標(biāo)示添加新的線條:

    /**
     * 刪除一列  同時(shí)添加初始化一列
     * @param hackLine 
     */
    public void addHackLine(HackLine hackLine){
            if(hackLine == null){
                return;
            }
            int num = hackLine.NUM;
            mHackLines.remove(hackLine);//如果存在  刪除   重新添加
            
            HackLine hl;
            hl= new HackLine();
            hl.p.x = mWidth/9*(num-1);
            hl.p.y = mHeight/12*(7-(num-1));
            for (int j = 0; j < 7; j++) {
                HackCode hc = new HackCode();
                hc.alpha -= 30*j;
                hc.code = CODES[new Random().nextInt(CODES.length)];
                hc.p.x = hl.p.x;
                hc.p.y = hl.p.y-dip2px(getContext(), 25)*j;
                hl.hcs.add(hc);
            }
            hl.NUM = num;
            mHackLines.add(hl);
    }

最后堡称,在控件移除屏幕的時(shí)候終止消息循環(huán)瞎抛,運(yùn)行時(shí)記得將根布局設(shè)置背景為黑色:

@Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        mHandler.removeCallbacksAndMessages(null);//停止刷新
    }

OKOK,字母雨已經(jīng)出來(lái)啦~~ 思路清晰之后還是很簡(jiǎn)單的哦~

二、實(shí)現(xiàn)代碼

整個(gè)代碼也不算很長(zhǎng)却紧,就直接貼上了:

/**
 * 字母雨
 * @author zhang
 *
 */
public class HackView extends View {
    /** 文字的畫(huà)筆 */
    private Paint mPaint;
    /** 控件的寬 */
    private int mWidth;
    /** 控件的高 */
    private int mHeight;
    /** 所有字母 */
    private static final String[] CODES = {
        "A","B","C","D","E","F","G","H","I","J","K",
        "L","M","N","O","P","Q","R","S","T","U","V",
        "W","K","Y","Z"
    };
    
    private static final int WHAT = 1;
    /** 所有的HackLine組合 */
    private List<HackLine> mHackLines = new ArrayList<HackView.HackLine>();
    
    private WeakHandler mHandler;
    
    public HackView(Context context) {
        this(context,null);
    }
    public HackView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }
    public HackView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
        mHandler = new WeakHandler((Activity) context);
    }
    
    class WeakHandler extends Handler{
        WeakReference<Activity> mActivity; 
        public WeakHandler(Activity activity){
            mActivity = new WeakReference<Activity>(activity);
        }
        @Override
        public void handleMessage(Message msg) {
            if(mActivity.get() != null){
                switch (msg.what) {
                case WHAT:
                    nextPlay(dip2px(getContext(), 20));
                    for (int i = 0; i < mHackLines.size(); i++) {
                        if(mHackLines.get(i).p.y >= mHeight/2*3){
                            addHackLine(mHackLines.get(i));
                        }
                    }
                    invalidate();
                    break;
                }
            }
        }
    }
    private void init() {
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.WHITE);
        mPaint.setTextSize(dip2px(getContext(), 20));
        mPaint.setStrokeCap(Cap.ROUND);
        mPaint.setStrokeWidth(dip2px(getContext(), 5));
        setLayerType(View.LAYER_TYPE_SOFTWARE, null);
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        
        mWidth = getMeasuredWidth();//獲取控件寬高
        mHeight = getMeasuredHeight();
        mHackLines.clear();//清空集合
        initPlayData();
    }
    /**
     *  下一幀播放
     * @param Nnum 每次下移多遠(yuǎn) 距離
     */
    public void nextPlay(int Nnum){
        for (int i = 0; i < mHackLines.size(); i++) {
            List<HackCode> hcs = mHackLines.get(i).hcs;
            hcs.clear();
            mHackLines.get(i).p.y+=Nnum;
            for (int j = 0; j < 7; j++) {
                HackCode hc = new HackCode();
                hc.alpha -= 30*j;
                hc.code = CODES[new Random().nextInt(CODES.length)];
                hc.p.x = mHackLines.get(i).p.x;
                hc.p.y = mHackLines.get(i).p.y-dip2px(getContext(), 25)*j;
                hcs.add(hc);
            }
        }
    }
    /**
     * 刪除一列  同時(shí)添加初始化一列
     * @param hackLine 
     */
    public void addHackLine(HackLine hackLine){
            if(hackLine == null){
                return;
            }
            int num = hackLine.NUM;
            mHackLines.remove(hackLine);//如果存在  刪除   重新添加
            
            HackLine hl;
            hl= new HackLine();
            hl.p.x = mWidth/9*(num-1);
            hl.p.y = mHeight/12*(7-(num-1));
            for (int j = 0; j < 7; j++) {
                HackCode hc = new HackCode();
                hc.alpha -= 30*j;
                hc.code = CODES[new Random().nextInt(CODES.length)];
                hc.p.x = hl.p.x;
                hc.p.y = hl.p.y-dip2px(getContext(), 25)*j;
                hl.hcs.add(hc);
            }
            hl.NUM = num;
            mHackLines.add(hl);
    }
    /**
     * 初始化每一行數(shù)據(jù)
     * @param x
     * @param y
     */
    public void initHackLine(int x,int y){
        HackLine hl;
        for (int i = 0; i < 9; i++) {
            hl= new HackLine();
            hl.p.x = x*i;
            hl.p.y = y*(7-i);
            for (int j = 0; j < 7; j++) {
                HackCode hc = new HackCode();
                hc.alpha -= 30*j;
                hc.code = CODES[new Random().nextInt(CODES.length)];
                hc.p.x = hl.p.x;
                hc.p.y = hl.p.y-dip2px(getContext(), 25)*j;
                hl.hcs.add(hc);
            }
            mHackLines.add(hl);
            hl.NUM = mHackLines.size();
        }
    }
    /**
     * 初始化播放數(shù)據(jù)
     */
    public void initPlayData(){
        initHackLine(mWidth/9, mHeight/12);
        initHackLine(mWidth/9, mHeight/7);
        HackLine hl;
        for (int i = 3; i < 9; i++) {
            hl= new HackLine();
            hl.p.x = mWidth/9*(i+1);
            hl.p.y = mHeight/7*(9-i);
            for (int j = 0; j < 7; j++) {
                HackCode hc = new HackCode();
                hc.alpha -= 30*j;
                hc.code = CODES[new Random().nextInt(CODES.length)];
                hc.p.x = hl.p.x;
                hc.p.y = hl.p.y-dip2px(getContext(), 25)*j;
                hl.hcs.add(hc);
            }
            mHackLines.add(hl);
            hl.NUM = mHackLines.size();
        }
    }
    @Override
    protected void onDraw(Canvas canvas) {
        for (int i = 0; i < mHackLines.size(); i++) {
            drawText(i, canvas);
        }
        mHandler.sendEmptyMessageDelayed(WHAT, 100);
    }
    
    public void drawText(int nindex,Canvas canvas){
        HackLine hackLine = mHackLines.get(nindex);
        for (int i = 0; i < hackLine.hcs.size(); i++) {
            HackCode hackCode = hackLine.hcs.get(i);
            mPaint.setAlpha(hackCode.alpha);
            canvas.drawText(hackCode.code, hackCode.p.x, hackCode.p.y, mPaint);
        }
    }
    /**
     *  每條線  包含多個(gè)字母
     **/
    class HackLine{
        public int NUM = 0;//用于記錄這列的標(biāo)示
        private Point p = new Point();//線的初始位置
        List<HackCode> hcs = new ArrayList<HackView.HackCode>();//黑客字母的一條線
    }
    /**
     * 每個(gè)字母
     */
    class HackCode{
         Point p = new Point();//每一個(gè)字母的坐標(biāo)
         int alpha = 255;//透明度值  默認(rèn)255
         String code = "A";//字母的值
    }
    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        mHandler.removeCallbacksAndMessages(null);//停止刷新
    }
     /** 
     * 根據(jù)手機(jī)的分辨率從 dip 的單位 轉(zhuǎn)成為 px(像素) 
     */  
    public static int dip2px(Context context, float dpValue) {  
        final float scale = context.getResources().getDisplayMetrics().density;  
        return (int) (dpValue * scale + 0.5f);  
    }  
}

xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#000"
    tools:context=".MainActivity" >

    <com.zk.hack.HackView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />

</RelativeLayout>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末桐臊,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子晓殊,更是在濱河造成了極大的恐慌断凶,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,430評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件巫俺,死亡現(xiàn)場(chǎng)離奇詭異认烁,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)介汹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén)砚著,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人痴昧,你說(shuō)我怎么就攤上這事稽穆。” “怎么了赶撰?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,834評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵舌镶,是天一觀的道長(zhǎng)柱彻。 經(jīng)常有香客問(wèn)我,道長(zhǎng)餐胀,這世上最難降的妖魔是什么哟楷? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,543評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮否灾,結(jié)果婚禮上卖擅,老公的妹妹穿的比我還像新娘。我一直安慰自己墨技,他們只是感情好惩阶,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,547評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著扣汪,像睡著了一般断楷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上崭别,一...
    開(kāi)封第一講書(shū)人閱讀 52,196評(píng)論 1 308
  • 那天冬筒,我揣著相機(jī)與錄音,去河邊找鬼茅主。 笑死舞痰,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的诀姚。 我是一名探鬼主播响牛,決...
    沈念sama閱讀 40,776評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼学搜!你這毒婦竟也來(lái)了娃善?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,671評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤瑞佩,失蹤者是張志新(化名)和其女友劉穎聚磺,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體炬丸,經(jīng)...
    沈念sama閱讀 46,221評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瘫寝,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,303評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了稠炬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片焕阿。...
    茶點(diǎn)故事閱讀 40,444評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖首启,靈堂內(nèi)的尸體忽然破棺而出暮屡,到底是詐尸還是另有隱情,我是刑警寧澤毅桃,帶...
    沈念sama閱讀 36,134評(píng)論 5 350
  • 正文 年R本政府宣布褒纲,位于F島的核電站准夷,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏莺掠。R本人自食惡果不足惜衫嵌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,810評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望彻秆。 院中可真熱鬧楔绞,春花似錦、人聲如沸唇兑。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,285評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)幔亥。三九已至耻讽,卻和暖如春察纯,著一層夾襖步出監(jiān)牢的瞬間帕棉,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,399評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工饼记, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留香伴,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,837評(píng)論 3 376
  • 正文 我出身青樓具则,卻偏偏與公主長(zhǎng)得像即纲,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子博肋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,455評(píng)論 2 359

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

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)低斋、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,118評(píng)論 4 61
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,277評(píng)論 25 707
  • 獨(dú)在異鄉(xiāng)為異客匪凡,每逢佳節(jié)倍思親膊畴。 --王維 今年開(kāi)年以來(lái),對(duì)于家鄉(xiāng)的思念憑空增加了許多病游,陪伴親人父母也成了一件首要...
    Skysper閱讀 460評(píng)論 0 1
  • 布局視口 在PC端唇跨,布局視口就是瀏覽器窗口 視口的寬度 = 瀏覽器窗口的寬度 通過(guò)以下JavaScript代碼獲取...
    饑人谷_曾濤閱讀 277評(píng)論 0 0
  • 尼采說(shuō),與無(wú)聊相斗衬衬,天神亦輸买猖。 雖然我并沒(méi)考證過(guò)這句話究竟是不是尼采說(shuō)的,但事實(shí)就是這樣沒(méi)錯(cuò)了滋尉。出來(lái)留學(xué)的人玉控,大抵...
    Emylia閱讀 578評(píng)論 1 2