Android achieves horizontal and vertical text marquee effects

Display projects often use text marquees, including horizontal and vertical directions. Common needs include controlling the playback speed. I found a lot of related effects on the Internet, but they were not satisfactory. I found a MarqueeView yesterday, which has relative functions. It fits the scene and is easy to configure. The usage method and problem points will be listed below, and a demo is attached at the end

First look at the effect:

 1. Customize View (just copy and paste it into your own project)

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 (under the res/values ​​directory)

<?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>

Layout file (simple example, see demo for details)

<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" />

Highlights:

Text color, this control cannot use android:textColor="@color/white"

Instead, you need to use app:textColor="@color/white" to set the text color

Otherwise, there will be no color, and the result is that there will be no text. When I organized the demo before, the code was all written, and it had no effect as soon as I ran it. I found the original demo and compared it for a long time before I found out that it was a problem with this attribute.

MainActivity (simple example, see demo for details)

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("水平跑马灯单行");

The important properties are to set the scrolling speed and scrolling direction

So far, the function has been realized

demo source code

Guess you like

Origin blog.csdn.net/weixin_53324308/article/details/130427159