美文网首页我爱编程andnroidAndroid开发经验谈
FlipDotView——磁翻点阵显示效果

FlipDotView——磁翻点阵显示效果

作者: 阪有桑 | 来源:发表于2016-09-01 21:59 被阅读0次

    *本篇文章已授权微信公众号 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

    相关文章

      网友评论

        本文标题:FlipDotView——磁翻点阵显示效果

        本文链接:https://www.haomeiwen.com/subject/ssqhettx.html