FlipDotView——磁翻點(diǎn)陣顯示效果

*本篇文章已授權(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)意見歡迎留言而账。

效果

2016-09-01_17_43_11.gif

目前支持顯示中文字符胰坟,圖片的顯示,英文點(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末吃媒,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子吕喘,更是在濱河造成了極大的恐慌赘那,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件氯质,死亡現(xiàn)場(chǎng)離奇詭異募舟,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)闻察,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門拱礁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來琢锋,“玉大人,你說我怎么就攤上這事呢灶∥獬” “怎么了?”我有些...
    開封第一講書人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵鸯乃,是天一觀的道長(zhǎng)鲸阻。 經(jīng)常有香客問我,道長(zhǎng)缨睡,這世上最難降的妖魔是什么鸟悴? 我笑而不...
    開封第一講書人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮奖年,結(jié)果婚禮上细诸,老公的妹妹穿的比我還像新娘。我一直安慰自己陋守,他們只是感情好震贵,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著嗅义,像睡著了一般屏歹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上之碗,一...
    開封第一講書人閱讀 51,443評(píng)論 1 302
  • 那天蝙眶,我揣著相機(jī)與錄音,去河邊找鬼褪那。 笑死幽纷,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的博敬。 我是一名探鬼主播友浸,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼偏窝!你這毒婦竟也來了收恢?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤祭往,失蹤者是張志新(化名)和其女友劉穎伦意,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體硼补,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡驮肉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了已骇。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片离钝。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡票编,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出卵渴,到底是詐尸還是另有隱情慧域,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布浪读,位于F島的核電站吊趾,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏瑟啃。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一揩尸、第九天 我趴在偏房一處隱蔽的房頂上張望蛹屿。 院中可真熱鬧,春花似錦岩榆、人聲如沸错负。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽犹撒。三九已至,卻和暖如春粒褒,著一層夾襖步出監(jiān)牢的瞬間识颊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工奕坟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留祥款,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓月杉,卻偏偏與公主長(zhǎng)得像刃跛,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子苛萎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫桨昙、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,103評(píng)論 4 62
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,116評(píng)論 25 707
  • [6] 明鏡與王澄甯依舊是好友腌歉,每周每月她還會(huì)與她一同繞路回家蛙酪,只是再?zèng)]見過王成棟,偶爾有幾次能碰上王成梁究履,他與王...
    李阿劑閱讀 274評(píng)論 0 0
  • (越暢整理滤否,轉(zhuǎn)載請(qǐng)注明出處) 解離:進(jìn)入到一個(gè)內(nèi)在世界,感覺到外面的世界很遙遠(yuǎn)最仑。 亨利老師舉例子: (當(dāng)用于創(chuàng)傷治...
    PhoenixScorpio閱讀 193評(píng)論 0 0
  • 學(xué)習(xí)思維導(dǎo)圖的目的就是訓(xùn)練自己的思維藐俺,讓自己學(xué)會(huì)思考炊甲,從而提高我們的工作和學(xué)習(xí)效率!今晚邀請(qǐng)到深圳的一線優(yōu)秀教...
    勿懟閱讀 536評(píng)論 0 3