*本篇文章已授權(quán)微信公眾號(hào) guolin_blog (郭霖)獨(dú)家發(fā)布
前言
大半年前在B站看到一系列關(guān)于磁翻顯示陣列的視頻拷肌,覺得挺有意思黔攒,本來打算用 Arduino 也實(shí)現(xiàn)一個(gè),可惜一直都沒有找到磁翻陣列的元器件舶斧,自己做也不太現(xiàn)實(shí)欣鳖,就擱置了。直到最近在家休息捧毛,想起來要不就寫個(gè) View 實(shí)現(xiàn)一下观堂,花了一個(gè)下午,基本算完成了呀忧,當(dāng)然還有很多可以改進(jìn)的地方师痕,這個(gè) Demo 也只是提供一個(gè)思路,有改進(jìn)意見歡迎留言而账。
效果
目前支持顯示中文字符胰坟,圖片的顯示,英文點(diǎn)陣也可以用相同思路實(shí)現(xiàn)(需要字符文件)泞辐,還可以根據(jù)自己需要拓展不同的動(dòng)畫效果笔横。
原理
原理很簡(jiǎn)單,就是ImageView的陣列咐吼,每個(gè) ImageView 只有兩種狀態(tài)吹缔,用兩個(gè)圖片表示,當(dāng)需要改變顯示圖案時(shí)锯茄,逐行逐列對(duì)每個(gè) ImageView 判斷厢塘,需要改變時(shí) 播放翻轉(zhuǎn)動(dòng)畫茶没,改變圖片內(nèi)容,最后記錄下當(dāng)前每個(gè)點(diǎn)的顯示狀態(tài)晚碾。
實(shí)現(xiàn)
attrs.xml
<declare-styleable name="FlipDot">
<attr name="dotSize" format="dimension"/>
<attr name="dotPadding" format="dimension"/>
<attr name="widthNum" format="integer"/>
<attr name="heightNum" format="integer"/>
<attr name="dotDrawable" format="reference"/>
<attr name="dotBackDrawable" format="reference"/>
<attr name="soundOn" format="boolean"/>
</declare-styleable>
目前抽取的屬性有以上這些:
dotSize 為每個(gè)ImageView 的邊長(zhǎng)(默認(rèn)為正方形)抓半;
dotPadding 為 ImageView 的 Padding 值,四邊相同格嘁;
widthNum 為顯示點(diǎn)陣列數(shù)笛求;
heightNum 為行數(shù);
dotDrawable 為 ImageView 狀態(tài)是顯示時(shí)的圖片糕簿;
dotBackDrawable 為 ImageView 狀態(tài)是不顯示時(shí)的圖片探入;
soundOn 是磁翻翻動(dòng)時(shí)的聲音開關(guān)。
另外還有一些參數(shù)冶伞,比如新症,動(dòng)畫總時(shí)長(zhǎng),動(dòng)畫間隔時(shí)長(zhǎng)响禽,動(dòng)畫方向等也可以根據(jù)需要添加徒爹。
activity_main.xml
...
<com.reikyz.flipdot.FlipDotView
android:id="@+id/fdv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#000"
app:dotBackDrawable="@mipmap/dot_back"
app:dotDrawable="@mipmap/dot"
app:dotPadding="1dp"
app:dotSize="20dp"
app:heightNum="24"
app:soundOn="true"
app:widthNum="18" />
...
在布局文件中的使用。
FlipDotView.java
/**
* Created by reikyZ on 16/8/31.
*/
public class FlipDotView extends LinearLayout {
Context mContext;
private float mDotSize;
private float mDotPadding;
private int mWidthNum;
private int mHeightNum;
private Drawable mDot;
private Drawable mDotBack;
private boolean mSoundOn;
int duration = 50;
List<List<Integer>> oldList = new ArrayList<>();
SoundPool soundPool;
HashMap<Integer, Integer> soundPoolMap = new HashMap<Integer, Integer>();
public FlipDotView(Context context, AttributeSet attrs) {
super(context, attrs);
setOrientation(LinearLayout.VERTICAL);
mContext = context;
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.FlipDot);
mDotSize = typedArray.getDimensionPixelSize(R.styleable.FlipDot_dotSize, 50);
.
.
.
mSoundOn = typedArray.getBoolean(R.styleable.FlipDot_soundOn, true);
typedArray.recycle();
initStauts();
initViews(context, attrs);
initSound();
}
private void initStauts() {
oldList.clear();
for (int i = 0; i < mHeightNum; i++) {
List<Integer> subList = new ArrayList<>();
subList.clear();
for (int j = 0; j < mWidthNum; j++) {
subList.add(1);
}
oldList.add(subList);
}
}
private void initViews(Context context, AttributeSet attrs) {
for (int i = 0; i < mHeightNum; i++) {
LinearLayout ll = new LinearLayout(context);
LayoutParams llParam = new LayoutParams((int) (mWidthNum * mDotSize), (int) mDotSize);
ll.setLayoutParams(llParam);
for (int j = 0; j < mWidthNum; j++) {
ImageView iv = new ImageView(context);
LayoutParams ivParam = new LayoutParams(
Math.round(mDotSize),
Math.round(mDotSize));
iv.setLayoutParams(ivParam);
int padding = (int) mDotPadding;
iv.setPadding(padding, padding, padding, padding);
iv.setImageDrawable(mDot);
ll.addView(iv);
}
addView(ll);
}
}
private void initSound() {
soundPool = new SoundPool(40, AudioManager.STREAM_MUSIC, 0)
soundPoolMap.put(0, soundPool.load(mContext, R.raw.click_0, 1));
soundPoolMap.put(1, soundPool.load(mContext, R.raw.click_1, 2));
soundPoolMap.put(2, soundPool.load(mContext, R.raw.click_2, 3));
}
...
}
首先根據(jù)需求芋类,這個(gè) View 繼承了 LinearLayout隆嗅,在構(gòu)造函數(shù)中初始化一些數(shù)據(jù)和視圖:
initStauts() 預(yù)先構(gòu)造一個(gè)二維容器,存放初始化的顯示狀態(tài)和改變后的狀態(tài)侯繁;
initViews(context, attires); View 的主要實(shí)現(xiàn)胖喳,根據(jù)得到的行數(shù)、列數(shù)以 LinearLayout 為容器贮竟,生成 ImageView 的陣列丽焊;
initSound() 初始化了 SoundPool, 作為磁翻翻動(dòng)時(shí)的聲效咕别,其中存放了幾個(gè)不同的輕微敲擊聲效技健,避免大量連續(xù)播放產(chǎn)生的機(jī)械感和爆音。
FlipDotView.java
public void flipFromCenter(final List<List<Integer>> list) {
Random random = new Random();
int centerX = (mHeightNum - 1) / 2, centerY = (mWidthNum - 1) / 2;
for (int i = 0; i < mHeightNum; i++) {
int delay = 0;
for (int j = 0; j < list.get(i).size(); j++) {
delay = distance(centerX, centerY, i, j) * 300 + duration * random.nextInt(5);
final ImageView iv = (ImageView) ((LinearLayout) getChildAt(i)).getChildAt(j);
final int finalI = i;
final int finalJ = j;
if (!oldList.get(i).get(j).equals(list.get(i).get(j))) {
iv.postDelayed(new Runnable() {
@Override
public void run() {
Rotate3d rotate = new Rotate3d();
rotate.setDuration(200);
rotate.setAngle(180);
iv.startAnimation(rotate);
if (list.get(finalI).get(finalJ) == 1) {
iv.setImageDrawable(mDot);
} else if (list.get(finalI).get(finalJ) == 0) {
iv.setImageDrawable(mDotBack);
} else {
Log.e("sssss", "ERROR");
}
if (mSoundOn)
new Thread(new Runnable() {
@Override
public void run() {
playSound(mContext, finalJ % soundPoolMap.size(), 0);
}
}).start();
}
}, delay);
oldList.get(i).set(j, list.get(i).get(j));
}
}
}
}
flipFromCenter(list) 實(shí)現(xiàn)了效果圖中 show Bitmap 的從 FlipDotView 中心向外輻射漸變翻動(dòng)效果:
1.首先計(jì)算出 FlipDotView 中心的行列位置惰拱;
2.遍歷二維數(shù)組雌贱,根據(jù)坐標(biāo)到中心的距離,根據(jù)每個(gè)距離間隔(示例為 300ms)偿短,加上一個(gè)隨機(jī)延遲(duration * random.nextInt(5))欣孤,獲得每個(gè) ImageVIew 的動(dòng)畫延遲 deley;
3.判斷目標(biāo)陣列(list)與當(dāng)前顯示陣列(oldList)中(i昔逗,j)位置上的值是否相同降传,相同則表示不需要翻動(dòng)操作,繼續(xù)遍歷勾怒;
4.如果不同搬瑰,則調(diào)用當(dāng)前點(diǎn) ImageView.postDelayed() 方法款票,將步驟[2]delay傳入;
5.判斷需要翻轉(zhuǎn)的 list 點(diǎn)狀態(tài)泽论,播放翻轉(zhuǎn)動(dòng)畫,并設(shè)置圖片卡乾;
6.最后翼悴,如果需要播放聲音,則播放聲效幔妨。
另外還有兩種顯示效果鹦赎,可以參考稍后的 Demo,流程與以上類似误堡。
使用
MainActivity.java
...
FontUtils font;
String[] strs = {"年", "輕", "天", "真"};
boolean[][] arr;
boolean[][] arrStr;
int offset = 0;
private void intiData() {
font = new FontUtils();
arrStr = font.drawString(this, "哈");
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.fdv:
Toast.makeText(this, "show Pattern", Toast.LENGTH_LONG).show();
fdv.flipFromLeftTop(getPattern());
offset++;
break;
case R.id.iv:
Toast.makeText(this, "show Bitmap", Toast.LENGTH_LONG).show();
flip(R.mipmap.chicken);
break;
case R.id.btn:
Toast.makeText(this, "show Character", Toast.LENGTH_LONG).show();
showChar();
break;
}
}
private List<List<Integer>> getPattern() {
List<List<Integer>> list = new ArrayList<>();
for (int i = 0; i < fdv.getmHeightNum(); i++) {
List<Integer> l = new ArrayList<>();
l.clear();
for (int j = 0; j < fdv.getmWidthNum(); j++) {
if ((i + j + 1 + offset) % 3 == 0) {
l.add(1);
} else {
l.add(0);
}
}
list.add(l);
}
return list;
}
private void flip(int rsid) {
int width = fdv.getmWidthNum();
int height = fdv.getmHeightNum();
Bitmap bitmap = BitmapUtils.zoomImage(BitmapUtils.readBitMap(this, rsid), width, height);
arr = BitmapUtils.getBitmapArr(bitmap, arr, width, height);
fdv.flipFromCenter(getBitmap(arr));
}
private List<List<Integer>> getBitmap(boolean[][] array) {
List<List<Integer>> list = new ArrayList<>();
for (int i = 0; i < fdv.getmHeightNum(); i++) {
List<Integer> l = new ArrayList<>();
l.clear();
for (int j = 0; j < fdv.getmWidthNum(); j++) {
if (i < array.length &&
j < array[i].length &&
array[i][j]) {
l.add(1);
} else {
l.add(0);
}
}
list.add(l);
}
return list;
}
private void showChar() {
fdv.flipFromLeftTop(getCharMap(arrStr));
arrStr = font.drawString(MainActivity.this, strs[offset % strs.length]);
offset++;
}
private List<List<Integer>> getCharMap(boolean[][] array) {
List<List<Integer>> list = new ArrayList<>();
for (int i = 0; i < fdv.getmHeightNum(); i++) {
List<Integer> l = new ArrayList<>();
l.clear();
for (int j = 0; j < fdv.getmWidthNum(); j++) {
if (i < array.length &&
j < array[i].length &&
array[i][j]) {
l.add(1);
} else {
l.add(0);
}
}
list.add(l);
}
return list;
}
...
Demo 中古话,點(diǎn)擊 FlipDotView,下方的 ImageView 與 Button锁施,分別傳入一個(gè)圖形陪踩、圖片、字符點(diǎn)陣悉抵,調(diào)用 FlipDotView 的 flipFromLeftTop(list)肩狂、 flipFromCenter(list) 方法,改變顯示內(nèi)容姥饰。
其中傻谁,圖片與字符轉(zhuǎn)點(diǎn)陣不是本文重點(diǎn),有興趣可以參考 Demo列粪。
補(bǔ)充审磁,F(xiàn)lipDotView 的翻動(dòng)音效,當(dāng)翻動(dòng)數(shù)量過多時(shí)聲音可能會(huì)不自然或者無聲岂座,可以調(diào)整 SoundPool 構(gòu)造函數(shù)的第一個(gè)參數(shù)态蒂,設(shè)置成一個(gè)比較大的數(shù)量,允許可能多的聲音對(duì)象播放掺逼。
最后是 Demo Git:https://github.com/ReikyZ/FlipDot