概述
看過演唱會的同學應該都看到過粉絲舉著LED點陣屏幕的牌子來支持自己心目中的男神或者女神侨把,感覺這種點陣屏幕的效果挺有意思的,于是花了點時間用Android實現了一下捂寿,實現效果如下:
相關的概念
點陣字庫與矢量字庫
點陣字庫就是每一個漢字用矩形點陣來表示龙优,然后用每個點的虛實來表示漢字的輪廓抬伺,常用來作為顯示字庫使用,這類點陣字庫漢字最大的缺點是不能放大读慎,一旦放大后就會發(fā)現文字邊緣的鋸齒教翩,常用的點陣矩陣有HZK12、HZK16和HZK24贪壳,我下面例子中的用到的字庫是HZK16饱亿。
矢量字庫保存的是對每一個漢字的描述信息,比如一個筆劃的起始闰靴、終止坐標彪笼,半徑、弧度等等蚂且。在顯示配猫、打印這一類字庫時,要經過一系列的數學運算才能輸出結果杏死,但是這一類字庫保存的漢字理論上可以被無限地放大泵肄,筆劃輪廓仍然能保持圓滑捆交,打印時使用的字庫均為此類字庫.點陣字庫結構
在漢字的點陣字庫中,每個字節(jié)的每個位都代表一個漢字的一個點腐巢,每個漢字都是由一個矩形點陣組成品追,0代表沒有,1代表有點冯丙,將0和1分別用不同顏色畫出肉瓦,就形成了一個漢字。字庫根據字節(jié)所表示的點是一行還是一列將字庫的存儲方式分為橫向和縱向胃惜,目前多數的字庫都是橫向的存儲方式(用得最多的應該是早期UCDOS字庫)泞莉,縱向一般是因為有某些液晶是采用縱向掃描顯示法,為了提高顯示速度船殉,于是便把字庫矩陣做成縱向鲫趁,省得在顯示時還要做矩陣轉換。我們接下去所描述的HZK16就是一種縱向字庫利虫。對于16*16字庫來說饮寞,它所需要的位數共是16*16=256個位,每個字節(jié)為8位列吼,因此幽崩,每個漢字都需要用256/8=32個字節(jié)來表示。即每兩個字節(jié)代表一行的16個點寞钥,共需要16行慌申,顯示漢字時,只需一次性讀取32個字節(jié)理郑,并將每兩個字節(jié)為一行打印出來蹄溉,即可形成一個漢字.漢字的區(qū)位碼
漢字通過GB2312編碼即每個漢字用兩個byte來表示,第一個byte表示這個漢字在字庫文件中的區(qū)碼您炉,第二個byte表示這個漢字在字庫文件中的位碼柒爵,通過這兩個值可以計算到這個漢字在字庫文件中的相對位置,根據這個位置讀取接下來的32個byte(對于16*16字庫)赚爵,就對應著這個漢字對應的字模信息棉胀,字模信息其實就是一個byte數組。
HZK16字庫是符合GB2312標準的16×16點陣字庫冀膝,HZK16的GB2312-80支持的漢字有6763個唁奢,符號682個。其中一級漢字有3755個窝剖,按聲序排列麻掸,二級漢字有3008個,按偏旁部首排列赐纱。通過機內碼獲取文字對應字模信息的起始位置
在PC機的文本文件中脊奋,漢字是以機內碼的形式存儲的熬北,每個漢字占用兩個字節(jié):第一個字節(jié)為區(qū)碼,為了與ASCII碼區(qū)別诚隙,范圍從十六進制的0A1H開始(小于80H的為ASCII碼字符)讶隐,對應區(qū)位碼中區(qū)碼的第一區(qū);第二個字節(jié)為位碼最楷,范圍也是從0A1H開始,對應某區(qū)中的第一個位碼待错。這樣籽孙,將漢字機內碼減去0A0A0H就得該漢字的區(qū)位碼。
例如漢字“房”的機內碼為十六進制的“B7BF”火俄,其中“B7”表示區(qū)碼犯建,“BF”表示位碼。所以“房”的區(qū)位碼為0B7BFH-0A0A0H=171FH瓜客。將區(qū)碼和位碼分別轉換為十進制得漢字“房”的區(qū)位碼為“2331”适瓦,即“房”的字模信息位于第23區(qū)的第31個字的位置,由于一個區(qū)包含94個漢字谱仪,所以第32×[(23-1) ×94+(31-1)]=67136Bit以后的32個字節(jié)為“房”的字模信息玻熙。
代碼實現解析
要實現上面的滾動字幕的效果,可以分為如下幾步:
- 獲取文字字符串的字模信息疯攒,并且將字模信息轉化為Boolean類型的二維數組(字模信息的每一bit中0代表沒有嗦随,1代表有點,將0轉換成false敬尺,將1轉換為true)
/**
* 獲取漢字字符串的點陣矩陣
* @param text
* @return
*/
public boolean[][] getWordsMatrix(Context context, String text) {
return getWordsMatrix(context, text, null);
}
public boolean[][] getWordsMatrix(Context context, String text, DotMatrixFontType dotMatrixFontType) {
if (null == dotMatrixFontType) {
this.mDotMatrixFontType = DotMatrixFontType.SIXTEEN_TYPE;
this.mWordByteByDots = DotMatrixFontType.SIXTEEN_TYPE.getValue() * DotMatrixFontType.SIXTEEN_TYPE.getValue() / 8;
} else {
this.mDotMatrixFontType = dotMatrixFontType;
this.mWordByteByDots = dotMatrixFontType.getValue() * dotMatrixFontType.getValue() / 8;
}
byte[] bytes = null;
try {
// 獲取漢字文本的字節(jié)編碼
bytes = text.getBytes(ENCODE);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
// 獲取每個字節(jié)對應正數編碼枚尼,即得到漢字對應的區(qū)碼和位碼
int[] code = new int[bytes.length];
for (int i = 0; i < bytes.length; i++) {
code[i] = bytes[i] < 0 ? 256 + bytes[i] : bytes[i];
}
int wordNumber = code.length / 2;
boolean[][] wordsMatrix = new boolean[mDotMatrixFontType.getValue()][mDotMatrixFontType.getValue() * wordNumber];
for (int i = 0; i < wordNumber; i++) {
// 通過區(qū)碼和位碼獲取字庫中對應的字模信息
byte[] temp = read(context, code[2 * i], code[2 * i + 1]);
for (int j = 0; j < mWordByteByDots; j++) {
for (int k = 0; k < 8; k++) {
// 將字模信息轉化為Boolean類型的二維數組并且進行縱向填充數組
int row = (j * 8 + k) / 16 + i * mDotMatrixFontType.getValue();
int col = (j * 8 + k) % 16;
if (((temp[j] >> (7 - k)) & 1) == 1) {
wordsMatrix[col][row] = true;
} else {
wordsMatrix[col][row] = false;
}
}
}
}
return wordsMatrix;
}
/**
* 從字庫中獲取指定區(qū)碼和位碼漢字的字模信息
* @param areaCode 區(qū)碼,對應編碼的第一個字節(jié)
* @param posCode 位碼滴某,對應編碼的第二個字節(jié)
* @return
*/
private byte[] read(Context context, int areaCode, int posCode) {
byte[] data = null;
try {
int area = areaCode - 0xa0;
int pos = posCode - 0xa0;
InputStream in = context.getAssets().open(DOT_MATRIX_FONT);
int offset = ((area - 1) * 94 + pos -1) * mWordByteByDots;
in.skip(offset);
data = new byte[mWordByteByDots];
in.read(data, 0, mWordByteByDots);
in.close();
} catch (IOException e) {
Log.d(TAG, "IOException e = " + e.getMessage());
}
return data;
}
- 根據Boolean類型的二維數組繪制點陣车伞,當Boolean值為false项鬼,表示要繪制空心圓,反之繪制實心圓盯质。
@Override
protected void onDraw(Canvas canvas) {
//super.onDraw(canvas);
for (int row = 0; row < mDotMatrixFontType.getValue(); row++) {
for (int col = 0; col < mDotMatrixFontType.getValue() * this.mWordNumber; col++) {
if (mWordsMatrix[row][col]) {
canvas.drawCircle(col * (mPointSpace + mPaintRadius * 2) + mPointSpace + mPaintRadius,
row * (mPointSpace + mPaintRadius * 2) + mPointSpace + mPaintRadius,
mPaintRadius, mFillPaint);
} else {
canvas.drawCircle(col * (mPointSpace + mPaintRadius * 2) + mPointSpace + mPaintRadius,
row * (mPointSpace + mPaintRadius * 2) + mPointSpace + mPaintRadius,
mPaintRadius, mHollowPaint);
}
}
}
Message message = new Message();
message.obj = this.mScrollDirection;
mHandler.sendMessageDelayed(message, mScrollSpeed.getValue());
}
- 通過Handler機制實現定期刷新界面,即實現滾動效果概而。在上面的代碼最后唤殴,就是在繪制后向Handler發(fā)送一個延遲消息,從而進入到滾動循環(huán)到腥,下面是在Handler中通過調用invalidate方法實現定期刷新朵逝,即實現滾動的效果:
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch ((Direction)msg.obj) {
case LEFT:
matrixMoveToLeft();
invalidate();
break;
case RIGHT:
matrixMoveToRight();
invalidate();
break;
}
}
};
private void matrixMoveToRight() {
for (int row = 0; row < mDotMatrixFontType.getValue(); row++) {
boolean temp = mWordsMatrix[row][mDotMatrixFontType.getValue() * mWordNumber - 1];
System.arraycopy(mWordsMatrix[row], 0, mWordsMatrix[row], 1, mDotMatrixFontType.getValue() * mWordNumber - 1);
mWordsMatrix[row][0] = temp;
}
}
private void matrixMoveToLeft() {
for (int row = 0; row < mDotMatrixFontType.getValue(); row++) {
boolean temp = mWordsMatrix[row][0];
System.arraycopy(mWordsMatrix[row], 1, mWordsMatrix[row], 0, mDotMatrixFontType.getValue() * mWordNumber - 1);
mWordsMatrix[row][mDotMatrixFontType.getValue() * mWordNumber - 1] = temp;
}
}
注意事項
- 將HZK16字庫放到assets文件夾中