最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Android實(shí)現(xiàn)文字滾動(dòng)播放效果的示例代碼

 更新時(shí)間:2025年05月06日 09:08:58   作者:Katie。  
在許多資訊類、新聞?lì)愐约捌髽I(yè)展示類 Android 應(yīng)用中,文字滾動(dòng)播放是非常常見(jiàn)的 UI 交互方式,文字滾動(dòng)不僅能夠節(jié)省屏幕空間,還能吸引用戶注意力,所以本項(xiàng)目通過(guò)原生 Android 技術(shù),從零開(kāi)始實(shí)現(xiàn)一套支持多種滾動(dòng)方向與動(dòng)畫(huà)曲線的文字滾動(dòng)播放控件,需要的朋友可以參考下

一、項(xiàng)目介紹

1. 背景與意義

在許多資訊類、新聞?lì)愐约捌髽I(yè)展示類 Android 應(yīng)用中,文字滾動(dòng)播放(也稱為跑馬燈效果、公告欄效果)是非常常見(jiàn)的 UI 交互方式,用于持續(xù)不斷地展示公告、新聞標(biāo)題、提示信息等。在影視推薦 App、地鐵公交查詢、股市行情等場(chǎng)景中,文字滾動(dòng)不僅能夠節(jié)省屏幕空間,還能吸引用戶注意力,使信息傳遞更具張力。本項(xiàng)目通過(guò)原生 Android 技術(shù),從零開(kāi)始實(shí)現(xiàn)一套高性能、高度可定制、支持多種滾動(dòng)方向與動(dòng)畫(huà)曲線的文字滾動(dòng)播放控件,滿足各類復(fù)雜需求。

2. 功能需求

  1. 文字內(nèi)容設(shè)定:可動(dòng)態(tài)設(shè)置一段或多段文字;

  2. 滾動(dòng)模式:支持水平、垂直兩種滾動(dòng)方向;

  3. 滾動(dòng)方式:支持循環(huán)播放與單次播放,支持往返式無(wú)縫銜接;

  4. 速度與間隔:可自定義滾動(dòng)速度與兩次滾動(dòng)之間的停留間隔;

  5. 動(dòng)畫(huà)曲線:內(nèi)置線性、加速、減速等插值器;

  6. 觸摸交互:支持用戶觸摸滑動(dòng)暫停與手動(dòng)拖動(dòng);

  7. 資源釋放:Activity/Fragment 銷毀時(shí)正確釋放動(dòng)畫(huà)與 Handler,防止內(nèi)存泄露;

  8. 可定制樣式:文字大小、顏色、字體、背景等可通過(guò) XML 屬性或代碼動(dòng)態(tài)配置;

  9. 高性能:在長(zhǎng)列表、多實(shí)例場(chǎng)景下,保持平滑的 60FPS。

3. 技術(shù)選型

  • 語(yǔ)言:Java

  • 最低 SDK:API 21(Android 5.0)

  • 核心組件

    • TextView 或自定義 View

    • 屬性動(dòng)畫(huà)(ObjectAnimator

    • ValueAnimator + Canvas.drawText()(高級(jí)方案)

    • Handler + Runnable(基礎(chǔ)方案)

    • Scroller / OverScroller(平滑滾動(dòng))

  • 布局容器:通常使用 FrameLayout、RelativeLayoutConstraintLayout 承載自定義控件

  • 開(kāi)發(fā)工具:Android Studio 最新穩(wěn)定版

二、相關(guān)知識(shí)詳解

1. Android 自定義 View 基礎(chǔ)

  • onMeasure():測(cè)量控件寬高;

  • onSizeChanged():尺寸變化回調(diào),初始化繪制區(qū)域;

  • onDraw(Canvas):繪制文字與背景;

  • 自定義屬性:通過(guò) res/values/attrs.xml 定義,可在 XML 中使用;

  • 硬件加速:確保動(dòng)畫(huà)平滑,必要時(shí)關(guān)閉硬件加速進(jìn)行文字陰影繪制。

2. 屬性動(dòng)畫(huà)與插值器

  • ObjectAnimator.ofFloat(view, "translationX", start, end);

  • ValueAnimator.ofFloat(start, end),在 addUpdateListener 中更新位置;

  • 常用插值器:LinearInterpolator、AccelerateInterpolator、DecelerateInterpolatorAccelerateDecelerateInterpolator;

  • 自定義插值器:實(shí)現(xiàn) TimeInterpolator。

3. Handler 與 Runnable

  • 適合循環(huán)式輕量調(diào)度;

  • postDelayed() 控制滾動(dòng)間隔;

  • Activity / Fragment 銷毀時(shí)要 removeCallbacks() 防止內(nèi)存泄漏。

4. Scroller / OverScroller

  • 實(shí)現(xiàn)流暢的物理滾動(dòng)效果;

  • scroller.startScroll() 或 fling();

  • 在 computeScroll() 中,調(diào)用 scroller.computeScrollOffset() 并 scrollTo(x, y);

  • 適用于需要手勢(shì)拖動(dòng)與慣性滾動(dòng)的場(chǎng)景。

5. TextView 與 Canvas.drawText()

  • 對(duì)于簡(jiǎn)單場(chǎng)景,可直接移動(dòng) TextView;

  • 對(duì)于更高性能與自定義效果,可在 View.onDraw() 中 canvas.drawText(),并通過(guò) canvas.translate() 實(shí)現(xiàn)滾動(dòng)。

三、項(xiàng)目實(shí)現(xiàn)思路

  1. 確定實(shí)現(xiàn)方案

    • 方案一(基礎(chǔ)):在布局中使用單個(gè) TextView,通過(guò) ObjectAnimator 或 TranslateAnimation 移動(dòng) TextView 的 translationX/Y。

    • 方案二(自定義View):繼承 View,在 onDraw() 中繪制文字并控制文字繪制位置偏移,實(shí)現(xiàn)更靈活的動(dòng)畫(huà)與樣式控制。

  2. 基礎(chǔ)流程

    • 初始化:讀取 XML 屬性或通過(guò) setter 獲取文字內(nèi)容、字體、顏色、速度等配置;

    • 測(cè)量與布局:在 onMeasure() 計(jì)算文字寬度/高度,確定 View 大??;

    • 啟動(dòng)動(dòng)畫(huà):在 onAttachedToWindow() 或 startScroll() 中,啟動(dòng)滾動(dòng)動(dòng)畫(huà);

    • 滾動(dòng)控制:使用 ValueAnimator 或 ObjectAnimator 不斷更新文字的偏移量;

    • 循環(huán)與間隔:監(jiān)聽(tīng)動(dòng)畫(huà)結(jié)束(AnimatorListener),在回調(diào)中 postDelayed() 再次啟動(dòng),以實(shí)現(xiàn)間隔播放;

    • 資源釋放:在 onDetachedFromWindow() 中取消所有動(dòng)畫(huà)與 Handler 調(diào)用。

  3. 多方向與多模式

    • 水平滾動(dòng):初始偏移為 viewWidth,終點(diǎn)為 -textWidth

    • 垂直滾動(dòng):初始偏移為 viewHeight,終點(diǎn)為 -textHeight

    • 往返模式:設(shè)置 repeatMode = ValueAnimator.REVERSE;

    • 無(wú)縫銜接:使用兩行文本交替滾動(dòng),一行滾出,一行緊隨其后。

  4. 觸摸暫停與拖動(dòng)

    • 在自定義 View 中重寫 onTouchEvent(),在 ACTION_DOWN 時(shí) pause() 動(dòng)畫(huà),ACTION_MOVE 時(shí)調(diào)整偏移,ACTION_UP 時(shí) resume() 或 fling()。

四、完整整合版代碼

4.1 attrs.xml

<!-- res/values/attrs.xml -->
<resources>
    <declare-styleable name="MarqueeTextView">
        <attr name="mtv_text" format="string" />
        <attr name="mtv_textColor" format="color" />
        <attr name="mtv_textSize" format="dimension" />
        <attr name="mtv_speed" format="float" />
        <attr name="mtv_direction">
            <flag name="horizontal" value="0" />
            <flag name="vertical" value="1" />
        </attr>
        <attr name="mtv_repeatDelay" format="integer" />
        <attr name="mtv_repeatMode">
            <enum name="restart" value="1" />
            <enum name="reverse" value="2" />
        </attr>
        <attr name="mtvInterpolator" format="reference" />
        <attr name="mtv_loop" format="boolean" />
    </declare-styleable>
</resources>

4.2 布局文件

<!-- res/layout/activity_main.xml -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp">
 
    <com.example.marquee.MarqueeTextView
        android:id="@+id/marqueeView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:mtv_text="歡迎使用Android文字滾動(dòng)播放控件"
        app:mtv_textColor="#FF5722"
        app:mtv_textSize="18sp"
        app:mtv_speed="100"
        app:mtv_direction="horizontal"
        app:mtv_repeatDelay="500"
        app:mtv_repeatMode="restart"
        app:mtvInterpolator="@android:anim/linear_interpolator"
        app:mtv_loop="true"/>
</FrameLayout>

4.3 自定義控件:MarqueeTextView.java

package com.example.marquee;
 
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator;
import com.example.R;
 
public class MarqueeTextView extends View {
 
    // ========== 可配置屬性 ==========
    private String text;
    private int textColor;
    private float textSize;
    private float speed;               // px/s
    private int direction;             // 0: horizontal, 1: vertical
    private long repeatDelay;          // ms
    private int repeatMode;            // ObjectAnimator.RESTART or REVERSE
    private boolean loop;              // 是否循環(huán)
    private TimeInterpolator interpolator;
 
    // ========== 繪制相關(guān) ==========
    private Paint paint;
    private float textWidth, textHeight;
    private float offset;              // 當(dāng)前滾動(dòng)偏移
 
    // ========== 動(dòng)畫(huà) ==========
    private ObjectAnimator animator;
 
    public MarqueeTextView(Context context) {
        this(context, null);
    }
 
    public MarqueeTextView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
 
    public MarqueeTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initAttributes(context, attrs);
        initPaint();
    }
 
    private void initAttributes(Context context, AttributeSet attrs) {
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MarqueeTextView);
        text = a.getString(R.styleable.MarqueeTextView_mtv_text);
        textColor = a.getColor(R.styleable.MarqueeTextView_mtv_textColor, 0xFF000000);
        textSize = a.getDimension(R.styleable.MarqueeTextView_mtv_textSize, 16 * getResources().getDisplayMetrics().scaledDensity);
        speed = a.getFloat(R.styleable.MarqueeTextView_mtv_speed, 50f);
        direction = a.getInt(R.styleable.MarqueeTextView_mtv_direction, 0);
        repeatDelay = a.getInt(R.styleable.MarqueeTextView_mtv_repeatDelay, 500);
        repeatMode = a.getInt(R.styleable.MarqueeTextView_mtv_repeatMode, ObjectAnimator.RESTART);
        loop = a.getBoolean(R.styleable.MarqueeTextView_mtv_loop, true);
        int interpRes = a.getResourceId(R.styleable.MarqueeTextView_mtvInterpolator, android.R.interpolator.linear);
        interpolator = android.view.animation.AnimationUtils.loadInterpolator(context, interpRes);
        a.recycle();
 
        if (TextUtils.isEmpty(text)) text = "";
    }
 
    private void initPaint() {
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(textColor);
        paint.setTextSize(textSize);
        paint.setStyle(Paint.Style.FILL);
 
        // 計(jì)算文字尺寸
        textWidth = paint.measureText(text);
        Paint.FontMetrics fm = paint.getFontMetrics();
        textHeight = fm.bottom - fm.top;
    }
 
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int desiredW = (int) (direction == 0 ? getSuggestedMinimumWidth() : textWidth + getPaddingLeft() + getPaddingRight());
        int desiredH = (int) (direction == 1 ? getSuggestedMinimumHeight() : textHeight + getPaddingTop() + getPaddingBottom());
 
        int width = resolveSize(desiredW, widthMeasureSpec);
        int height = resolveSize(desiredH, heightMeasureSpec);
        setMeasuredDimension(width, height);
    }
 
    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        startScroll();
    }
 
    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (animator != null) animator.cancel();
    }
 
    private void startScroll() {
        if (animator != null && animator.isRunning()) return;
 
        float start, end, distance;
        if (direction == 0) {
            // 水平滾動(dòng):從右側(cè)外開(kāi)始,到左側(cè)外結(jié)束
            start = getWidth();
            end = -textWidth;
            distance = start - end;
        } else {
            // 垂直滾動(dòng):從底部外開(kāi)始,到頂部外結(jié)束
            start = getHeight();
            end = -textHeight;
            distance = start - end;
        }
        long duration = (long) (distance / speed * 1000);
 
        animator = ObjectAnimator.ofFloat(this, "offset", start, end);
        animator.setInterpolator(interpolator);
        animator.setDuration(duration);
        animator.setRepeatCount(loop ? ObjectAnimator.INFINITE : 0);
        animator.setRepeatMode(repeatMode);
        animator.setStartDelay(repeatDelay);
        animator.addListener(new Animator.AnimatorListener() {
            @Override public void onAnimationStart(Animator animation) { }
            @Override public void onAnimationEnd(Animator animation) { }
            @Override public void onAnimationCancel(Animator animation) { }
            @Override public void onAnimationRepeat(Animator animation) { }
        });
        animator.start();
    }
 
    public void setOffset(float value) {
        this.offset = value;
        invalidate();
    }
 
    public float getOffset() { return offset; }
 
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (direction == 0) {
            // 水平
            float y = getPaddingTop() - paint.getFontMetrics().top;
            canvas.drawText(text, offset, y, paint);
        } else {
            // 垂直
            float x = getPaddingLeft();
            canvas.drawText(text, x, offset - paint.getFontMetrics().top, paint);
        }
    }
 
    // ==== 可添加更多 API:pause(), resume(), setText(), setSpeed() 等 ====
}

五、代碼解讀

  1. 自定義屬性

    • 在 attrs.xml 中定義了文字內(nèi)容、顏色、大小、速度、方向、間隔、循環(huán)模式、插值器等屬性;

    • 在控件構(gòu)造函數(shù)中通過(guò) TypedArray 讀取并初始化。

  2. 測(cè)量邏輯

    • onMeasure() 根據(jù)滾動(dòng)方向決定控件的期望寬高;

    • 對(duì)水平滾動(dòng),寬度由父容器決定,高度由文字高度加內(nèi)邊距決定;

    • 對(duì)垂直滾動(dòng),反之亦然。

  3. 繪制邏輯

    • onDraw() 中,根據(jù)當(dāng)前 offset 繪制文字;

    • 使用 paint.measureText() 和 paint.getFontMetrics() 計(jì)算文字寬高與基線。

  4. 動(dòng)畫(huà)邏輯

    • startScroll() 中,計(jì)算從起始位置到結(jié)束位置的距離與時(shí)長(zhǎng);

    • 使用 ObjectAnimator 對(duì) offset 屬性做動(dòng)畫(huà);

    • 設(shè)置插值器、循環(huán)次數(shù)、循環(huán)模式與延時(shí);

    • 在 onDetachedFromWindow() 中取消動(dòng)畫(huà),防止泄漏。

  5. 可擴(kuò)展性

    • 暴露 setText()、setSpeed()pause()、resume() 等方法;

    • 監(jiān)聽(tīng)用戶觸摸,支持滑動(dòng)暫停與手動(dòng)拖動(dòng);

    • 對(duì)接 RecyclerView、ListView,實(shí)現(xiàn)列表內(nèi)多個(gè)跑馬燈。

六、項(xiàng)目總結(jié)與拓展

  1. 項(xiàng)目收獲

    • 深入掌握自定義 View 的測(cè)量、繪制與屬性動(dòng)畫(huà);

    • 學(xué)會(huì)在自定義控件中優(yōu)雅管理動(dòng)畫(huà)生命周期;

    • 掌握跑馬燈效果的核心算法:偏移量計(jì)算與時(shí)長(zhǎng)轉(zhuǎn)換;

    • 學(xué)會(huì)如何通過(guò) XML 屬性實(shí)現(xiàn)高度可配置化。

  2. 性能優(yōu)化

    • 確保硬件加速開(kāi)啟,避免文字繪制卡頓;

    • 對(duì)于超長(zhǎng)文字或多列文字,可使用 StaticLayout 分段緩存;

    • 結(jié)合 Choreographer 精確控制幀率;

  3. 高級(jí)拓展

    • 觸摸控制:拖動(dòng)暫停、手動(dòng)快進(jìn)快退;

    • 多行跑馬燈:支持同時(shí)滾動(dòng)多行文字,或背景漸變;

    • 動(dòng)態(tài)數(shù)據(jù)源:與網(wǎng)絡(luò)或數(shù)據(jù)庫(kù)結(jié)合,實(shí)時(shí)更新滾動(dòng)內(nèi)容;

    • Jetpack Compose 實(shí)現(xiàn):基于 Canvas 與 Modifier.offset() 的 Compose 方案;

以上就是Android實(shí)現(xiàn)文字滾動(dòng)播放效果的示例代碼的詳細(xì)內(nèi)容,更多關(guān)于Android文字滾動(dòng)播放的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論

福泉市| 论坛| 宜宾市| 尉犁县| 丽水市| 马鞍山市| 南投市| 彭阳县| 泸定县| 紫云| 金昌市| 平塘县| 石门县| 平顶山市| 桂林市| 郓城县| 班玛县| 泽库县| 沾化县| 瑞金市| 江川县| 兰溪市| 册亨县| 哈尔滨市| 泾川县| 安宁市| 盖州市| 荣昌县| 东城区| 大足县| 雅江县| 克什克腾旗| 洛川县| 磴口县| 茌平县| 昌乐县| 云龙县| 城固县| 景东| 多伦县| 邢台市|