很多app的首页都会有一个用于显示热点消息的banner,通过垂直切换文本的方式动态展示消息。垂直切换的方式可以有效利用空间显示更多的内容,动态的效果也更能吸引用户的注意力。

Android 文字数组上下滚动 安卓手机怎么滚动字幕_滚动

       实现这个效果,我能想到的方式大概有两种:

1、继承一个LineLayout,在里面添加两个TextView,通过动画实现TextView的移动、显示、隐藏。

2、继承TextView,手动去绘制文字,然后动态的改变文字的绘制,以实现切换的动效。

       相比之下,第一种方式要简单一些,而且方法1不只可以添加TextVIew,还可以添加两个ViewGroup,然后构建更加复杂的布局,如添加图片等。为了体现自定义控件的特点,这里使用第二种方式来实现这个功能。具体看下代码的实现:

private static final int DEFAULT_SWITCH_DURATION = 500;
    private static final int DEFAULT_IDLE_DURATION = 2000;
    private Context mContext;
 
    private List<String> lists;//会循环显示的文本内容
    private int contentSize;
    private String outStr;//当前滑出的文本内容
    private String inStr;//当前滑入的文本内容
    private float textBaseY;//文本显示的baseline
    private int currentIndex = 0;//当前显示到第几个文本
 
    private int switchDuaration = DEFAULT_SWITCH_DURATION;//切换时间
    private int idleDuaration = DEFAULT_IDLE_DURATION;//间隔时间
    private int switchOrientation = 0;
 
    private float currentAnimatedValue = 0.0f;
    private ValueAnimator animator;
 
    private int verticalOffset = 0;
    private int mWidth;
    private int mHeight;
    private int paddingLeft = 0;
    private int paddingBottom = 0;
    private int paddingTop = 0;
 
    private Paint mPaint;
 
    //回调接口,用来通知调用者控件当前的状态
    public VerticalSwitchTextViewCbInterface cbInterface;
 
    public VerticalSwitchTextView(Context context) {
        this(context, null);
    }
 
    public VerticalSwitchTextView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
 
    public VerticalSwitchTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.VerticalSwitchTextView);
        try {
            switchDuaration = array.getInt(R.styleable.VerticalSwitchTextView_switchDuaration, DEFAULT_SWITCH_DURATION);
            idleDuaration = array.getInt(R.styleable.VerticalSwitchTextView_idleDuaration, DEFAULT_IDLE_DURATION);
            switchOrientation = array.getInt(R.styleable.VerticalSwitchTextView_switchOrientation, 0);
        } finally {
            array.recycle();
        }
        init();
    }

  首先定义一些常量和变量,并实现了三个构造方法。

       常量的含义可以直接看代码的标注,构造方法中只有一个参数的方法是在代码里使用new生成View的时候调用的,两个参数的构造方法是在xml中定义View的时候调用,三个参数的构造方法不常用,当有自定义style的时候会用到。构造方法中对自定义属性进行了解析,后面会用到。

<resources>
    <declare-styleable name="VerticalSwitchTextView">
        <attr name="switchDuaration" format="integer"/>
        <attr name="idleDuaration" format="integer"/>
        <attr name="switchOrientation">
            <enum name="up"  value="0"/>
            <enum name="down"  value="1"/>
        </attr>
    </declare-styleable>
</resources>

上面是自定义属性的内容,分别对应于切换时长、切换间隔和切换方向。

private void init() {
        setOnClickListener(this);
 
        mPaint = getPaint();
        mPaint.setColor(getCurrentTextColor());
 
        animator = ValueAnimator.ofFloat(0f, 1f).setDuration(switchDuaration);
        animator.setStartDelay(idleDuaration);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                currentAnimatedValue = (float) animation.getAnimatedValue();
                if (currentAnimatedValue < 1.0f) {
                    invalidate();
                }
            }
        });
        animator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
 
            }
 
            @Override
            public void onAnimationEnd(Animator animation) {
                currentIndex = (++currentIndex) % contentSize;
                if (cbInterface != null) {
                    cbInterface.showNext(currentIndex);
                }
                outStr = lists.get(currentIndex);
                inStr = lists.get((currentIndex + 1) % contentSize);
 
                animator.setStartDelay(idleDuaration);
                animator.start();
            }
 
            @Override
            public void onAnimationCancel(Animator animation) {
 
            }
 
            @Override
            public void onAnimationRepeat(Animator animation) {
 
            }
        });
    }

init()方法中定义了一个属性动画,通过属性值的更新控制绘制的进度,在AnimatorUpdateListener的onAnimationUpdate()方法中,不断调用invalidate(),从而触发View的onDraw()方法回调。在AnimatorListener的onAnimationEnd()方法中,对要显示的内容进行更新,同时延时一定间隔再循环执行动画。

/**
     * 设置循环显示的文本内容
     *
     * @param content 内容list
     */
    public void setTextContent(List<String> content) {
        lists = content;
        if (lists == null || lists.size() == 0) {
            return;
        }
        contentSize = lists.size();
 
        outStr = lists.get(0);
        if (contentSize > 1) {
            inStr = lists.get(1);
        } else {
            inStr = lists.get(0);
        }
 
        if (contentSize > 0) {
            animator.start();
        }
    }

setTextContent()方法用于动态设置切换的文本列表,考虑到大部分应用场景下内容都是动态设置的,这里没有自定义属性提供xml文件的静态设置,如果有需求可以另行优化。

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mWidth = MeasureSpec.getSize(widthMeasureSpec);
 
        Rect bounds = new Rect();
        if (contentSize <= 0) {
            return;
        }
        String text = lists.get(0);
        mPaint.getTextBounds(text, 0, text.length(), bounds);
        int textHeight = bounds.height();
        Log.d("viclee", "onMeasure height is " + mHeight);
 
        paddingLeft = getPaddingLeft();
        paddingBottom = getPaddingBottom();
        paddingTop = getPaddingTop();
        mHeight = textHeight + paddingBottom + paddingTop;
 
        Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
        //计算文字高度
        float fontHeight = fontMetrics.bottom - fontMetrics.top;
        //计算文字的baseline
        textBaseY = mHeight - (mHeight - fontHeight) / 2 - fontMetrics.bottom;
 
        setMeasuredDimension(mWidth, mHeight);
    }

onMeasure()方法中主要做了两件事:根据文本的高度、padding设置View的高度和确定文本内容绘制的baseline。具体baseline的原理和计算方法请自行google。

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (contentSize <= 0) {
            return;
        }
 
        //直接使用mHeight控制文本绘制,会因为text的baseline的问题不能居中显示
        verticalOffset = Math.round(2 * textBaseY * (0.5f - currentAnimatedValue));
        Log.d("viclee", "verticalOffset is " + verticalOffset);
        if (switchOrientation == 0) {//向上滚动切换
            if (verticalOffset > 0) {
                canvas.drawText(outStr, paddingLeft, verticalOffset, mPaint);
            } else {
                canvas.drawText(inStr, paddingLeft, 2 * textBaseY + verticalOffset, mPaint);
            }
        } else {
            if (verticalOffset > 0) {//向下滚动切换
                canvas.drawText(outStr, paddingLeft,  2 * textBaseY - verticalOffset, mPaint);
            } else {
                canvas.drawText(inStr, paddingLeft, -verticalOffset, mPaint);
            }
        }
    }

onDraw()中调用Canvas的drawText()方法绘制文本,根据我们设置的滚动方向的不同,绘制的y坐标的计算方式有所差别,向上滚动时,y值变小,向下滚动时,y值变大。需要注意的是,在调用drawText()的时候,x坐标为设置的左边距paddingLeft,y坐标不是控件的高度mHeight,也不能是字体的高度textHeight,只能是text的baseline,这个大家可以自己验证一下。

//回调接口,用来通知调用者控件当前的状态,index表示开始显示哪一个文本内容
    public interface VerticalSwitchTextViewCbInterface {
        void showNext(int index);
        void onItemClick(int index);
    }
 
    public void setCbInterface(VerticalSwitchTextViewCbInterface cb) {
        cbInterface = cb;
    }

最后提供了一个接口,通过回调通知调用者View的状态更新(切换到新的内容或者当前内容被点击等)。如果需要其他功能,可以根据自定义View的原理自行扩展。

================================

 

2016.8.16更新:

增加gravity属性,水平方向的gravity属性可以设置为left、right、center,分别表示文字左对齐、右对齐和居中对齐。

增加ellipsis属性,可以设置为start、end、middle,分别表示当文字长度超出View长度时,在头部、尾部、中间显示省略号。

核心代码分析:

private void generateEllipsisText() {
        if (ellipsisLists != null) {//防止重复计算
            return;
        }
        ellipsisLists = new ArrayList<>();
        if (lists != null && lists.size() != 0) {
            for (String item : lists) {
                int avail = mWidth - paddingLeft - paddingRight;
                float remaining = avail - ellipsisLen;
                if (avail <= 0) {
                    ellipsisLists.add("");
                } else {
                    float itemWidth = mPaint.measureText(item, 0, item.length());
                    if (itemWidth < avail) {
                        ellipsisLists.add(item);
                    } else if (remaining <= 0) {
                        ellipsisLists.add(ellipsis);
                    } else {
                        int len = item.length();
                        float[] widths = new float[len];
                        mPaint.getTextWidths(item, 0, item.length(), widths);
                        if (mEllipsize == TextUtils.TruncateAt.END) {
                            float blockWidth = 0f;
                            for (int i = 0; i < len; i++) {
                                blockWidth += widths[i];
                                if (blockWidth > remaining) {
                                    ellipsisLists.add(item.substring(0, i) + ellipsis);
                                    break;
                                }
                            }
                        } else if (mEllipsize == TextUtils.TruncateAt.START) {
                            float blockWidth = 0f;
                            for (int i = len - 1; i >= 0; i--) {
                                blockWidth += widths[i];
                                if (blockWidth > remaining) {
                                    ellipsisLists.add(ellipsis + item.substring(i, len - 1));
                                    break;
                                }
                            }
                        } else if (mEllipsize == TextUtils.TruncateAt.MIDDLE) {
                            float blockWidth = 0f;
                            for (int i = 0, j = len - 1; i < j; i++, j--) {
                                blockWidth += (widths[i] + widths[j]);
                                if (blockWidth > remaining) {
                                    if (blockWidth - widths[j] < remaining) {
                                        ellipsisLists.add(item.substring(0, i + 1) + ellipsis + item.substring(j, len - 1));
                                    } else {
                                        ellipsisLists.add(item.substring(0, i) + ellipsis + item.substring(j, len - 1));
                                    }
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        }
        lists = ellipsisLists;
    }

generateEllipsisText()方法用来对文字内容进行处理,当文字超过View长度时,多余的部分用省略号“...”代替。ellipsisLists就是用来存储处理后的文字的。avail表示可以显示文字的区域宽度,remaining是去除省略号“...”的宽度后,可以显示文字内容的宽度。使用Paint的measureText()方法可以获得字符串的宽度,这个宽度是对应于某一种字体和字号的,字体和字号不同,获得的宽度也就不同。Paint的getTextWidths()方法可以获得一个数组,数组中存储的是每一个字符的宽度。然后根据当前的ellipsize,通过计算字符长度与View宽度的关系,来确定最终要显示的文字内容。

//计算绘制的文字中心位置
        switch (alignment) {
            case TEXT_ALIGN_CENTER:
                inTextCenterX = outTextCenterX = (mWidth - paddingLeft - paddingRight) / 2 + paddingLeft;
                break;
            case TEXT_ALIGN_LEFT:
                inTextCenterX = paddingLeft + mPaint.measureText(inStr) / 2;
                outTextCenterX = paddingLeft + mPaint.measureText(outStr) / 2;
                break;
            case TEXT_ALIGN_RIGHT:
                inTextCenterX = mWidth - paddingRight - mPaint.measureText(inStr) / 2;
                outTextCenterX = mWidth - paddingRight - mPaint.measureText(outStr) / 2;
                break;
        }

 上面的代码根据当前的gravity来计算文字绘制的x轴的中心位置,注意由于每项文字的长度不同,他们x轴的中心位置也不相同。

效果图如下

 

带有gravity属性和ellipsis属性的效果:

Android 文字数组上下滚动 安卓手机怎么滚动字幕_文字广告_02

普通效果:

Android 文字数组上下滚动 安卓手机怎么滚动字幕_滚动_03

实例:

attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="VerticalSwitchTextView">
        <attr name="switchDuaration" format="integer"/>
        <attr name="idleDuaration" format="integer"/>
        <attr name="switchOrientation">
            <enum name="up"  value="0"/>
            <enum name="down"  value="1"/>
        </attr>
        <attr name="alignment">
            <enum name="center" value="0"/>
            <enum name="left" value="1"/>
            <enum name="right" value="2"/>
        </attr>
    </declare-styleable>
</resources>

string.xml 

<resources>
    <string name="app_name">VerticalSwitchTextView</string>
    <string name="ellipsis">\u2026</string>
</resources>

VerticalSwitchTextView.java

package com.viclee.verticalswitchtextview;

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;


public class VerticalSwitchTextView extends TextView implements View.OnClickListener {
    private static final int DEFAULT_SWITCH_DURATION = 500;
    private static final int DEFAULT_IDLE_DURATION = 2000;
    public static final int TEXT_ALIGN_CENTER = 0;
    public static final int TEXT_ALIGN_LEFT = 1;
    public static final int TEXT_ALIGN_RIGHT = 2;
    private Context mContext;

    private List<String> lists;//会循环显示的文本内容
    private List<String> ellipsisLists;
    private int contentSize;
    private String outStr;//当前滑出的文本内容
    private String inStr;//当前滑入的文本内容
    private float textBaseY;//文本显示的baseline
    private int currentIndex = 0;//当前显示到第几个文本
    private String ellipsis;
    private float ellipsisLen = 0;

    private int switchDuaration = DEFAULT_SWITCH_DURATION;//切换时间
    private int idleDuaration = DEFAULT_IDLE_DURATION;//间隔时间
    private int switchOrientation = 0;
    private int alignment = TEXT_ALIGN_CENTER;

    /**
     * 文本中轴线X坐标
     */
    private float inTextCenterX;
    private float outTextCenterX;
    private float currentAnimatedValue = 0.0f;
    private ValueAnimator animator;

    private TextUtils.TruncateAt mEllipsize;

    private int verticalOffset = 0;
    private int mWidth;
    private int mHeight;
    private int paddingLeft = 0;
    private int paddingBottom = 0;
    private int paddingTop = 0;
    private int paddingRight = 0;

    private Paint mPaint;

    //回调接口,用来通知调用者控件当前的状态
    public VerticalSwitchTextViewCbInterface cbInterface;

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

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

    public VerticalSwitchTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.VerticalSwitchTextView);
        try {
            switchDuaration = array.getInt(R.styleable.VerticalSwitchTextView_switchDuaration, DEFAULT_SWITCH_DURATION);
            idleDuaration = array.getInt(R.styleable.VerticalSwitchTextView_idleDuaration, DEFAULT_IDLE_DURATION);
            switchOrientation = array.getInt(R.styleable.VerticalSwitchTextView_switchOrientation, 0);
            alignment = array.getInt(R.styleable.VerticalSwitchTextView_alignment, TEXT_ALIGN_CENTER);
        } finally {
            array.recycle();
        }
        init();
    }

    private void init() {
        setOnClickListener(this);
        mPaint = getPaint();
        mPaint.setTextAlign(Paint.Align.CENTER);
        ellipsis = getContext().getString(R.string.ellipsis);
        ellipsisLen = mPaint.measureText(ellipsis);
        mEllipsize = getEllipsize();

        animator = ValueAnimator.ofFloat(0f, 1f).setDuration(switchDuaration);
        animator.setStartDelay(idleDuaration);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                currentAnimatedValue = (float) animation.getAnimatedValue();
                if (currentAnimatedValue < 1.0f) {
                    invalidate();
                }
            }
        });
        animator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                currentIndex = (++currentIndex) % contentSize;
                if (cbInterface != null) {
                    cbInterface.showNext(currentIndex);
                }
                outStr = lists.get(currentIndex);
                inStr = lists.get((currentIndex + 1) % contentSize);

                animator.setStartDelay(idleDuaration);
                animator.start();
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
    }

    /**
     * 设置循环显示的文本内容
     *
     * @param content 内容list
     */
    public void setTextContent(List<String> content) {
        lists = content;
//        lists.clear();
//        lists = new ArrayList<>();
//        lists.add("1适当放松放松放松点水电费水电费史蒂夫水电费水电费");
//        lists.add("2适当放松放松放松点水电费水电费史蒂夫水电费水电费");
//        lists.add("3适当放松放");
//        lists.add("3适当放松放1111222333");
        if (lists == null || lists.size() == 0) {
            return;
        }
        contentSize = lists.size();

        if (contentSize > 0) {
            animator.start();
        }
    }

    private void generateEllipsisText() {
        if (ellipsisLists != null) {//防止重复计算
            return;
        }
        ellipsisLists = new ArrayList<>();
        if (lists != null && lists.size() != 0) {
            for (String item : lists) {
                int avail = mWidth - paddingLeft - paddingRight;
                float remaining = avail - ellipsisLen;
                if (avail <= 0) {
                    ellipsisLists.add("");
                } else {
                    float itemWidth = mPaint.measureText(item, 0, item.length());
                    if (itemWidth < avail) {
                        ellipsisLists.add(item);
                    } else if (remaining <= 0) {
                        ellipsisLists.add(ellipsis);
                    } else {
                        int len = item.length();
                        float[] widths = new float[len];
                        mPaint.getTextWidths(item, 0, item.length(), widths);
                        if (mEllipsize == TextUtils.TruncateAt.END) {
                            float blockWidth = 0f;
                            for (int i = 0; i < len; i++) {
                                blockWidth += widths[i];
                                if (blockWidth > remaining) {
                                    ellipsisLists.add(item.substring(0, i) + ellipsis);
                                    break;
                                }
                            }
                        } else if (mEllipsize == TextUtils.TruncateAt.START) {
                            float blockWidth = 0f;
                            for (int i = len - 1; i >= 0; i--) {
                                blockWidth += widths[i];
                                if (blockWidth > remaining) {
                                    ellipsisLists.add(ellipsis + item.substring(i, len - 1));
                                    break;
                                }
                            }
                        } else if (mEllipsize == TextUtils.TruncateAt.MIDDLE) {
                            float blockWidth = 0f;
                            for (int i = 0, j = len - 1; i < j; i++, j--) {
                                blockWidth += (widths[i] + widths[j]);
                                if (blockWidth > remaining) {
                                    if (blockWidth - widths[j] < remaining) {
                                        ellipsisLists.add(item.substring(0, i + 1) + ellipsis + item.substring(j, len - 1));
                                    } else {
                                        ellipsisLists.add(item.substring(0, i) + ellipsis + item.substring(j, len - 1));
                                    }
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        }
        lists = ellipsisLists;
    }

    /**
     * 主要用来调整TextView的高度
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mWidth = MeasureSpec.getSize(widthMeasureSpec);

        Rect bounds = new Rect();
        if (contentSize <= 0) {
            return;
        }
        String text = lists.get(0);
        mPaint.getTextBounds(text, 0, text.length(), bounds);
        int textHeight = bounds.height();

        paddingLeft = getPaddingLeft();
        paddingRight = getPaddingRight();
        paddingBottom = getPaddingBottom();
        paddingTop = getPaddingTop();

        if (mEllipsize != null) {
            generateEllipsisText();
        }

        outStr = lists.get(0);
        if (contentSize > 1) {
            inStr = lists.get(1);
        } else {
            inStr = lists.get(0);
        }

        mHeight = textHeight + paddingBottom + paddingTop;

        Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
        //计算文字高度
        float fontHeight = fontMetrics.bottom - fontMetrics.top;
        //计算文字的baseline
        textBaseY = mHeight - (mHeight - fontHeight) / 2 - fontMetrics.bottom;

        setMeasuredDimension(mWidth, mHeight);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (contentSize <= 0) {
            return;
        }
        //计算绘制的文字中心位置
        switch (alignment) {
            case TEXT_ALIGN_CENTER:
                inTextCenterX = outTextCenterX = (mWidth - paddingLeft - paddingRight) / 2 + paddingLeft;
                break;
            case TEXT_ALIGN_LEFT:
                inTextCenterX = paddingLeft + mPaint.measureText(inStr) / 2;
                outTextCenterX = paddingLeft + mPaint.measureText(outStr) / 2;
                break;
            case TEXT_ALIGN_RIGHT:
                inTextCenterX = mWidth - paddingRight - mPaint.measureText(inStr) / 2;
                outTextCenterX = mWidth - paddingRight - mPaint.measureText(outStr) / 2;
                break;
        }

        //直接使用mHeight控制文本绘制,会因为text的baseline的问题不能居中显示
        verticalOffset = Math.round(2 * textBaseY * (0.5f - currentAnimatedValue));
//        L.d("verticalOffset is " + verticalOffset);
        if (switchOrientation == 0) {//向上滚动切换
            if (verticalOffset > 0) {
                canvas.drawText(outStr, outTextCenterX, verticalOffset, mPaint);
            } else {
                canvas.drawText(inStr, inTextCenterX, 2 * textBaseY + verticalOffset, mPaint);
            }
        } else {
            if (verticalOffset > 0) {//向下滚动切换
                canvas.drawText(outStr, outTextCenterX, 2 * textBaseY - verticalOffset, mPaint);
            } else {
                canvas.drawText(inStr, inTextCenterX, -verticalOffset, mPaint);
            }
        }
    }

    @Override
    public void onClick(View v) {
        if (contentSize > currentIndex) {
            if (cbInterface != null) {
                cbInterface.onItemClick(currentIndex);
            }
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        mContext = null;
        if (animator != null) {
            animator.cancel();
        }
    }

    //回调接口,用来通知调用者控件当前的状态,index表示开始显示哪一个文本内容
    public interface VerticalSwitchTextViewCbInterface {
        void showNext(int index);

        void onItemClick(int index);
    }

    public void setCbInterface(VerticalSwitchTextViewCbInterface cb) {
        cbInterface = cb;
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:vswitch="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">
    <com.viclee.verticalswitchtextview.VerticalSwitchTextView
        android:id="@+id/vertical_switch_textview1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:textColor="#aa0090"
        android:textSize="30sp"
        android:padding="8dp"
        android:background="#999999"
        android:ellipsize="end"
        vswitch:alignment="left"
        vswitch:switchDuaration = "500"
        vswitch:idleDuaration = "1500"
        vswitch:switchOrientation="up"/>
    <com.viclee.verticalswitchtextview.VerticalSwitchTextView
        android:id="@+id/vertical_switch_textview2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:layout_centerInParent="true"
        android:textColor="#aa0090"
        android:textSize="30sp"
        android:padding="8dp"
        android:background="#999999"
        android:ellipsize="start"
        vswitch:alignment="center"
        vswitch:switchDuaration = "800"
        vswitch:idleDuaration = "1000"
        vswitch:switchOrientation="down"/>
</LinearLayout>

MainActivity.java

package com.viclee.verticalswitchtextview;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.WindowManager;

import java.util.ArrayList;
import java.util.Arrays;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        setContentView(R.layout.activity_main);

        ArrayList<String> list = new ArrayList<String>(Arrays.asList("能够适应多行长文本的Android TextView的例子", "理解的也很简单,我只是列出这个库里面我用到的一些方法",
                "适当放松放", "密钥", "实现了两种方式来检测Android app前后台切换的状态", "科比"));
        VerticalSwitchTextView verticalSwitchTextView1 = (VerticalSwitchTextView) findViewById(R.id.vertical_switch_textview1);
        VerticalSwitchTextView verticalSwitchTextView2 = (VerticalSwitchTextView) findViewById(R.id.vertical_switch_textview2);
        verticalSwitchTextView1.setCbInterface(new VerticalSwitchTextView.VerticalSwitchTextViewCbInterface() {
            @Override
            public void showNext(int index) {

            }

            @Override
            public void onItemClick(int index) {
                Log.e("aaaaaaaaa", "点击了" + index);
            }
        });
        verticalSwitchTextView2.setCbInterface(new VerticalSwitchTextView.VerticalSwitchTextViewCbInterface() {
            @Override
            public void showNext(int index) {

            }

            @Override
            public void onItemClick(int index) {
                Log.e("aaaaaaaaa", "点击了" + index);
            }
        });
        verticalSwitchTextView1.setTextContent(list);
        verticalSwitchTextView2.setTextContent(list);
    }
}

完!!!

注:此方法会频繁更新绘制,容易造成页面卡顿!!!

可以考虑使用TextSwitcher控件,参考下一篇