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>


猜你喜欢
- 在有些产品的研发过程中,一般我们都有很多条数据记录在一个LOG文件中。在查看最新的数据记录都是从最开始保存的那条开始存储,所以,参考了网上一
- 如何避开在ListView等AdapterView上动态添加删除项的陷阱,下面就为大家分享,具体内容如下首先,定义如下array资源,作为列
- 目录1、二分查找算法思想2、二分查找图示说明3、二分查找优缺点3、java代码实现3.1 使用递归实现3.1 不使用递归实现(while循环
- SpringBoot 工厂模式自动注入Map一、建立工厂类public interface AnimalFactory { S
- 跨域的产生就是因为浏览器的同源策略。它是浏览器的核心安全功能,所谓的同源,就是指域名,协议,还有端口要相同。传统的方案就是JSONP(前端处
- 1.CAS1)CAS概念CAS时Compare And Swap缩写,即比较与交换是用于实现多线程同步的原子指令,它将内存位置的内容与给定值
- jQuery的方法连缀使用起来非常方便,可以简化语句,让代码变得清晰简洁。那C#的类方法能不能也实现类似的功能呢?基于这样的疑惑,研究了一下
- 今天来记录一下,在项目中因为基本类型,所产生的bug包装类:8种基本类型的包装类应用场景:数据库建立实体映射多用包装类这两句话是重点:就是建
- 手机一般有两种类型的输入设备。一种是键盘类型的输入设备,通常它包含电源键和音量下键。另一种是触摸类型的输入设备,触摸屏就属于这种类型。键盘类
- 在android 中可以广泛看到的template<typename T> class Sp 句柄类实际上是android 为实
- 本教程的目的是使用Java编写的分离的层去访问数据库中的表,这一层通常称为数据访问层(DAL)使用DAL的最大好处是通过直接使用一些类似in
- Java线程的生命周期的详解对于多线程编程而言,理解线程的生命周期非常重要,本文就针对这一点进行讲解。一、线程的状态线程的存在有几种不同的状
- 一、项目简述功能包括: 前台实现:用户浏览菜单、菜品分类筛选、查看菜单详 情、添加购物车、购物车结算、会员券、个人订单查询等 等。 后台实现
- 前言日常开发中,我们可能会碰到需要进行防重放与操作幂等的业务,本文记录SpringBoot实现简单防重与幂等防重放,防止数据重复提交操作幂等
- fork()函数用于从已存在的进程中创建一个新进程。新进程称为子进程,而园进程称为父进程。使用fork()函数得到的子进程是父进程的一个复制
- 开篇点题:正则表达式方法效果=0(下面会提到效果)空行问题: VS:在使用过程中对于VS的自动整理不太满意,因为不会自动删除空行当出现这种情
- 模式虽然精妙,却难完美,比如观察者模式中观察者生命周期的问题;比如访问者模式中循环依赖的问题等等;其它很多模式也存在这样那样的一些不足之处,
- 前言之前我们探讨过一个.class文件是如何被加载到jvm中的。但是jvm内又是如何划分内存的呢?这个内被加载到了那一块内存中?jvm内存划
- 1.使用API设置主题如下所示,在Activity中使用setThemesetTheme(R.style.MyTheme1);2.调用API
- java eclipse经常会用到整个类进行查找,ctrl+f,然后replaceall(XX,toXX)。但是最近要对webservice