一篇文本跳动控件,为你打开一扇大门,学会这两点心得,控件你也会写

本篇文章已授权微信公众号(鸿洋)独家发布

做开发已经3年有余了,一事无成,一直静不下心来安心做好一件事情,很多时候内心很浮躁,迷失了前行的方向,我该当何去何从?一个人闲下来的时候,总想放纵自己,二个人在一起总会吵吵闹闹,在物质驱使的年代里,活得一点不像自己,像我这样的人,还有多少人?

心里的话一直想找人说,让大家见笑了。本篇是讲技术的,而不是来听我的感慨。先来看看文本跳动控件的最终效果图:

跳动文本控件

一款优秀的 app 往往会有一些新颖的控件,让用户在视觉与体验都会觉得很棒,第一时间留住用户的心。那么怎样打造一款炫酷的自定义控件(只考虑编程层面)?

群里有很大一部分童鞋反应,觉得写自定义控件好难,害怕去写,有的还没开始就结束了,还有的开始没多久就放弃了,剩下的都在放弃的路上。其实写自定义控件并没有那么难,那我谈谈自己的心得,有两点:

  1. 第一,拆分

  2. 第二,模仿

控件的难点在于动画,动画往往有规律可循,那么我们需要把动画变慢去寻找规律,一是ui设计师给出相应的参数,有时需要模仿竞品的控件效果,可以在开发者选择中设置动画缩放时长:

动画缩放时长

还可以通过adb命令进行录屏与截图:

// 截屏
adb shell /system/bin/screencap -p /sdcard/a.png  
// 录屏
adb shell screenrecord /sdcard/a.mp4
// 拉到电脑上
adb pull /sdcard/a.mp4 

动画变慢了,我们就可以进行拆分,在平面(2D)动画中我们一般只考虑横纵坐标,那么就可以拆分为 xy 方向的动画,动画无非就是平移,旋转,缩放以及透明度变换中的一种或者几种,化繁为简,本篇会以文本跳动控件具体讲解。

第二点心得是模仿,腾讯,小米一直在模仿,一直很成功,那么写控件一样,也需要模仿,尤其对于初学者,更需要模仿。先不论你写的控件是不是最优方案,性能是否有过渡损耗,动手模仿写一写,可以找些简单例子练练手,强力推荐 启舰大牛,写的很细很全面,有时候需要知道竞品使用了什么控件,可以让你少走许多弯路?推荐一款sdk自带的工具:

viewer

看到这里,你有信心写好上方跳动文本控件吗?如果是你拿到这个控件需求,你会怎么分析?欢迎留言,很多时候实现方案往往不会是一种,你的答案,可能对他人有所帮助,接下来具体讲解跳动文本控件的实现。

跳动文本案例

需求分析:

1、筛选出新旧文本的相同字符并记录索引值

2、计算筛选出每个相同字符在动画周期内的偏移量

3、旧文本剩余字符的平移与透明度动画

4、新文本剩余字符的平移与透明度动画

为了方便理解,简化字符,延迟动画时长,效果如下:

跳动文本控件

从以上效果图可以知道,红色字体的 wen 为已经绘制的文本(旧文本),白色的 tianxia 为即将绘制的文本(新文本),新旧文本相同的字符为 n ,针对新旧文本拆分 x,y 轴方向的动画:

旧文本

  • x 轴方向字符 n 平移
  • y 轴方向除 n 外的字符平移,透明度(向上平移,透明度0~1)

新文本

  • x 轴方向无动画
  • y 轴方向除 n 外的字符平移,透明度(向上平移,透明度1~0)

拆分后你会发现就只有简单的平移与透明度动画,是不是一下就觉得简单了许多。

筛选相同字符

直接看代码:

    public static List<CharacterDiffResult> diff(CharSequence oldText, CharSequence newText) {
        List<CharacterDiffResult> differentList = new ArrayList<>();
        Set<Integer> skip = new HashSet<>();
        for (int i = 0; i < oldText.length(); i++) {
            char c = oldText.charAt(i);
            for (int j = 0; j < newText.length(); j++) {
                if (!skip.contains(j) && c == newText.charAt(j)) {
                    skip.add(j);
                    CharacterDiffResult different = new CharacterDiffResult();
                    different.c = c;
                    //  在旧文本中的位置
                    different.fromIndex = i;
                   // 在新文本中的位置
                    different.moveIndex = j;
                    differentList.add(different);
                    break;
                }
            }
        }
        return differentList;
    }

CharacterDiffResult类中存取了新旧文本的相同字符以及在新旧文本中的位置,这个方法应该不难理解,接下来分析相同字符在动画周期内的偏移量。

相同字符偏移量

首先需要弄清一个概念,那就是绘制字符的xy坐标:


/**
* text:要绘制的文字
* x:绘制原点x坐标
* y:绘制原点y坐标
* paint:用来做画的画笔
*/
public void drawText(String text, float x, float y, Paint paint)

这里的xy表示的是基线xy坐标,具体请参考自定义控件之绘图篇( 五):drawText()详解

通过以下方法可以获取新旧文本的基线x坐标:

   int layoutDirection = ViewCompat.getLayoutDirection(EvaporateTextView.this);
  // 获取x坐标
   oldStartX = layoutDirection == LAYOUT_DIRECTION_LTR ? getLayout().getLineLeft(0) : getLayout().getLineRight(0);

为了计算偏移量,需要存取新旧字符串中每个字符的宽度:

    mOldPaint.setTextSize(mTextSize);
    mOldPaint.setColor(getCurrentTextColor());
    mOldPaint.setTypeface(getTypeface());
    oldGapList.clear();
    for (int i = 0; i < mOldText.length(); i++) {
         // measureText 测量文本宽度
        oldGapList.add(mOldPaint.measureText(String.valueOf(mOldText.charAt(i))));
    }
}

通过以上参数就可以获取到动画周期内相同字符的偏移量(基线的x坐标):

    /**
     * 获取旧文本字符n的x坐标
     *
     * @param from      旧文本字符n的索引位置
     * @param move      新文本字符n的索引位置
     * @param progress  当前进度
     * @param startX    新文本baseline的x坐标
     * @param oldStartX 旧文本baseline的y坐标
     * @param gaps      新文本每个字符对应的x坐标集合
     * @param oldGaps   旧文本每个字符对应的x坐标集合
     * @return
     */
    public static float getOffset(int from, int move, float progress, float startX, float oldStartX,
                                  List<Float> gaps, List<Float> oldGaps) {
                                  
        float dist = startX;
        for (int i = 0; i < move; i++) {
            dist += gaps.get(i);
        }
        
        float cur = oldStartX;
        for (int i = 0; i < from; i++) {
            cur += oldGaps.get(i);
        }

        return cur + (dist - cur) * progress;
    }

根据数学公式(n 字符x坐标 = 起点 + (终点 - 起点)x 进度),起点与终点分别对应旧新文本字符 n 所在的 x 轴坐标,进度的取值范围为[0~1]

旧文本平移透明动画

  // 透明度动画
   mOldPaint.setAlpha((int) ((1 - pp) * 255));
  // pp 表示的是进度
   float y = startY - pp * mTextHeight;
   // (oldGapList.get(i) - width) / 2 值为0   oldOffset + (oldGapList.get(i) - width) / 2
  // oldOffset 基线x坐标  平移动画
   canvas.drawText(mOldText.charAt(i) + "", 0, 1, oldOffset, y, mOldPaint);
   oldOffset += oldGapList.get(i);

请参考注释,或者下载demo对应代码理解,文末会给出demo地址,还有干货哟。

新文本平移透明动画

动画比较简单,直接贴代码:

 if (i < mText.length()) {
    if (!CharacterUtils.stayHere(i, differentList)) {
        // 渐显效果 延迟 alpha 的计算稍微费脑一点
        int alpha = (int) (255f / charTime * (progress * duration - charTime * i / mostCount));
        alpha = alpha > 255 ? 255 : alpha;
        alpha = alpha < 0 ? 0 : alpha;
        mPaint.setAlpha(alpha);
        mPaint.setTextSize(mTextSize);
        //   float pp = progress * duration / (charTime + charTime / mostCount * (mText.length() - 1));
        float pp = progress;
        float y = mTextHeight + startY - pp * mTextHeight;
        float width = mPaint.measureText(mText.charAt(i) + "");
        canvas.drawText(mText.charAt(i) + "", 0, 1, offset + (gapList.get(i) - width) / 2, y, mPaint);
    }
    offset += gapList.get(i);
}

跳动文本讲到这里就差不多了,有什么不懂的地方,请留言讨论。

总结

写好控件在于分析与拆分,再复杂的动画也是由简单的动画组合而成,先化繁为简,后以简组繁,多练多写,多借鉴更优的实现方案。本篇的文本跳动控件,是否为你打开了一扇大门?

小编正在维护MeiWidgetView库,有炫酷的控件可以推荐,希望有人能够和小编一起维护,万分感谢,给小编一颗 star,才是最好最美的回报。

源码地址

参考的相关文章地址:

https://github.com/hanks-zyh/HTextView
https://blog.csdn.net/harvic880925/article/details/50995268

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

推荐阅读更多精彩内容