字母雨的实现

有段时间没写博文了,前段时间比较忙,这几天闲下来,想着写点东西,脑袋一下就闪过以前学习Android的时候见到的别人实现的黑客帝国的字母雨效果,当时对于小菜鸟的自己,那叫一个膜拜啊,时隔几年,自己实现一下,算是对以前的自己一个交代吧。

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

先看效果:


字母雨

一、实现原理

在实现过程中,主要考虑整个界面由若干个字母组成的子母线条组成,这样的话把固定数量的字母封装成一个字母线条,而每个字母又封装成一个对象,这样的话,就形成了如下组成效果:

字母对象--》字母线条对象--》界面效果

每个字母都应该知道自己的位置坐标,自己上面的字母、以及自己的透明度:

class HackCode{
         Point p = new Point();//每一个字母的坐标
         int alpha = 255;//透明度值  默认255
         String code = "A";//字母的值
    }

而每个子母线条对象都有自己这条线条的初始底部起点,内部的多个字母都是根据线条的初始底部起点依次排列,包含多个字母对象集合,以及这条线条的唯一标示:

class HackLine{
    public int NUM = 0;//用于记录这列的标示
    private Point p = new Point();//线的初始位置
    List<HackCode> hcs = new ArrayList<HackView.HackCode>();//黑客字母的一条线
    }

在初始化的时候创建所有子母线条对象以及字母对象存入集合中:

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        
        mWidth = getMeasuredWidth();//获取控件宽高
        mHeight = getMeasuredHeight();
        mHackLines.clear();//清空集合
        initPlayData();//初始化播放数据
    }

    /**
     * 初始化播放数据
     */
    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);//用于开启循环  线条滚动
    }

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发送一个延时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;
                }
            }
        }
    }

让整个线条往下走其实也就只用将线条的底部初始值Y坐标不断增加,内部字母随之更新位置就可以了:

/**
     *  下一帧播放
     * @param Nnum 每次下移多远 距离
     */
    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);
    }

最后,在控件移除屏幕的时候终止消息循环,运行时记得将根布局设置背景为黑色:

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

OKOK,字母雨已经出来啦~~ 思路清晰之后还是很简单的哦~

二、实现代码

整个代码也不算很长,就直接贴上了:

/**
 * 字母雨
 * @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 每次下移多远 距离
     */
    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);
    }
    /**
     * 初始化每一行数据
     * @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();
        }
    }
    /**
     * 初始化播放数据
     */
    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;//用于记录这列的标示
        private Point p = new Point();//线的初始位置
        List<HackCode> hcs = new ArrayList<HackView.HackCode>();//黑客字母的一条线
    }
    /**
     * 每个字母
     */
    class HackCode{
         Point p = new Point();//每一个字母的坐标
         int alpha = 255;//透明度值  默认255
         String code = "A";//字母的值
    }
    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        mHandler.removeCallbacksAndMessages(null);//停止刷新
    }
     /** 
     * 根据手机的分辨率从 dip 的单位 转成为 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>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,519评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,842评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,544评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,742评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,646评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,027评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,513评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,169评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,324评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,268评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,299评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,996评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,591评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,667评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,911评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,288评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,871评论 2 341

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,979评论 4 60
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,263评论 25 707
  • 独在异乡为异客,每逢佳节倍思亲。 --王维 今年开年以来,对于家乡的思念凭空增加了许多,陪伴亲人父母也成了一件首要...
    Skysper阅读 453评论 0 1
  • 布局视口 在PC端,布局视口就是浏览器窗口 视口的宽度 = 浏览器窗口的宽度 通过以下JavaScript代码获取...
    饥人谷_曾涛阅读 273评论 0 0
  • 尼采说,与无聊相斗,天神亦输。 虽然我并没考证过这句话究竟是不是尼采说的,但事实就是这样没错了。出来留学的人,大抵...
    Emylia阅读 570评论 1 2