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。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容