Android 竖滚加横滚的跑马灯(自定义 MarqueeView )

一.效果图

没错,就是要做一个能竖屏滚动+横屏滚动的花式跑马灯。

Android 竖滚加横滚的跑马灯(自定义 MarqueeView )

 

二.思路

竖屏滚动首先想到的肯定是ViewFlipper,那么竖屏滚动的ViewFlipper加上自定义的横屏滚动跑马灯岂不是就大功告成。来,开始操作:

1.自定义一个横向滚动的TexHorizontalView:

具体步骤:

a.计算需要展示文字的宽度textWidth和控件的宽度viewWitdh,判断文字的宽度textWidth是否大于控件控件宽度viewWitdh,如果大于则需要滚动。

b.设置值动画同时计算应到滚动的坐标,然后根据计算出的坐标用postInvalidate进行重绘。

c.postInvalidate会调用onDraw方法,然后在onDraw中根据坐标进行drawText绘制文本。

d.当值动画完毕设置横向滚动完毕事件的回调给外部使用。

2.自定义竖屏滚动的MarqueeView:

具体步骤:

a.自定义MarqueeView继承ViewFlipper,并在MarqueeView中addView添加对应个数的TexHorizontalView。

b.开启TexHorizontalView的横向滚动;当MarqueeView中的子TexHorizontalView横向滚动完毕,在TexHorizontalView滚动完毕的回调中开启MarqueeView的竖向滚动,从而展示下一个子TexHorizontalView。

c.当MarqueeView的竖向滚动动画完毕后,在动画完毕的监听开启当前TexHorizontalView开始横滚。

 

三.代码

1.TextHorizontalView

public class TextHorizontalView extends View {
    /**
     * 文字颜色,默认黑色
     */
    private int mTextColor = Color.BLACK;
    /**
     * 文字大小
     */
    private int mTextSize = 12;
    /**
     * 文本的x坐标
     */
    private float mXLocation = 0;
    /**
     * 滚动每个字的时间
     */
    private int mScrollSpeedTime = 300;
    /**
     * 画笔
     */
    private TextPaint mPaint;
    private Rect mRect;
    /**
     * 内容
     */
    private String mContent;
    /**
     * 文字高度
     */
    private float mTextHeight;
    /**
     * 值动画,配合postInvalidate刷新界面
     */
    private ValueAnimator verticalSwitchAnimator;
    /**
     * 开启动画
     */
    private boolean startRoll;

    /**
     * 滚动完毕的监听
     */
    public interface OnTextScrollFinishListener {
        void scrollFinish();
    }

    private OnTextScrollFinishListener onTextScrollFinishListener;

    public void setOnTextScrollFinishListener(OnTextScrollFinishListener onTextScrollFinishListener) {
        this.onTextScrollFinishListener = onTextScrollFinishListener;
    }

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

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

    public TextHorizontalView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initAttrs(attrs);
        initPaint();
    }

    @SuppressLint("RestrictedApi")
    private void initAttrs(AttributeSet attrs) {
        TintTypedArray tta = TintTypedArray.obtainStyledAttributes(getContext(), attrs, R.styleable.TextHorizontalView);
        mTextColor = tta.getColor(R.styleable.TextHorizontalView_text_color, mTextColor);
        mTextSize = tta.getInt(R.styleable.TextHorizontalView_text_size, mTextSize);
        mScrollSpeedTime = tta.getInt(R.styleable.TextHorizontalView_text_scroll_speed_time, mScrollSpeedTime);
        tta.recycle();
    }

    private void initPaint() {
        mRect = new Rect();
        //初始化文本画笔
        mPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setStyle(Paint.Style.FILL);
        //文字颜色值,可以不设定
        mPaint.setColor(mTextColor);
        //文字大小
        mPaint.setTextSize(mTextSize);

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (startRoll) {
            startRoll = false;
            operateScroll();
        }
        //把文字画出来
        if (mContent != null) {
            getContentWidth(mContent);
            canvas.drawText(mContent, mXLocation, getHeight() / 2 + mTextHeight / 2, mPaint);
        }
    }

    /**
     * 开始滚动
     * postInvalidate调用onDraw中的operateScroll开始绘制
     */
    public void startRoll() {
        startRoll = true;
        postInvalidate();
    }

    /**
     * 停止滚动,将值动画cancel
     */
    public void stopScroll() {
        if (verticalSwitchAnimator != null) {
            verticalSwitchAnimator.cancel();
        }
    }

    /**
     * 重置视图
     */
    public void resetVew() {
        mXLocation = 0;
        postInvalidate();
    }

    private void operateScroll() {
        final int moreWidth = (int) (getContentWidth(mContent) - getMeasuredWidth()) + mTextSize;
        //文字是否超出
        if (moreWidth <= 0) {
            if (onTextScrollFinishListener != null) {
                onTextScrollFinishListener.scrollFinish();
            }
        } else {
            verticalSwitchAnimator = ValueAnimator.ofFloat(0, 1);
            verticalSwitchAnimator.setDuration(mScrollSpeedTime * moreWidth / mTextSize);
            verticalSwitchAnimator.setInterpolator(new LinearInterpolator());
            verticalSwitchAnimator.start();
            verticalSwitchAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    float value = (float) animation.getAnimatedValue();
                    mXLocation = -value * moreWidth;
                    postInvalidate();
                }
            });
            verticalSwitchAnimator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    super.onAnimationEnd(animation);
                    if (onTextScrollFinishListener != null) {
                        onTextScrollFinishListener.scrollFinish();
                    }
                }
            });
        }
    }

    private float getContentWidth(String mContent) {
        if (TextUtils.isEmpty(mContent)) {
            return 0;
        }
        if (mRect == null) {
            mRect = new Rect();
        }
        mPaint.getTextBounds(mContent, 0, mContent.length(), mRect);
        mTextHeight = getContentHeight();
        return mRect.width();
    }

    private float getContentHeight() {
        Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
        return Math.abs((fontMetrics.bottom - fontMetrics.top)) / 2;
    }

    /**
     * 设置文字颜色
     */
    public void setTextColor(int mTextColor) {
        if (mTextColor != 0) {
            this.mTextColor = mTextColor;
            //文字颜色值,可以不设定
            mPaint.setColor(mTextColor);
        }
    }

    /**
     * 设置文字大小
     */
    public void setTextSize(int mTextSize) {
        if (mTextSize > 0) {
            this.mTextSize = mTextSize;
            mPaint.setTextSize(mTextSize);
        }
    }

    /**
     * 设置每个字的滚动时间
     */
    public void setTextSpeedTime(int mScrollSpeedTime) {
        this.mScrollSpeedTime = mScrollSpeedTime;
    }


    /**
     * 设置滚动的条目内容  字符串形式的
     */
    public void setData(String mContent) {
        if (TextUtils.isEmpty(mContent)) {
            return;
        }
        this.mContent = mContent;
    }
}

2.MarqueeView

public class MarqueeView extends ViewFlipper {
    /**
     * 当前横向滚动文本的索引
     */
    private int mCurrentIndex;
    /**
     * 横向滚动文本的宽
     */
    private int mTextWith = 500;
    /**
     * 横向滚动文本的高
     */
    private int mTextHeight = 70;
    /**
     * 横向滚动文本字体大小
     */
    private int mTextSize = 40;
    /**
     * 文本横向滚动的速度
     */
    private int mTextScrollSpeed = 200;
    /**
     * 竖向滚动的时间
     */
    private int mSwitchTime = 1000;
    /**
     * 用来做延迟操作
     */
    private Handler handler = new Handler(Looper.getMainLooper());

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

    public MarqueeView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs);
    }

    @SuppressLint("RestrictedApi")
    private void init(AttributeSet attrs) {
        TintTypedArray typedArray = TintTypedArray.obtainStyledAttributes(getContext(), attrs, R.styleable.MarqueeView);
        mTextScrollSpeed = typedArray.getInt(R.styleable.MarqueeView_horizontal_text_speed, mTextScrollSpeed);
        mTextSize = (int) typedArray.getDimension(R.styleable.MarqueeView_horizontal_text_size, mTextSize);
        mTextWith = (int) typedArray.getDimension(R.styleable.MarqueeView_horizontal_text_width, mTextWith);
        mTextHeight = (int) typedArray.getDimension(R.styleable.MarqueeView_horizontal_text_height, mTextHeight);
        mSwitchTime = typedArray.getInt(R.styleable.MarqueeView_horizontal_text_switch_time, mSwitchTime);
        typedArray.recycle();
    }

    public void setDataAndScroll(List<String> textList, List<Integer> colorList) {
        if (textList == null || textList.size() == 0 || colorList == null || colorList.size() == 0) {
            return;
        }
        //添加横向滚动view到当前控件
        addTextView(textList, colorList);
        //初始化监听
        initListener(textList.size());
        //开启滚动第一行
        startScroll(0, textList.size());
    }

    private void addTextView(final List<String> textList, List<Integer> colorList) {
        if (textList.size() == 1) {
            textList.addAll(textList);
        }
        //回收资源
        recyclerResource();
        removeAllViews();

        for (int i = 0; i < textList.size(); i++) {
            final TextHorizontalView textHorizontalView = new TextHorizontalView(getContext());

            LayoutParams params = new LayoutParams(mTextWith, mTextHeight);
            //设置速度
            textHorizontalView.setTextSpeedTime(mTextScrollSpeed);
            //设置文字大小
            textHorizontalView.setTextSize(mTextSize);
            textHorizontalView.setData(textList.get(i));
            if (colorList.size() == 1) {
                textHorizontalView.setTextColor(getResources().getColor(colorList.get(0)));
            } else if (colorList.size() > i) {
                textHorizontalView.setTextColor(getResources().getColor(colorList.get(i)));
            }

            final int finalI = i;
            textHorizontalView.setOnTextScrollFinishListener(new TextHorizontalView.OnTextScrollFinishListener() {

                @Override
                public void scrollFinish() {
                    int position = finalI + 1;
                    if (position == textList.size()) {
                        position = 0;
                    }
                    mCurrentIndex = position;
                    //横向滚动完毕,延时mSwitchTime开始下一个竖向滚动
                    handler.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            if (isFlipping()) {
                                return;
                            }
                            if (finalI == 0) {
                                stopFlipping();
                                startFlipping();
                            }
                            showNext();
                        }
                    }, mSwitchTime);

                }
            });
            textHorizontalView.setLayoutParams(params);
            addView(textHorizontalView);
        }
    }

    private void initListener(final int size) {
        //设置动画
        Animation inAnim = AnimationUtils.loadAnimation(getContext(), R.anim.anim_come_in);
        setInAnimation(inAnim);
        Animation outAnim = AnimationUtils.loadAnimation(getContext(), R.anim.anim_get_out);
        setOutAnimation(outAnim);

        getInAnimation().setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {

            }

            @Override
            public void onAnimationEnd(Animation animation) {
                //竖向滚动完毕,开始横滚
                startScroll(mCurrentIndex, size);
            }

            @Override
            public void onAnimationRepeat(Animation animation) {

            }
        });
    }

    private void startScroll(int index, int size) {
        //停止竖滚,开始横向滚动
        stopFlipping();

        int lastIndex;
        if (index == 0) {
            lastIndex = size - 1;
        } else {
            lastIndex = index - 1;
        }
        //上次的重置
        TextHorizontalView horizontalLastView = (TextHorizontalView) getChildAt(lastIndex);
        horizontalLastView.resetVew();
        //当前滚动
        TextHorizontalView horizontalView = (TextHorizontalView) getChildAt(index);
        horizontalView.startRoll();
    }


    private void stopScroll() {
        //停止竖滚,开始横向滚动
        stopFlipping();
        //当前滚动
        TextHorizontalView horizontalView = (TextHorizontalView) getChildAt(mCurrentIndex);
        if (horizontalView != null) {
            horizontalView.stopScroll();
        }
    }


    private void recyclerResource() {
        //回收资源
        stopScroll();

        if (handler != null) {
            handler.removeCallbacksAndMessages(null);
        }

    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        recyclerResource();
        if (handler != null) {
            handler = null;
        }
    }
}

3.使用代码:

布局文件中:

 <com.xaf.marqueelib.MarqueeView
        android:id="@+id/marqueeView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/marquee_bg"
        android:paddingLeft="20dp"
        android:paddingRight="20dp"
        app:horizontal_text_height="40dp"
        app:horizontal_text_size="20sp"
        app:horizontal_text_width="300dp" />

java代码

MarqueeView marquee = findViewById(R.id.marqueeView);
List<String> contentList = new ArrayList<>();
List<Integer> colorList = new ArrayList<>();
colorList.add(R.color.colorAccent);
contentList.add("1.“网上95%的名人名言都是瞎掰,包括这句。”——鲁迅");
//开启滚动
marquee.setDataAndScroll(contentList, colorList);

//点击事件
marquee.getCurrentView().setOnClickListener(new View.OnClickListener() {
	@Override
	public void onClick(View arg0) {
		Log.d("cccc","hhhh");
	}

});

4.res->anim目录下的动画

anim_come_in.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">

    <translate
        android:fromYDelta="100%p"
        android:toYDelta="0"
        android:duration="800"/>
</set>

anim_get_out.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">

    <translate
        android:fromYDelta="0"
        android:toYDelta="-100%p"
        android:duration="800"/>
</set>

5.res->values目录下的自定义属性:

attrs_marquee.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MarqueeView">
        <!--横向文字滚动速度-->
        <attr name="horizontal_text_speed" format="integer" />
        <!-- 文字大小 -->
        <attr name="horizontal_text_size" format="dimension" />
        <!-- 横向滚动控件的宽 -->
        <attr name="horizontal_text_width" format="dimension" />
        <!-- 横向滚动控件的高 -->
        <attr name="horizontal_text_height" format="dimension" />
        <!--竖向滚动时间-->
        <attr name="horizontal_text_switch_time" format="integer" />
    </declare-styleable>

</resources>

attrs_text_horizontal.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="TextHorizontalView">
        <!-- 文字颜色 -->
        <attr name="text_color" format="color|reference" />
        <!-- 文字大小 -->
        <attr name="text_size" format="integer" />
        <!--每个字的滚动时间-->
        <attr name="text_scroll_speed_time" format="boolean" />
    </declare-styleable>

</resources>

四.demo:

需要demo的可以去github上下载,顺手点个star。

github地址