Android 跑马灯--谷歌“偷渡”法

遇到问题

一个跑马灯的需求,在网上扒了几个,发现都是写一个线程去循环画需要显示的字。效果来说不是很理想。
偶然下我发现了TextView自己就带了跑马灯的属性,只是谷歌为了方便文本内容超过了TextView的范围实现的。这个效果还是很不错的。
一般情况给TextView设置了 setEllipsize(TextUtils.TruncateAt.MARQUEE);是不跑的。会跑动的条件第一是TextView要显示的内容超出TextView的显示范围,第二是TextView获得了焦点

实现

根据这个两个特性我们可以自己根据TextView改造出一个跑马灯来
我就直接贴代码了:


import android.content.Context;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.support.v7.widget.AppCompatTextView;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.ViewTreeObserver;

@SuppressWarnings("unused")
public class MarqueeView extends AppCompatTextView {

    private volatile int spaceCount = 0;
    private String layout_width;

    public MarqueeView(Context context) {
        super(context);
    }

    public MarqueeView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        if (attrs != null) {
            //直接获取XML中设置的宽度
            layout_width = attrs.getAttributeValue("http://schemas.android.com/apk/res/android", "layout_width");
            switch (layout_width) {
                case "-2"://宽度设置为wrap_content的情况。这个时候宽度是文本内容的宽度决定的。
                    setWidthByText(getText().toString(),getTextSize());
                    initView();
                    break;
                default:
                    getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                        @Override
                        public void onGlobalLayout() {//系统确认了宽度和高度时回调的
                            if(getViewTreeObserver().isAlive()){
                                getViewTreeObserver().removeOnGlobalLayoutListener(this);
                                int viewWidth= getMeasuredWidth();
                                float spaceWidth = getCharacterWidth(getTextSize());
                                spaceCount = viewWidth / (int)spaceWidth;
                                initView();
                            }
                        }
                    });
                    break;
            }
        }

    }

    public MarqueeView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    private void initView() {
        setSelected(true);
        setSingleLine(true);
        setEllipsize(TextUtils.TruncateAt.MARQUEE);
        setMarqueeRepeatLimit(-1);
    }

    @Override
    public boolean isFocused() {
        //强制保持TextView是一直存在焦点的,而实际是没有获得焦点的。(俗称骗自己有焦点)
        return true;
    }

    @Override
    public void setFocusable(boolean focusable) {
        super.setFocusable(false);
    }

    @Override
    public void setFocusable(int focusable) {
        super.setFocusable(false);
    }

    @Override
    public void setFocusableInTouchMode(boolean focusableInTouchMode) {
        super.setFocusableInTouchMode(false);
    }

    @Override
    public void setText(CharSequence text, BufferType type) {
        if(layout_width != null && layout_width.equals("-2")){
            setWidthByText(text.toString(),getTextSize());
        }
        if(spaceCount != 0) {
            text = getCount(spaceCount - 2 ) + text;
        }
        super.setText(text, type);
    }

    @Override
    public CharSequence getText() {
        return super.getText().toString().trim();
    }


    public void setWidthByText(final String string,final float textSize){
        setWidth((int)getCharacterWidth(string, textSize));
        float spaceWidth = getCharacterWidth(textSize);
        spaceCount = (int) (getCharacterWidth(string, textSize)/spaceWidth);
    }

    public float getCharacterWidth(final float size) {
        Paint paint = new Paint();
        paint.setTextSize(size);
        return paint.measureText(" ");
    }

    public float getCharacterWidth(String string ,float size) {
        Paint paint = new Paint();
        paint.setTextSize(size);
        return paint.measureText(string);
    }

    public String getCount(int count) {
        if (count < 0) return "";
        StringBuilder st = new StringBuilder();
        for (int i = 0; i < count; i++) {
            st.append(" ");
        }
        return st.toString();
    }
}

重点是利用空格去填充TextView,让原本要显示的内容隐藏。而需要多少个空格可以通过

        Paint paint = new Paint();
        paint.setTextSize(size);
        paint.measureText(" ");//返回这个文本的宽度单位是px.

而调用getText()的时候只需要复写一下方法,通过trim()去掉两端的空格。

super.getText().toString().trim();

实现的效果:


效果图,GIF的帧率太低了,所以感觉很卡顿。

好了,基本需求解决了,这个只是一个demo,你们可以根据你的实际需求利用好TextView的这个特性去实现你自己的跑马灯。

你可能感兴趣的:(Android 跑马灯--谷歌“偷渡”法)