亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Android?TextView跑馬燈實(shí)現(xiàn)原理及方法實(shí)例

 更新時(shí)間:2022年05月07日 15:58:34   作者:guodongAndroid  
字的跑馬燈效果在移動(dòng)端開發(fā)中是一個(gè)比較常見的需求場(chǎng)景,下面這篇文章主要給大家介紹了關(guān)于Android?TextView跑馬燈實(shí)現(xiàn)原理及方法的相關(guān)資料,需要的朋友可以參考下

前言

自定義View實(shí)現(xiàn)的跑馬燈一直沒有實(shí)現(xiàn)類似 Android TextView 的跑馬燈首尾相接的效果,所以一直想看看Android TextView 的跑馬燈是如何實(shí)現(xiàn)

本文主要探秘 Android TextView 的跑馬燈實(shí)現(xiàn)原理及實(shí)現(xiàn)自下往上效果的跑馬燈

探秘

TextView#onDraw

原生 Android TextView 如何設(shè)置開啟跑馬燈效果,此處不再描述,View 的繪制都在 onDraw 方法中,這里直接查看 TextView#onDraw() 方法,刪減一些不關(guān)心的代碼

 protected void onDraw(Canvas canvas) {
     // 是否需要重啟啟動(dòng)跑馬燈
     restartMarqueeIfNeeded();
 ?
     // Draw the background for this view
     super.onDraw(canvas);
         
     // 刪減不關(guān)心的代碼
 ?
     // 創(chuàng)建`mLayout`對(duì)象, 此處為`StaticLayout`
     if (mLayout == null) {
         assumeLayout();
     }
 ?
     Layout layout = mLayout;
 ?
     canvas.save();
 ?
     // 刪減不關(guān)心的代碼
 ?
     final int layoutDirection = getLayoutDirection();
     final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
 ?
     // 判斷跑馬燈設(shè)置項(xiàng)是否正確
     if (isMarqueeFadeEnabled()) {
         if (!mSingleLine && getLineCount() == 1 && canMarquee()
               && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
            final int width = mRight - mLeft;
            final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
            final float dx = mLayout.getLineRight(0) - (width - padding);
            canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
         }
 ?
         // 判斷跑馬燈是否啟動(dòng)
         if (mMarquee != null && mMarquee.isRunning()) {
             final float dx = -mMarquee.getScroll();
             // 移動(dòng)畫布
             canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
         }
     }
 ?
     final int cursorOffsetVertical = voffsetCursor - voffsetText;
 ?
     Path highlight = getUpdatedHighlightPath();
     if (mEditor != null) {
         mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
     } else {
         // 繪制文本
         layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
     }
 ?
     // 判斷是否可以繪制尾部文本
     if (mMarquee != null && mMarquee.shouldDrawGhost()) {
         final float dx = mMarquee.getGhostOffset();
         // 移動(dòng)畫布
         canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
         // 繪制尾部文本
         layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
     }
 ?
     canvas.restore();
 }

Marquee

根據(jù) onDraw() 方法分析,跑馬燈效果的實(shí)現(xiàn)主要依賴 mMarquee 這個(gè)對(duì)象來實(shí)現(xiàn),好的,看下 Marquee 吧,Marquee 代碼較少,就貼上全部源碼吧

 private static final class Marquee {
     // TODO: Add an option to configure this
     // 縮放相關(guān),不關(guān)心此字段
     private static final float MARQUEE_DELTA_MAX = 0.07f;
     
     // 跑馬燈跑完一次后多久開始下一次
     private static final int MARQUEE_DELAY = 1200;
     
     // 繪制一次跑多長(zhǎng)距離因子,此字段與速度相關(guān)
     private static final int MARQUEE_DP_PER_SECOND = 30;
 ?
     // 跑馬燈狀態(tài)常量
     private static final byte MARQUEE_STOPPED = 0x0;
     private static final byte MARQUEE_STARTING = 0x1;
     private static final byte MARQUEE_RUNNING = 0x2;
 ?
     // 對(duì)TextView進(jìn)行弱引用
     private final WeakReference<TextView> mView;
     
     // 幀率相關(guān)
     private final Choreographer mChoreographer;
 ?
     // 狀態(tài)
     private byte mStatus = MARQUEE_STOPPED;
     
     // 繪制一次跑多長(zhǎng)距離
     private final float mPixelsPerMs;
     
     // 最大滾動(dòng)距離
     private float mMaxScroll;
     
     // 是否可以繪制右陰影, 右側(cè)淡入淡出效果
     private float mMaxFadeScroll;
     
     // 尾部文本什么時(shí)候開始繪制
     private float mGhostStart;
     
     // 尾部文本繪制位置偏移量
     private float mGhostOffset;
     
     // 是否可以繪制左陰影,左側(cè)淡入淡出效果
     private float mFadeStop;
     
     // 重復(fù)限制
     private int mRepeatLimit;
 ?
     // 跑動(dòng)距離
     private float mScroll;
     
     // 最后一次跑動(dòng)時(shí)間,單位毫秒
     private long mLastAnimationMs;
 ?
     Marquee(TextView v) {
         final float density = v.getContext().getResources().getDisplayMetrics().density;
         // 計(jì)算每次跑多長(zhǎng)距離
         mPixelsPerMs = MARQUEE_DP_PER_SECOND * density / 1000f;
         mView = new WeakReference<TextView>(v);
         mChoreographer = Choreographer.getInstance();
     }
 ?
     // 幀率回調(diào),用于跑馬燈跑動(dòng)
     private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() {
         @Override
         public void doFrame(long frameTimeNanos) {
             tick();
         }
     };
 ?
     // 幀率回調(diào),用于跑馬燈開始跑動(dòng)
     private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
         @Override
         public void doFrame(long frameTimeNanos) {
             mStatus = MARQUEE_RUNNING;
             mLastAnimationMs = mChoreographer.getFrameTime();
             tick();
         }
     };
 ?
     // 幀率回調(diào),用于跑馬燈重新跑動(dòng)
     private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
         @Override
         public void doFrame(long frameTimeNanos) {
             if (mStatus == MARQUEE_RUNNING) {
                 if (mRepeatLimit >= 0) {
                     mRepeatLimit--;
                 }
                 start(mRepeatLimit);
             }
         }
     };
 ?
     // 跑馬燈跑動(dòng)實(shí)現(xiàn)
     void tick() {
         if (mStatus != MARQUEE_RUNNING) {
             return;
         }
 ?
         mChoreographer.removeFrameCallback(mTickCallback);
 ?
         final TextView textView = mView.get();
         // 判斷TextView是否處于獲取焦點(diǎn)或選中狀態(tài)
         if (textView != null && (textView.isFocused() || textView.isSelected())) {
             // 獲取當(dāng)前時(shí)間
             long currentMs = mChoreographer.getFrameTime();
             // 計(jì)算當(dāng)前時(shí)間與上次時(shí)間的差值
             long deltaMs = currentMs - mLastAnimationMs;
             mLastAnimationMs = currentMs;
             // 根據(jù)時(shí)間差計(jì)算本次跑動(dòng)的距離,減輕視覺上跳動(dòng)/卡頓
             float deltaPx = deltaMs * mPixelsPerMs;
             // 計(jì)算跑動(dòng)距離
             mScroll += deltaPx;
             // 判斷是否已經(jīng)跑完
             if (mScroll > mMaxScroll) {
                 mScroll = mMaxScroll;
                 // 發(fā)送重新開始跑動(dòng)事件
                 mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
             } else {
                 // 發(fā)送下一次跑動(dòng)事件
                 mChoreographer.postFrameCallback(mTickCallback);
             }
             // 調(diào)用此方法會(huì)觸發(fā)執(zhí)行`onDraw`方法
             textView.invalidate();
         }
     }
 ?
     // 停止跑馬燈
     void stop() {
         mStatus = MARQUEE_STOPPED;
         mChoreographer.removeFrameCallback(mStartCallback);
         mChoreographer.removeFrameCallback(mRestartCallback);
         mChoreographer.removeFrameCallback(mTickCallback);
         resetScroll();
     }
 ?
     private void resetScroll() {
         mScroll = 0.0f;
         final TextView textView = mView.get();
         if (textView != null) textView.invalidate();
     }
 ?
     // 啟動(dòng)跑馬燈
     void start(int repeatLimit) {
         if (repeatLimit == 0) {
             stop();
             return;
         }
         mRepeatLimit = repeatLimit;
         final TextView textView = mView.get();
         if (textView != null && textView.mLayout != null) {
             // 設(shè)置狀態(tài)為在跑
             mStatus = MARQUEE_STARTING;
             // 重置跑動(dòng)距離
             mScroll = 0.0f;
             // 計(jì)算TextView寬度
             final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft()
                 - textView.getCompoundPaddingRight();
             // 獲取文本第0行的寬度
             final float lineWidth = textView.mLayout.getLineWidth(0);
             // 取TextView寬度的三分之一
             final float gap = textWidth / 3.0f;
             // 計(jì)算什么時(shí)候可以開始繪制尾部文本:首部文本跑動(dòng)到哪里可以繪制尾部文本
             mGhostStart = lineWidth - textWidth + gap;
             // 計(jì)算最大滾動(dòng)距離:什么時(shí)候認(rèn)為跑完一次
             mMaxScroll = mGhostStart + textWidth;
             // 尾部文本繪制偏移量
             mGhostOffset = lineWidth + gap;
             // 跑動(dòng)到哪里時(shí)不繪制左側(cè)陰影
             mFadeStop = lineWidth + textWidth / 6.0f;
             // 跑動(dòng)到哪里時(shí)不繪制右側(cè)陰影
             mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
 ?
             textView.invalidate();
             // 開始跑動(dòng)
             mChoreographer.postFrameCallback(mStartCallback);
         }
     }
 ?
     // 獲取尾部文本繪制位置偏移量
     float getGhostOffset() {
         return mGhostOffset;
     }
 ?
     // 獲取當(dāng)前滾動(dòng)距離
     float getScroll() {
         return mScroll;
     }
 ?
     // 獲取可以右側(cè)陰影繪制的最大距離
     float getMaxFadeScroll() {
         return mMaxFadeScroll;
     }
 ?
     // 判斷是否可以繪制左側(cè)陰影
     boolean shouldDrawLeftFade() {
         return mScroll <= mFadeStop;
     }
 ?
     // 判斷是否可以繪制尾部文本
     boolean shouldDrawGhost() {
         return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
     }
 ?
     // 跑馬燈是否在跑
     boolean isRunning() {
         return mStatus == MARQUEE_RUNNING;
     }
 ?
     // 跑馬燈是否不跑
     boolean isStopped() {
         return mStatus == MARQUEE_STOPPED;
     }
 }

好的,分析完 Marquee,跑馬燈實(shí)現(xiàn)原理豁然明亮

  • 在 TextView 開啟跑馬燈效果時(shí)調(diào)用 Marquee#start() 方法
  • 在 Marquee#start() 方法中觸發(fā) TextView 重繪,開始計(jì)算跑動(dòng)距離
  • 在 TextView#onDraw() 方法中根據(jù)跑動(dòng)距離移動(dòng)畫布并繪制首部文本,再根據(jù)跑動(dòng)距離判斷是否可以移動(dòng)畫布繪制尾部文本

小結(jié)

TextView 通過移動(dòng)畫布繪制兩次文本實(shí)現(xiàn)跑馬燈效果,根據(jù)兩幀繪制的時(shí)間差計(jì)算跑動(dòng)距離,怎一個(gè)"妙"字了得

應(yīng)用

上面分析完原生 Android TextView 跑馬燈的實(shí)現(xiàn)原理,但是原生 Android TextView 跑馬燈有幾點(diǎn)不足:

  • 無法設(shè)置跑動(dòng)速度
  • 無法設(shè)置重跑間隔時(shí)長(zhǎng)
  • 無法實(shí)現(xiàn)上下跑動(dòng)

以上第1、2點(diǎn)在上面 Marquee 分析中已經(jīng)有解決方案,接下來根據(jù)原生實(shí)現(xiàn)原理實(shí)現(xiàn)第3點(diǎn)上下跑動(dòng)

MarqueeTextView

這里給出實(shí)現(xiàn)方案,列出主要實(shí)現(xiàn)邏輯,繼承 AppCompatTextView,復(fù)寫 onDraw() 方法,上下跑動(dòng)主要是計(jì)算上下跑動(dòng)的距離,然后再次重繪 TextView 上下移動(dòng)畫布繪制文本

 /**
  * 繼承AppCompatTextView,復(fù)寫onDraw方法
  */
 public class MarqueeTextView extends AppCompatTextView {
 ?
     private static final int DEFAULT_BG_COLOR = Color.parseColor("#FFEFEFEF");
 ?
     @IntDef({HORIZONTAL, VERTICAL})
     @Retention(RetentionPolicy.SOURCE)
     public @interface OrientationMode {
     }
 ?
     public static final int HORIZONTAL = 0;
     public static final int VERTICAL = 1;
 ?
     private Marquee mMarquee;
     private boolean mRestartMarquee;
     private boolean isMarquee;
 ?
     private int mOrientation;
 ?
     public MarqueeTextView(@NonNull Context context) {
         this(context, null);
     }
 ?
     public MarqueeTextView(@NonNull Context context, @Nullable AttributeSet attrs) {
         this(context, attrs, 0);
     }
 ?
     public MarqueeTextView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
 ?
         TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MarqueeTextView, defStyleAttr, 0);
 ?
         mOrientation = ta.getInt(R.styleable.MarqueeTextView_orientation, HORIZONTAL);
 ?
         ta.recycle();
     }
 ?
     @Override
     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
         super.onSizeChanged(w, h, oldw, oldh);
 ?
         if (mOrientation == HORIZONTAL) {
             if (getWidth() > 0) {
                 mRestartMarquee = true;
             }
         } else {
             if (getHeight() > 0) {
                 mRestartMarquee = true;
             }
         }
     }
 ?
     private void restartMarqueeIfNeeded() {
         if (mRestartMarquee) {
             mRestartMarquee = false;
             startMarquee();
         }
     }
 ?
     public void setMarquee(boolean marquee) {
         boolean wasStart = isMarquee();
 ?
         isMarquee = marquee;
 ?
         if (wasStart != marquee) {
             if (marquee) {
                 startMarquee();
             } else {
                 stopMarquee();
             }
         }
     }
 ?
     public void setOrientation(@OrientationMode int orientation) {
         mOrientation = orientation;
     }
 ?
     public int getOrientation() {
         return mOrientation;
     }
 ?
     public boolean isMarquee() {
         return isMarquee;
     }
 ?
     private void stopMarquee() {
         if (mOrientation == HORIZONTAL) {
             setHorizontalFadingEdgeEnabled(false);
         } else {
             setVerticalFadingEdgeEnabled(false);
         }
 ?
         requestLayout();
         invalidate();
 ?
         if (mMarquee != null && !mMarquee.isStopped()) {
             mMarquee.stop();
         }
     }
 ?
     private void startMarquee() {
         if (canMarquee()) {
 ?
             if (mOrientation == HORIZONTAL) {
                 setHorizontalFadingEdgeEnabled(true);
             } else {
                 setVerticalFadingEdgeEnabled(true);
             }
 ?
             if (mMarquee == null) mMarquee = new Marquee(this);
             mMarquee.start(-1);
         }
     }
 ?
     private boolean canMarquee() {
         if (mOrientation == HORIZONTAL) {
             int viewWidth = getWidth() - getCompoundPaddingLeft() -
                 getCompoundPaddingRight();
             float lineWidth = getLayout().getLineWidth(0);
             return (mMarquee == null || mMarquee.isStopped())
                 && (isFocused() || isSelected() || isMarquee())
                 && viewWidth > 0
                 && lineWidth > viewWidth;
         } else {
             int viewHeight = getHeight() - getCompoundPaddingTop() -
                 getCompoundPaddingBottom();
             float textHeight = getLayout().getHeight();
             return (mMarquee == null || mMarquee.isStopped())
                 && (isFocused() || isSelected() || isMarquee())
                 && viewHeight > 0
                 && textHeight > viewHeight;
         }
     }
 ?
     /**
      * 仿照TextView#onDraw()方法
      */
     @Override
     protected void onDraw(Canvas canvas) {
         restartMarqueeIfNeeded();
 ?
         super.onDraw(canvas);
 ?
         // 再次繪制背景色,覆蓋下面由TextView繪制的文本,視情況可以不調(diào)用`super.onDraw(canvas);`
         // 如果沒有背景色則使用默認(rèn)顏色
         Drawable background = getBackground();
         if (background != null) {
             background.draw(canvas);
         } else {
             canvas.drawColor(DEFAULT_BG_COLOR);
         }
 ?
         canvas.save();
 ?
         canvas.translate(0, 0);
 ?
         // 實(shí)現(xiàn)左右跑馬燈
         if (mOrientation == HORIZONTAL) {
             if (mMarquee != null && mMarquee.isRunning()) {
                 final float dx = -mMarquee.getScroll();
                 canvas.translate(dx, 0.0F);
             }
 ?
             getLayout().draw(canvas, null, null, 0);
 ?
             if (mMarquee != null && mMarquee.shouldDrawGhost()) {
                 final float dx = mMarquee.getGhostOffset();
                 canvas.translate(dx, 0.0F);
                 getLayout().draw(canvas, null, null, 0);
             }
         } else {
             // 實(shí)現(xiàn)上下跑馬燈
             if (mMarquee != null && mMarquee.isRunning()) {
                 final float dy = -mMarquee.getScroll();
                 canvas.translate(0.0F, dy);
             }
 ?
             getLayout().draw(canvas, null, null, 0);
 ?
             if (mMarquee != null && mMarquee.shouldDrawGhost()) {
                 final float dy = mMarquee.getGhostOffset();
                 canvas.translate(0.0F, dy);
                 getLayout().draw(canvas, null, null, 0);
             }
         }
 ?
         canvas.restore();
     }
 }

Marquee

 private static final class Marquee {
     // 修改此字段設(shè)置重跑時(shí)間間隔 - 對(duì)應(yīng)不足點(diǎn)2
     private static final int MARQUEE_DELAY = 1200;
 ?
     // 修改此字段設(shè)置跑動(dòng)速度 - 對(duì)應(yīng)不足點(diǎn)1
     private static final int MARQUEE_DP_PER_SECOND = 30;
 ?
     private static final byte MARQUEE_STOPPED = 0x0;
     private static final byte MARQUEE_STARTING = 0x1;
     private static final byte MARQUEE_RUNNING = 0x2;
 ?
     private static final String METHOD_GET_FRAME_TIME = "getFrameTime";
 ?
     private final WeakReference<MarqueeTextView> mView;
     private final Choreographer mChoreographer;
 ?
     private byte mStatus = MARQUEE_STOPPED;
     private final float mPixelsPerSecond;
     private float mMaxScroll;
     private float mMaxFadeScroll;
     private float mGhostStart;
     private float mGhostOffset;
     private float mFadeStop;
     private int mRepeatLimit;
 ?
     private float mScroll;
     private long mLastAnimationMs;
 ?
     Marquee(MarqueeTextView v) {
         final float density = v.getContext().getResources().getDisplayMetrics().density;
         mPixelsPerSecond = MARQUEE_DP_PER_SECOND * density;
         mView = new WeakReference<>(v);
         mChoreographer = Choreographer.getInstance();
     }
 ?
     private final Choreographer.FrameCallback mTickCallback = frameTimeNanos -> tick();
 ?
     private final Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
         @Override
         public void doFrame(long frameTimeNanos) {
             mStatus = MARQUEE_RUNNING;
             mLastAnimationMs = getFrameTime();
             tick();
         }
     };
 ?
     /**
      * `getFrameTime`是隱藏api,此處使用反射調(diào)用,高系統(tǒng)版本可能失效,可使用某些方案繞過此限制
      */
     @SuppressLint("PrivateApi")
     private long getFrameTime() {
         try {
             Class<? extends Choreographer> clz = mChoreographer.getClass();
             Method getFrameTime = clz.getDeclaredMethod(METHOD_GET_FRAME_TIME);
             getFrameTime.setAccessible(true);
             return (long) getFrameTime.invoke(mChoreographer);
         } catch (Exception e) {
             e.printStackTrace();
             return 0;
         }
     }
 ?
     private final Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
         @Override
         public void doFrame(long frameTimeNanos) {
             if (mStatus == MARQUEE_RUNNING) {
                 if (mRepeatLimit >= 0) {
                     mRepeatLimit--;
                 }
                 start(mRepeatLimit);
             }
         }
     };
 ?
     void tick() {
         if (mStatus != MARQUEE_RUNNING) {
             return;
         }
 ?
         mChoreographer.removeFrameCallback(mTickCallback);
 ?
         final MarqueeTextView textView = mView.get();
         if (textView != null && (textView.isFocused() || textView.isSelected() || textView.isMarquee())) {
             long currentMs = getFrameTime();
             long deltaMs = currentMs - mLastAnimationMs;
             mLastAnimationMs = currentMs;
             float deltaPx = deltaMs / 1000F * mPixelsPerSecond;
             mScroll += deltaPx;
             if (mScroll > mMaxScroll) {
                 mScroll = mMaxScroll;
                 mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
             } else {
                 mChoreographer.postFrameCallback(mTickCallback);
             }
             textView.invalidate();
         }
     }
 ?
     void stop() {
         mStatus = MARQUEE_STOPPED;
         mChoreographer.removeFrameCallback(mStartCallback);
         mChoreographer.removeFrameCallback(mRestartCallback);
         mChoreographer.removeFrameCallback(mTickCallback);
         resetScroll();
     }
 ?
     private void resetScroll() {
         mScroll = 0.0F;
         final MarqueeTextView textView = mView.get();
         if (textView != null) textView.invalidate();
     }
 ?
     void start(int repeatLimit) {
         if (repeatLimit == 0) {
             stop();
             return;
         }
         mRepeatLimit = repeatLimit;
         final MarqueeTextView textView = mView.get();
         if (textView != null && textView.getLayout() != null) {
             mStatus = MARQUEE_STARTING;
             mScroll = 0.0F;
 ?
             // 分別計(jì)算左右和上下跑動(dòng)所需的數(shù)據(jù)
             if (textView.getOrientation() == HORIZONTAL) {
                 int viewWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
                     textView.getCompoundPaddingRight();
                 float lineWidth = textView.getLayout().getLineWidth(0);
                 float gap = viewWidth / 3.0F;
                 mGhostStart = lineWidth - viewWidth + gap;
                 mMaxScroll = mGhostStart + viewWidth;
                 mGhostOffset = lineWidth + gap;
                 mFadeStop = lineWidth + viewWidth / 6.0F;
                 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
             } else {
                 int viewHeight = textView.getHeight() - textView.getCompoundPaddingTop() -
                     textView.getCompoundPaddingBottom();
                 float textHeight = textView.getLayout().getHeight();
                 float gap = viewHeight / 3.0F;
                 mGhostStart = textHeight - viewHeight + gap;
                 mMaxScroll = mGhostStart + viewHeight;
                 mGhostOffset = textHeight + gap;
                 mFadeStop = textHeight + viewHeight / 6.0F;
                 mMaxFadeScroll = mGhostStart + textHeight + textHeight;
             }
 ?
             textView.invalidate();
             mChoreographer.postFrameCallback(mStartCallback);
         }
     }
 ?
     float getGhostOffset() {
         return mGhostOffset;
     }
 ?
     float getScroll() {
         return mScroll;
     }
 ?
     float getMaxFadeScroll() {
         return mMaxFadeScroll;
     }
 ?
     boolean shouldDrawLeftFade() {
         return mScroll <= mFadeStop;
     }
 ?
     boolean shouldDrawTopFade() {
         return mScroll <= mFadeStop;
     }
 ?
     boolean shouldDrawGhost() {
         return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
     }
 ?
     boolean isRunning() {
         return mStatus == MARQUEE_RUNNING;
     }
 ?
     boolean isStopped() {
         return mStatus == MARQUEE_STOPPED;
     }
 }

效果

總結(jié)

到此這篇關(guān)于Android TextView跑馬燈實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Android TextView跑馬燈內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論