Android实现字母雨的效果

作者:daisy 时间:2023-12-13 02:26:33 

首先来看效果:

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方法中绘制:


@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>
标签:android,文字雨效果
0
投稿

猜你喜欢

  • MyBatis中${} 和 #{} 有什么区别小结

    2021-09-21 14:42:35
  • Spring Boot 集成Mybatis实现主从(多数据源)分离方案示例

    2023-08-10 07:43:21
  • c# 日历控件的实现

    2022-08-21 11:02:22
  • SpringBoot拦截器的配置使用介绍

    2021-06-20 07:25:54
  • c# 线程定时器 System.Threading.Timer的使用

    2022-07-08 01:28:09
  • java多线程之CyclicBarrier的使用方法

    2023-11-04 21:52:54
  • spring cloud将spring boot服务注册到Eureka Server上的方法

    2023-12-08 19:42:09
  • C#中的静态成员、静态方法、静态类介绍

    2022-04-25 20:06:51
  • 使用Mybatis-Plus时的SqlSessionFactory问题及处理

    2022-01-30 07:40:04
  • Java异常学习之自定义异常详解

    2023-09-25 00:57:27
  • 基于CXF搭建webService的实例讲解

    2023-07-02 10:41:34
  • MyBatis-Plus联表查询(Mybatis-Plus-Join)的功能实现

    2023-11-25 03:24:33
  • 浅谈java常量池

    2023-11-18 09:43:50
  • Android Java try catch 失效问题及解决

    2023-06-17 17:07:33
  • Android仿微信朋友圈添加图片的实例代码

    2021-06-26 13:19:05
  • java 示例讲解循环语句的使用

    2021-12-17 06:23:01
  • 自定义注解和springAOP捕获Service层异常,并处理自定义异常操作

    2023-04-04 05:26:04
  • Java 实现网络爬虫框架详细代码

    2021-12-11 05:15:43
  • Android自定义ViewGroup实现带箭头的圆角矩形菜单

    2022-11-26 14:40:25
  • 深入同步访问共享的可变数据分析

    2023-05-08 00:16:49
  • asp之家 软件编程 m.aspxhome.com