Android 通过自定义View实现纵向跑马灯效果

录制的gif有点卡,真实的效果还是很流畅的。

跑马灯在我们日常使用的app中还是很常见的,以前做外卖app的时候商家公告就使用了此效果,但是它是横向滚动的,横向滚动多适用于单条信息;但凡涉及到多条信息的滚动展示,用纵向滚动效果会有更好的用户体验,今天我们通过自定义View来看看如何实现纵向跑马灯效果。

思路

通过上面的gif图可以得出结论,其实它就是同时绘制两条文本信息,然后通过动画不断的改变两条文本信息距离顶部的高度,以此来实现滚动的效果。

具体实现

  • 首先定义一些要用到的属性
<declare-styleable name="MarqueeViewStyle">    
  <attr name="textSize" format="dimension" />    
  <attr name="textColor" format="color" />    
  <attr name="paddingLeft" format="dimension" />    
  <attr name="paddingTop" format="dimension" />    
  <attr name="paddingBottom" format="dimension" />    
  <attr name="paddingTopBottom" format="dimension"/>    
  <attr name="startDelayTime" format="integer"/> 动画开始延迟时间
  <attr name="reRepeatDelayTime" format="integer"/> 动画重复延迟时间 
  <attr name="itemAnimationTime" format="integer"/> 单个动画的执行时间
</declare-styleable>
  • 接下来解析属性值
private void init(Context context, AttributeSet attrs) {    
    TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MarqueeViewStyle);    
    mTextColor = typedArray.getColor(R.styleable.MarqueeViewStyle_textColor, Color.BLACK);    
    mTextSize = typedArray.getDimensionPixelSize(R.styleable.MarqueeViewStyle_textSize, 45);    

    mPaddingLeft = typedArray.getDimensionPixelSize(R.styleable.MarqueeViewStyle_paddingLeft, 15);    
    mPaddingTop = mPaddingBottom = typedArray.getDimensionPixelSize(R.styleable.MarqueeViewStyle_paddingTopBottom, 25);    
    mPaddingTop = typedArray.getDimensionPixelSize(R.styleable.MarqueeViewStyle_paddingTop, mPaddingTop);    
    mPaddingBottom = typedArray.getDimensionPixelSize(R.styleable.MarqueeViewStyle_paddingBottom, mPaddingBottom);    

    itemAnimationTime = typedArray.getInteger(R.styleable.MarqueeViewStyle_itemAnimationTime, 1000);    
    reRepeatDelayTime = typedArray.getInteger(R.styleable.MarqueeViewStyle_reRepeatDelayTime, 1000);    
    startDelayTime = typedArray.getInteger(R.styleable.MarqueeViewStyle_startDelayTime, 500);    
    typedArray.recycle();    

     mPaint = new Paint();    
     mPaint.setAntiAlias(true);    
     mPaint.setTextSize(mTextSize);    
     mPaint.setColor(mTextColor);    
     mPaint.setTextAlign(Paint.Align.LEFT);}
  • 重写onMeasure方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    
     if(mTextArray == null || mTextArray.length == 0) {      
          super.onMeasure(widthMeasureSpec, heightMeasureSpec);    
     } else {        
          int width = MeasureSpec.getSize(widthMeasureSpec);    
          int height = MeasureSpec.getSize(heightMeasureSpec);     
          ViewGroup.LayoutParams lp = getLayoutParams();        
          setMeasuredDimension(getViewWidth(lp, width), getViewHeight(lp, height));    
     }
}

数据为空时调用父类的方法,设置数据以后根据不同的布局计算宽高。

  • 设置数据
public void setTextArray(String[] textArray) {    
     if(textArray == null || textArray.length <= 1) return;    
      mTextArray = textArray;    
      initTextRect();    
      setTextCurrentOrNextStatus(0, 1, true);    
      startAnimation();}

initTextRect()方法是计算出单个文本的高度和数组中最大文本的宽度,文本的高度用来计算绘制文本时的位置,文本的最大宽度在onMeasure()方法的时候会用到。

setTextCurrentOrNextStatus()方法设置当前的position,文本以及下一个position,文本。还有文本距离顶部的初始化高度。

startAnimation() 方法 开始执行动画。

  • 动画实现
private void startAnimation() {    
    va = ValueAnimator.ofFloat(0, 1);    
    va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {        
    @Override        
    public void onAnimationUpdate(ValueAnimator animation) {   
        mProgress = (float) animation.getAnimatedValue();            
.     int moveOffset = (int) (mTextMoveOffset * mProgress);  
        mCurrentTextMoveMarginTop = mCurrentTextInitMarginTop - moveOffset;            
        mNextTextMoveMarginTop = mNextTextInitMarginTop - moveOffset;            
        postInvalidate();        
    }    
   });    
  
   va.addListener(new AnimatorListenerAdapter() {        
        @Override        
        public void onAnimationRepeat(Animator animation) {     
         va.pause();                
         setTextCurrentOrNextStatus(mNextTextPosition, mNextTextPosition + 1, false);            
         handler.postDelayed(new Runnable() {                
           @Override                
           public void run() {                    
             if(!mIsPause) {    
                va.resume();
             }             
           } 
         }, reRepeatDelayTime);       
        }   
   });    
   va.setRepeatCount(-1);    
   va.setDuration(itemAnimationTime);    
   va.setStartDelay(startDelayTime);    
   va.start();
}

va.setRepeatCount(-1); 设置动画无限重复
onAnimationUpdate() 方法得到动画执行的进度,计算出text距离顶部的距离,调用postInvalidate()方法刷新界面。

onAnimationRepeat() 方法,通过handler延迟reRepeatDelayTime时间,再重新执行动画。

  • 绘制
protected void onDraw(Canvas canvas) {    
   if(mTextArray == null || mTextArray.length == 0) {     
     super.onDraw(canvas);    
   } else {        
     canvas.drawText(mCurrentText, mPaddingLeft, mCurrentTextMoveMarginTop, mPaint);   
     canvas.drawText(mNextText, mPaddingLeft, mNextTextMoveMarginTop, mPaint);    
   }
}
  • 结束
    至此所有的代码已经分析完毕,其实实现这个效果还有很多种方法,通过继承ViewGroup等等都可以实现,大家有兴趣可以自己尝试。

最后放上源码 https://github.com/chenpengfei88/VerticalMarqueeView
有兴趣的朋友可以看看,欢迎大家Start,Follow。

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

推荐阅读更多精彩内容