有段时间没写博文了,前段时间比较忙,这几天闲下来,想着写点东西,脑袋一下就闪过以前学习Android的时候见到的别人实现的黑客帝国的字母雨效果,当时对于小菜鸟的自己,那叫一个膜拜啊,时隔几年,自己实现一下,算是对以前的自己一个交代吧。
先看效果:
一、实现原理
在实现过程中,主要考虑整个界面由若干个字母组成的子母线条组成,这样的话把固定数量的字母封装成一个字母线条,而每个字母又封装成一个对象,这样的话,就形成了如下组成效果:
字母对象--》字母线条对象--》界面效果
每个字母都应该知道自己的位置坐标,自己上面的字母、以及自己的透明度:
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方法中绘制:
@Overrideprotected 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>