Android 实现水平、垂直方向文字跑马灯效果

展示类项目经常会用到文字跑马灯,包含水平方向和垂直方向,常见的需求还有控制播放速度等,网上找了很多相关的效果,但是不尽如人意,昨天找到了个MarqueeView,功能相对符合场景,便于配置,下面将把使用方法和问题点列出来,结尾附demo

先看效果:

 1.自定义View(直接复制粘贴到自己项目里就好)

public class MarqueeView extends TextView {
    //滚动方向
    //不滚动
    public static final int SCROLL_NO = 1;
    //从下往上
    public static final int SCROLL_BT = 2;
    //从右往左
    public static final int SCROLL_RL = 3;

    //垂直滚动需要的数据
    private float lineSpace;
    private float verticalSpeed = 0.1f;
    private List<String> textList = new ArrayList<>();
    private StringBuilder textBuilder = new StringBuilder();

    //水平滚动需要的数据
    private float horizontalSpeed = 2f;
    private Rect rect;

    private Paint paint;
    //默认不滚动
    private int scrollType;
    //每次更滚动的距离
    private float scrollStep = 0f;

    public MarqueeView(Context context) {
        this(context, null);
    }

    public MarqueeView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MarqueeView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MarqueeView);
        scrollType = array.getInt(R.styleable.MarqueeView_scrollType, SCROLL_NO);
        int color = array.getColor(R.styleable.MarqueeView_textColor, 0x000000);
        lineSpace = array.getInt(R.styleable.MarqueeView_lineSpace, 0);
        array.recycle();
        setSpeed(0, 1f);
        setSpeed(1, 5f);
        paint = getPaint();
        paint.setColor(color);
        rect = new Rect();
    }

    @Override
    public void setTextColor(int color) {
        super.setTextColor(color);
        paint.setColor(color);
    }

    /**
     * 设置滚动方向
     *
     * @param scrollType 滚动方向
     */
    public void setScrollType(int scrollType) {
        this.scrollType = scrollType;
        invalidate();
    }

    /**
     * 设置滚动速度
     *
     * @param type 滚动方向 0垂直 1水平
     */
    public void setSpeed(int type, float speed) {
        if (0 == type) {
            verticalSpeed = speed;
        } else {
            horizontalSpeed = speed;
        }
        invalidate();
    }

    /**
     * 设置行高
     *
     * @param lineSpace 行高
     */
    public void setLineSpace(float lineSpace) {
        this.lineSpace = lineSpace;
        invalidate();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        String text = getText().toString();
        if (!TextUtils.isEmpty(text) && scrollType == SCROLL_BT) {
            //由下往上滚动需要测量高度
            setTextList(widthMeasureSpec, text);
        }
    }

    /**
     * 根据TextView宽度和字体大小,计算显示的行数。
     *
     * @param widthMeasureSpec 测量模式
     * @param text             文本
     */
    private void setTextList(int widthMeasureSpec, String text) {
        textList.clear();
        float width = MeasureSpec.getSize(widthMeasureSpec);
        float length = 0;
        for (int i = 0; i < text.length(); i++) {
            if (length <= width) {
                textBuilder.append(text.charAt(i));
                length += paint.measureText(text.substring(i, i + 1));
                if (i == text.length() - 1) {
                    if (length <= width) {
                        textList.add(textBuilder.toString());
                    } else {
                        if (textBuilder.toString().length() == 1) {
                            //每行最多显示一个字
                            textList.add(text.substring(text.length() - 1));
                        } else {
                            //去掉最后一个字,否则最后一个字显示不完整
                            textList.add(textBuilder.toString().substring(0, textBuilder.toString().length() - 1));
                            //最后一个字单独一行
                            textList.add(text.substring(text.length() - 1));
                        }
                    }
                }
            } else {
                if (textBuilder.toString().length() == 1) {
                    //每行最多显示一个字
                    textList.add(textBuilder.toString());
                    textBuilder.delete(0, textBuilder.length());
                    i--;
                    length = 0;
                } else {
                    //去掉最后一个字,否则最后一个字显示不完整
                    textList.add(textBuilder.toString().substring(0, textBuilder.toString().length() - 1));
                    textBuilder.delete(0, textBuilder.length() - 1);
                    i--;
                    length = paint.measureText(text.substring(i, i + 1));
                }
            }
        }
        //清空textBuilder
        textBuilder.delete(0, textBuilder.length());
    }

    @Override
    public void onDraw(Canvas canvas) {
        String text = getText().toString();
        if (TextUtils.isEmpty(text)) {
            super.onDraw(canvas);
            return;
        }
        switch (scrollType) {
            case SCROLL_NO:
                super.onDraw(canvas);
                break;
            case SCROLL_BT:
                //从下往上滚动,首次不显示文字,后续从下往上显示
                float textSize = paint.getTextSize();
                for (int i = 0; i < textList.size(); i++) {
                    float currentY = getHeight() + (i + 1) * textSize - scrollStep;
                    if (i > 0) {
                        currentY = currentY + i * lineSpace;
                    }
                    if (textList.size() > 1) {
                        canvas.drawText(textList.get(i), 0, currentY, paint);
                    } else {
                        canvas.drawText(textList.get(i), getWidth() / 2 - paint.measureText(text) / 2, currentY, paint);
                    }
                }
                scrollStep = scrollStep + verticalSpeed;
                if (scrollStep >= getHeight() + textList.size() * textSize + (textList.size() - 1) * lineSpace) {
                    scrollStep = 0;
                }
                invalidate();
                break;
            case SCROLL_RL:
                //从右向左滚动,首次不显示文字,后续每次往左偏移speed像素
                paint.getTextBounds(text, 0, text.length(), rect);
                int textWidth = rect.width();
                int viewWidth = getWidth();
                float currentX = viewWidth - scrollStep;
                canvas.drawText(text, currentX, getHeight() / 2 + (paint.getFontMetrics().descent - paint.getFontMetrics().ascent) / 2 - paint.getFontMetrics().descent, paint);
                scrollStep = scrollStep + horizontalSpeed;
                if (scrollStep >= viewWidth + textWidth) {
                    scrollStep = 0;
                }
                invalidate();
                break;
            default:
                break;
        }
    }
}

attrs.xml(res/values目录下)

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MarqueeView">
        <attr name="scrollType">
            <enum name="no" value="1" />
            <enum name="vertical" value="2" />
            <enum name="horizontal" value="3" />
        </attr>
        <attr name="speedType">
            <enum name="slow" value="4" />
            <enum name="normal" value="5" />
            <enum name="fast" value="6" />
            <enum name="express" value="7" />
        </attr>

        <attr name="textColor" format="color" />

        <!--行高,只对垂直滚动有效-->
        <attr name="lineSpace" format="integer" />
    </declare-styleable>

</resources>

布局文件(简单举例,详细看demo)

<com.hjly.marqueetextdemo.MarqueeView
        android:id="@+id/marquee_horizontal"
        android:layout_width="match_parent"
        android:layout_height="80dp"
        android:background="#abc"
        android:singleLine="true"
        app:textColor="#333"
        android:textSize="30sp" />

重点说明:

文本颜色,这个控件不能使用 android:textColor="@color/white"

而需要使用 app:textColor="@color/white"来设置文字颜色

否则,都会没有颜色,导致的结果就是没有文字,我之前自己整理demo的时候,代码都写完了,一运行就是没有效果,我就把原始的demo找出来,对比半天才发现是这个属性的问题

MainActivity(简单举例,详细看demo)

marquee_horizontal = findViewById(R.id.marquee_horizontal);

marquee_horizontal.setFocusable(true);
marquee_horizontal.setFocusableInTouchMode(true);
marquee_horizontal.setSpeed(1, (float) 5);
marquee_horizontal.setScrollType(SCROLL_RL);
marquee_horizontal.setText("水平跑马灯单行");

重要的属性就是设置滚动速度以及滚动方向

至此就实现了功能

demo源码

猜你喜欢

转载自blog.csdn.net/weixin_53324308/article/details/130427159