Android實(shí)現(xiàn)類似黑客帝國字母雨效果
效果圖:

一、實(shí)現(xiàn)原理
在實(shí)現(xiàn)過程中,主要考慮整個(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)255String 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ì)象存入集合中:
@Overrideprotected 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方法中繪制:
@Overrideprotected void onDraw(Canvas canvas) {for (int i = 0; i < mHackLines.size(); i++) {drawText(i, canvas);}mHandler.sendEmptyMessageDelayed(WHAT, 100);//用于開啟循環(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);}}
接下來要滾動(dòng)顯示由Handler發(fā)送一個(gè)延時(shí)100毫秒的消息開始:
class WeakHandler extends Handler{WeakReference<Activity> mActivity;public WeakHandler(Activity activity){mActivity = new WeakReference<Activity>(activity);}@Overridepublic 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í)間移除掉不需要的字母線條并增加新的子母線條,這里我是判斷如果線條底部超過屏幕高度的一半時(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è)置背景為黑色:
@Overrideprotected void onDetachedFromWindow() {super.onDetachedFromWindow();mHandler.removeCallbacksAndMessages(null);//停止刷新}
OKOK,字母雨已經(jīng)出來啦~~ 思路清晰之后還是很簡單的哦~
二、實(shí)現(xiàn)代碼
整個(gè)代碼也不算很長,就直接貼上了:
/*** 字母雨*/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);}@Overridepublic 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);}@Overrideprotected 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();}}@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);}}/*** 每條線 包含多個(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)255String code = "A";//字母的值}@Overrideprotected 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.HackViewandroid:layout_width="match_parent"android:layout_height="match_parent"/></RelativeLayout>
到這里就結(jié)束啦。點(diǎn)擊這里留言交流哦
評(píng)論
圖片
表情
