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

有段時間沒寫博文了,前段時間比較忙,這幾天閑下來,想著寫點(diǎn)東西,腦袋一下就閃過以前學(xué)習(xí)Android的時候見到的別人實(shí)現(xiàn)的黑客帝國的字母雨效果,當(dāng)時對于小菜鳥的自己,那叫一個膜拜啊,時隔幾年,自己實(shí)現(xiàn)一下,算是對以前的自己一個交代吧。

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

先看效果:


字母雨

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

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

字母對象--》字母線條對象--》界面效果

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

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

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

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

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

@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);//用于開啟循環(huán)  線條滾動
    }

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);
        }
    }

接下來要滾動顯示由Handler發(fā)送一個延時100毫秒的消息開始:

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;
                }
            }
        }
    }

讓整個線條往下走其實(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);
            }
        }
    }

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

    /**
     * 刪除一列  同時添加初始化一列
     * @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);
    }

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

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

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

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

整個代碼也不算很長,就直接貼上了:

/**
 * 字母雨
 * @author zhang
 *
 */
public class HackView extends View {
    /** 文字的畫筆 */
    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);
            }
        }
    }
    /**
     * 刪除一列  同時添加初始化一列
     * @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);
        }
    }
    /**
     *  每條線  包含多個字母
     **/
    class HackLine{
        public int NUM = 0;//用于記錄這列的標(biāo)示
        private Point p = new Point();//線的初始位置
        List<HackCode> hcs = new ArrayList<HackView.HackCode>();//黑客字母的一條線
    }
    /**
     * 每個字母
     */
    class HackCode{
         Point p = new Point();//每一個字母的坐標(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)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容