FlipDotView——磁翻点阵显示效果

*本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布

前言

大半年前在B站看到一系列关于磁翻显示阵列的视频,觉得挺有意思,本来打算用 Arduino 也实现一个,可惜一直都没有找到磁翻阵列的元器件,自己做也不太现实,就搁置了。直到最近在家休息,想起来要不就写个 View 实现一下,花了一个下午,基本算完成了,当然还有很多可以改进的地方,这个 Demo 也只是提供一个思路,有改进意见欢迎留言。

效果

2016-09-01_17_43_11.gif

目前支持显示中文字符,图片的显示,英文点阵也可以用相同思路实现(需要字符文件),还可以根据自己需要拓展不同的动画效果。

原理

原理很简单,就是ImageView的阵列,每个 ImageView 只有两种状态,用两个图片表示,当需要改变显示图案时,逐行逐列对每个 ImageView 判断,需要改变时 播放翻转动画,改变图片内容,最后记录下当前每个点的显示状态。

实现

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 为每个ImageView 的边长(默认为正方形);
dotPadding 为 ImageView 的 Padding 值,四边相同;
widthNum 为显示点阵列数;
heightNum 为行数;
dotDrawable 为 ImageView 状态是显示时的图片;
dotBackDrawable 为 ImageView 状态是不显示时的图片;
soundOn 是磁翻翻动时的声音开关。
另外还有一些参数,比如,动画总时长,动画间隔时长,动画方向等也可以根据需要添加。

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));
    }
    ...
}

首先根据需求,这个 View 继承了 LinearLayout,在构造函数中初始化一些数据和视图:
initStauts() 预先构造一个二维容器,存放初始化的显示状态和改变后的状态;
initViews(context, attires); View 的主要实现,根据得到的行数、列数以 LinearLayout 为容器,生成 ImageView 的阵列;
initSound() 初始化了 SoundPool, 作为磁翻翻动时的声效,其中存放了几个不同的轻微敲击声效,避免大量连续播放产生的机械感和爆音。

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) 实现了效果图中 show Bitmap 的从 FlipDotView 中心向外辐射渐变翻动效果:
1.首先计算出 FlipDotView 中心的行列位置;
2.遍历二维数组,根据坐标到中心的距离,根据每个距离间隔(示例为 300ms),加上一个随机延迟(duration * random.nextInt(5)),获得每个 ImageVIew 的动画延迟 deley;
3.判断目标阵列(list)与当前显示阵列(oldList)中(i,j)位置上的值是否相同,相同则表示不需要翻动操作,继续遍历;
4.如果不同,则调用当前点 ImageView.postDelayed() 方法,将步骤[2]delay传入;
5.判断需要翻转的 list 点状态,播放翻转动画,并设置图片;
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 中,点击 FlipDotView,下方的 ImageView 与 Button,分别传入一个图形、图片、字符点阵,调用 FlipDotView 的 flipFromLeftTop(list)、 flipFromCenter(list) 方法,改变显示内容。
其中,图片与字符转点阵不是本文重点,有兴趣可以参考 Demo

补充,FlipDotView 的翻动音效,当翻动数量过多时声音可能会不自然或者无声,可以调整 SoundPool 构造函数的第一个参数,设置成一个比较大的数量,允许可能多的声音对象播放。

最后是 Demo Git:https://github.com/ReikyZ/FlipDot

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,067评论 4 62
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,880评论 25 707
  • [6] 明镜与王澄甯依旧是好友,每周每月她还会与她一同绕路回家,只是再没见过王成栋,偶尔有几次能碰上王成梁,他与王...
    李阿剂阅读 272评论 0 0
  • (越畅整理,转载请注明出处) 解离:进入到一个内在世界,感觉到外面的世界很遥远。 亨利老师举例子: (当用于创伤治...
    PhoenixScorpio阅读 186评论 0 0
  • 学习思维导图的目的就是训练自己的思维,让自己学会思考,从而提高我们的工作和学习效率!今晚邀请到深圳的一线优秀教...
    勿怼阅读 529评论 0 3